mirror of
https://github.com/Choices-js/Choices.git
synced 2026-03-14 14:45:47 +01:00
Initial logic for the dropdownParent feature
# Conflicts: # src/scripts/choices.ts
This commit is contained in:
parent
095ecb3589
commit
0592dcc3be
5 changed files with 67 additions and 50 deletions
|
|
@ -158,6 +158,10 @@ class Choices {
|
|||
|
||||
_docRoot: ShadowRoot | HTMLElement;
|
||||
|
||||
_dropdownParent: HTMLElement | null;
|
||||
|
||||
_dropdownFixed: boolean;
|
||||
|
||||
constructor(
|
||||
element: string | Element | HTMLInputElement | HTMLSelectElement = '[data-choice]',
|
||||
userConfig: Partial<Options> = {},
|
||||
|
|
@ -260,7 +264,7 @@ class Choices {
|
|||
|
||||
this._store = new Store(config);
|
||||
this._currentValue = '';
|
||||
config.searchEnabled = !isText && config.searchEnabled;
|
||||
config.searchEnabled = (!isText && config.searchEnabled) || isSelectMultiple;
|
||||
this._canSearch = config.searchEnabled;
|
||||
this._isScrollingOnIe = false;
|
||||
this._highlightPosition = 0;
|
||||
|
|
@ -319,6 +323,18 @@ class Choices {
|
|||
return;
|
||||
}
|
||||
|
||||
// Position fixed for dropdown items
|
||||
this._dropdownFixed = false;
|
||||
|
||||
if (config.dropdownParent) {
|
||||
const parent = this._docRoot.querySelector<HTMLElement>(config.dropdownParent);
|
||||
|
||||
if (parent) {
|
||||
this._dropdownFixed = true;
|
||||
this._dropdownParent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's go
|
||||
this.init();
|
||||
// preserve the selected item list after setup for form reset
|
||||
|
|
@ -513,23 +529,22 @@ class Choices {
|
|||
|
||||
requestAnimationFrame(() => {
|
||||
this.dropdown.show();
|
||||
const rect = this.dropdown.element.getBoundingClientRect();
|
||||
this.containerOuter.open(rect.bottom, rect.height);
|
||||
|
||||
if (this._dropdownFixed) {
|
||||
const containerRect = this.containerOuter.element.getBoundingClientRect();
|
||||
this.dropdown.element.style.top = `${containerRect.bottom}px`;
|
||||
this.dropdown.element.style.left = `${containerRect.left}px`;
|
||||
this.dropdown.element.style.width = `${containerRect.width}px`;
|
||||
}
|
||||
|
||||
const dropdownRect = this.dropdown.element.getBoundingClientRect();
|
||||
this.containerOuter.open(dropdownRect.bottom, dropdownRect.height);
|
||||
|
||||
if (!preventInputFocus) {
|
||||
this.input.focus();
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
@ -540,8 +555,6 @@ class Choices {
|
|||
return this;
|
||||
}
|
||||
|
||||
this._removeHighlightedChoices();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.dropdown.hide();
|
||||
this.containerOuter.close();
|
||||
|
|
@ -970,15 +983,11 @@ class Choices {
|
|||
const renderableChoices = (choices: ChoiceFull[]): ChoiceFull[] =>
|
||||
choices.filter(
|
||||
(choice) =>
|
||||
!choice.placeholder &&
|
||||
(isSearching
|
||||
? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank
|
||||
: config.renderSelectedChoices || !choice.selected),
|
||||
!choice.placeholder && (isSearching ? !!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
|
||||
|
|
@ -1006,8 +1015,6 @@ class Choices {
|
|||
fragment.appendChild(dropdownItem);
|
||||
if (isSearching || !choice.selected) {
|
||||
selectableChoices = true;
|
||||
} else if (!highlightedEl) {
|
||||
highlightedEl = dropdownItem;
|
||||
}
|
||||
|
||||
return index < choiceLimit;
|
||||
|
|
@ -1069,7 +1076,9 @@ class Choices {
|
|||
this._renderNotice(fragment);
|
||||
this.choiceList.element.replaceChildren(fragment);
|
||||
|
||||
this._highlightChoice(highlightedEl);
|
||||
if (selectableChoices) {
|
||||
this._highlightChoice();
|
||||
}
|
||||
}
|
||||
|
||||
_renderItems(): void {
|
||||
|
|
@ -2054,10 +2063,14 @@ class Choices {
|
|||
this.containerOuter.addInvalidState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any highlighted choice options
|
||||
*/
|
||||
_removeHighlightedChoices(): void {
|
||||
_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;
|
||||
const highlightedChoices = Array.from(
|
||||
this.dropdown.element.querySelectorAll<HTMLElement>(getClassNamesSelector(highlightedState)),
|
||||
|
|
@ -2068,19 +2081,6 @@ 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);
|
||||
|
|
@ -2280,6 +2280,7 @@ class Choices {
|
|||
_createStructure(): void {
|
||||
const { containerInner, containerOuter, passedElement } = this;
|
||||
const dropdownElement = this.dropdown.element;
|
||||
let dropdownParent: HTMLElement = containerOuter.element;
|
||||
|
||||
// Hide original element
|
||||
passedElement.conceal();
|
||||
|
|
@ -2288,26 +2289,32 @@ class Choices {
|
|||
// Wrapper inner container with outer container
|
||||
containerOuter.wrap(containerInner.element);
|
||||
|
||||
containerOuter.element.appendChild(containerInner.element);
|
||||
containerOuter.element.appendChild(dropdownElement);
|
||||
containerInner.element.appendChild(this.itemList.element);
|
||||
dropdownElement.appendChild(this.choiceList.element);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (this._dropdownFixed && this._dropdownParent instanceof HTMLElement) {
|
||||
const { fixed } = this.config.classNames;
|
||||
dropdownParent = this._dropdownParent;
|
||||
addClassesToElement(dropdownElement, fixed);
|
||||
}
|
||||
|
||||
containerOuter.element.appendChild(containerInner.element);
|
||||
dropdownParent.appendChild(dropdownElement);
|
||||
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);
|
||||
}
|
||||
|
||||
this._highlightPosition = 0;
|
||||
this._isSearching = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export const DEFAULT_CLASSNAMES: ClassNames = {
|
|||
itemChoice: ['choices__item--choice'],
|
||||
description: ['choices__description'],
|
||||
placeholder: ['choices__placeholder'],
|
||||
fixed: ['choices__position--fixed'],
|
||||
group: ['choices__group'],
|
||||
groupHeading: ['choices__heading'],
|
||||
button: ['choices__button'],
|
||||
|
|
@ -95,4 +96,5 @@ export const DEFAULT_CONFIG: Options = {
|
|||
callbackOnCreateTemplates: null,
|
||||
classNames: DEFAULT_CLASSNAMES,
|
||||
appendGroupInSearch: false,
|
||||
dropdownParent: null,
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ export interface ClassNames {
|
|||
description: string | Array<string>;
|
||||
/** @default ['choices__placeholder'] */
|
||||
placeholder: string | Array<string>;
|
||||
/** @default ['choices__position-fixed'] */
|
||||
fixed: string | Array<string>;
|
||||
/** @default ['choices__group'] */
|
||||
group: string | Array<string>;
|
||||
/** @default ['choices__heading'] */
|
||||
|
|
|
|||
|
|
@ -634,4 +634,6 @@ export interface Options {
|
|||
callbackOnCreateTemplates: CallbackOnCreateTemplatesFn | null;
|
||||
|
||||
appendGroupInSearch: boolean;
|
||||
|
||||
dropdownParent: string | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,6 +272,10 @@ $choices-placeholder-opacity: 0.5 !default;
|
|||
}
|
||||
}
|
||||
|
||||
.#{$choices-selector}__position--fixed {
|
||||
position: fixed !important;
|
||||
}
|
||||
|
||||
%choices-dropdown {
|
||||
display: none;
|
||||
z-index: var(--choices-z-index, #{$choices-z-index});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue