mirror of
https://github.com/Choices-js/Choices.git
synced 2026-03-14 14:45:47 +01:00
Update event listener handling for the dropdown container
This commit is contained in:
parent
71f9b01534
commit
5860b55050
1 changed files with 105 additions and 35 deletions
|
|
@ -264,7 +264,7 @@ class Choices {
|
|||
|
||||
this._store = new Store(config);
|
||||
this._currentValue = '';
|
||||
config.searchEnabled = (!isText && config.searchEnabled) || isSelectMultiple;
|
||||
config.searchEnabled = !isText && config.searchEnabled;
|
||||
this._canSearch = config.searchEnabled;
|
||||
this._isScrollingOnIe = false;
|
||||
this._highlightPosition = 0;
|
||||
|
|
@ -310,6 +310,7 @@ class Choices {
|
|||
this._onDeleteKey = this._onDeleteKey.bind(this);
|
||||
this._onChange = this._onChange.bind(this);
|
||||
this._onInvalid = this._onInvalid.bind(this);
|
||||
this._onWindowResize = this._onWindowResize.bind(this);
|
||||
|
||||
// If element has already been initialised with Choices, fail silently
|
||||
if (this.passedElement.isActive) {
|
||||
|
|
@ -528,23 +529,17 @@ class Choices {
|
|||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const containerRect = this.containerOuter.element.getBoundingClientRect();
|
||||
const dropdownElement = this.dropdown.element;
|
||||
|
||||
if (this._dropdownDetached) {
|
||||
dropdownElement.style.top = `${window.scrollY + containerRect.bottom}px`;
|
||||
dropdownElement.style.left = `${containerRect.left}px`;
|
||||
dropdownElement.style.width = `${containerRect.width}px`;
|
||||
this.setHorizontalDropdownPosition();
|
||||
}
|
||||
|
||||
this.dropdown.show();
|
||||
|
||||
const dropdownRect = dropdownElement.getBoundingClientRect();
|
||||
const flipped = this.containerOuter.open(dropdownElement, dropdownRect.bottom, dropdownRect.height);
|
||||
const rect = this.dropdown.element.getBoundingClientRect();
|
||||
const dropdownAbove = this.containerOuter.open(rect.bottom, rect.height, this.dropdown.element);
|
||||
|
||||
if (this._dropdownDetached && flipped) {
|
||||
dropdownElement.style.top = 'auto'; // ToDo: calc from bottom or top - find a better way
|
||||
dropdownElement.style.bottom = `${document.body.offsetHeight - containerRect.top}px`; // /*- (containerRect.height + dropdownRect.height)}*/
|
||||
if (this._dropdownDetached) {
|
||||
this.setVerticalDropdownPosition(dropdownAbove);
|
||||
}
|
||||
|
||||
if (!preventInputFocus) {
|
||||
|
|
@ -552,6 +547,15 @@ class Choices {
|
|||
}
|
||||
|
||||
this.passedElement.triggerEvent(EventType.showDropdown);
|
||||
|
||||
const activeElement = this.choiceList.element.querySelector<HTMLElement>(
|
||||
getClassNamesSelector(this.config.classNames.selectedState),
|
||||
);
|
||||
|
||||
if (activeElement !== null && !isScrolledIntoView(activeElement, this.choiceList.element)) {
|
||||
// We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll.
|
||||
activeElement.scrollIntoView();
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
|
|
@ -562,6 +566,8 @@ class Choices {
|
|||
return this;
|
||||
}
|
||||
|
||||
this._removeHighlightedChoices();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.dropdown.hide();
|
||||
this.containerOuter.close(this.dropdown.element);
|
||||
|
|
@ -577,6 +583,29 @@ class Choices {
|
|||
return this;
|
||||
}
|
||||
|
||||
setHorizontalDropdownPosition(): this {
|
||||
const containerRect = this.containerOuter.element.getBoundingClientRect();
|
||||
|
||||
this.dropdown.element.style.top = `${window.scrollY + containerRect.bottom}px`;
|
||||
this.dropdown.element.style.left = `${containerRect.left}px`;
|
||||
this.dropdown.element.style.width = `${containerRect.width}px`;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setVerticalDropdownPosition(above: boolean = false): this {
|
||||
if (!above) {
|
||||
return this;
|
||||
}
|
||||
|
||||
const rect = this.containerOuter.element.getBoundingClientRect();
|
||||
|
||||
this.dropdown.element.style.top = 'auto'; // ToDo: calc from bottom or top - find a better way
|
||||
this.dropdown.element.style.bottom = `${document.body.offsetHeight - rect.top}px`; // /*- (containerRect.height + dropdownRect.height)}*/
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
getValue<B extends boolean = false>(valueOnly?: B): EventChoiceValueType<B> | EventChoiceValueType<B>[] {
|
||||
const values = this._store.items.map((item) => {
|
||||
return (valueOnly ? item.value : getChoiceForOutput(item)) as EventChoiceValueType<B>;
|
||||
|
|
@ -990,11 +1019,15 @@ class Choices {
|
|||
const renderableChoices = (choices: ChoiceFull[]): ChoiceFull[] =>
|
||||
choices.filter(
|
||||
(choice) =>
|
||||
!choice.placeholder && (isSearching ? !!choice.rank : config.renderSelectedChoices || !choice.selected),
|
||||
!choice.placeholder &&
|
||||
(isSearching
|
||||
? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank
|
||||
: config.renderSelectedChoices || !choice.selected),
|
||||
);
|
||||
|
||||
const showLabel = config.appendGroupInSearch && isSearching;
|
||||
let selectableChoices = false;
|
||||
let highlightedEl: HTMLElement | null = null;
|
||||
const renderChoices = (choices: ChoiceFull[], withinGroup: boolean): void => {
|
||||
if (isSearching) {
|
||||
// sortByRank is used to ensure stable sorting, as scores are non-unique
|
||||
|
|
@ -1022,6 +1055,8 @@ class Choices {
|
|||
fragment.appendChild(dropdownItem);
|
||||
if (isSearching || !choice.selected) {
|
||||
selectableChoices = true;
|
||||
} else if (!highlightedEl) {
|
||||
highlightedEl = dropdownItem;
|
||||
}
|
||||
|
||||
return index < choiceLimit;
|
||||
|
|
@ -1083,9 +1118,7 @@ class Choices {
|
|||
this._renderNotice(fragment);
|
||||
this.choiceList.element.replaceChildren(fragment);
|
||||
|
||||
if (selectableChoices) {
|
||||
this._highlightChoice();
|
||||
}
|
||||
this._highlightChoice(highlightedEl);
|
||||
}
|
||||
|
||||
_renderItems(): void {
|
||||
|
|
@ -1532,6 +1565,7 @@ class Choices {
|
|||
_addEventListeners(): void {
|
||||
const documentElement = this._docRoot;
|
||||
const outerElement = this.containerOuter.element;
|
||||
const dropdownElement = this.dropdown.element;
|
||||
const inputElement = this.input.element;
|
||||
const passedElement = this.passedElement.element;
|
||||
|
||||
|
|
@ -1539,13 +1573,15 @@ class Choices {
|
|||
documentElement.addEventListener('touchend', this._onTouchEnd, true);
|
||||
outerElement.addEventListener('keydown', this._onKeyDown, true);
|
||||
outerElement.addEventListener('mousedown', this._onMouseDown, true);
|
||||
dropdownElement.addEventListener('keydown', this._onKeyDown, true);
|
||||
dropdownElement.addEventListener('mousedown', this._onMouseDown, true);
|
||||
|
||||
// passive events - doesn't call `preventDefault` or `stopPropagation`
|
||||
documentElement.addEventListener('click', this._onClick, { passive: true });
|
||||
documentElement.addEventListener('touchmove', this._onTouchMove, {
|
||||
passive: true,
|
||||
});
|
||||
this.dropdown.element.addEventListener('mouseover', this._onMouseOver, {
|
||||
dropdownElement.addEventListener('mouseover', this._onMouseOver, {
|
||||
passive: true,
|
||||
});
|
||||
|
||||
|
|
@ -1588,22 +1624,29 @@ class Choices {
|
|||
});
|
||||
}
|
||||
|
||||
if (this._dropdownDetached) {
|
||||
window.addEventListener('resize', this._onWindowResize);
|
||||
}
|
||||
|
||||
this.input.addEventListeners();
|
||||
}
|
||||
|
||||
_removeEventListeners(): void {
|
||||
const documentElement = this._docRoot;
|
||||
const outerElement = this.containerOuter.element;
|
||||
const dropdownElement = this.dropdown.element;
|
||||
const inputElement = this.input.element;
|
||||
const passedElement = this.passedElement.element;
|
||||
|
||||
documentElement.removeEventListener('touchend', this._onTouchEnd, true);
|
||||
outerElement.removeEventListener('keydown', this._onKeyDown, true);
|
||||
outerElement.removeEventListener('mousedown', this._onMouseDown, true);
|
||||
dropdownElement.removeEventListener('keydown', this._onKeyDown);
|
||||
dropdownElement.removeEventListener('mousedown', this._onMouseDown);
|
||||
|
||||
documentElement.removeEventListener('click', this._onClick);
|
||||
documentElement.removeEventListener('touchmove', this._onTouchMove);
|
||||
this.dropdown.element.removeEventListener('mouseover', this._onMouseOver);
|
||||
dropdownElement.removeEventListener('mouseover', this._onMouseOver);
|
||||
|
||||
if (this._isSelectOneElement) {
|
||||
outerElement.removeEventListener('focus', this._onFocus);
|
||||
|
|
@ -1624,6 +1667,10 @@ class Choices {
|
|||
passedElement.removeEventListener('invalid', this._onInvalid);
|
||||
}
|
||||
|
||||
if (this._dropdownDetached) {
|
||||
window.removeEventListener('resize', this._onWindowResize);
|
||||
}
|
||||
|
||||
this.input.removeEventListeners();
|
||||
}
|
||||
|
||||
|
|
@ -2070,14 +2117,24 @@ class Choices {
|
|||
this.containerOuter.addInvalidState();
|
||||
}
|
||||
|
||||
_highlightChoice(el: HTMLElement | null = null): void {
|
||||
const choices = Array.from(this.dropdown.element.querySelectorAll<HTMLElement>(selectableChoiceIdentifier));
|
||||
_onWindowResize(): void {
|
||||
this.setHorizontalDropdownPosition();
|
||||
|
||||
if (!choices.length) {
|
||||
if (!this.dropdown.isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
let passedEl = el;
|
||||
const rect = this.dropdown.element.getBoundingClientRect();
|
||||
|
||||
const dropdownAbove = this.containerOuter.shouldFlip(rect.bottom, rect.height);
|
||||
|
||||
this.setVerticalDropdownPosition(dropdownAbove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any highlighted choice options
|
||||
*/
|
||||
_removeHighlightedChoices(): void {
|
||||
const { highlightedState } = this.config.classNames;
|
||||
const highlightedChoices = Array.from(
|
||||
this.dropdown.element.querySelectorAll<HTMLElement>(getClassNamesSelector(highlightedState)),
|
||||
|
|
@ -2088,6 +2145,19 @@ class Choices {
|
|||
removeClassesFromElement(choice, highlightedState);
|
||||
choice.setAttribute('aria-selected', 'false');
|
||||
});
|
||||
}
|
||||
|
||||
_highlightChoice(el: HTMLElement | null = null): void {
|
||||
const choices = Array.from(this.dropdown.element.querySelectorAll<HTMLElement>(selectableChoiceIdentifier));
|
||||
|
||||
if (!choices.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let passedEl = el;
|
||||
const { highlightedState } = this.config.classNames;
|
||||
|
||||
this._removeHighlightedChoices();
|
||||
|
||||
if (passedEl) {
|
||||
this._highlightPosition = choices.indexOf(passedEl);
|
||||
|
|
@ -2296,15 +2366,6 @@ class Choices {
|
|||
// Wrapper inner container with outer container
|
||||
containerOuter.wrap(containerInner.element);
|
||||
|
||||
if (this._isSelectOneElement) {
|
||||
this.input.placeholder = this.config.searchPlaceholderValue || '';
|
||||
} else {
|
||||
if (this._placeholderValue) {
|
||||
this.input.placeholder = this._placeholderValue;
|
||||
}
|
||||
this.input.setWidth();
|
||||
}
|
||||
|
||||
if (this._dropdownDetached && this._dropdownParent instanceof HTMLElement) {
|
||||
dropdownParent = this._dropdownParent;
|
||||
}
|
||||
|
|
@ -2314,10 +2375,19 @@ class Choices {
|
|||
containerInner.element.appendChild(this.itemList.element);
|
||||
dropdownElement.appendChild(this.choiceList.element);
|
||||
|
||||
if (!this._isSelectOneElement) {
|
||||
containerInner.element.appendChild(this.input.element);
|
||||
} else if (this.config.searchEnabled) {
|
||||
dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild);
|
||||
if (this._isSelectOneElement) {
|
||||
this.input.placeholder = this.config.searchPlaceholderValue || '';
|
||||
if (this.config.searchEnabled) {
|
||||
dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild);
|
||||
}
|
||||
} else {
|
||||
if (!this._isSelectMultipleElement || this.config.searchEnabled) {
|
||||
containerInner.element.appendChild(this.input.element);
|
||||
}
|
||||
if (this._placeholderValue) {
|
||||
this.input.placeholder = this._placeholderValue;
|
||||
}
|
||||
this.input.setWidth();
|
||||
}
|
||||
|
||||
this._highlightPosition = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue