Skip to content

Commit d0a7e70

Browse files
committed
Merge branch 'opt-group_fix'
2 parents 3839cec + 59f7d67 commit d0a7e70

23 files changed

Lines changed: 119 additions & 85 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
* Fix regression "no choices to choose from"/"no results found" notice did not reliably trigger. [#1185](https://github.com/Choices-js/Choices/issues/1185) [#1191](https://github.com/Choices-js/Choices/issues/1191)
1313
* Fix regression of `UnhighlightItem` event not firing [#1173](https://github.com/Choices-js/Choices/issues/1173)
1414
* Fix `clearChoices()` would remove items, and clear the search flag.
15+
* Fixes for opt-group handling/rendering
16+
* Fix `removeChoice()` did not properly remove a choice which was part of a group
1517

1618
### Chore
1719
* Add e2e tests for "no choices" behavior to match v10

public/test/select-multiple/index.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,9 @@ <h2>Select multiple inputs</h2>
914914
multiple
915915
>
916916
<option value="Choice 1">Choice 1</option>
917-
<option value="Choice 2">Choice 2</option>
917+
<optgroup label="test">
918+
<option value="Choice 2">Choice 2</option>
919+
</optgroup>
918920
<option value="Choice 3">Choice 3</option>
919921
</select>
920922
<button class="destroy">Destroy</button>

public/test/select-one/index.html

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,9 @@ <h2>Select one inputs</h2>
870870
id="choices-new-destroy-init"
871871
>
872872
<option value="Choice 1">Choice 1</option>
873-
<option value="Choice 2">Choice 2</option>
873+
<optgroup label="test">
874+
<option value="Choice 2">Choice 2</option>
875+
</optgroup>
874876
<option value="Choice 3">Choice 3</option>
875877
</select>
876878
<button class="destroy">Destroy</button>
@@ -926,7 +928,7 @@ <h2>Select one inputs</h2>
926928
</script>
927929
</div>
928930

929-
<div data-test-hook="autocomplete">
931+
<div data-test-hook="autocomplete">
930932
<label for="choices-autocomplete">Autocomplete example</label>
931933
<select
932934
class="form-control"
@@ -966,7 +968,7 @@ <h2>Select one inputs</h2>
966968
</script>
967969
</div>
968970

969-
971+
</div>
970972
</div>
971973
</body>
972974
</html>

src/scripts/choices.ts

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
21
import { activateChoices, addChoice, removeChoice, filterChoices } from './actions/choices';
32
import { addGroup } from './actions/groups';
43
import { addItem, highlightItem, removeItem } from './actions/items';
@@ -28,7 +27,7 @@ import Store from './store/store';
2827
import { coerceBool, mapInputToChoice } from './lib/choice-input';
2928
import { ChoiceFull } from './interfaces/choice-full';
3029
import { GroupFull } from './interfaces/group-full';
31-
import { EventType, KeyCodeMap, PassedElementType, PassedElementTypes } from './interfaces';
30+
import { EventChoiceValueType, EventType, KeyCodeMap, PassedElementType, PassedElementTypes } from './interfaces';
3231
import { EventChoice } from './interfaces/event-choice';
3332
import { NoticeType, NoticeTypes, Templates } from './interfaces/templates';
3433
import { isHtmlInputElement, isHtmlSelectElement } from './lib/html-guard-statements';
@@ -539,13 +538,10 @@ class Choices {
539538
return this;
540539
}
541540

542-
getValue(valueOnly = false): string[] | EventChoice[] | EventChoice | string {
543-
const values = this._store.items.reduce<any[]>((selectedItems, item) => {
544-
const itemValue = valueOnly ? item.value : this._getChoiceForOutput(item);
545-
selectedItems.push(itemValue);
546-
547-
return selectedItems;
548-
}, []);
541+
getValue<B extends boolean = false>(valueOnly?: B): EventChoiceValueType<B> | EventChoiceValueType<B>[] {
542+
const values = this._store.items.map((item) => {
543+
return (valueOnly ? item.value : this._getChoiceForOutput(item)) as EventChoiceValueType<B>;
544+
});
549545

550546
return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values;
551547
}
@@ -788,16 +784,21 @@ class Choices {
788784

789785
this.clearStore(false);
790786

791-
choicesFromOptions.forEach((groupOrChoice) => {
792-
if ('choices' in groupOrChoice) {
793-
return;
794-
}
795-
const choice = groupOrChoice;
787+
const updateChoice = (choice: ChoiceFull): void => {
796788
if (deselectAll) {
797789
this._store.dispatch(removeItem(choice));
798790
} else if (existingItems[choice.value]) {
799791
choice.selected = true;
800792
}
793+
};
794+
795+
choicesFromOptions.forEach((groupOrChoice) => {
796+
if ('choices' in groupOrChoice) {
797+
groupOrChoice.choices.forEach(updateChoice);
798+
799+
return;
800+
}
801+
updateChoice(groupOrChoice);
801802
});
802803

803804
/* @todo only generate add events for the added options instead of all
@@ -984,7 +985,7 @@ class Choices {
984985
if (!this._hasNonChoicePlaceholder && !isSearching && this._isSelectOneElement) {
985986
// If we have a placeholder choice along with groups
986987
renderChoices(
987-
activeChoices.filter((choice) => choice.placeholder && !choice.groupId),
988+
activeChoices.filter((choice) => choice.placeholder && !choice.group),
988989
false,
989990
undefined,
990991
);
@@ -995,6 +996,13 @@ class Choices {
995996
if (config.shouldSort) {
996997
activeGroups.sort(config.sorter);
997998
}
999+
// render Choices without group first, regardless of sort, otherwise they won't be distinguishable
1000+
// from the last group
1001+
renderChoices(
1002+
activeChoices.filter((choice) => !choice.placeholder && !choice.group),
1003+
false,
1004+
undefined,
1005+
);
9981006

9991007
activeGroups.forEach((group) => {
10001008
const groupChoices = renderableChoices(group.choices);
@@ -1160,13 +1168,8 @@ class Choices {
11601168
}
11611169
}
11621170

1163-
_getChoiceForOutput(choice?: ChoiceFull, keyCode?: number): EventChoice | undefined {
1164-
if (!choice) {
1165-
return undefined;
1166-
}
1167-
1168-
const group = choice.groupId ? this._store.getGroupById(choice.groupId) : null;
1169-
1171+
// eslint-disable-next-line class-methods-use-this
1172+
_getChoiceForOutput(choice: ChoiceFull, keyCode?: number): EventChoice {
11701173
return {
11711174
id: choice.id,
11721175
highlighted: choice.highlighted,
@@ -1178,7 +1181,7 @@ class Choices {
11781181
label: choice.label,
11791182
placeholder: choice.placeholder,
11801183
value: choice.value,
1181-
groupValue: group && group.label ? group.label : undefined,
1184+
groupValue: choice.group ? choice.group.label : undefined,
11821185
element: choice.element,
11831186
keyCode,
11841187
};
@@ -2131,7 +2134,7 @@ class Choices {
21312134
group.id = this._lastAddedGroupId;
21322135

21332136
group.choices.forEach((item: ChoiceFull) => {
2134-
item.groupId = group.id;
2137+
item.group = group;
21352138
if (group.disabled) {
21362139
item.disabled = true;
21372140
}

src/scripts/components/wrapped-select.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default class WrappedSelect extends WrappedElement<HTMLSelectElement> {
7777

7878
return {
7979
id: 0,
80-
groupId: 0,
80+
group: null,
8181
score: 0,
8282
rank: 0,
8383
value: option.value,

src/scripts/interfaces/choice-full.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
21
import { StringUntrusted } from './string-untrusted';
3-
4-
export type CustomProperties = Record<string, any> | string;
2+
import { Types } from './types';
3+
// eslint-disable-next-line import/no-cycle
4+
import { GroupFull } from './group-full';
55

66
/*
77
A disabled choice appears in the choice dropdown but cannot be selected
@@ -16,11 +16,11 @@ export interface ChoiceFull {
1616
choiceEl?: HTMLElement;
1717
labelClass?: Array<string>;
1818
labelDescription?: string;
19-
customProperties?: CustomProperties;
19+
customProperties?: Types.CustomProperties;
2020
disabled: boolean;
2121
active: boolean;
2222
elementId?: string;
23-
groupId: number;
23+
group: GroupFull | null;
2424
label: StringUntrusted | string;
2525
placeholder: boolean;
2626
selected: boolean;

src/scripts/interfaces/event-choice.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { InputChoice } from './input-choice';
22

3+
export type EventChoiceValueType<B extends boolean> = B extends true ? string : EventChoice;
4+
35
export interface EventChoice extends InputChoice {
46
element?: HTMLOptionElement | HTMLOptGroupElement;
57
groupValue?: string;

src/scripts/interfaces/group-full.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
1+
// eslint-disable-next-line import/no-cycle
32
import { ChoiceFull } from './choice-full';
43

54
export interface GroupFull {
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
31
import { StringUntrusted } from './string-untrusted';
2+
import { Types } from './types';
43

54
export interface InputChoice {
65
id?: number;
76
highlighted?: boolean;
87
labelClass?: string | Array<string>;
98
labelDescription?: string;
10-
customProperties?: Record<string, any> | string;
9+
customProperties?: Types.CustomProperties;
1110
disabled?: boolean;
1211
active?: boolean;
1312
label: StringUntrusted | string;
1413
placeholder?: boolean;
1514
selected?: boolean;
15+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1616
value: any;
1717
}

src/scripts/interfaces/input-group.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
31
import { InputChoice } from './input-choice';
42
import { StringUntrusted } from './string-untrusted';
53

0 commit comments

Comments
 (0)