feat(autocomplete): implement shift modifier interactions
parent
f481158d36
commit
db25dbae3f
|
@ -102,6 +102,26 @@
|
||||||
<td><i>pageDown</i></td>
|
<td><i>pageDown</i></td>
|
||||||
<td>Jumps visual focus to last option.</td>
|
<td>Jumps visual focus to last option.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><i>shift</i> + <i>down arrow</i></td>
|
||||||
|
<td>Moves focus to the next option and toggles the selection state.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><i>shift</i> + <i>up arrow</i></td>
|
||||||
|
<td>Moves focus to the previous option and toggles the selection state.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><i>shift</i> + <i>space</i></td>
|
||||||
|
<td>Selects the items between the most recently selected option and the focused option.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><i>control</i> + <i>shift</i> + <i>home</i></td>
|
||||||
|
<td>Selects the focused options and all the options up to the first one.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><i>control</i> + <i>shift</i> + <i>end</i></td>
|
||||||
|
<td>Selects the focused options and all the options down to the last one.</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -212,6 +212,7 @@ export default {
|
||||||
virtualScroller: null,
|
virtualScroller: null,
|
||||||
searchTimeout: null,
|
searchTimeout: null,
|
||||||
dirty: false,
|
dirty: false,
|
||||||
|
startRangeIndex: -1,
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: this.$attrs.id,
|
id: this.$attrs.id,
|
||||||
|
@ -389,6 +390,7 @@ export default {
|
||||||
|
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'NumpadEnter':
|
case 'NumpadEnter':
|
||||||
|
case 'Space':
|
||||||
this.onEnterKey(event);
|
this.onEnterKey(event);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -400,6 +402,11 @@ export default {
|
||||||
this.onTabKey(event);
|
this.onTabKey(event);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'ShiftLeft':
|
||||||
|
case 'ShiftRight':
|
||||||
|
this.onShiftKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'Backspace':
|
case 'Backspace':
|
||||||
this.onBackspaceKey(event);
|
this.onBackspaceKey(event);
|
||||||
break;
|
break;
|
||||||
|
@ -553,6 +560,21 @@ export default {
|
||||||
this.changeFocusedOptionIndex(event, index);
|
this.changeFocusedOptionIndex(event, index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onOptionSelectRange(event, start = -1, end = -1) {
|
||||||
|
start === -1 && (start = this.findNearestSelectedOptionIndex(end, true));
|
||||||
|
end === -1 && (end = this.findNearestSelectedOptionIndex(start));
|
||||||
|
|
||||||
|
if (start !== -1 && end !== -1) {
|
||||||
|
const rangeStart = Math.min(start, end);
|
||||||
|
const rangeEnd = Math.max(start, end);
|
||||||
|
const value = this.visibleOptions
|
||||||
|
.slice(rangeStart, rangeEnd + 1)
|
||||||
|
.filter((option) => this.isValidOption(option))
|
||||||
|
.map((option) => this.getOptionValue(option));
|
||||||
|
|
||||||
|
this.updateModel(event, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
onOverlayClick(event) {
|
onOverlayClick(event) {
|
||||||
OverlayEventBus.emit('overlay-click', {
|
OverlayEventBus.emit('overlay-click', {
|
||||||
originalEvent: event,
|
originalEvent: event,
|
||||||
|
@ -576,6 +598,10 @@ export default {
|
||||||
|
|
||||||
const optionIndex = this.focusedOptionIndex !== -1 ? this.findNextOptionIndex(this.focusedOptionIndex) : this.clicked ? this.findFirstOptionIndex() : this.findFirstFocusedOptionIndex();
|
const optionIndex = this.focusedOptionIndex !== -1 ? this.findNextOptionIndex(this.focusedOptionIndex) : this.clicked ? this.findFirstOptionIndex() : this.findFirstFocusedOptionIndex();
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
this.onOptionSelectRange(event, this.startRangeIndex, optionIndex);
|
||||||
|
}
|
||||||
|
|
||||||
this.changeFocusedOptionIndex(event, optionIndex);
|
this.changeFocusedOptionIndex(event, optionIndex);
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -595,6 +621,10 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
const optionIndex = this.focusedOptionIndex !== -1 ? this.findPrevOptionIndex(this.focusedOptionIndex) : this.clicked ? this.findLastOptionIndex() : this.findLastFocusedOptionIndex();
|
const optionIndex = this.focusedOptionIndex !== -1 ? this.findPrevOptionIndex(this.focusedOptionIndex) : this.clicked ? this.findLastOptionIndex() : this.findLastFocusedOptionIndex();
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
this.onOptionSelectRange(event, optionIndex, this.startRangeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
this.changeFocusedOptionIndex(event, optionIndex);
|
this.changeFocusedOptionIndex(event, optionIndex);
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -622,6 +652,12 @@ export default {
|
||||||
onHomeKey(event) {
|
onHomeKey(event) {
|
||||||
const { currentTarget } = event;
|
const { currentTarget } = event;
|
||||||
const len = currentTarget.value.length;
|
const len = currentTarget.value.length;
|
||||||
|
const metaKey = event.metaKey || event.ctrlKey;
|
||||||
|
const optionIndex = this.findFirstOptionIndex();
|
||||||
|
|
||||||
|
if (event.shiftKey && metaKey) {
|
||||||
|
this.onOptionSelectRange(event, optionIndex, this.startRangeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
currentTarget.setSelectionRange(0, event.shiftKey ? len : 0);
|
currentTarget.setSelectionRange(0, event.shiftKey ? len : 0);
|
||||||
this.focusedOptionIndex = -1;
|
this.focusedOptionIndex = -1;
|
||||||
|
@ -631,6 +667,12 @@ export default {
|
||||||
onEndKey(event) {
|
onEndKey(event) {
|
||||||
const { currentTarget } = event;
|
const { currentTarget } = event;
|
||||||
const len = currentTarget.value.length;
|
const len = currentTarget.value.length;
|
||||||
|
const metaKey = event.metaKey || event.ctrlKey;
|
||||||
|
const optionIndex = this.findLastOptionIndex();
|
||||||
|
|
||||||
|
if (event.shiftKey && metaKey) {
|
||||||
|
this.onOptionSelectRange(event, this.startRangeIndex, optionIndex);
|
||||||
|
}
|
||||||
|
|
||||||
currentTarget.setSelectionRange(event.shiftKey ? 0 : len, len);
|
currentTarget.setSelectionRange(event.shiftKey ? 0 : len, len);
|
||||||
this.focusedOptionIndex = -1;
|
this.focusedOptionIndex = -1;
|
||||||
|
@ -657,7 +699,12 @@ export default {
|
||||||
this.onArrowDownKey(event);
|
this.onArrowDownKey(event);
|
||||||
} else {
|
} else {
|
||||||
if (this.focusedOptionIndex !== -1) {
|
if (this.focusedOptionIndex !== -1) {
|
||||||
this.onOptionSelect(event, this.visibleOptions[this.focusedOptionIndex]);
|
if (event.shiftKey) {
|
||||||
|
this.onOptionSelectRange(event, this.focusedOptionIndex);
|
||||||
|
event.preventDefault();
|
||||||
|
} else {
|
||||||
|
this.onOptionSelect(event, this.visibleOptions[this.focusedOptionIndex]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hide();
|
this.hide();
|
||||||
|
@ -677,6 +724,9 @@ export default {
|
||||||
|
|
||||||
this.overlayVisible && this.hide();
|
this.overlayVisible && this.hide();
|
||||||
},
|
},
|
||||||
|
onShiftKey() {
|
||||||
|
this.startRangeIndex = this.focusedOptionIndex;
|
||||||
|
},
|
||||||
onBackspaceKey(event) {
|
onBackspaceKey(event) {
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
if (isNotEmpty(this.d_value) && !this.$refs.focusInput.value) {
|
if (isNotEmpty(this.d_value) && !this.$refs.focusInput.value) {
|
||||||
|
@ -923,6 +973,31 @@ export default {
|
||||||
},
|
},
|
||||||
virtualScrollerRef(el) {
|
virtualScrollerRef(el) {
|
||||||
this.virtualScroller = el;
|
this.virtualScroller = el;
|
||||||
|
},
|
||||||
|
findNextSelectedOptionIndex(index) {
|
||||||
|
const matchedOptionIndex = this.$filled && index < this.visibleOptions.length - 1 ? this.visibleOptions.slice(index + 1).findIndex((option) => this.isValidSelectedOption(option)) : -1;
|
||||||
|
|
||||||
|
return matchedOptionIndex > -1 ? matchedOptionIndex + index + 1 : -1;
|
||||||
|
},
|
||||||
|
findPrevSelectedOptionIndex(index) {
|
||||||
|
const matchedOptionIndex = this.$filled && index > 0 ? findLastIndex(this.visibleOptions.slice(0, index), (option) => this.isValidSelectedOption(option)) : -1;
|
||||||
|
|
||||||
|
return matchedOptionIndex > -1 ? matchedOptionIndex : -1;
|
||||||
|
},
|
||||||
|
findNearestSelectedOptionIndex(index, firstCheckUp = false) {
|
||||||
|
let matchedOptionIndex = -1;
|
||||||
|
|
||||||
|
if (this.$filled) {
|
||||||
|
if (firstCheckUp) {
|
||||||
|
matchedOptionIndex = this.findPrevSelectedOptionIndex(index);
|
||||||
|
matchedOptionIndex = matchedOptionIndex === -1 ? this.findNextSelectedOptionIndex(index) : matchedOptionIndex;
|
||||||
|
} else {
|
||||||
|
matchedOptionIndex = this.findNextSelectedOptionIndex(index);
|
||||||
|
matchedOptionIndex = matchedOptionIndex === -1 ? this.findPrevSelectedOptionIndex(index) : matchedOptionIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedOptionIndex > -1 ? matchedOptionIndex : index;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
Loading…
Reference in New Issue