govuk_publishing_components 65.1.0 → 65.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/component_guide/application.scss +13 -15
- data/app/assets/stylesheets/govuk_publishing_components/all-components.scss +0 -1
- data/app/assets/stylesheets/govuk_publishing_components/components/_contents-list.scss +25 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_details.scss +34 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_heading.scss +1 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/helpers/_markdown-typography.scss +4 -0
- data/app/assets/stylesheets/govuk_publishing_components/specific-components.scss +0 -1
- data/app/controllers/govuk_publishing_components/applications_page_controller.rb +1 -1
- data/app/controllers/govuk_publishing_components/audit_controller.rb +0 -1
- data/app/models/govuk_publishing_components/component_doc.rb +10 -3
- data/app/models/govuk_publishing_components/component_wrapper_helper_options.rb +4 -2
- data/app/views/govuk_publishing_components/component_guide/component_doc/_component.html.erb +1 -1
- data/app/views/govuk_publishing_components/component_guide/component_doc/_preview.html.erb +2 -2
- data/app/views/govuk_publishing_components/component_guide/show.html.erb +47 -33
- data/app/views/govuk_publishing_components/components/_details.html.erb +4 -0
- data/app/views/govuk_publishing_components/components/_devolved_nations.html.erb +2 -2
- data/app/views/govuk_publishing_components/components/_option_select.html.erb +2 -2
- data/app/views/govuk_publishing_components/components/docs/details.yml +16 -0
- data/lib/govuk_publishing_components/presenters/devolved_nations_helper.rb +13 -7
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/choices.js/README.md +140 -30
- data/node_modules/choices.js/package.json +10 -13
- data/node_modules/choices.js/public/assets/scripts/choices.js +144 -89
- data/node_modules/choices.js/public/assets/scripts/choices.min.js +2 -2
- data/node_modules/choices.js/public/assets/scripts/choices.mjs +144 -89
- data/node_modules/choices.js/public/assets/scripts/choices.search-basic.js +144 -86
- data/node_modules/choices.js/public/assets/scripts/choices.search-basic.min.js +2 -2
- data/node_modules/choices.js/public/assets/scripts/choices.search-basic.mjs +144 -86
- data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.js +139 -77
- data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.min.js +2 -2
- data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.mjs +139 -77
- data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.js +137 -76
- data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.min.js +2 -2
- data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.mjs +137 -76
- data/node_modules/choices.js/public/assets/styles/base.css +39 -9
- data/node_modules/choices.js/public/assets/styles/base.css.map +1 -1
- data/node_modules/choices.js/public/assets/styles/base.min.css +1 -1
- data/node_modules/choices.js/public/assets/styles/choices.css +93 -95
- data/node_modules/choices.js/public/assets/styles/choices.css.map +1 -1
- data/node_modules/choices.js/public/assets/styles/choices.min.css +1 -1
- data/node_modules/choices.js/public/types/src/scripts/choices.d.ts +11 -0
- data/node_modules/choices.js/public/types/src/scripts/components/container.d.ts +2 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/choice-full.d.ts +2 -1
- data/node_modules/choices.js/public/types/src/scripts/interfaces/class-names.d.ts +2 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/input-choice.d.ts +2 -1
- data/node_modules/choices.js/public/types/src/scripts/interfaces/options.d.ts +19 -3
- data/node_modules/choices.js/public/types/src/scripts/interfaces/store.d.ts +2 -1
- data/node_modules/choices.js/public/types/src/scripts/interfaces/types.d.ts +2 -1
- data/node_modules/choices.js/public/types/src/scripts/lib/utils.d.ts +3 -1
- data/node_modules/choices.js/src/scripts/choices.ts +110 -64
- data/node_modules/choices.js/src/scripts/components/container.ts +8 -0
- data/node_modules/choices.js/src/scripts/components/wrapped-select.ts +3 -1
- data/node_modules/choices.js/src/scripts/defaults.ts +12 -7
- data/node_modules/choices.js/src/scripts/interfaces/choice-full.ts +2 -1
- data/node_modules/choices.js/src/scripts/interfaces/class-names.ts +2 -0
- data/node_modules/choices.js/src/scripts/interfaces/event-choice.ts +1 -0
- data/node_modules/choices.js/src/scripts/interfaces/input-choice.ts +4 -2
- data/node_modules/choices.js/src/scripts/interfaces/options.ts +21 -3
- data/node_modules/choices.js/src/scripts/interfaces/store.ts +2 -1
- data/node_modules/choices.js/src/scripts/interfaces/types.ts +3 -1
- data/node_modules/choices.js/src/scripts/lib/utils.ts +27 -4
- data/node_modules/choices.js/src/scripts/search/kmp.ts +2 -1
- data/node_modules/choices.js/src/scripts/store/store.ts +4 -1
- data/node_modules/choices.js/src/scripts/templates.ts +6 -3
- data/node_modules/choices.js/src/styles/base.scss +42 -9
- data/node_modules/choices.js/src/styles/choices.scss +119 -93
- metadata +2 -3
- data/app/assets/stylesheets/govuk_publishing_components/components/helpers/_contents-list-helper.scss +0 -24
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
escapeForTemplate,
|
|
14
14
|
generateId,
|
|
15
15
|
getAdjacentEl,
|
|
16
|
+
getChoiceForOutput,
|
|
16
17
|
getClassNames,
|
|
17
18
|
getClassNamesSelector,
|
|
18
19
|
isScrolledIntoView,
|
|
@@ -259,7 +260,7 @@ class Choices {
|
|
|
259
260
|
|
|
260
261
|
this._store = new Store(config);
|
|
261
262
|
this._currentValue = '';
|
|
262
|
-
config.searchEnabled =
|
|
263
|
+
config.searchEnabled = !isText && config.searchEnabled;
|
|
263
264
|
this._canSearch = config.searchEnabled;
|
|
264
265
|
this._isScrollingOnIe = false;
|
|
265
266
|
this._highlightPosition = 0;
|
|
@@ -303,6 +304,8 @@ class Choices {
|
|
|
303
304
|
this._onEscapeKey = this._onEscapeKey.bind(this);
|
|
304
305
|
this._onDirectionKey = this._onDirectionKey.bind(this);
|
|
305
306
|
this._onDeleteKey = this._onDeleteKey.bind(this);
|
|
307
|
+
this._onChange = this._onChange.bind(this);
|
|
308
|
+
this._onInvalid = this._onInvalid.bind(this);
|
|
306
309
|
|
|
307
310
|
// If element has already been initialised with Choices, fail silently
|
|
308
311
|
if (this.passedElement.isActive) {
|
|
@@ -415,7 +418,7 @@ class Choices {
|
|
|
415
418
|
this._store.dispatch(highlightItem(choice, true));
|
|
416
419
|
|
|
417
420
|
if (runEvent) {
|
|
418
|
-
this.passedElement.triggerEvent(EventType.highlightItem,
|
|
421
|
+
this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(choice));
|
|
419
422
|
}
|
|
420
423
|
|
|
421
424
|
return this;
|
|
@@ -433,7 +436,7 @@ class Choices {
|
|
|
433
436
|
this._store.dispatch(highlightItem(choice, false));
|
|
434
437
|
|
|
435
438
|
if (runEvent) {
|
|
436
|
-
this.passedElement.triggerEvent(EventType.unhighlightItem,
|
|
439
|
+
this.passedElement.triggerEvent(EventType.unhighlightItem, getChoiceForOutput(choice));
|
|
437
440
|
}
|
|
438
441
|
|
|
439
442
|
return this;
|
|
@@ -445,7 +448,7 @@ class Choices {
|
|
|
445
448
|
if (!item.highlighted) {
|
|
446
449
|
this._store.dispatch(highlightItem(item, true));
|
|
447
450
|
|
|
448
|
-
this.passedElement.triggerEvent(EventType.highlightItem,
|
|
451
|
+
this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item));
|
|
449
452
|
}
|
|
450
453
|
});
|
|
451
454
|
});
|
|
@@ -459,7 +462,7 @@ class Choices {
|
|
|
459
462
|
if (item.highlighted) {
|
|
460
463
|
this._store.dispatch(highlightItem(item, false));
|
|
461
464
|
|
|
462
|
-
this.passedElement.triggerEvent(EventType.highlightItem,
|
|
465
|
+
this.passedElement.triggerEvent(EventType.highlightItem, getChoiceForOutput(item));
|
|
463
466
|
}
|
|
464
467
|
});
|
|
465
468
|
});
|
|
@@ -518,6 +521,15 @@ class Choices {
|
|
|
518
521
|
}
|
|
519
522
|
|
|
520
523
|
this.passedElement.triggerEvent(EventType.showDropdown);
|
|
524
|
+
|
|
525
|
+
const activeElement = this.choiceList.element.querySelector<HTMLElement>(
|
|
526
|
+
getClassNamesSelector(this.config.classNames.selectedState),
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
if (activeElement !== null && !isScrolledIntoView(activeElement, this.choiceList.element)) {
|
|
530
|
+
// We use the native scrollIntoView function instead of choiceList.scrollToChildElement to avoid animated scroll.
|
|
531
|
+
activeElement.scrollIntoView();
|
|
532
|
+
}
|
|
521
533
|
});
|
|
522
534
|
|
|
523
535
|
return this;
|
|
@@ -528,6 +540,8 @@ class Choices {
|
|
|
528
540
|
return this;
|
|
529
541
|
}
|
|
530
542
|
|
|
543
|
+
this._removeHighlightedChoices();
|
|
544
|
+
|
|
531
545
|
requestAnimationFrame(() => {
|
|
532
546
|
this.dropdown.hide();
|
|
533
547
|
this.containerOuter.close();
|
|
@@ -545,7 +559,7 @@ class Choices {
|
|
|
545
559
|
|
|
546
560
|
getValue<B extends boolean = false>(valueOnly?: B): EventChoiceValueType<B> | EventChoiceValueType<B>[] {
|
|
547
561
|
const values = this._store.items.map((item) => {
|
|
548
|
-
return (valueOnly ? item.value :
|
|
562
|
+
return (valueOnly ? item.value : getChoiceForOutput(item)) as EventChoiceValueType<B>;
|
|
549
563
|
});
|
|
550
564
|
|
|
551
565
|
return this._isSelectOneElement || this.config.singleModeForMultiSelect ? values[0] : values;
|
|
@@ -848,7 +862,7 @@ class Choices {
|
|
|
848
862
|
this._searcher.reset();
|
|
849
863
|
|
|
850
864
|
if (choice.selected) {
|
|
851
|
-
this.passedElement.triggerEvent(EventType.removeItem,
|
|
865
|
+
this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(choice));
|
|
852
866
|
}
|
|
853
867
|
|
|
854
868
|
return this;
|
|
@@ -943,12 +957,7 @@ class Choices {
|
|
|
943
957
|
const { config, _isSearching: isSearching } = this;
|
|
944
958
|
const { activeGroups, activeChoices } = this._store;
|
|
945
959
|
|
|
946
|
-
|
|
947
|
-
if (isSearching && config.searchResultLimit > 0) {
|
|
948
|
-
renderLimit = config.searchResultLimit;
|
|
949
|
-
} else if (config.renderChoiceLimit > 0) {
|
|
950
|
-
renderLimit = config.renderChoiceLimit;
|
|
951
|
-
}
|
|
960
|
+
const renderLimit = isSearching ? config.searchResultLimit : config.renderChoiceLimit;
|
|
952
961
|
|
|
953
962
|
if (this._isSelectElement) {
|
|
954
963
|
const backingOptions = activeChoices.filter((choice) => !choice.element);
|
|
@@ -961,11 +970,16 @@ class Choices {
|
|
|
961
970
|
const renderableChoices = (choices: ChoiceFull[]): ChoiceFull[] =>
|
|
962
971
|
choices.filter(
|
|
963
972
|
(choice) =>
|
|
964
|
-
!choice.placeholder &&
|
|
973
|
+
!choice.placeholder &&
|
|
974
|
+
(isSearching
|
|
975
|
+
? (config.searchRenderSelectedChoices || !choice.selected) && !!choice.rank
|
|
976
|
+
: config.renderSelectedChoices || !choice.selected),
|
|
965
977
|
);
|
|
966
978
|
|
|
979
|
+
const showLabel = config.appendGroupInSearch && isSearching;
|
|
967
980
|
let selectableChoices = false;
|
|
968
|
-
|
|
981
|
+
let highlightedEl: HTMLElement | null = null;
|
|
982
|
+
const renderChoices = (choices: ChoiceFull[], withinGroup: boolean): void => {
|
|
969
983
|
if (isSearching) {
|
|
970
984
|
// sortByRank is used to ensure stable sorting, as scores are non-unique
|
|
971
985
|
// this additionally ensures fuseOptions.sortFn is not ignored
|
|
@@ -975,17 +989,25 @@ class Choices {
|
|
|
975
989
|
}
|
|
976
990
|
|
|
977
991
|
let choiceLimit = choices.length;
|
|
978
|
-
choiceLimit = !withinGroup && renderLimit && choiceLimit > renderLimit ? renderLimit : choiceLimit;
|
|
992
|
+
choiceLimit = !withinGroup && renderLimit > 0 && choiceLimit > renderLimit ? renderLimit : choiceLimit;
|
|
979
993
|
choiceLimit--;
|
|
980
994
|
|
|
981
995
|
choices.every((choice, index) => {
|
|
982
996
|
// choiceEl being empty signals the contents has probably significantly changed
|
|
983
997
|
const dropdownItem =
|
|
984
|
-
choice.choiceEl ||
|
|
998
|
+
choice.choiceEl ||
|
|
999
|
+
this._templates.choice(
|
|
1000
|
+
config,
|
|
1001
|
+
choice,
|
|
1002
|
+
config.itemSelectText,
|
|
1003
|
+
showLabel && choice.group ? choice.group.label : undefined,
|
|
1004
|
+
);
|
|
985
1005
|
choice.choiceEl = dropdownItem;
|
|
986
1006
|
fragment.appendChild(dropdownItem);
|
|
987
1007
|
if (isSearching || !choice.selected) {
|
|
988
1008
|
selectableChoices = true;
|
|
1009
|
+
} else if (!highlightedEl) {
|
|
1010
|
+
highlightedEl = dropdownItem;
|
|
989
1011
|
}
|
|
990
1012
|
|
|
991
1013
|
return index < choiceLimit;
|
|
@@ -1002,7 +1024,6 @@ class Choices {
|
|
|
1002
1024
|
renderChoices(
|
|
1003
1025
|
activeChoices.filter((choice) => choice.placeholder && !choice.group),
|
|
1004
1026
|
false,
|
|
1005
|
-
undefined,
|
|
1006
1027
|
);
|
|
1007
1028
|
}
|
|
1008
1029
|
|
|
@@ -1016,7 +1037,6 @@ class Choices {
|
|
|
1016
1037
|
renderChoices(
|
|
1017
1038
|
activeChoices.filter((choice) => !choice.placeholder && !choice.group),
|
|
1018
1039
|
false,
|
|
1019
|
-
undefined,
|
|
1020
1040
|
);
|
|
1021
1041
|
|
|
1022
1042
|
activeGroups.forEach((group) => {
|
|
@@ -1028,11 +1048,11 @@ class Choices {
|
|
|
1028
1048
|
dropdownGroup.remove();
|
|
1029
1049
|
fragment.appendChild(dropdownGroup);
|
|
1030
1050
|
}
|
|
1031
|
-
renderChoices(groupChoices, true
|
|
1051
|
+
renderChoices(groupChoices, true);
|
|
1032
1052
|
}
|
|
1033
1053
|
});
|
|
1034
1054
|
} else {
|
|
1035
|
-
renderChoices(renderableChoices(activeChoices), false
|
|
1055
|
+
renderChoices(renderableChoices(activeChoices), false);
|
|
1036
1056
|
}
|
|
1037
1057
|
}
|
|
1038
1058
|
|
|
@@ -1049,9 +1069,7 @@ class Choices {
|
|
|
1049
1069
|
this._renderNotice(fragment);
|
|
1050
1070
|
this.choiceList.element.replaceChildren(fragment);
|
|
1051
1071
|
|
|
1052
|
-
|
|
1053
|
-
this._highlightChoice();
|
|
1054
|
-
}
|
|
1072
|
+
this._highlightChoice(highlightedEl);
|
|
1055
1073
|
}
|
|
1056
1074
|
|
|
1057
1075
|
_renderItems(): void {
|
|
@@ -1183,23 +1201,12 @@ class Choices {
|
|
|
1183
1201
|
}
|
|
1184
1202
|
}
|
|
1185
1203
|
|
|
1204
|
+
/**
|
|
1205
|
+
* @deprecated Use utils.getChoiceForOutput
|
|
1206
|
+
*/
|
|
1186
1207
|
// eslint-disable-next-line class-methods-use-this
|
|
1187
1208
|
_getChoiceForOutput(choice: ChoiceFull, keyCode?: number): EventChoice {
|
|
1188
|
-
return
|
|
1189
|
-
id: choice.id,
|
|
1190
|
-
highlighted: choice.highlighted,
|
|
1191
|
-
labelClass: choice.labelClass,
|
|
1192
|
-
labelDescription: choice.labelDescription,
|
|
1193
|
-
customProperties: choice.customProperties,
|
|
1194
|
-
disabled: choice.disabled,
|
|
1195
|
-
active: choice.active,
|
|
1196
|
-
label: choice.label,
|
|
1197
|
-
placeholder: choice.placeholder,
|
|
1198
|
-
value: choice.value,
|
|
1199
|
-
groupValue: choice.group ? choice.group.label : undefined,
|
|
1200
|
-
element: choice.element,
|
|
1201
|
-
keyCode,
|
|
1202
|
-
};
|
|
1209
|
+
return getChoiceForOutput(choice, keyCode);
|
|
1203
1210
|
}
|
|
1204
1211
|
|
|
1205
1212
|
_triggerChange(value): void {
|
|
@@ -1218,7 +1225,7 @@ class Choices {
|
|
|
1218
1225
|
return;
|
|
1219
1226
|
}
|
|
1220
1227
|
|
|
1221
|
-
const id = element && parseDataSetId(element.
|
|
1228
|
+
const id = element && parseDataSetId(element.closest('[data-id]'));
|
|
1222
1229
|
const itemToRemove = id && items.find((item) => item.id === id);
|
|
1223
1230
|
if (!itemToRemove) {
|
|
1224
1231
|
return;
|
|
@@ -1428,7 +1435,7 @@ class Choices {
|
|
|
1428
1435
|
|
|
1429
1436
|
if (canAddItem && typeof config.addItemFilter === 'function' && !config.addItemFilter(value)) {
|
|
1430
1437
|
canAddItem = false;
|
|
1431
|
-
notice = resolveNoticeFunction(config.customAddItemText, value);
|
|
1438
|
+
notice = resolveNoticeFunction(config.customAddItemText, value, undefined);
|
|
1432
1439
|
}
|
|
1433
1440
|
|
|
1434
1441
|
if (canAddItem) {
|
|
@@ -1442,13 +1449,13 @@ class Choices {
|
|
|
1442
1449
|
}
|
|
1443
1450
|
if (!config.duplicateItemsAllowed) {
|
|
1444
1451
|
canAddItem = false;
|
|
1445
|
-
notice = resolveNoticeFunction(config.uniqueItemText, value);
|
|
1452
|
+
notice = resolveNoticeFunction(config.uniqueItemText, value, undefined);
|
|
1446
1453
|
}
|
|
1447
1454
|
}
|
|
1448
1455
|
}
|
|
1449
1456
|
|
|
1450
1457
|
if (canAddItem) {
|
|
1451
|
-
notice = resolveNoticeFunction(config.addItemText, value);
|
|
1458
|
+
notice = resolveNoticeFunction(config.addItemText, value, undefined);
|
|
1452
1459
|
}
|
|
1453
1460
|
|
|
1454
1461
|
if (notice) {
|
|
@@ -1510,6 +1517,7 @@ class Choices {
|
|
|
1510
1517
|
const documentElement = this._docRoot;
|
|
1511
1518
|
const outerElement = this.containerOuter.element;
|
|
1512
1519
|
const inputElement = this.input.element;
|
|
1520
|
+
const passedElement = this.passedElement.element;
|
|
1513
1521
|
|
|
1514
1522
|
// capture events - can cancel event processing or propagation
|
|
1515
1523
|
documentElement.addEventListener('touchend', this._onTouchEnd, true);
|
|
@@ -1554,6 +1562,16 @@ class Choices {
|
|
|
1554
1562
|
});
|
|
1555
1563
|
}
|
|
1556
1564
|
|
|
1565
|
+
if (passedElement.hasAttribute('required')) {
|
|
1566
|
+
passedElement.addEventListener('change', this._onChange, {
|
|
1567
|
+
passive: true,
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
passedElement.addEventListener('invalid', this._onInvalid, {
|
|
1571
|
+
passive: true,
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1557
1575
|
this.input.addEventListeners();
|
|
1558
1576
|
}
|
|
1559
1577
|
|
|
@@ -1561,6 +1579,7 @@ class Choices {
|
|
|
1561
1579
|
const documentElement = this._docRoot;
|
|
1562
1580
|
const outerElement = this.containerOuter.element;
|
|
1563
1581
|
const inputElement = this.input.element;
|
|
1582
|
+
const passedElement = this.passedElement.element;
|
|
1564
1583
|
|
|
1565
1584
|
documentElement.removeEventListener('touchend', this._onTouchEnd, true);
|
|
1566
1585
|
outerElement.removeEventListener('keydown', this._onKeyDown, true);
|
|
@@ -1584,6 +1603,11 @@ class Choices {
|
|
|
1584
1603
|
inputElement.form.removeEventListener('reset', this._onFormReset);
|
|
1585
1604
|
}
|
|
1586
1605
|
|
|
1606
|
+
if (passedElement.hasAttribute('required')) {
|
|
1607
|
+
passedElement.removeEventListener('change', this._onChange);
|
|
1608
|
+
passedElement.removeEventListener('invalid', this._onInvalid);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1587
1611
|
this.input.removeEventListeners();
|
|
1588
1612
|
}
|
|
1589
1613
|
|
|
@@ -1882,7 +1906,7 @@ class Choices {
|
|
|
1882
1906
|
*/
|
|
1883
1907
|
_onMouseDown(event: MouseEvent): void {
|
|
1884
1908
|
const { target } = event;
|
|
1885
|
-
if (!(target instanceof
|
|
1909
|
+
if (!(target instanceof Element)) {
|
|
1886
1910
|
return;
|
|
1887
1911
|
}
|
|
1888
1912
|
|
|
@@ -1994,7 +2018,7 @@ class Choices {
|
|
|
1994
2018
|
containerOuter.removeFocusState();
|
|
1995
2019
|
|
|
1996
2020
|
// Also close the dropdown if search is disabled
|
|
1997
|
-
if (!this.
|
|
2021
|
+
if (!this.config.searchEnabled) {
|
|
1998
2022
|
this.hideDropdown(true);
|
|
1999
2023
|
}
|
|
2000
2024
|
}
|
|
@@ -2018,14 +2042,22 @@ class Choices {
|
|
|
2018
2042
|
});
|
|
2019
2043
|
}
|
|
2020
2044
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
if (!choices.length) {
|
|
2045
|
+
_onChange(event: Event & { target: HTMLInputElement | HTMLSelectElement }): void {
|
|
2046
|
+
if (!event.target.checkValidity()) {
|
|
2025
2047
|
return;
|
|
2026
2048
|
}
|
|
2027
2049
|
|
|
2028
|
-
|
|
2050
|
+
this.containerOuter.removeInvalidState();
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
_onInvalid(): void {
|
|
2054
|
+
this.containerOuter.addInvalidState();
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
/**
|
|
2058
|
+
* Removes any highlighted choice options
|
|
2059
|
+
*/
|
|
2060
|
+
_removeHighlightedChoices(): void {
|
|
2029
2061
|
const { highlightedState } = this.config.classNames;
|
|
2030
2062
|
const highlightedChoices = Array.from(
|
|
2031
2063
|
this.dropdown.element.querySelectorAll<HTMLElement>(getClassNamesSelector(highlightedState)),
|
|
@@ -2036,6 +2068,19 @@ class Choices {
|
|
|
2036
2068
|
removeClassesFromElement(choice, highlightedState);
|
|
2037
2069
|
choice.setAttribute('aria-selected', 'false');
|
|
2038
2070
|
});
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
_highlightChoice(el: HTMLElement | null = null): void {
|
|
2074
|
+
const choices = Array.from(this.dropdown.element.querySelectorAll<HTMLElement>(selectableChoiceIdentifier));
|
|
2075
|
+
|
|
2076
|
+
if (!choices.length) {
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
let passedEl = el;
|
|
2081
|
+
const { highlightedState } = this.config.classNames;
|
|
2082
|
+
|
|
2083
|
+
this._removeHighlightedChoices();
|
|
2039
2084
|
|
|
2040
2085
|
if (passedEl) {
|
|
2041
2086
|
this._highlightPosition = choices.indexOf(passedEl);
|
|
@@ -2080,10 +2125,11 @@ class Choices {
|
|
|
2080
2125
|
this._store.dispatch(addItem(item));
|
|
2081
2126
|
|
|
2082
2127
|
if (withEvents) {
|
|
2083
|
-
|
|
2128
|
+
const eventChoice = getChoiceForOutput(item);
|
|
2129
|
+
this.passedElement.triggerEvent(EventType.addItem, eventChoice);
|
|
2084
2130
|
|
|
2085
2131
|
if (userTriggered) {
|
|
2086
|
-
this.passedElement.triggerEvent(EventType.choice,
|
|
2132
|
+
this.passedElement.triggerEvent(EventType.choice, eventChoice);
|
|
2087
2133
|
}
|
|
2088
2134
|
}
|
|
2089
2135
|
}
|
|
@@ -2099,7 +2145,7 @@ class Choices {
|
|
|
2099
2145
|
this._clearNotice();
|
|
2100
2146
|
}
|
|
2101
2147
|
|
|
2102
|
-
this.passedElement.triggerEvent(EventType.removeItem,
|
|
2148
|
+
this.passedElement.triggerEvent(EventType.removeItem, getChoiceForOutput(item));
|
|
2103
2149
|
}
|
|
2104
2150
|
|
|
2105
2151
|
_addChoice(choice: ChoiceFull, withEvents: boolean = true, userTriggered = false): void {
|
|
@@ -2242,26 +2288,26 @@ class Choices {
|
|
|
2242
2288
|
// Wrapper inner container with outer container
|
|
2243
2289
|
containerOuter.wrap(containerInner.element);
|
|
2244
2290
|
|
|
2291
|
+
containerOuter.element.appendChild(containerInner.element);
|
|
2292
|
+
containerOuter.element.appendChild(dropdownElement);
|
|
2293
|
+
containerInner.element.appendChild(this.itemList.element);
|
|
2294
|
+
dropdownElement.appendChild(this.choiceList.element);
|
|
2295
|
+
|
|
2245
2296
|
if (this._isSelectOneElement) {
|
|
2246
2297
|
this.input.placeholder = this.config.searchPlaceholderValue || '';
|
|
2298
|
+
if (this.config.searchEnabled) {
|
|
2299
|
+
dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild);
|
|
2300
|
+
}
|
|
2247
2301
|
} else {
|
|
2302
|
+
if (!this._isSelectMultipleElement || this.config.searchEnabled) {
|
|
2303
|
+
containerInner.element.appendChild(this.input.element);
|
|
2304
|
+
}
|
|
2248
2305
|
if (this._placeholderValue) {
|
|
2249
2306
|
this.input.placeholder = this._placeholderValue;
|
|
2250
2307
|
}
|
|
2251
2308
|
this.input.setWidth();
|
|
2252
2309
|
}
|
|
2253
2310
|
|
|
2254
|
-
containerOuter.element.appendChild(containerInner.element);
|
|
2255
|
-
containerOuter.element.appendChild(dropdownElement);
|
|
2256
|
-
containerInner.element.appendChild(this.itemList.element);
|
|
2257
|
-
dropdownElement.appendChild(this.choiceList.element);
|
|
2258
|
-
|
|
2259
|
-
if (!this._isSelectOneElement) {
|
|
2260
|
-
containerInner.element.appendChild(this.input.element);
|
|
2261
|
-
} else if (this.config.searchEnabled) {
|
|
2262
|
-
dropdownElement.insertBefore(this.input.element, dropdownElement.firstChild);
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
2311
|
this._highlightPosition = 0;
|
|
2266
2312
|
this._isSearching = false;
|
|
2267
2313
|
}
|
|
@@ -100,6 +100,14 @@ export default class Container {
|
|
|
100
100
|
removeClassesFromElement(this.element, this.classNames.focusState);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
addInvalidState(): void {
|
|
104
|
+
addClassesToElement(this.element, this.classNames.invalidState);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
removeInvalidState(): void {
|
|
108
|
+
removeClassesFromElement(this.element, this.classNames.invalidState);
|
|
109
|
+
}
|
|
110
|
+
|
|
103
111
|
enable(): void {
|
|
104
112
|
removeClassesFromElement(this.element, this.classNames.disabledState);
|
|
105
113
|
this.element.removeAttribute('aria-disabled');
|
|
@@ -94,7 +94,9 @@ export default class WrappedSelect extends WrappedElement<HTMLSelectElement> {
|
|
|
94
94
|
labelClass:
|
|
95
95
|
typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined,
|
|
96
96
|
labelDescription:
|
|
97
|
-
typeof option.dataset.labelDescription !== 'undefined'
|
|
97
|
+
typeof option.dataset.labelDescription !== 'undefined'
|
|
98
|
+
? { trusted: option.dataset.labelDescription }
|
|
99
|
+
: undefined,
|
|
98
100
|
customProperties: parseCustomProperties(option.dataset.customProperties),
|
|
99
101
|
};
|
|
100
102
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ClassNames } from './interfaces/class-names';
|
|
2
2
|
import { Options } from './interfaces/options';
|
|
3
|
-
import { sortByAlpha } from './lib/utils';
|
|
3
|
+
import { sanitise, sortByAlpha } from './lib/utils';
|
|
4
|
+
import { EventChoice } from './interfaces';
|
|
4
5
|
|
|
5
6
|
export const DEFAULT_CLASSNAMES: ClassNames = {
|
|
6
7
|
containerOuter: ['choices'],
|
|
@@ -28,6 +29,7 @@ export const DEFAULT_CLASSNAMES: ClassNames = {
|
|
|
28
29
|
selectedState: ['is-selected'],
|
|
29
30
|
flippedState: ['is-flipped'],
|
|
30
31
|
loadingState: ['is-loading'],
|
|
32
|
+
invalidState: ['is-invalid'],
|
|
31
33
|
notice: ['choices__notice'],
|
|
32
34
|
addChoice: ['choices__item--selectable', 'add-choice'],
|
|
33
35
|
noResults: ['has-no-results'],
|
|
@@ -44,7 +46,7 @@ export const DEFAULT_CONFIG: Options = {
|
|
|
44
46
|
singleModeForMultiSelect: false,
|
|
45
47
|
addChoices: false,
|
|
46
48
|
addItems: true,
|
|
47
|
-
addItemFilter: (value) => !!value && value !== '',
|
|
49
|
+
addItemFilter: (value: string): boolean => !!value && value !== '',
|
|
48
50
|
removeItems: true,
|
|
49
51
|
removeItemButton: false,
|
|
50
52
|
removeItemButtonAlignLeft: false,
|
|
@@ -56,6 +58,7 @@ export const DEFAULT_CONFIG: Options = {
|
|
|
56
58
|
paste: true,
|
|
57
59
|
searchEnabled: true,
|
|
58
60
|
searchChoices: true,
|
|
61
|
+
searchDisabledChoices: false,
|
|
59
62
|
searchFloor: 1,
|
|
60
63
|
searchResultLimit: 4,
|
|
61
64
|
searchFields: ['label', 'value'],
|
|
@@ -71,17 +74,19 @@ export const DEFAULT_CONFIG: Options = {
|
|
|
71
74
|
prependValue: null,
|
|
72
75
|
appendValue: null,
|
|
73
76
|
renderSelectedChoices: 'auto',
|
|
77
|
+
searchRenderSelectedChoices: true,
|
|
74
78
|
loadingText: 'Loading...',
|
|
75
79
|
noResultsText: 'No results found',
|
|
76
80
|
noChoicesText: 'No choices to choose from',
|
|
77
81
|
itemSelectText: 'Press to select',
|
|
78
82
|
uniqueItemText: 'Only unique values can be added',
|
|
79
83
|
customAddItemText: 'Only values matching specific conditions can be added',
|
|
80
|
-
addItemText: (value) => `Press Enter to add <b>"${value}"</b>`,
|
|
81
|
-
removeItemIconText: () => `Remove item`,
|
|
82
|
-
removeItemLabelText: (value
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
addItemText: (value: string) => `Press Enter to add <b>"${value}"</b>`,
|
|
85
|
+
removeItemIconText: (): string => `Remove item`,
|
|
86
|
+
removeItemLabelText: (value: string, _valueRaw: string, i?: EventChoice): string =>
|
|
87
|
+
`Remove item: ${i ? sanitise<string>(i.label) : value}`,
|
|
88
|
+
maxItemText: (maxItemCount: number): string => `Only ${maxItemCount} values can be added`,
|
|
89
|
+
valueComparer: (value1: string, value2: string): boolean => value1 === value2,
|
|
85
90
|
fuseOptions: {
|
|
86
91
|
includeScore: true,
|
|
87
92
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StringUntrusted } from './string-untrusted';
|
|
2
|
+
import { StringPreEscaped } from './string-pre-escaped';
|
|
2
3
|
import { Types } from './types';
|
|
3
4
|
// eslint-disable-next-line import/no-cycle
|
|
4
5
|
import { GroupFull } from './group-full';
|
|
@@ -15,7 +16,7 @@ export interface ChoiceFull {
|
|
|
15
16
|
itemEl?: HTMLElement;
|
|
16
17
|
choiceEl?: HTMLElement;
|
|
17
18
|
labelClass?: Array<string>;
|
|
18
|
-
labelDescription?: string;
|
|
19
|
+
labelDescription?: StringPreEscaped | StringUntrusted | string;
|
|
19
20
|
customProperties?: Types.CustomProperties;
|
|
20
21
|
disabled: boolean;
|
|
21
22
|
active: boolean;
|
|
@@ -50,6 +50,8 @@ export interface ClassNames {
|
|
|
50
50
|
flippedState: string | Array<string>;
|
|
51
51
|
/** @default ['is-loading'] */
|
|
52
52
|
loadingState: string | Array<string>;
|
|
53
|
+
/** @default ['is-invalid'] */
|
|
54
|
+
invalidState: string | Array<string>;
|
|
53
55
|
/** @default ['choices__notice'] */
|
|
54
56
|
notice: string | Array<string>;
|
|
55
57
|
/** @default ['choices__item--selectable', 'add-choice'] */
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { StringUntrusted } from './string-untrusted';
|
|
2
|
+
import { StringPreEscaped } from './string-pre-escaped';
|
|
3
|
+
// eslint-disable-next-line
|
|
2
4
|
import { Types } from './types';
|
|
3
5
|
|
|
4
6
|
export interface InputChoice {
|
|
5
7
|
id?: number;
|
|
6
8
|
highlighted?: boolean;
|
|
7
9
|
labelClass?: string | Array<string>;
|
|
8
|
-
labelDescription?: string;
|
|
10
|
+
labelDescription?: StringPreEscaped | StringUntrusted | string;
|
|
9
11
|
customProperties?: Types.CustomProperties;
|
|
10
12
|
disabled?: boolean;
|
|
11
13
|
active?: boolean;
|
|
@@ -13,5 +15,5 @@ export interface InputChoice {
|
|
|
13
15
|
placeholder?: boolean;
|
|
14
16
|
selected?: boolean;
|
|
15
17
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
-
value: any;
|
|
18
|
+
value: any; // string;
|
|
17
19
|
}
|
|
@@ -200,7 +200,7 @@ export interface Options {
|
|
|
200
200
|
*
|
|
201
201
|
* @default
|
|
202
202
|
* ```
|
|
203
|
-
* (value, valueRaw) => `Remove item`;
|
|
203
|
+
* (value, valueRaw, item) => `Remove item`;
|
|
204
204
|
* ```
|
|
205
205
|
*/
|
|
206
206
|
removeItemIconText: string | Types.NoticeStringFunction;
|
|
@@ -215,7 +215,7 @@ export interface Options {
|
|
|
215
215
|
*
|
|
216
216
|
* @default
|
|
217
217
|
* ```
|
|
218
|
-
* (value, valueRaw) => `Remove item: ${value}`;
|
|
218
|
+
* (value, valueRaw, item) => `Remove item: ${value}`;
|
|
219
219
|
* ```
|
|
220
220
|
*/
|
|
221
221
|
removeItemLabelText: string | Types.NoticeStringFunction;
|
|
@@ -321,6 +321,15 @@ export interface Options {
|
|
|
321
321
|
*/
|
|
322
322
|
searchChoices: boolean;
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Whether disabled choices should be included in search results. If `true`, disabled choices will appear in search results but still cannot be selected.
|
|
326
|
+
*
|
|
327
|
+
* **Input types affected:** select-one, select-multiple
|
|
328
|
+
*
|
|
329
|
+
* @default false
|
|
330
|
+
*/
|
|
331
|
+
searchDisabledChoices: boolean;
|
|
332
|
+
|
|
324
333
|
/**
|
|
325
334
|
* The minimum length a search value should be before choices are searched.
|
|
326
335
|
*
|
|
@@ -471,6 +480,15 @@ export interface Options {
|
|
|
471
480
|
*/
|
|
472
481
|
renderSelectedChoices: 'auto' | 'always' | boolean;
|
|
473
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Whether selected choices should be removed from the list during search.
|
|
485
|
+
*
|
|
486
|
+
* **Input types affected:** select-multiple
|
|
487
|
+
*
|
|
488
|
+
* @default false;
|
|
489
|
+
*/
|
|
490
|
+
searchRenderSelectedChoices: boolean;
|
|
491
|
+
|
|
474
492
|
/**
|
|
475
493
|
* The text that is shown whilst choices are being populated via AJAX.
|
|
476
494
|
*
|
|
@@ -615,5 +633,5 @@ export interface Options {
|
|
|
615
633
|
*/
|
|
616
634
|
callbackOnCreateTemplates: CallbackOnCreateTemplatesFn | null;
|
|
617
635
|
|
|
618
|
-
appendGroupInSearch:
|
|
636
|
+
appendGroupInSearch: boolean;
|
|
619
637
|
}
|
|
@@ -53,7 +53,8 @@ export interface Store {
|
|
|
53
53
|
get activeChoices(): ChoiceFull[];
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Get choices that can be searched (excluding placeholders
|
|
56
|
+
* Get choices that can be searched (excluding placeholders,
|
|
57
|
+
* optionally excluding disabled based on config)
|
|
57
58
|
*/
|
|
58
59
|
get searchableChoices(): ChoiceFull[];
|
|
59
60
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { StringUntrusted } from './string-untrusted';
|
|
2
2
|
import { StringPreEscaped } from './string-pre-escaped';
|
|
3
|
+
// eslint-disable-next-line import/no-cycle
|
|
4
|
+
import { EventChoice } from './event-choice';
|
|
3
5
|
|
|
4
6
|
export namespace Types {
|
|
5
7
|
export type StrToEl = (str: string) => HTMLElement | HTMLInputElement | HTMLOptionElement;
|
|
6
8
|
export type EscapeForTemplateFn = (allowHTML: boolean, s: StringUntrusted | StringPreEscaped | string) => string;
|
|
7
9
|
export type GetClassNamesFn = (s: string | Array<string>) => string;
|
|
8
10
|
export type StringFunction = () => string;
|
|
9
|
-
export type NoticeStringFunction = (value: string, valueRaw: string) => string;
|
|
11
|
+
export type NoticeStringFunction = (value: string, valueRaw: string, item?: EventChoice) => string;
|
|
10
12
|
export type NoticeLimitFunction = (maxItemCount: number) => string;
|
|
11
13
|
export type FilterFunction = (value: string) => boolean;
|
|
12
14
|
export type ValueCompareFunction = (value1: string, value2: string) => boolean;
|