Skip to content

Commit 981135c

Browse files
authored
fix(QSelect): Keyboard navigation enhancements quasarframework#7382 (quasarframework#7386)
* guard keyboard search for index -1 (initial state when no option selected); * allow space in search if it's not first; * allow search for any printable char
1 parent e9ea690 commit 981135c

File tree

2 files changed

+139
-11
lines changed

2 files changed

+139
-11
lines changed

ui/dev/src/pages/form/select-part-5-kbd.vue

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,104 @@
360360
</q-select>
361361
</div>
362362
</div>
363+
364+
<div class="column q-gutter-y-md q-mt-lg">
365+
<q-select
366+
filled
367+
v-model="modelK"
368+
label="Single - strings - kbd search - option label/value str"
369+
:options="optionsK"
370+
option-value="value"
371+
option-label="label"
372+
clearable
373+
:behavior="behavior"
374+
/>
375+
376+
<q-select
377+
filled
378+
v-model="modelK"
379+
label="Single - strings - kbd search - option label/value str - map"
380+
:options="optionsK"
381+
option-value="value"
382+
option-label="label"
383+
emit-value
384+
map-options
385+
clearable
386+
:behavior="behavior"
387+
/>
388+
389+
<q-select
390+
filled
391+
v-model="modelK"
392+
label="Single - objs - kbd search - option label/value str - map"
393+
:options="objOptionsK"
394+
option-value="value"
395+
option-label="label"
396+
emit-value
397+
map-options
398+
clearable
399+
:behavior="behavior"
400+
/>
401+
402+
<q-select
403+
filled
404+
v-model="modelKO"
405+
label="Single - objs - kbd search - option label/value str"
406+
:options="objOptionsK"
407+
option-value="value"
408+
option-label="label"
409+
clearable
410+
:behavior="behavior"
411+
/>
412+
413+
<q-select
414+
filled
415+
v-model="modelK"
416+
label="Single - strings - kbd search - option label/value fn"
417+
:options="optionsK"
418+
:option-value="kOptionValue"
419+
:option-label="kOptionLabel"
420+
clearable
421+
:behavior="behavior"
422+
/>
423+
424+
<q-select
425+
filled
426+
v-model="modelK"
427+
label="Single - strings - kbd search - option label/value fn - map"
428+
:options="optionsK"
429+
:option-value="kOptionValue"
430+
:option-label="kOptionLabel"
431+
emit-value
432+
map-options
433+
clearable
434+
:behavior="behavior"
435+
/>
436+
437+
<q-select
438+
filled
439+
v-model="modelK"
440+
label="Single - objs - kbd search - option label/value fn - map"
441+
:options="objOptionsK"
442+
:option-value="kOptionValue"
443+
:option-label="kOptionLabel"
444+
emit-value
445+
map-options
446+
clearable
447+
:behavior="behavior"
448+
/>
449+
450+
<q-select
451+
filled
452+
v-model="modelKO"
453+
label="Single - objs - kbd search - option label/value fn"
454+
:options="objOptionsK"
455+
:option-value="kOptionValue"
456+
:option-label="kOptionLabel"
457+
clearable
458+
:behavior="behavior"
459+
/>
460+
</div>
363461
</div>
364462
</template>
365463

@@ -371,16 +469,27 @@ const
371469
'Google 3', 'Facebook 3', 'Twitter 3', 'Apple 3', 'Oracle 3',
372470
'A very long text of an options that goes over the length limit when you open the debugger'
373471
],
374-
objOptions = options.map((label, value) => ({ label, value }))
472+
objOptions = options.map((label, value) => ({ label, value })),
473+
474+
optionsK = [
475+
'A A label', 'A B label', 'A C label',
476+
'B A label', 'B B label', 'B C label',
477+
'C A label', 'C B label', 'C C label'
478+
],
479+
objOptionsK = optionsK.map((label, value) => ({ label, value }))
375480
376481
export default {
377482
data () {
378483
return {
379484
modelS: null,
380485
modelM: null,
381486
modelO: [3, 4, 5],
487+
modelK: null,
488+
modelKO: null,
382489
options,
490+
optionsK,
383491
objOptions,
492+
objOptionsK,
384493
forceMenu: null
385494
}
386495
},
@@ -437,6 +546,18 @@ export default {
437546
const needle = val.toLowerCase()
438547
this.objOptions = objOptions.filter(v => v.label.toLowerCase().indexOf(needle) > -1)
439548
})
549+
},
550+
551+
kOptionValue (item) {
552+
item === void 0 && console.trace('kOptionValue', item)
553+
554+
return item === Object(item) ? item.value : item
555+
},
556+
557+
kOptionLabel (item) {
558+
item === void 0 && console.trace('kOptionLabel', item)
559+
560+
return item === Object(item) ? item.label : item
440561
}
441562
}
442563
}

ui/src/components/select/QSelect.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -655,29 +655,36 @@ export default Vue.extend({
655655

656656
const optionsLength = this.virtualScrollLength
657657

658+
// clear search buffer if expired
659+
if (this.searchBuffer === void 0 || this.searchBufferExp < Date.now()) {
660+
this.searchBuffer = ''
661+
}
662+
658663
// keyboard search when not having use-input
659-
if (optionsLength > 0 && this.useInput !== true && e.keyCode >= 48 && e.keyCode <= 90) {
664+
if (
665+
optionsLength > 0 &&
666+
this.useInput !== true &&
667+
e.key.length === 1 && // printable char
668+
e.altKey === e.ctrlKey && // not kbd shortcut
669+
(e.keyCode !== 32 || this.searchBuffer.length > 0) // space in middle of search
670+
) {
660671
this.menu !== true && this.showPopup(e)
661672

662-
// clear search buffer if expired
663-
if (this.searchBuffer === void 0 || this.searchBufferExp < Date.now()) {
664-
this.searchBuffer = ''
665-
}
666-
667673
const
668674
char = String.fromCharCode(e.keyCode).toLocaleLowerCase(),
669675
keyRepeat = this.searchBuffer.length === 1 && this.searchBuffer[0] === char
670676

671677
this.searchBufferExp = Date.now() + 1500
672678
if (keyRepeat === false) {
679+
stopAndPrevent(e)
673680
this.searchBuffer += char
674681
}
675682

676683
const searchRe = new RegExp('^' + this.searchBuffer.split('').join('.*'), 'i')
677684

678685
let index = this.optionIndex
679686

680-
if (keyRepeat === true || searchRe.test(this.getOptionLabel(this.options[index])) !== true) {
687+
if (keyRepeat === true || index < 0 || searchRe.test(this.getOptionLabel(this.options[index])) !== true) {
681688
do {
682689
index = normalizeToInterval(index + 1, -1, optionsLength - 1)
683690
}
@@ -701,12 +708,12 @@ export default Vue.extend({
701708
return
702709
}
703710

704-
// enter, space (when not using use-input), or tab (when not using multiple and option selected)
711+
// enter, space (when not using use-input and not in search), or tab (when not using multiple and option selected)
705712
// same target is checked above
706713
if (
707714
e.keyCode !== 13 &&
708-
(this.useInput === true || e.keyCode !== 32) &&
709-
(tabShouldSelect === false || e.keyCode !== 9)
715+
(e.keyCode !== 32 || this.useInput === true || this.searchBuffer !== '') &&
716+
(e.keyCode !== 9 || tabShouldSelect === false)
710717
) { return }
711718

712719
e.keyCode !== 9 && stopAndPrevent(e)

0 commit comments

Comments
 (0)