Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0592dcc
Initial logic for the `dropdownParent` feature
zoglo Aug 23, 2025
e2ca9e2
Remove the unnecessary `fixed` class (absolute should work fine)
zoglo Aug 25, 2025
07eea9c
Remove the 100% height on the demo height
zoglo Aug 25, 2025
15fa680
Update the dropdown flipped state to be applied to the dropdown and n…
zoglo Aug 25, 2025
b667952
Apply the flipped state to the dropdown
zoglo Aug 25, 2025
f1228e8
Document dropdownParent config option
Xon Feb 7, 2026
6099879
Fix unit test and simplify calling open/close as the dropdown element…
Xon Feb 7, 2026
71f9b01
Reorder the arguments for the `open` method
zoglo Feb 23, 2026
5860b55
Update event listener handling for the dropdown container
zoglo Feb 23, 2026
64ed121
Add the flipped state class to the element
zoglo Feb 24, 2026
74dbc29
Apply single and multiple classes to (decoupled) dropdowns
zoglo Feb 24, 2026
ec925c4
Apply single and multiple classes to (decoupled) dropdowns (Part 2)
zoglo Feb 24, 2026
079849b
Update the vertical dropdown positioning in decoupled mode
zoglo Feb 24, 2026
a9417cf
Update the base demo styles for dropdownParents
zoglo Feb 24, 2026
b38f8c5
Add decoupled styles for dropdowns
zoglo Feb 24, 2026
8c21266
Update the README.md
zoglo Feb 24, 2026
c1bfe22
Ignore the `.choices__list` class
zoglo Feb 24, 2026
2c4e4e8
Build the assets
zoglo Feb 24, 2026
d90693d
Only add events and more when the dropdown is detached
zoglo Feb 24, 2026
185e3ac
Build the assets
zoglo Feb 24, 2026
a734408
Update the dropdown position on scroll
zoglo Feb 25, 2026
a5458f1
Build
zoglo Feb 25, 2026
21dc793
Rebuild the assets
zoglo Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ import "choices.js/public/assets/styles/choices.css";
listItems: ['choices__list--multiple'],
listSingle: ['choices__list--single'],
listDropdown: ['choices__list--dropdown'],
dropdownMultiple: ['choices__dropdown--multiple'],
dropdownSingle: ['choices__dropdown--single'],
item: ['choices__item'],
itemSelectable: ['choices__item--selectable'],
itemDisabled: ['choices__item--disabled'],
Expand Down Expand Up @@ -536,6 +538,14 @@ var choices = new Choices(el, {
});
```

### dropdownParent

**Type:** `string | null` **Default:** null

**Input types affected:** `select-one`, `select-multiple`

**Usage:** CSS selector for the element to append the dropdown to. Similar to select2's appendTo option

### position

**Type:** `String` **Default:** `auto`
Expand Down
105 changes: 92 additions & 13 deletions public/assets/scripts/choices.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,25 +409,32 @@
Container.prototype.removeActiveDescendant = function () {
this.element.removeAttribute('aria-activedescendant');
};
Container.prototype.open = function (dropdownPos, dropdownHeight) {
Container.prototype.open = function (dropdownPos, dropdownHeight, dropdown) {
if (dropdown === void 0) { dropdown = null; }
addClassesToElement(this.element, this.classNames.openState);
this.element.setAttribute('aria-expanded', 'true');
this.isOpen = true;
if (this.shouldFlip(dropdownPos, dropdownHeight)) {
addClassesToElement(this.element, this.classNames.flippedState);
if (dropdown !== null) {
addClassesToElement(dropdown, this.classNames.flippedState);
}
this.isFlipped = true;
}
return this.isFlipped;
};
Container.prototype.close = function () {
Container.prototype.close = function (dropdown) {
if (dropdown === void 0) { dropdown = null; }
removeClassesFromElement(this.element, this.classNames.openState);
this.element.setAttribute('aria-expanded', 'false');
this.removeActiveDescendant();
this.isOpen = false;
// A dropdown flips if it does not have space within the page
if (this.isFlipped) {
removeClassesFromElement(this.element, this.classNames.flippedState);
this.isFlipped = false;
removeClassesFromElement(this.element, this.classNames.flippedState);
if (dropdown !== null) {
removeClassesFromElement(dropdown, this.classNames.flippedState);
}
this.isFlipped = false;
};
Container.prototype.addFocusState = function () {
addClassesToElement(this.element, this.classNames.focusState);
Expand Down Expand Up @@ -937,6 +944,8 @@
listItems: ['choices__list--multiple'],
listSingle: ['choices__list--single'],
listDropdown: ['choices__list--dropdown'],
dropdownMultiple: ['choices__dropdown--multiple'],
dropdownSingle: ['choices__dropdown--single'],
item: ['choices__item'],
itemSelectable: ['choices__item--selectable'],
itemDisabled: ['choices__item--disabled'],
Expand Down Expand Up @@ -1020,6 +1029,7 @@
callbackOnCreateTemplates: null,
classNames: DEFAULT_CLASSNAMES,
appendGroupInSearch: false,
dropdownParent: null,
};

var removeItem = function (item) {
Expand Down Expand Up @@ -3285,11 +3295,12 @@
}
return inp;
},
dropdown: function (_a) {
var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown;
dropdown: function (_a, isSelectOneElement) {
var _b = _a.classNames, list = _b.list, listDropdown = _b.listDropdown, dropdownSingle = _b.dropdownSingle, dropdownMultiple = _b.dropdownMultiple;
var div = document.createElement('div');
addClassesToElement(div, list);
addClassesToElement(div, listDropdown);
addClassesToElement(div, isSelectOneElement ? dropdownSingle : dropdownMultiple);
div.setAttribute('aria-expanded', 'false');
return div;
},
Expand Down Expand Up @@ -3475,6 +3486,8 @@
this._onDeleteKey = this._onDeleteKey.bind(this);
this._onChange = this._onChange.bind(this);
this._onInvalid = this._onInvalid.bind(this);
this._onWindowResize = this._onWindowResize.bind(this);
this._onScroll = this._onScroll.bind(this);
// If element has already been initialised with Choices, fail silently
if (this.passedElement.isActive) {
if (!config.silent) {
Expand All @@ -3484,6 +3497,15 @@
this.initialisedOK = false;
return;
}
// Dropdown is detached from the original wrapper
this._dropdownDetached = false;
if (config.dropdownParent) {
var parent_1 = this._docRoot.querySelector(config.dropdownParent);
if (parent_1) {
this._dropdownDetached = true;
this._dropdownParent = parent_1;
}
}
// Let's go
this.init();
// preserve the selected item list after setup for form reset
Expand Down Expand Up @@ -3666,9 +3688,15 @@
preventInputFocus = !this._canSearch;
}
requestAnimationFrame(function () {
if (_this._dropdownDetached) {
_this.setHorizontalDropdownPosition();
}
_this.dropdown.show();
var rect = _this.dropdown.element.getBoundingClientRect();
_this.containerOuter.open(rect.bottom, rect.height);
var dropdownAbove = _this.containerOuter.open(rect.bottom, rect.height, _this.dropdown.element);
if (_this._dropdownDetached) {
_this.setVerticalDropdownPosition(dropdownAbove);
}
if (!preventInputFocus) {
_this.input.focus();
}
Expand All @@ -3689,7 +3717,7 @@
this._removeHighlightedChoices();
requestAnimationFrame(function () {
_this.dropdown.hide();
_this.containerOuter.close();
_this.containerOuter.close(_this.dropdown.element);
if (!preventInputBlur && _this._canSearch) {
_this.input.removeActiveDescendant();
_this.input.blur();
Expand All @@ -3698,6 +3726,22 @@
});
return this;
};
Choices.prototype.setHorizontalDropdownPosition = function () {
var containerRect = this.containerOuter.element.getBoundingClientRect();
this.dropdown.element.style.top = "".concat(window.scrollY + containerRect.bottom, "px");
this.dropdown.element.style.left = "".concat(containerRect.left, "px");
this.dropdown.element.style.width = "".concat(containerRect.width, "px");
return this;
};
Choices.prototype.setVerticalDropdownPosition = function (above) {
if (above === void 0) { above = false; }
if (!above || !(this._dropdownParent instanceof HTMLElement)) {
return this;
}
var dropdownRect = this.dropdown.element.getBoundingClientRect();
this.dropdown.element.style.top = "".concat(this.containerOuter.element.offsetTop + 1 - dropdownRect.height, "px");
return this;
};
Choices.prototype.getValue = function (valueOnly) {
var values = this._store.items.map(function (item) {
return (valueOnly ? item.value : getChoiceForOutput(item));
Expand Down Expand Up @@ -4518,6 +4562,7 @@
Choices.prototype._addEventListeners = function () {
var documentElement = this._docRoot;
var outerElement = this.containerOuter.element;
var dropdownElement = this.dropdown.element;
var inputElement = this.input.element;
var passedElement = this.passedElement.element;
// capture events - can cancel event processing or propagation
Expand All @@ -4529,7 +4574,7 @@
documentElement.addEventListener('touchmove', this._onTouchMove, {
passive: true,
});
this.dropdown.element.addEventListener('mouseover', this._onMouseOver, {
dropdownElement.addEventListener('mouseover', this._onMouseOver, {
passive: true,
});
if (this._isSelectOneElement) {
Expand Down Expand Up @@ -4565,19 +4610,28 @@
passive: true,
});
}
if (this._dropdownDetached) {
dropdownElement.addEventListener('keydown', this._onKeyDown, true);
dropdownElement.addEventListener('mousedown', this._onMouseDown, true);
window.addEventListener('resize', this._onWindowResize);
window.addEventListener('scroll', this._onScroll, {
passive: true,
});
}
this.input.addEventListeners();
};
Choices.prototype._removeEventListeners = function () {
var documentElement = this._docRoot;
var outerElement = this.containerOuter.element;
var dropdownElement = this.dropdown.element;
var inputElement = this.input.element;
var passedElement = this.passedElement.element;
documentElement.removeEventListener('touchend', this._onTouchEnd, true);
outerElement.removeEventListener('keydown', this._onKeyDown, true);
outerElement.removeEventListener('mousedown', this._onMouseDown, true);
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);
outerElement.removeEventListener('blur', this._onBlur);
Expand All @@ -4593,6 +4647,12 @@
passedElement.removeEventListener('change', this._onChange);
passedElement.removeEventListener('invalid', this._onInvalid);
}
if (this._dropdownDetached) {
dropdownElement.removeEventListener('keydown', this._onKeyDown);
dropdownElement.removeEventListener('mousedown', this._onMouseDown);
window.removeEventListener('resize', this._onWindowResize);
window.removeEventListener('scroll', this._onScroll);
}
this.input.removeEventListeners();
};
Choices.prototype._onKeyDown = function (event) {
Expand Down Expand Up @@ -4984,6 +5044,21 @@
Choices.prototype._onInvalid = function () {
this.containerOuter.addInvalidState();
};
Choices.prototype._onWindowResize = function () {
this._moveDropdown();
};
Choices.prototype._onScroll = function () {
this._moveDropdown();
};
Choices.prototype._moveDropdown = function () {
if (!this.dropdown.isActive) {
return;
}
this.setHorizontalDropdownPosition();
var rect = this.dropdown.element.getBoundingClientRect();
var dropdownAbove = this.containerOuter.shouldFlip(rect.bottom, rect.height);
this.setVerticalDropdownPosition(dropdownAbove);
};
/**
* Removes any highlighted choice options
*/
Expand Down Expand Up @@ -5162,22 +5237,26 @@
element: templating.itemList(config, isSelectOneElement),
});
this.dropdown = new Dropdown({
element: templating.dropdown(config),
element: templating.dropdown(config, isSelectOneElement),
classNames: classNames,
type: elementType,
});
};
Choices.prototype._createStructure = function () {
var _a = this, containerInner = _a.containerInner, containerOuter = _a.containerOuter, passedElement = _a.passedElement;
var dropdownElement = this.dropdown.element;
var dropdownParent = containerOuter.element;
// Hide original element
passedElement.conceal();
// Wrap input in container preserving DOM ordering
containerInner.wrap(passedElement.element);
// Wrapper inner container with outer container
containerOuter.wrap(containerInner.element);
if (this._dropdownDetached && this._dropdownParent instanceof HTMLElement) {
dropdownParent = this._dropdownParent;
}
containerOuter.element.appendChild(containerInner.element);
containerOuter.element.appendChild(dropdownElement);
dropdownParent.appendChild(dropdownElement);
containerInner.element.appendChild(this.itemList.element);
dropdownElement.appendChild(this.choiceList.element);
if (this._isSelectOneElement) {
Expand Down
2 changes: 1 addition & 1 deletion public/assets/scripts/choices.min.js

Large diffs are not rendered by default.

Loading
Loading