diff --git a/cypress/e2e/select-multiple.spec.ts b/cypress/e2e/select-multiple.spec.ts
index c35f781a7..4b88ca885 100644
--- a/cypress/e2e/select-multiple.spec.ts
+++ b/cypress/e2e/select-multiple.spec.ts
@@ -702,6 +702,19 @@ describe('Choices - select multiple', () => {
});
});
+ describe('some options with a group and some without', () => {
+ it('Shows all options, whether they are in a group or not', () => {
+ cy.get('[data-test-hook=mixed-groups]')
+ .find('.choices__input--cloned')
+ .focus();
+ cy.get('[data-test-hook=mixed-groups]')
+ .find('.choices__list--dropdown .choices__item')
+ .should(($choices) => {
+ expect($choices.length).to.equal(3);
+ });
+ });
+ });
+
describe('custom properties', () => {
beforeEach(() => {
cy.get('[data-test-hook=custom-properties]')
diff --git a/public/test/select-multiple/index.html b/public/test/select-multiple/index.html
index e4091e588..9f621b588 100644
--- a/public/test/select-multiple/index.html
+++ b/public/test/select-multiple/index.html
@@ -283,6 +283,23 @@
Select multiple inputs
+
+
+
+
@@ -481,6 +498,10 @@
Select multiple inputs
allowHTML: true,
});
+ new Choices('#mixed-choices-groups', {
+ allowHTML: false,
+ });
+
new Choices('#choices-custom-properties', {
allowHTML: true,
searchFields: ['label', 'value', 'customProperties.country'],
diff --git a/src/scripts/choices.ts b/src/scripts/choices.ts
index 36a51da40..d58395f1a 100644
--- a/src/scripts/choices.ts
+++ b/src/scripts/choices.ts
@@ -46,7 +46,6 @@ import {
isType,
sortByScore,
strToEl,
- parseCustomProperties,
} from './lib/utils';
import { defaultState } from './reducers';
import Store from './store/store';
@@ -283,19 +282,10 @@ class Choices implements Choices {
}
// Create array of choices from option elements
if ((this.passedElement as WrappedSelect).options) {
- (this.passedElement as WrappedSelect).options.forEach((option) => {
- this._presetChoices.push({
- value: option.value,
- label: option.innerHTML,
- selected: !!option.selected,
- disabled: option.disabled || option.parentNode.disabled,
- placeholder:
- option.value === '' || option.hasAttribute('placeholder'),
- customProperties: parseCustomProperties(
- option.dataset.customProperties,
- ),
- });
- });
+ const choicesFromOptions = (
+ this.passedElement as WrappedSelect
+ ).optionsAsChoices();
+ this._presetChoices.push(...choicesFromOptions);
}
this._render = this._render.bind(this);
@@ -903,6 +893,13 @@ class Choices implements Choices {
groups.sort(this.config.sorter);
}
+ // Add Choices without group first, regardless of sort, otherwise they won't be distinguishable
+ // from the last group
+ const choicesWithoutGroup = choices.filter((c) => c.groupId == -1);
+ if (choicesWithoutGroup.length > 0) {
+ this._createChoicesFragment(choicesWithoutGroup, fragment, false);
+ }
+
groups.forEach((group) => {
const groupChoices = getGroupChoices(group);
if (groupChoices.length >= 1) {
@@ -2216,12 +2213,7 @@ class Choices implements Choices {
this._highlightPosition = 0;
this._isSearching = false;
this._startLoading();
-
- if (this._presetGroups.length) {
- this._addPredefinedGroups(this._presetGroups);
- } else {
- this._addPredefinedChoices(this._presetChoices);
- }
+ this._addPredefinedChoices(this._presetChoices);
this._stopLoading();
}
diff --git a/src/scripts/components/wrapped-select.ts b/src/scripts/components/wrapped-select.ts
index 3b2ada6c6..c0a22badc 100644
--- a/src/scripts/components/wrapped-select.ts
+++ b/src/scripts/components/wrapped-select.ts
@@ -1,6 +1,10 @@
+import { Choice } from '../interfaces/choice';
+import { parseCustomProperties } from '../lib/utils';
import { ClassNames } from '../interfaces/class-names';
import { Item } from '../interfaces/item';
import WrappedElement from './wrapped-element';
+import { isHTMLOptgroup } from '../lib/htmlElementGuards';
+import { isHTMLOption } from '../lib/htmlElementGuards';
export default class WrappedSelect extends WrappedElement {
element: HTMLSelectElement;
@@ -53,6 +57,42 @@ export default class WrappedSelect extends WrappedElement {
this.appendDocFragment(fragment);
}
+ optionsAsChoices(): Partial[] {
+ const choices: Partial[] = [];
+
+ for (const e of Array.from(this.element.querySelectorAll(':scope > *'))) {
+ if (isHTMLOption(e)) {
+ choices.push(this._optionToChoice(e as HTMLOptionElement));
+ } else if (isHTMLOptgroup(e)) {
+ choices.push(this._optgroupToChoice(e as HTMLOptGroupElement));
+ }
+ // There should only be those two in a