Resolve IE11 single select box issue by not opening dropdown on focus + use objects instead of switch statements

This commit is contained in:
Josh Johnson 2016-09-04 15:23:19 +01:00
parent 2386ffab81
commit 349b14386e
4 changed files with 522 additions and 556 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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;
}
} }
} }