mirror of
https://github.com/Choices-js/Choices.git
synced 2024-06-01 13:32:23 +02:00
Resolve IE11 single select box issue by not opening dropdown on focus + use objects instead of switch statements
This commit is contained in:
parent
2386ffab81
commit
349b14386e
771
assets/scripts/dist/choices.js
vendored
771
assets/scripts/dist/choices.js
vendored
File diff suppressed because it is too large
Load diff
2
assets/scripts/dist/choices.js.map
vendored
2
assets/scripts/dist/choices.js.map
vendored
File diff suppressed because one or more lines are too long
6
assets/scripts/dist/choices.min.js
vendored
6
assets/scripts/dist/choices.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -148,9 +148,6 @@ export default class Choices {
|
||||||
this._onPaste = this._onPaste.bind(this);
|
this._onPaste = this._onPaste.bind(this);
|
||||||
this._onInput = this._onInput.bind(this);
|
this._onInput = this._onInput.bind(this);
|
||||||
|
|
||||||
// Focus containerOuter but not show dropdown if true
|
|
||||||
this.focusAndHideDropdown = false;
|
|
||||||
|
|
||||||
// Monitor touch taps/scrolls
|
// Monitor touch taps/scrolls
|
||||||
this.wasTap = true;
|
this.wasTap = true;
|
||||||
|
|
||||||
|
@ -689,7 +686,6 @@ export default class Choices {
|
||||||
|
|
||||||
// Keep focus on select-one element
|
// Keep focus on select-one element
|
||||||
if (this.passedElement.type === 'select-one') {
|
if (this.passedElement.type === 'select-one') {
|
||||||
this.focusAndHideDropdown = true;
|
|
||||||
this.containerOuter.focus();
|
this.containerOuter.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -963,7 +959,12 @@ export default class Choices {
|
||||||
|
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
|
|
||||||
const ctrlDownKey = e.ctrlKey || e.metaKey;
|
const activeItems = this.store.getItemsFilteredByActive();
|
||||||
|
const hasFocusedInput = this.input === document.activeElement;
|
||||||
|
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||||
|
const hasItems = this.itemList && this.itemList.children;
|
||||||
|
const keyString = String.fromCharCode(e.keyCode);
|
||||||
|
|
||||||
const backKey = 46;
|
const backKey = 46;
|
||||||
const deleteKey = 8;
|
const deleteKey = 8;
|
||||||
const enterKey = 13;
|
const enterKey = 13;
|
||||||
|
@ -971,128 +972,133 @@ export default class Choices {
|
||||||
const escapeKey = 27;
|
const escapeKey = 27;
|
||||||
const upKey = 38;
|
const upKey = 38;
|
||||||
const downKey = 40;
|
const downKey = 40;
|
||||||
|
const ctrlDownKey = e.ctrlKey || e.metaKey;
|
||||||
const activeItems = this.store.getItemsFilteredByActive();
|
|
||||||
const hasFocusedInput = this.input === document.activeElement;
|
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
|
||||||
const hasItems = this.itemList && this.itemList.children;
|
|
||||||
const keyString = String.fromCharCode(e.keyCode);
|
|
||||||
|
|
||||||
// If a user is typing and the dropdown is not active
|
// If a user is typing and the dropdown is not active
|
||||||
if (this.passedElement.type !== 'text' && /[a-zA-Z0-9-_ ]/.test(keyString) && !hasActiveDropdown) {
|
if (this.passedElement.type !== 'text' && /[a-zA-Z0-9-_ ]/.test(keyString) && !hasActiveDropdown) {
|
||||||
this.showDropdown();
|
this.showDropdown(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canSearch = this.config.search;
|
this.canSearch = this.config.search;
|
||||||
|
|
||||||
switch (e.keyCode) {
|
const onAKey = () => {
|
||||||
case aKey:
|
// If CTRL + A or CMD + A have been pressed and there are items to select
|
||||||
// If CTRL + A or CMD + A have been pressed and there are items to select
|
if (ctrlDownKey && hasItems) {
|
||||||
if (ctrlDownKey && hasItems) {
|
this.canSearch = false;
|
||||||
this.canSearch = false;
|
if (this.config.removeItems && !this.input.value && this.input === document.activeElement) {
|
||||||
if (this.config.removeItems && !this.input.value && this.input === document.activeElement) {
|
// Highlight items
|
||||||
// Highlight items
|
this.highlightAll(this.itemList.children);
|
||||||
this.highlightAll(this.itemList.children);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
};
|
||||||
|
|
||||||
case enterKey:
|
const onEnterKey = () => {
|
||||||
// If enter key is pressed and the input has a value
|
// If enter key is pressed and the input has a value
|
||||||
if (this.passedElement.type === 'text' && target.value) {
|
if (this.passedElement.type === 'text' && target.value) {
|
||||||
const value = this.input.value;
|
const value = this.input.value;
|
||||||
const canAddItem = this._canAddItem(activeItems, value);
|
const canAddItem = this._canAddItem(activeItems, value);
|
||||||
|
|
||||||
// All is good, add
|
// All is good, add
|
||||||
if (canAddItem.response) {
|
if (canAddItem.response) {
|
||||||
if (hasActiveDropdown) {
|
if (hasActiveDropdown) {
|
||||||
this.hideDropdown();
|
|
||||||
}
|
|
||||||
this._addItem(value);
|
|
||||||
this._triggerChange(value);
|
|
||||||
this.clearInput(this.passedElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.hasAttribute('data-button')) {
|
|
||||||
this._handleButtonAction(activeItems, target);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasActiveDropdown) {
|
|
||||||
const highlighted = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
|
|
||||||
|
|
||||||
if (highlighted) {
|
|
||||||
this._handleChoiceAction(activeItems, highlighted);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We always want to hide the dropdown for single selects
|
|
||||||
// regardless of whether an item was added
|
|
||||||
if (hasActiveDropdown && this.passedElement.type === 'select-one') {
|
|
||||||
this.hideDropdown();
|
this.hideDropdown();
|
||||||
}
|
}
|
||||||
} else if (this.passedElement.type === 'select-one') {
|
this._addItem(value);
|
||||||
// Open single select dropdown if it's not active
|
this._triggerChange(value);
|
||||||
if (!hasActiveDropdown) {
|
this.clearInput(this.passedElement);
|
||||||
this.showDropdown(true);
|
}
|
||||||
e.preventDefault();
|
}
|
||||||
}
|
|
||||||
|
if (target.hasAttribute('data-button')) {
|
||||||
|
this._handleButtonAction(activeItems, target);
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasActiveDropdown) {
|
||||||
|
const highlighted = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
|
||||||
|
|
||||||
|
// If we have a highlighted choice
|
||||||
|
if (highlighted) {
|
||||||
|
this._handleChoiceAction(activeItems, highlighted);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
// We always want to hide the dropdown for single selects
|
||||||
|
// regardless of whether an item was added
|
||||||
case escapeKey:
|
if (hasActiveDropdown && this.passedElement.type === 'select-one') {
|
||||||
if (hasActiveDropdown) this.toggleDropdown();
|
this.hideDropdown();
|
||||||
break;
|
}
|
||||||
|
} else if (this.passedElement.type === 'select-one') {
|
||||||
case downKey:
|
// Open single select dropdown if it's not active
|
||||||
case upKey:
|
if (!hasActiveDropdown) {
|
||||||
// If up or down key is pressed, traverse through options
|
this.showDropdown(true);
|
||||||
if (hasActiveDropdown || this.passedElement.type === 'select-one') {
|
|
||||||
// Show dropdown if focus
|
|
||||||
if (!hasActiveDropdown) {
|
|
||||||
this.showDropdown(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentEl = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
|
|
||||||
const directionInt = e.keyCode === downKey ? 1 : -1;
|
|
||||||
let nextEl;
|
|
||||||
|
|
||||||
this.canSearch = false;
|
|
||||||
|
|
||||||
if (currentEl) {
|
|
||||||
nextEl = getAdjacentEl(currentEl, '[data-choice-selectable]', directionInt);
|
|
||||||
} else {
|
|
||||||
nextEl = this.dropdown.querySelector('[data-choice-selectable]');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextEl) {
|
|
||||||
// We prevent default to stop the cursor moving
|
|
||||||
// when pressing the arrow
|
|
||||||
if (!isScrolledIntoView(nextEl, this.choiceList, directionInt)) {
|
|
||||||
this._scrollToChoice(nextEl, directionInt);
|
|
||||||
}
|
|
||||||
this._highlightChoice(nextEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent default to maintain cursor position whilst
|
|
||||||
// traversing dropdown options
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
};
|
||||||
|
|
||||||
case backKey:
|
const onEscapeKey = () => {
|
||||||
case deleteKey:
|
if (hasActiveDropdown) {
|
||||||
// If backspace or delete key is pressed and the input has no value
|
this.toggleDropdown();
|
||||||
if (hasFocusedInput && !e.target.value && this.passedElement.type !== 'select-one') {
|
}
|
||||||
this._handleBackspace(activeItems);
|
};
|
||||||
e.preventDefault();
|
|
||||||
|
const onDirectionKey = () => {
|
||||||
|
// If up or down key is pressed, traverse through options
|
||||||
|
if (hasActiveDropdown || this.passedElement.type === 'select-one') {
|
||||||
|
// Show dropdown if focus
|
||||||
|
if (!hasActiveDropdown) {
|
||||||
|
this.showDropdown(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
const currentEl = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
|
||||||
|
const directionInt = e.keyCode === downKey ? 1 : -1;
|
||||||
|
let nextEl;
|
||||||
|
|
||||||
default:
|
this.canSearch = false;
|
||||||
break;
|
|
||||||
|
if (currentEl) {
|
||||||
|
nextEl = getAdjacentEl(currentEl, '[data-choice-selectable]', directionInt);
|
||||||
|
} else {
|
||||||
|
nextEl = this.dropdown.querySelector('[data-choice-selectable]');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextEl) {
|
||||||
|
// We prevent default to stop the cursor moving
|
||||||
|
// when pressing the arrow
|
||||||
|
if (!isScrolledIntoView(nextEl, this.choiceList, directionInt)) {
|
||||||
|
this._scrollToChoice(nextEl, directionInt);
|
||||||
|
}
|
||||||
|
this._highlightChoice(nextEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent default to maintain cursor position whilst
|
||||||
|
// traversing dropdown options
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteKey = () => {
|
||||||
|
// If backspace or delete key is pressed and the input has no value
|
||||||
|
if (hasFocusedInput && !e.target.value && this.passedElement.type !== 'select-one') {
|
||||||
|
this._handleBackspace(activeItems);
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map keys to key actions
|
||||||
|
const keyDownActions = {
|
||||||
|
[aKey]: onAKey,
|
||||||
|
[enterKey]: onEnterKey,
|
||||||
|
[escapeKey]: onEscapeKey,
|
||||||
|
[upKey]: onDirectionKey,
|
||||||
|
[downKey]: onDirectionKey,
|
||||||
|
[deleteKey]: onDeleteKey,
|
||||||
|
[backKey]: onDeleteKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
// If keycode has a function, run it
|
||||||
|
if (keyDownActions[e.keyCode]) {
|
||||||
|
keyDownActions[e.keyCode]();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1326,47 +1332,22 @@ export default class Choices {
|
||||||
// If target is something that concerns us
|
// If target is something that concerns us
|
||||||
if (this.containerOuter.contains(target)) {
|
if (this.containerOuter.contains(target)) {
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||||
|
const focusActions = {
|
||||||
switch (this.passedElement.type) {
|
text: () => {
|
||||||
case 'text': {
|
|
||||||
if (target === this.input) {
|
if (target === this.input) {
|
||||||
this.containerOuter.classList.add(this.config.classNames.focusState);
|
this.containerOuter.classList.add(this.config.classNames.focusState);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
break;
|
'select-one': () => {
|
||||||
}
|
this.containerOuter.classList.add(this.config.classNames.focusState);
|
||||||
case 'select-one': {
|
|
||||||
if (target === this.containerOuter) {
|
|
||||||
// If element is a select box, the focussed element is the container and the dropdown
|
|
||||||
// isn't already open, focus and show dropdown
|
|
||||||
this.containerOuter.classList.add(this.config.classNames.focusState);
|
|
||||||
|
|
||||||
// Show dropdown if it isn't already showing
|
|
||||||
if (!hasActiveDropdown) {
|
|
||||||
if (!this.focusAndHideDropdown && this.canSearch && document.activeElement !== this.input) {
|
|
||||||
this.showDropdown(true);
|
|
||||||
} else {
|
|
||||||
this.showDropdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.focusAndHideDropdown = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target === this.input) {
|
if (target === this.input) {
|
||||||
// If element is a select box, the focussed element is the container and the dropdown
|
|
||||||
// isn't already open, focus and show dropdown
|
|
||||||
this.containerOuter.classList.add(this.config.classNames.focusState);
|
|
||||||
|
|
||||||
// Show dropdown if it isn't already showing
|
// Show dropdown if it isn't already showing
|
||||||
if (!hasActiveDropdown) {
|
if (!hasActiveDropdown) {
|
||||||
this.showDropdown();
|
this.showDropdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
break;
|
'select-multiple': () => {
|
||||||
}
|
|
||||||
case 'select-multiple': {
|
|
||||||
if (target === this.input) {
|
if (target === this.input) {
|
||||||
// If element is a select box, the focussed element is the container and the dropdown
|
// If element is a select box, the focussed element is the container and the dropdown
|
||||||
// isn't already open, focus and show dropdown
|
// isn't already open, focus and show dropdown
|
||||||
|
@ -1376,13 +1357,10 @@ export default class Choices {
|
||||||
this.showDropdown(true);
|
this.showDropdown(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
break;
|
focusActions[this.passedElement.type]();
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1399,9 +1377,8 @@ export default class Choices {
|
||||||
const activeItems = this.store.getItemsFilteredByActive();
|
const activeItems = this.store.getItemsFilteredByActive();
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||||
const hasHighlightedItems = activeItems.some((item) => item.highlighted === true);
|
const hasHighlightedItems = activeItems.some((item) => item.highlighted === true);
|
||||||
|
const blurActions = {
|
||||||
switch (this.passedElement.type) {
|
text: () => {
|
||||||
case 'text': {
|
|
||||||
if (target === this.input) {
|
if (target === this.input) {
|
||||||
// Remove the focus state
|
// Remove the focus state
|
||||||
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
||||||
|
@ -1414,13 +1391,10 @@ export default class Choices {
|
||||||
this.hideDropdown();
|
this.hideDropdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
break;
|
'select-one': () => {
|
||||||
}
|
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
||||||
case 'select-one': {
|
|
||||||
if (target === this.containerOuter) {
|
if (target === this.containerOuter) {
|
||||||
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
|
||||||
|
|
||||||
// Hide dropdown if it is showing
|
// Hide dropdown if it is showing
|
||||||
if (hasActiveDropdown && !this.canSearch) {
|
if (hasActiveDropdown && !this.canSearch) {
|
||||||
this.hideDropdown();
|
this.hideDropdown();
|
||||||
|
@ -1428,17 +1402,13 @@ export default class Choices {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target === this.input) {
|
if (target === this.input) {
|
||||||
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
|
||||||
|
|
||||||
// Hide dropdown if it is showing
|
// Hide dropdown if it is showing
|
||||||
if (hasActiveDropdown) {
|
if (hasActiveDropdown) {
|
||||||
this.hideDropdown();
|
this.hideDropdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
break;
|
'select-multiple': () => {
|
||||||
}
|
|
||||||
case 'select-multiple': {
|
|
||||||
if (target === this.input) {
|
if (target === this.input) {
|
||||||
// Remove the focus state
|
// Remove the focus state
|
||||||
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
||||||
|
@ -1450,13 +1420,10 @@ export default class Choices {
|
||||||
this.unhighlightAll();
|
this.unhighlightAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
break;
|
blurActions[this.passedElement.type]();
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue