make addItems to work with select boxes, handle special situations like disabled items, highlighting

This commit is contained in:
alex 2020-04-16 00:19:13 +02:00
parent b86afadd59
commit 2b89d38a68
2 changed files with 135 additions and 39 deletions

View file

@ -791,7 +791,16 @@ class Choices implements Choices {
} }
_renderChoices(): void { _renderChoices(): void {
const { activeGroups, activeChoices } = this._store; const {
activeItems,
disabledChoices,
choices,
activeGroups,
activeChoices,
} = this._store;
const { value } = this.input;
const canAddItem = this._canAddItem(activeItems, value);
let choiceListFragment = document.createDocumentFragment(); let choiceListFragment = document.createDocumentFragment();
this.choiceList.clear(); this.choiceList.clear();
@ -830,15 +839,37 @@ class Choices implements Choices {
choiceListFragment.childNodes && choiceListFragment.childNodes &&
choiceListFragment.childNodes.length > 0 choiceListFragment.childNodes.length > 0
) { ) {
const { activeItems } = this._store; let addNotice = false;
const canAddItem = this._canAddItem(activeItems, this.input.value);
// ...and we can select them // ...and we can select them
if (canAddItem.response) { if (canAddItem.response) {
// ...append them and highlight the first choice // ...append them
this.choiceList.append(choiceListFragment); this.choiceList.append(choiceListFragment);
this._highlightChoice(); // ...handle case that items can be added
if (this.config.addItems && value) {
const isInChoices = existsInArray(choices, value);
addNotice = true;
if (
this._isSelectMultipleElement &&
existsInArray(disabledChoices, value)
) {
// adding disabled element is disabled here, so remove message
addNotice = false;
} else if (this._isSelectOneElement && isInChoices) {
// adding existing element is disabled here, so remove message
addNotice = false;
}
// ...highlight the first choice when element exists in choices
if (isInChoices) {
this._highlightChoice();
}
} else {
// ...highlight the first choice
this._highlightChoice();
}
} else { } else {
addNotice = true;
}
if (addNotice) {
const notice = this._getTemplate('notice', canAddItem.notice); const notice = this._getTemplate('notice', canAddItem.notice);
this.choiceList.append(notice); this.choiceList.append(notice);
} }
@ -847,7 +878,9 @@ class Choices implements Choices {
let dropdownItem; let dropdownItem;
let notice; let notice;
if (this._isSearching) { if (this.config.addItems && canAddItem.response && value) {
dropdownItem = this._getTemplate('notice', canAddItem.notice);
} else if (this._isSearching) {
notice = notice =
typeof this.config.noResultsText === 'function' typeof this.config.noResultsText === 'function'
? this.config.noResultsText() ? this.config.noResultsText()
@ -862,7 +895,6 @@ class Choices implements Choices {
dropdownItem = this._getTemplate('notice', notice, 'no-choices'); dropdownItem = this._getTemplate('notice', notice, 'no-choices');
} }
this.choiceList.append(dropdownItem); this.choiceList.append(dropdownItem);
} }
} }
@ -1294,20 +1326,19 @@ class Choices implements Choices {
? this.config.uniqueItemText(value) ? this.config.uniqueItemText(value)
: this.config.uniqueItemText; : this.config.uniqueItemText;
} }
}
if ( if (
this._isTextElement && this.config.addItems &&
this.config.addItems && canAddItem &&
canAddItem && typeof this.config.addItemFilter === 'function' &&
typeof this.config.addItemFilter === 'function' && !this.config.addItemFilter(value)
!this.config.addItemFilter(value) ) {
) { canAddItem = false;
canAddItem = false; notice =
notice = typeof this.config.customAddItemText === 'function'
typeof this.config.customAddItemText === 'function' ? this.config.customAddItemText(value)
? this.config.customAddItemText(value) : this.config.customAddItemText;
: this.config.customAddItemText;
}
} }
return { return {
@ -1436,7 +1467,7 @@ class Choices implements Choices {
_onKeyDown(event: KeyboardEvent): void { _onKeyDown(event: KeyboardEvent): void {
const { keyCode } = event; const { keyCode } = event;
const { activeItems } = this._store; const { activeItems, choices, disabledChoices } = this._store;
const hasFocusedInput = this.input.isFocussed; const hasFocusedInput = this.input.isFocussed;
const hasActiveDropdown = this.dropdown.isActive; const hasActiveDropdown = this.dropdown.isActive;
const hasItems = this.itemList.hasChildren(); const hasItems = this.itemList.hasChildren();
@ -1473,14 +1504,44 @@ class Choices implements Choices {
case A_KEY: case A_KEY:
return this._onSelectKey(event, hasItems); return this._onSelectKey(event, hasItems);
case ENTER_KEY: case ENTER_KEY:
return this._onEnterKey(event, activeItems, hasActiveDropdown); // existing choices are not producable
if (this._isSelectOneElement) {
return this._onEnterKey(
event,
activeItems,
choices,
hasActiveDropdown,
);
}
return this._onEnterKey(
event,
activeItems,
disabledChoices,
hasActiveDropdown,
);
case ESC_KEY: case ESC_KEY:
return this._onEscapeKey(hasActiveDropdown); return this._onEscapeKey(hasActiveDropdown);
case UP_KEY: case UP_KEY:
case PAGE_UP_KEY: case PAGE_UP_KEY:
case DOWN_KEY: case DOWN_KEY:
case PAGE_DOWN_KEY: case PAGE_DOWN_KEY:
return this._onDirectionKey(event, hasActiveDropdown); // don't activate deselect for existing choices
if (this._isSelectOneElement) {
return this._onDirectionKey(
event,
activeItems,
choices,
hasActiveDropdown,
);
}
return this._onDirectionKey(
event,
activeItems,
disabledChoices,
hasActiveDropdown,
);
case DELETE_KEY: case DELETE_KEY:
case BACK_KEY: case BACK_KEY:
return this._onDeleteKey(event, activeItems, hasFocusedInput); return this._onDeleteKey(event, activeItems, hasFocusedInput);
@ -1549,24 +1610,14 @@ class Choices implements Choices {
_onEnterKey( _onEnterKey(
event: KeyboardEvent, event: KeyboardEvent,
activeItems: Item[], activeItems: Item[],
nonproducibleChoices: Choice[],
hasActiveDropdown: boolean, hasActiveDropdown: boolean,
): void { ): void {
const { target } = event; const { target } = event;
const { ENTER_KEY: enterKey } = KEY_CODES; const { ENTER_KEY: enterKey } = KEY_CODES;
const targetWasButton = const targetWasButton =
target && (target as HTMLElement).hasAttribute('data-button'); target && (target as HTMLElement).hasAttribute('data-button');
let highlightedChoice: null | HTMLElement = null;
if (this._isTextElement && target && (target as HTMLInputElement).value) {
const { value } = this.input;
const canAddItem = this._canAddItem(activeItems, value);
if (canAddItem.response) {
this.hideDropdown(true);
this._addItem({ value });
this._triggerChange(value);
this.clearInput();
}
}
if (targetWasButton) { if (targetWasButton) {
this._handleButtonAction(activeItems, target as HTMLElement); this._handleButtonAction(activeItems, target as HTMLElement);
@ -1574,7 +1625,7 @@ class Choices implements Choices {
} }
if (hasActiveDropdown) { if (hasActiveDropdown) {
const highlightedChoice = this.dropdown.getChild( highlightedChoice = this.dropdown.getChild(
`.${this.config.classNames.highlightedState}`, `.${this.config.classNames.highlightedState}`,
); );
@ -1590,6 +1641,25 @@ class Choices implements Choices {
} else if (this._isSelectOneElement) { } else if (this._isSelectOneElement) {
this.showDropdown(); this.showDropdown();
event.preventDefault(); event.preventDefault();
return;
}
if (
this.config.addItems &&
!highlightedChoice &&
target &&
(target as HTMLInputElement).value
) {
const { value } = this.input;
const canAddItem = this._canAddItem(activeItems, value);
if (canAddItem.response && !existsInArray(nonproducibleChoices, value)) {
this.hideDropdown(true);
this._setChoiceOrItem({ value });
this._triggerChange(value);
this.clearInput();
}
} }
} }
@ -1600,8 +1670,13 @@ class Choices implements Choices {
} }
} }
_onDirectionKey(event: KeyboardEvent, hasActiveDropdown: boolean): void { _onDirectionKey(
const { keyCode, metaKey } = event; event: KeyboardEvent,
activeItems: Item[],
nonproducibleChoices: Choice[],
hasActiveDropdown: boolean,
): void {
const { keyCode, metaKey, target } = event;
const { const {
DOWN_KEY: downKey, DOWN_KEY: downKey,
PAGE_UP_KEY: pageUpKey, PAGE_UP_KEY: pageUpKey,
@ -1656,6 +1731,20 @@ class Choices implements Choices {
this.choiceList.scrollToChildElement(nextEl, directionInt); this.choiceList.scrollToChildElement(nextEl, directionInt);
} }
this._highlightChoice(nextEl); this._highlightChoice(nextEl);
} else if (
this.config.addItems &&
target &&
(target as HTMLInputElement).value
) {
// can unselect all items for creating new options
const { value } = this.input;
const canAddItem = this._canAddItem(activeItems, value);
if (
canAddItem.response &&
!existsInArray(nonproducibleChoices, value)
) {
this.unhighlightAll();
}
} }
// Prevent default to maintain cursor position whilst // Prevent default to maintain cursor position whilst

View file

@ -73,6 +73,13 @@ export default class Store {
return this.choices.filter((choice) => choice.active === true); return this.choices.filter((choice) => choice.active === true);
} }
/**
* Get disabled choices from store
*/
get disabledChoices(): Choice[] {
return this.choices.filter(choice => choice.disabled === true);
}
/** /**
* Get selectable choices from store * Get selectable choices from store
*/ */