mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-17 21:16:34 +02:00
make addItems to work with select boxes, handle special situations like disabled items, highlighting
This commit is contained in:
parent
b86afadd59
commit
2b89d38a68
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue