Skip to content

Commit

Permalink
implement cycle-on-tab better
Browse files Browse the repository at this point in the history
  • Loading branch information
stcruy committed Aug 12, 2019
1 parent 0834eb5 commit e19248c
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 30 deletions.
24 changes: 24 additions & 0 deletions src/subcomponents/Term.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,30 @@ describe('sub/Term', () => {
});


it('emits `key-tab`+index+\'\'/\'shift\'/\'ignore\', depending on ' +
'`tabListenMode` (0/1/2/3)', () => {
function testCase(shiftKey, tabListenMode, str) {
make({ term: { }, hasInput: true, tabListenMode });
_itrigger('keydown.tab', shiftKey ? { shiftKey } : undefined);
_emitL(0, 'key-tab').should.deep.equal([55, str]);
}
// - For cycleOnTab==false
// - In rightmost input => mode 2 => emit 'ignore'/'shift' (<-Tab/STab).
testCase(false, 2, 'ignore');
testCase(true, 2, 'shift');
// - In leftmost input => mode 1 => emit ''/'ignore' (for Tab/Shft+Tab).
testCase(false, 1, '');
testCase(true, 1, 'ignore');
// + If both left&rightmost=>mode 0 => emit 'ignore'/'ignore'.
testCase(false, 0, 'ignore');
testCase(true, 0, 'ignore');
// - In any other input => mode 3 => emit ''/'shift'.
// - For cycleOnTab==true => mode 3 => emit ''/'shift'.
testCase(false, 3, '');
testCase(true, 3, 'shift');
});


it('emits `key-alt-up/down`+index, for both plain and ' +
'vsmAC-input', () => {
function testCase(type, arrow) {
Expand Down
38 changes: 28 additions & 10 deletions src/subcomponents/Term.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
@keydown.down.alt.exact="onKeyAltDown"
@keydown.46.ctrl.exact="onKeyCtrlDelete"
@keydown.8.ctrl.exact="onKeyCtrlBksp"
@keydown.tab.exact="onKeyTab"
@keydown.tab.shift.exact="onKeyShiftTab"
@mouseover="onMouseenter"
@mouseleave="onMouseleave"
@mousedown.left.exact.self.prevent.stop="onMousedown_div"
Expand Down Expand Up @@ -55,8 +57,6 @@
@keydown.enter.exact="onKeyEnter_plain"
@keydown.enter.ctrl.exact="onKeyCtrlEnter"
@keydown.enter.shift.exact="onKeyShiftEnter"
@keydown.tab.exact.prevent="() => onKeyTab('')"
@keydown.tab.shift.exact.prevent="() => onKeyTab('shift')"
><span
v-if="finalPlaceholder"
:class="['placehold plain', {
Expand Down Expand Up @@ -85,7 +85,6 @@
@key-bksp="onKeyBksp"
@key-ctrl-enter="onKeyCtrlEnter"
@key-shift-enter="onKeyShiftEnter"
@key-tab="onKeyTab"
@item-select="onItemSelect"
@list-open="onListOpen"
@mouseover.native.stop="x => x"
Expand Down Expand Up @@ -149,6 +148,10 @@ export default {
type: [String, Boolean],
default: false
},
tabListenMode: { // One of : 0,1,2,3.
type: Number,
default: 3
},
maxStringLengths: {
type: Object,
required: true
Expand Down Expand Up @@ -239,30 +242,45 @@ export default {
},
onKeyBksp_plain() { // Backspace on plain <input>-element. Not on autocompl.
// If only whitespace and cursor at start, empty first. Then, if empty,
// call the real `onKeyBksp()`. (This mimics VsmAutocomplete's behavior).
onKeyBksp_plain() {
/* Called for Backspace on plain <input>-element; not on autocomplete.
If only whitespace and cursor at start, empty first. Then, if empty,
call the real `onKeyBksp()`. (Mimics VsmAutocomplete's behavior). */
var el = this.$refs.input_plain;
if (el.value && !el.value.trim() && !el.selectionStart) {
this.emitInput(el.value = '');
}
if (!el.value) this.onKeyBksp();
},
onKeyCtrlBksp() { // If empty/whitespace, emit 'key-ctrl-bksp'.
// If not empty: if cursor at start, make input get emptied;
// else, let browser handle Ctrl+Bksp.
onKeyCtrlBksp() {
/* If empty/whitespace, emit 'key-ctrl-bksp'.
If not empty: if cursor at start, make input get emptied;
else, let browser handle Ctrl+Bksp. */
if (!this.term.str.trim()) this.emit2('key-ctrl-bksp');
else {
var el = this.inputElement();
if (el && !el.selectionStart) this.emitInput(el.value = '');
}
},
onKeyTab(ev) { // If `tabListenMode` is 1 or 3, respond to a pure Tab.
this.tabHandler(ev, '', this.tabListenMode & 1);
},
onKeyShiftTab(ev) { // If `tabListenMode` is 2 or 3, respond to Shift+Tab.
this.tabHandler(ev, 'shift', this.tabListenMode & 2);
},
tabHandler(ev, str, consume) {
// If we respond to (Shift+/)Tab, then make the browser not respond.
if (consume) ev.preventDefault();
this.emit2('key-tab', consume ? str : 'ignore');
},
onKeyEsc() { this.emit2('key-esc') },
onKeyBksp() { this.emit2('key-bksp') },
onKeyCtrlEnter() { this.emit2('key-ctrl-enter') },
onKeyTab(str) { this.emit2('key-tab', str) },
onKeyAltUp() { this.emit2('key-alt-up') },
onKeyAltDown() { this.emit2('key-alt-down') },
onKeyCtrlDelete() { this.emit2('key-ctrl-delete') },
Expand Down
39 changes: 19 additions & 20 deletions src/subcomponents/TheTerms.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
:query-options="queryOptions"
:autofocus="index == inputIndex && autofocus"
:placeholder="terms.length == 1 && placeholder"
:tab-listen-mode="index == inputIndex ? inputTabListenMode : 3"
:fresh-list-delay="freshListDelay"
:max-string-lengths="maxStringLengths"
:has-item-literal="!!(allowClassNull || advancedSearch)"
Expand Down Expand Up @@ -208,6 +209,16 @@ export default {
},
inputTabListenMode() {
if (this.cycleOnTab) return 3;
for (var i = 0, a = []; i < this.terms.length; i++) {
if (to.isEditable(this.terms[i])) a.push(i);
}
return (this.inputIndex == a[a.length - 1] ? 0 : 1) +
( this.inputIndex == a[0] ? 0 : 2);
},
/**
* Makes vsm-autocomplete's default item-literal `titleStr` more informative,
* applies a `customItemLiteral` function prop if given, and
Expand Down Expand Up @@ -490,16 +501,19 @@ export default {
},
/**
* This will be called with `str` == '' for a Tab-press, or 'shift' for
* a Shift+Tab. Or 'ignore' for Tab at the endTerm, or for Shift+Tab at
* the first Edit-Term. On 'ignore', Term lets the event pass to the browser.
*/
onKeyTab(index, str) {
if (str == 'ignore') return this.hidePopup();
this.moveInputToNextEditTerm(index, str ? -1 : 1);
},
moveInputToNextEditTerm(index, step) {
this.hidePopup();
var j = this.getNextEditTermIndex(index, step);
if (j == index && !this.cycleOnTab) this.focusNextOutsideTheTerms(step);
else this.moveInputTo(j);
this.moveInputTo( this.getNextEditTermIndex(index, step) );
},
Expand All @@ -508,23 +522,8 @@ export default {
var pos = index;
while (1) { // eslint-disable-line no-constant-condition
pos = (pos + step + n) % n; // Search moving 1 step right/left cyclingly.
if (pos == index || to.isEditable(this.terms[pos])) break;
if (pos == index || to.isEditable(this.terms[pos])) return pos;
}
// If cycling not allowed but it did, return the given `index`.
return !this.cycleOnTab && step * pos <= step * index ? index : pos;
},
focusNextOutsideTheTerms(step) {
var a = [].map.call( document.querySelectorAll([
'input', 'select', 'a[href]', 'textarea', 'button', '[tabindex]'
]), (e, i) => ({ e, i }) )
.filter(o => o.e.tabIndex >= 0 && !o.e.disabled && o.e.offsetParent)
.sort((o, p) => o.e.tabIndex === p.e.tabIndex ? o.i - p.i :
(o.e.tabIndex || 9e9) - (p.e.tabIndex || 9e9))
.map(o => o.e);
var e = a[(a.indexOf(this.inputElement()) + step + a.length) % a.length];
if (e) e.focus();
},
Expand Down

0 comments on commit e19248c

Please sign in to comment.