@navikt/ds-react 5.15.0 → 5.16.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.
Files changed (70) hide show
  1. package/_docs.json +145 -1
  2. package/cjs/form/combobox/Combobox.js +1 -1
  3. package/cjs/form/combobox/ComboboxProvider.js +2 -1
  4. package/cjs/form/combobox/ComboboxWrapper.js +1 -1
  5. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +59 -41
  6. package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +3 -1
  7. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +15 -3
  8. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js +52 -32
  9. package/cjs/form/combobox/Input/Input.js +3 -1
  10. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js +3 -1
  11. package/cjs/help-text/HelpText.js +1 -1
  12. package/cjs/util/create-context.js +72 -0
  13. package/cjs/util/hooks/descendants/descendant.js +117 -0
  14. package/cjs/util/hooks/descendants/useDescendant.js +108 -0
  15. package/cjs/util/hooks/descendants/utils.js +53 -0
  16. package/esm/form/combobox/Combobox.js +1 -1
  17. package/esm/form/combobox/Combobox.js.map +1 -1
  18. package/esm/form/combobox/ComboboxProvider.js +2 -1
  19. package/esm/form/combobox/ComboboxProvider.js.map +1 -1
  20. package/esm/form/combobox/ComboboxWrapper.js +1 -1
  21. package/esm/form/combobox/ComboboxWrapper.js.map +1 -1
  22. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +59 -41
  23. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  24. package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +2 -1
  25. package/esm/form/combobox/FilteredOptions/filtered-options-util.js +3 -1
  26. package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
  27. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +15 -3
  28. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  29. package/esm/form/combobox/FilteredOptions/useVirtualFocus.d.ts +2 -4
  30. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js +52 -32
  31. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
  32. package/esm/form/combobox/Input/Input.js +3 -1
  33. package/esm/form/combobox/Input/Input.js.map +1 -1
  34. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.d.ts +5 -2
  35. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js +3 -1
  36. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
  37. package/esm/form/combobox/types.d.ts +14 -0
  38. package/esm/help-text/HelpText.js +1 -1
  39. package/esm/help-text/HelpText.js.map +1 -1
  40. package/esm/util/create-context.d.ts +23 -0
  41. package/esm/util/create-context.js +46 -0
  42. package/esm/util/create-context.js.map +1 -0
  43. package/esm/util/hooks/descendants/descendant.d.ts +47 -0
  44. package/esm/util/hooks/descendants/descendant.js +114 -0
  45. package/esm/util/hooks/descendants/descendant.js.map +1 -0
  46. package/esm/util/hooks/descendants/useDescendant.d.ts +14 -0
  47. package/esm/util/hooks/descendants/useDescendant.js +82 -0
  48. package/esm/util/hooks/descendants/useDescendant.js.map +1 -0
  49. package/esm/util/hooks/descendants/utils.d.ts +12 -0
  50. package/esm/util/hooks/descendants/utils.js +46 -0
  51. package/esm/util/hooks/descendants/utils.js.map +1 -0
  52. package/package.json +3 -3
  53. package/src/form/combobox/Combobox.tsx +1 -1
  54. package/src/form/combobox/ComboboxProvider.tsx +2 -0
  55. package/src/form/combobox/ComboboxWrapper.tsx +0 -1
  56. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +131 -92
  57. package/src/form/combobox/FilteredOptions/filtered-options-util.ts +9 -2
  58. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +22 -3
  59. package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +63 -45
  60. package/src/form/combobox/Input/Input.tsx +3 -1
  61. package/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx +11 -1
  62. package/src/form/combobox/combobox.stories.tsx +36 -1
  63. package/src/form/combobox/combobox.test.tsx +1 -3
  64. package/src/form/combobox/types.ts +15 -0
  65. package/src/help-text/HelpText.tsx +1 -1
  66. package/src/util/create-context.tsx +67 -0
  67. package/src/util/hooks/descendants/descendant.stories.tsx +147 -0
  68. package/src/util/hooks/descendants/descendant.ts +161 -0
  69. package/src/util/hooks/descendants/useDescendant.tsx +111 -0
  70. package/src/util/hooks/descendants/utils.ts +56 -0
package/_docs.json CHANGED
@@ -10641,6 +10641,107 @@
10641
10641
  }
10642
10642
  }
10643
10643
  },
10644
+ {
10645
+ "filePath": "src/util/create-context.tsx",
10646
+ "displayName": "createContext",
10647
+ "props": {
10648
+ "hookName": {
10649
+ "defaultValue": null,
10650
+ "description": "",
10651
+ "name": "hookName",
10652
+ "parent": {
10653
+ "fileName": "src/util/create-context.tsx",
10654
+ "name": "CreateContextOptions"
10655
+ },
10656
+ "declarations": [
10657
+ {
10658
+ "fileName": "src/util/create-context.tsx",
10659
+ "name": "CreateContextOptions"
10660
+ }
10661
+ ],
10662
+ "required": false,
10663
+ "type": {
10664
+ "name": "string"
10665
+ }
10666
+ },
10667
+ "providerName": {
10668
+ "defaultValue": null,
10669
+ "description": "",
10670
+ "name": "providerName",
10671
+ "parent": {
10672
+ "fileName": "src/util/create-context.tsx",
10673
+ "name": "CreateContextOptions"
10674
+ },
10675
+ "declarations": [
10676
+ {
10677
+ "fileName": "src/util/create-context.tsx",
10678
+ "name": "CreateContextOptions"
10679
+ }
10680
+ ],
10681
+ "required": false,
10682
+ "type": {
10683
+ "name": "string"
10684
+ }
10685
+ },
10686
+ "errorMessage": {
10687
+ "defaultValue": null,
10688
+ "description": "",
10689
+ "name": "errorMessage",
10690
+ "parent": {
10691
+ "fileName": "src/util/create-context.tsx",
10692
+ "name": "CreateContextOptions"
10693
+ },
10694
+ "declarations": [
10695
+ {
10696
+ "fileName": "src/util/create-context.tsx",
10697
+ "name": "CreateContextOptions"
10698
+ }
10699
+ ],
10700
+ "required": false,
10701
+ "type": {
10702
+ "name": "string"
10703
+ }
10704
+ },
10705
+ "name": {
10706
+ "defaultValue": null,
10707
+ "description": "",
10708
+ "name": "name",
10709
+ "parent": {
10710
+ "fileName": "src/util/create-context.tsx",
10711
+ "name": "CreateContextOptions"
10712
+ },
10713
+ "declarations": [
10714
+ {
10715
+ "fileName": "src/util/create-context.tsx",
10716
+ "name": "CreateContextOptions"
10717
+ }
10718
+ ],
10719
+ "required": false,
10720
+ "type": {
10721
+ "name": "string"
10722
+ }
10723
+ },
10724
+ "defaultValue": {
10725
+ "defaultValue": null,
10726
+ "description": "",
10727
+ "name": "defaultValue",
10728
+ "parent": {
10729
+ "fileName": "src/util/create-context.tsx",
10730
+ "name": "CreateContextOptions"
10731
+ },
10732
+ "declarations": [
10733
+ {
10734
+ "fileName": "src/util/create-context.tsx",
10735
+ "name": "CreateContextOptions"
10736
+ }
10737
+ ],
10738
+ "required": false,
10739
+ "type": {
10740
+ "name": "T"
10741
+ }
10742
+ }
10743
+ }
10744
+ },
10644
10745
  {
10645
10746
  "filePath": "src/date/context/useSharedMonthContext.tsx",
10646
10747
  "displayName": "SharedMonthProvider",
@@ -14984,6 +15085,25 @@
14984
15085
  "name": "string[]"
14985
15086
  }
14986
15087
  },
15088
+ "maxSelected": {
15089
+ "defaultValue": null,
15090
+ "description": "Options for the maximum number of selected options.",
15091
+ "name": "maxSelected",
15092
+ "parent": {
15093
+ "fileName": "react/src/form/combobox/types.ts",
15094
+ "name": "ComboboxProps"
15095
+ },
15096
+ "declarations": [
15097
+ {
15098
+ "fileName": "react/src/form/combobox/types.ts",
15099
+ "name": "ComboboxProps"
15100
+ }
15101
+ ],
15102
+ "required": false,
15103
+ "type": {
15104
+ "name": "MaxSelected"
15105
+ }
15106
+ },
14987
15107
  "shouldAutocomplete": {
14988
15108
  "defaultValue": {
14989
15109
  "value": "false"
@@ -15437,6 +15557,25 @@
15437
15557
  "name": "string[]"
15438
15558
  }
15439
15559
  },
15560
+ "maxSelected": {
15561
+ "defaultValue": null,
15562
+ "description": "Options for the maximum number of selected options.",
15563
+ "name": "maxSelected",
15564
+ "parent": {
15565
+ "fileName": "react/src/form/combobox/types.ts",
15566
+ "name": "ComboboxProps"
15567
+ },
15568
+ "declarations": [
15569
+ {
15570
+ "fileName": "react/src/form/combobox/types.ts",
15571
+ "name": "ComboboxProps"
15572
+ }
15573
+ ],
15574
+ "required": false,
15575
+ "type": {
15576
+ "name": "MaxSelected"
15577
+ }
15578
+ },
15440
15579
  "shouldAutocomplete": {
15441
15580
  "defaultValue": {
15442
15581
  "value": "false"
@@ -19527,7 +19666,7 @@
19527
19666
  ],
19528
19667
  "required": true,
19529
19668
  "type": {
19530
- "name": "Pick<ComboboxProps, \"allowNewValues\" | \"isMultiSelect\" | \"options\" | \"selectedOptions\" | \"onToggleSelected\">"
19669
+ "name": "Pick<ComboboxProps, \"allowNewValues\" | \"isMultiSelect\" | \"options\" | \"selectedOptions\" | \"onToggleSelected\" | \"maxSelected\">"
19531
19670
  }
19532
19671
  }
19533
19672
  }
@@ -19682,5 +19821,10 @@
19682
19821
  }
19683
19822
  }
19684
19823
  }
19824
+ },
19825
+ {
19826
+ "filePath": "src/util/hooks/descendants/useDescendant.tsx",
19827
+ "displayName": "createDescendantContext",
19828
+ "props": {}
19685
19829
  }
19686
19830
  ]
@@ -67,7 +67,7 @@ exports.Combobox = (0, react_1.forwardRef)((props, ref) => {
67
67
  }), id: inputDescriptionId, size: size }, description)),
68
68
  react_1.default.createElement("div", { className: "navds-combobox__wrapper" },
69
69
  react_1.default.createElement("div", { className: (0, clsx_1.default)("navds-combobox__wrapper-inner navds-text-field__input", {
70
- "navds-combobox__wrapper-inner--virtually-unfocused": activeDecendantId !== null,
70
+ "navds-combobox__wrapper-inner--virtually-unfocused": activeDecendantId !== undefined,
71
71
  }), onClick: focusInput },
72
72
  !shouldShowSelectedOptions ? (react_1.default.createElement(Input_1.default, Object.assign({ id: inputProps.id, ref: mergedInputRef, inputClassName: inputClassName }, rest))) : (react_1.default.createElement(SelectedOptions_1.default, { selectedOptions: selectedOptions, size: size },
73
73
  react_1.default.createElement(Input_1.default, Object.assign({ id: inputProps.id, ref: mergedInputRef, inputClassName: inputClassName }, rest)))),
@@ -66,7 +66,7 @@ const customOptionsContext_1 = require("./customOptionsContext");
66
66
  * ```
67
67
  */
68
68
  const ComboboxProvider = (0, react_1.forwardRef)((props, ref) => {
69
- const { allowNewValues = false, children, defaultValue, error, errorId, filteredOptions, id, isListOpen, isLoading = false, isMultiSelect, onToggleSelected, selectedOptions, options, value, onChange, onClear, shouldAutocomplete, size } = props, rest = __rest(props, ["allowNewValues", "children", "defaultValue", "error", "errorId", "filteredOptions", "id", "isListOpen", "isLoading", "isMultiSelect", "onToggleSelected", "selectedOptions", "options", "value", "onChange", "onClear", "shouldAutocomplete", "size"]);
69
+ const { allowNewValues = false, children, defaultValue, error, errorId, filteredOptions, id, isListOpen, isLoading = false, isMultiSelect, onToggleSelected, selectedOptions, maxSelected, options, value, onChange, onClear, shouldAutocomplete, size } = props, rest = __rest(props, ["allowNewValues", "children", "defaultValue", "error", "errorId", "filteredOptions", "id", "isListOpen", "isLoading", "isMultiSelect", "onToggleSelected", "selectedOptions", "maxSelected", "options", "value", "onChange", "onClear", "shouldAutocomplete", "size"]);
70
70
  return (react_1.default.createElement(inputContext_1.InputContextProvider, { value: {
71
71
  defaultValue,
72
72
  error,
@@ -83,6 +83,7 @@ const ComboboxProvider = (0, react_1.forwardRef)((props, ref) => {
83
83
  allowNewValues,
84
84
  isMultiSelect,
85
85
  selectedOptions,
86
+ maxSelected,
86
87
  onToggleSelected,
87
88
  options,
88
89
  } },
@@ -50,6 +50,6 @@ const ComboboxWrapper = ({ children, className, hasError, inputProps, inputSize,
50
50
  "navds-combobox--error": hasError,
51
51
  "navds-combobox--disabled": !!inputProps.disabled,
52
52
  "navds-combobox--focused": hasFocusWithin,
53
- }), onFocus: onFocusInsideWrapper, onBlur: onBlurWrapper, tabIndex: -1 }, children));
53
+ }), onFocus: onFocusInsideWrapper, onBlur: onBlurWrapper }, children));
54
54
  };
55
55
  exports.default = ComboboxWrapper;
@@ -13,50 +13,68 @@ const selectedOptionsContext_1 = require("../SelectedOptions/selectedOptionsCont
13
13
  const filtered_options_util_1 = __importDefault(require("./filtered-options-util"));
14
14
  const filteredOptionsContext_1 = require("./filteredOptionsContext");
15
15
  const FilteredOptions = () => {
16
+ var _a;
16
17
  const { inputProps: { id }, size, value, } = (0, inputContext_1.useInputContext)();
17
18
  const { allowNewValues, isLoading, isListOpen, filteredOptions, setFilteredOptionsRef, isMouseLastUsedInputDevice, setIsMouseLastUsedInputDevice, isValueNew, toggleIsListOpen, activeDecendantId, virtualFocus, } = (0, filteredOptionsContext_1.useFilteredOptionsContext)();
18
- const { isMultiSelect, selectedOptions, toggleOption } = (0, selectedOptionsContext_1.useSelectedOptionsContext)();
19
- return (react_1.default.createElement("ul", { ref: setFilteredOptionsRef, className: (0, clsx_1.default)("navds-combobox__list", {
19
+ const { isMultiSelect, selectedOptions, toggleOption, maxSelected } = (0, selectedOptionsContext_1.useSelectedOptionsContext)();
20
+ const isDisabled = (option) => (maxSelected === null || maxSelected === void 0 ? void 0 : maxSelected.isLimitReached) && !selectedOptions.includes(option);
21
+ const shouldRenderNonSelectables = (maxSelected === null || maxSelected === void 0 ? void 0 : maxSelected.isLimitReached) || // Render maxSelected message
22
+ isLoading || // Render loading message
23
+ (!isLoading && filteredOptions.length === 0); // Render no hits message
24
+ const shouldRenderFilteredOptionsList = (allowNewValues && isValueNew && !(maxSelected === null || maxSelected === void 0 ? void 0 : maxSelected.isLimitReached)) || // Render add new option
25
+ filteredOptions.length > 0; // Render filtered options
26
+ return (react_1.default.createElement("div", { className: (0, clsx_1.default)("navds-combobox__list", {
20
27
  "navds-combobox__list--closed": !isListOpen,
21
28
  "navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
22
- }), id: filtered_options_util_1.default.getFilteredOptionsId(id), role: "listbox", tabIndex: -1 },
23
- isLoading && (react_1.default.createElement("li", { className: "navds-combobox__list-item--loading", role: "option", "aria-selected": false, id: filtered_options_util_1.default.getIsLoadingId(id), "data-no-focus": "true" },
24
- react_1.default.createElement(loader_1.Loader, { "aria-label": "S\u00F8ker..." }))),
25
- isValueNew && allowNewValues && (react_1.default.createElement("li", { tabIndex: -1, onMouseMove: () => {
26
- if (activeDecendantId !== filtered_options_util_1.default.getAddNewOptionId(id)) {
27
- virtualFocus.moveFocusToElement(filtered_options_util_1.default.getAddNewOptionId(id));
28
- setIsMouseLastUsedInputDevice(true);
29
- }
30
- }, onPointerUp: (event) => {
31
- toggleOption(value, event);
32
- if (!isMultiSelect && !selectedOptions.includes(value))
33
- toggleIsListOpen(false);
34
- }, id: filtered_options_util_1.default.getAddNewOptionId(id), className: (0, clsx_1.default)("navds-combobox__list-item__new-option", {
35
- "navds-combobox__list-item__new-option--focus": activeDecendantId === filtered_options_util_1.default.getAddNewOptionId(id),
36
- }), role: "option", "aria-selected": false },
37
- react_1.default.createElement(aksel_icons_1.PlusIcon, { "aria-hidden": true }),
38
- react_1.default.createElement(typography_1.BodyShort, { size: size },
39
- "Legg til",
40
- " ",
41
- react_1.default.createElement(typography_1.Label, { as: "span", size: size },
42
- "\u201C",
43
- value,
44
- "\u201D")))),
45
- !isLoading && filteredOptions.length === 0 && (react_1.default.createElement("li", { className: "navds-combobox__list-item__no-options", role: "option", "aria-selected": false, id: filtered_options_util_1.default.getNoHitsId(id), "data-no-focus": "true" }, "Ingen s\u00F8ketreff")),
46
- filteredOptions.map((option) => (react_1.default.createElement("li", { className: (0, clsx_1.default)("navds-combobox__list-item", {
47
- "navds-combobox__list-item--focus": activeDecendantId === filtered_options_util_1.default.getOptionId(id, option),
48
- "navds-combobox__list-item--selected": selectedOptions.includes(option),
49
- }), id: filtered_options_util_1.default.getOptionId(id, option), key: option, tabIndex: -1, onMouseMove: () => {
50
- if (activeDecendantId !== filtered_options_util_1.default.getOptionId(id, option)) {
51
- virtualFocus.moveFocusToElement(filtered_options_util_1.default.getOptionId(id, option));
52
- setIsMouseLastUsedInputDevice(true);
53
- }
54
- }, onPointerUp: (event) => {
55
- toggleOption(option, event);
56
- if (!isMultiSelect && !selectedOptions.includes(option))
57
- toggleIsListOpen(false);
58
- }, role: "option", "aria-selected": selectedOptions.includes(option) },
59
- react_1.default.createElement(typography_1.BodyShort, { size: size }, option),
60
- selectedOptions.includes(option) && react_1.default.createElement(aksel_icons_1.CheckmarkIcon, null))))));
29
+ }), id: filtered_options_util_1.default.getFilteredOptionsId(id), tabIndex: -1 },
30
+ shouldRenderNonSelectables && (react_1.default.createElement("div", { className: "navds-combobox__list_non-selectables", role: "status" },
31
+ (maxSelected === null || maxSelected === void 0 ? void 0 : maxSelected.isLimitReached) && (react_1.default.createElement("div", { className: "navds-combobox__list-item--max-selected", id: filtered_options_util_1.default.getMaxSelectedOptionsId(id) }, (_a = maxSelected.message) !== null && _a !== void 0 ? _a : `${selectedOptions.length} av ${maxSelected.limit} er valgt.`)),
32
+ isLoading && (react_1.default.createElement("div", { className: "navds-combobox__list-item--loading", id: filtered_options_util_1.default.getIsLoadingId(id) },
33
+ react_1.default.createElement(loader_1.Loader, { title: "S\u00F8ker..." }))),
34
+ !isLoading && filteredOptions.length === 0 && (react_1.default.createElement("div", { className: "navds-combobox__list-item--no-options", id: filtered_options_util_1.default.getNoHitsId(id) }, "Ingen s\u00F8ketreff")))),
35
+ shouldRenderFilteredOptionsList && (react_1.default.createElement("ul", { ref: setFilteredOptionsRef, role: "listbox", className: "navds-combobox__list-options" },
36
+ isValueNew && !(maxSelected === null || maxSelected === void 0 ? void 0 : maxSelected.isLimitReached) && allowNewValues && (react_1.default.createElement("li", { tabIndex: -1, onMouseMove: () => {
37
+ if (activeDecendantId !==
38
+ filtered_options_util_1.default.getAddNewOptionId(id)) {
39
+ virtualFocus.moveFocusToElement(filtered_options_util_1.default.getAddNewOptionId(id));
40
+ setIsMouseLastUsedInputDevice(true);
41
+ }
42
+ }, onPointerUp: (event) => {
43
+ toggleOption(value, event);
44
+ if (!isMultiSelect && !selectedOptions.includes(value))
45
+ toggleIsListOpen(false);
46
+ }, id: filtered_options_util_1.default.getAddNewOptionId(id), className: (0, clsx_1.default)("navds-combobox__list-item navds-combobox__list-item--new-option", {
47
+ "navds-combobox__list-item--new-option--focus": activeDecendantId ===
48
+ filtered_options_util_1.default.getAddNewOptionId(id),
49
+ }), role: "option", "aria-selected": false },
50
+ react_1.default.createElement(aksel_icons_1.PlusIcon, { "aria-hidden": true }),
51
+ react_1.default.createElement(typography_1.BodyShort, { size: size },
52
+ "Legg til",
53
+ " ",
54
+ react_1.default.createElement(typography_1.Label, { as: "span", size: size },
55
+ "\u201C",
56
+ value,
57
+ "\u201D")))),
58
+ filteredOptions.map((option) => (react_1.default.createElement("li", { className: (0, clsx_1.default)("navds-combobox__list-item", {
59
+ "navds-combobox__list-item--focus": activeDecendantId ===
60
+ filtered_options_util_1.default.getOptionId(id, option),
61
+ "navds-combobox__list-item--selected": selectedOptions.includes(option),
62
+ }), "data-no-focus": isDisabled(option) || undefined, id: filtered_options_util_1.default.getOptionId(id, option), key: option, tabIndex: -1, onMouseMove: () => {
63
+ if (activeDecendantId !==
64
+ filtered_options_util_1.default.getOptionId(id, option)) {
65
+ virtualFocus.moveFocusToElement(filtered_options_util_1.default.getOptionId(id, option));
66
+ setIsMouseLastUsedInputDevice(true);
67
+ }
68
+ }, onPointerUp: (event) => {
69
+ if (isDisabled(option)) {
70
+ return;
71
+ }
72
+ toggleOption(option, event);
73
+ if (!isMultiSelect && !selectedOptions.includes(option)) {
74
+ toggleIsListOpen(false);
75
+ }
76
+ }, role: "option", "aria-selected": selectedOptions.includes(option), "aria-disabled": isDisabled(option) || undefined },
77
+ react_1.default.createElement(typography_1.BodyShort, { size: size }, option),
78
+ selectedOptions.includes(option) && react_1.default.createElement(aksel_icons_1.CheckmarkIcon, null))))))));
61
79
  };
62
80
  exports.default = FilteredOptions;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const normalizeText = (text) => typeof text === "string" ? text.toLocaleLowerCase().trim() : "";
4
4
  const isPartOfText = (value, text) => normalizeText(text).startsWith(normalizeText(value !== null && value !== void 0 ? value : ""));
5
5
  const isValueInList = (value, list) => list === null || list === void 0 ? void 0 : list.find((listItem) => normalizeText(value) === normalizeText(listItem));
6
- const getMatchingValuesFromList = (value, list) => list === null || list === void 0 ? void 0 : list.filter((listItem) => isPartOfText(value, listItem));
6
+ const getMatchingValuesFromList = (value, list, alwaysIncluded) => list === null || list === void 0 ? void 0 : list.filter((listItem) => isPartOfText(value, listItem) || alwaysIncluded.includes(listItem));
7
7
  const getFilteredOptionsId = (comboboxId) => `${comboboxId}-filtered-options`;
8
8
  const getOptionId = (comboboxId, option) => `${comboboxId.toLocaleLowerCase()}-option-${option
9
9
  .replace(" ", "-")
@@ -11,6 +11,7 @@ const getOptionId = (comboboxId, option) => `${comboboxId.toLocaleLowerCase()}-o
11
11
  const getAddNewOptionId = (comboboxId) => `${comboboxId}-combobox-new-option`;
12
12
  const getIsLoadingId = (comboboxId) => `${comboboxId}-is-loading`;
13
13
  const getNoHitsId = (comboboxId) => `${comboboxId}-no-hits`;
14
+ const getMaxSelectedOptionsId = (comboboxId) => `${comboboxId}-max-selected-options`;
14
15
  exports.default = {
15
16
  normalizeText,
16
17
  isPartOfText,
@@ -21,4 +22,5 @@ exports.default = {
21
22
  getOptionId,
22
23
  getIsLoadingId,
23
24
  getNoHitsId,
25
+ getMaxSelectedOptionsId,
24
26
  };
@@ -31,6 +31,7 @@ const clsx_1 = __importDefault(require("clsx"));
31
31
  const react_1 = __importStar(require("react"));
32
32
  const hooks_1 = require("../../../util/hooks");
33
33
  const inputContext_1 = require("../Input/inputContext");
34
+ const selectedOptionsContext_1 = require("../SelectedOptions/selectedOptionsContext");
34
35
  const customOptionsContext_1 = require("../customOptionsContext");
35
36
  const filtered_options_util_1 = __importDefault(require("./filtered-options-util"));
36
37
  const useVirtualFocus_1 = __importDefault(require("./useVirtualFocus"));
@@ -40,6 +41,7 @@ const FilteredOptionsProvider = ({ children, value: props, }) => {
40
41
  const [filteredOptionsRef, setFilteredOptionsRef] = (0, react_1.useState)(null);
41
42
  const virtualFocus = (0, useVirtualFocus_1.default)(filteredOptionsRef);
42
43
  const { inputProps: { "aria-describedby": partialAriaDescribedBy, id }, value, searchTerm, setValue, setSearchTerm, shouldAutocomplete, } = (0, inputContext_1.useInputContext)();
44
+ const { selectedOptions, maxSelected } = (0, selectedOptionsContext_1.useSelectedOptionsContext)();
43
45
  const [isInternalListOpen, setInternalListOpen] = (0, react_1.useState)(false);
44
46
  const { customOptions } = (0, customOptionsContext_1.useCustomOptionsContext)();
45
47
  const filteredOptions = (0, react_1.useMemo)(() => {
@@ -47,8 +49,14 @@ const FilteredOptionsProvider = ({ children, value: props, }) => {
47
49
  return externalFilteredOptions;
48
50
  }
49
51
  const opts = [...customOptions, ...options];
50
- return filtered_options_util_1.default.getMatchingValuesFromList(searchTerm, opts);
51
- }, [customOptions, externalFilteredOptions, options, searchTerm]);
52
+ return filtered_options_util_1.default.getMatchingValuesFromList(searchTerm, opts, selectedOptions);
53
+ }, [
54
+ customOptions,
55
+ externalFilteredOptions,
56
+ options,
57
+ searchTerm,
58
+ selectedOptions,
59
+ ]);
52
60
  const previousSearchTerm = (0, hooks_1.usePrevious)(searchTerm);
53
61
  const [isMouseLastUsedInputDevice, setIsMouseLastUsedInputDevice] = (0, react_1.useState)(false);
54
62
  const filteredOptionsMap = (0, react_1.useMemo)(() => options.reduce((map, _option) => (Object.assign(Object.assign({}, map), { [filtered_options_util_1.default.getOptionId(id, _option)]: _option })), {
@@ -94,10 +102,14 @@ const FilteredOptionsProvider = ({ children, value: props, }) => {
94
102
  activeOption = filtered_options_util_1.default.getIsLoadingId(id);
95
103
  }
96
104
  }
97
- return (0, clsx_1.default)(activeOption, partialAriaDescribedBy) || undefined;
105
+ const maybeMaxSelectedOptionsId = (maxSelected === null || maxSelected === void 0 ? void 0 : maxSelected.isLimitReached) &&
106
+ filtered_options_util_1.default.getMaxSelectedOptionsId(id);
107
+ return ((0, clsx_1.default)(activeOption, maybeMaxSelectedOptionsId, partialAriaDescribedBy) ||
108
+ undefined);
98
109
  }, [
99
110
  isListOpen,
100
111
  isLoading,
112
+ maxSelected === null || maxSelected === void 0 ? void 0 : maxSelected.isLimitReached,
101
113
  value,
102
114
  partialAriaDescribedBy,
103
115
  shouldAutocomplete,
@@ -2,41 +2,62 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const react_1 = require("react");
4
4
  const useVirtualFocus = (containerRef) => {
5
- const [index, setIndex] = (0, react_1.useState)(-1);
6
- const listOfAllChildren = (containerRef === null || containerRef === void 0 ? void 0 : containerRef.children)
7
- ? Array.prototype.slice.call(containerRef === null || containerRef === void 0 ? void 0 : containerRef.children)
8
- : [];
9
- const elementsAbleToReceiveFocus = listOfAllChildren.filter((child) => child.getAttribute("data-no-focus") !== "true");
10
- const activeElement = elementsAbleToReceiveFocus[index];
11
- const getElementById = (id) => listOfAllChildren.find((element) => element.id === id);
12
- const isFocusOnTheTop = index === 0;
13
- const isFocusOnTheBottom = index === elementsAbleToReceiveFocus.length - 1;
14
- const scrollToOption = (newIndex) => {
15
- const indexOfElementToScrollTo = Math.min(Math.max(newIndex, 0), (containerRef === null || containerRef === void 0 ? void 0 : containerRef.children.length) || 0);
16
- if (containerRef === null || containerRef === void 0 ? void 0 : containerRef.children[indexOfElementToScrollTo]) {
17
- const child = containerRef.children[indexOfElementToScrollTo];
18
- const { top, bottom } = child.getBoundingClientRect();
19
- const parentRect = containerRef.getBoundingClientRect();
20
- if (top < parentRect.top || bottom > parentRect.bottom) {
21
- child.scrollIntoView({ block: "nearest" });
22
- }
5
+ const [activeElement, setActiveElement] = (0, react_1.useState)(undefined);
6
+ const getListOfAllChildren = () => { var _a; return Array.from((_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.children) !== null && _a !== void 0 ? _a : []); };
7
+ const getElementsAbleToReceiveFocus = () => getListOfAllChildren().filter((child) => child.getAttribute("data-no-focus") !== "true");
8
+ const getElementById = (id) => getListOfAllChildren().find((element) => element.id === id);
9
+ const isFocusOnTheTop = () => activeElement
10
+ ? getElementsAbleToReceiveFocus().indexOf(activeElement) === 0
11
+ : false;
12
+ const isFocusOnTheBottom = () => {
13
+ const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
14
+ return activeElement
15
+ ? elementsAbleToReceiveFocus.indexOf(activeElement) ===
16
+ elementsAbleToReceiveFocus.length - 1
17
+ : false;
18
+ };
19
+ const _moveFocusAndScrollTo = (_element) => {
20
+ var _a;
21
+ setActiveElement(_element);
22
+ (_a = _element === null || _element === void 0 ? void 0 : _element.scrollIntoView) === null || _a === void 0 ? void 0 : _a.call(_element, { block: "nearest" });
23
+ };
24
+ const moveFocusUp = () => {
25
+ if (!activeElement) {
26
+ return;
27
+ }
28
+ const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
29
+ const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
30
+ const elementAbove = elementsAbleToReceiveFocus[_currentIndex - 1];
31
+ if (_currentIndex === 0) {
32
+ setActiveElement(undefined);
33
+ }
34
+ else {
35
+ _moveFocusAndScrollTo(elementAbove);
36
+ }
37
+ };
38
+ const moveFocusDown = () => {
39
+ const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
40
+ if (!activeElement) {
41
+ _moveFocusAndScrollTo(elementsAbleToReceiveFocus[0]);
42
+ return;
43
+ }
44
+ const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
45
+ if (_currentIndex === elementsAbleToReceiveFocus.length - 1) {
46
+ return;
47
+ }
48
+ else {
49
+ _moveFocusAndScrollTo(elementsAbleToReceiveFocus[_currentIndex + 1]);
23
50
  }
24
51
  };
25
- const _moveFocusAndScrollTo = (_index) => {
26
- setIndex(_index);
27
- scrollToOption(_index);
52
+ const moveFocusToTop = () => _moveFocusAndScrollTo(undefined);
53
+ const moveFocusToBottom = () => {
54
+ const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
55
+ return _moveFocusAndScrollTo(elementsAbleToReceiveFocus[elementsAbleToReceiveFocus.length - 1]);
28
56
  };
29
- const moveFocusUp = () => _moveFocusAndScrollTo(Math.max(index - 1, -1));
30
- const moveFocusDown = () => _moveFocusAndScrollTo(Math.min(index + 1, elementsAbleToReceiveFocus.length - 1));
31
- const moveFocusToTop = () => _moveFocusAndScrollTo(-1);
32
- const moveFocusToBottom = () => _moveFocusAndScrollTo(elementsAbleToReceiveFocus.length - 1);
33
57
  const moveFocusToElement = (id) => {
34
- const thisElement = elementsAbleToReceiveFocus.find((_element) => _element.getAttribute("id") === id);
35
- const indexOfElement = thisElement
36
- ? elementsAbleToReceiveFocus.indexOf(thisElement)
37
- : -1;
38
- if (indexOfElement >= 0) {
39
- setIndex(indexOfElement);
58
+ const _element = getElementsAbleToReceiveFocus().find((_focusableElement) => _focusableElement.getAttribute("id") === id);
59
+ if (_element) {
60
+ setActiveElement(_element);
40
61
  }
41
62
  };
42
63
  return {
@@ -44,7 +65,6 @@ const useVirtualFocus = (containerRef) => {
44
65
  getElementById,
45
66
  isFocusOnTheTop,
46
67
  isFocusOnTheBottom,
47
- setIndex,
48
68
  moveFocusUp,
49
69
  moveFocusDown,
50
70
  moveFocusToElement,
@@ -101,9 +101,11 @@ const Input = (0, react_1.forwardRef)((_a, ref) => {
101
101
  onEnter(e);
102
102
  break;
103
103
  case "Home":
104
+ toggleIsListOpen(false);
104
105
  virtualFocus.moveFocusToTop();
105
106
  break;
106
107
  case "End":
108
+ toggleIsListOpen(true);
107
109
  virtualFocus.moveFocusToBottom();
108
110
  break;
109
111
  default:
@@ -134,7 +136,7 @@ const Input = (0, react_1.forwardRef)((_a, ref) => {
134
136
  // Otherwise ignore keystrokes, so it doesn't interfere with text editing
135
137
  if (isListOpen && activeDecendantId) {
136
138
  e.preventDefault();
137
- if (virtualFocus.isFocusOnTheTop) {
139
+ if (virtualFocus.isFocusOnTheTop()) {
138
140
  toggleIsListOpen(false);
139
141
  }
140
142
  virtualFocus.moveFocusUp();
@@ -32,7 +32,7 @@ const SelectedOptionsContext = (0, react_1.createContext)({});
32
32
  const SelectedOptionsProvider = ({ children, value, }) => {
33
33
  const { clearInput, focusInput } = (0, inputContext_1.useInputContext)();
34
34
  const { customOptions, removeCustomOption, addCustomOption, setCustomOptions, } = (0, customOptionsContext_1.useCustomOptionsContext)();
35
- const { allowNewValues, isMultiSelect, selectedOptions: externalSelectedOptions, onToggleSelected, options, } = value;
35
+ const { allowNewValues, isMultiSelect, selectedOptions: externalSelectedOptions, onToggleSelected, options, maxSelected, } = value;
36
36
  const [internalSelectedOptions, setSelectedOptions] = (0, react_1.useState)([]);
37
37
  const selectedOptions = (0, react_1.useMemo)(() => externalSelectedOptions !== null && externalSelectedOptions !== void 0 ? externalSelectedOptions : [...customOptions, ...internalSelectedOptions], [customOptions, externalSelectedOptions, internalSelectedOptions]);
38
38
  const addSelectedOption = (0, react_1.useCallback)((option) => {
@@ -90,6 +90,7 @@ const SelectedOptionsProvider = ({ children, value, }) => {
90
90
  selectedOptions,
91
91
  ]);
92
92
  const prevSelectedOptions = (0, hooks_1.usePrevious)(selectedOptions);
93
+ const isLimitReached = !!(maxSelected === null || maxSelected === void 0 ? void 0 : maxSelected.limit) && selectedOptions.length >= maxSelected.limit;
93
94
  const selectedOptionsState = {
94
95
  addSelectedOption,
95
96
  isMultiSelect,
@@ -98,6 +99,7 @@ const SelectedOptionsProvider = ({ children, value, }) => {
98
99
  selectedOptions,
99
100
  setSelectedOptions,
100
101
  toggleOption,
102
+ maxSelected: maxSelected && Object.assign(Object.assign({}, maxSelected), { isLimitReached }),
101
103
  };
102
104
  return (react_1.default.createElement(SelectedOptionsContext.Provider, { value: selectedOptionsState }, children));
103
105
  };
@@ -63,7 +63,7 @@ exports.HelpText = (0, react_1.forwardRef)((_a, ref) => {
63
63
  const mergedRef = (0, useMergeRefs_1.useMergeRefs)(buttonRef, ref);
64
64
  const [open, setOpen] = (0, react_1.useState)(false);
65
65
  return (react_1.default.createElement("div", { className: (0, clsx_1.default)("navds-help-text", wrapperClassName) },
66
- react_1.default.createElement("button", Object.assign({}, rest, { ref: mergedRef, onClick: (0, composeEventHandlers_1.composeEventHandlers)(onClick, () => setOpen((x) => x)), className: (0, clsx_1.default)(className, "navds-help-text__button"), type: "button", "aria-expanded": open }),
66
+ react_1.default.createElement("button", Object.assign({}, rest, { ref: mergedRef, onClick: (0, composeEventHandlers_1.composeEventHandlers)(onClick, () => setOpen((x) => !x)), className: (0, clsx_1.default)(className, "navds-help-text__button"), type: "button", "aria-expanded": open }),
67
67
  react_1.default.createElement(HelpTextIcon_1.HelpTextIcon, { title: title }),
68
68
  react_1.default.createElement(HelpTextIcon_1.HelpTextIcon, { filled: true, title: title })),
69
69
  react_1.default.createElement(popover_1.Popover, { onClose: () => setOpen(false), className: "navds-help-text__popover", open: open, anchorEl: buttonRef.current, placement: placement, strategy: strategy, offset: 12 },
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __rest = (this && this.__rest) || function (s, e) {
26
+ var t = {};
27
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
28
+ t[p] = s[p];
29
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
30
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
31
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
32
+ t[p[i]] = s[p[i]];
33
+ }
34
+ return t;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.createContext = void 0;
38
+ /**
39
+ * Custom createContext to consolidate context-implementation across the system
40
+ * Inspired by:
41
+ * - https://github.com/radix-ui/primitives/blob/main/packages/react/context/src/createContext.tsx
42
+ * - https://github.com/chakra-ui/chakra-ui/blob/5ec0be610b5a69afba01a9c22365155c1b519136/packages/hooks/context/src/index.ts
43
+ */
44
+ const react_1 = __importStar(require("react"));
45
+ function getErrorMessage(hook, provider) {
46
+ return `${hook} returned \`undefined\`. Seems you forgot to wrap component within ${provider}`;
47
+ }
48
+ function createContext(options = {}) {
49
+ const { name, hookName = "useContext", providerName = "Provider", errorMessage, defaultValue, } = options;
50
+ const Context = (0, react_1.createContext)(defaultValue);
51
+ function Provider(_a) {
52
+ var { children } = _a, context = __rest(_a, ["children"]);
53
+ // Only re-memoize when prop values change
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ const value = react_1.default.useMemo(() => context, Object.values(context));
56
+ return react_1.default.createElement(Context.Provider, { value: value }, children);
57
+ }
58
+ function useContext() {
59
+ var _a;
60
+ const context = (0, react_1.useContext)(Context);
61
+ if (!context) {
62
+ const error = new Error(errorMessage !== null && errorMessage !== void 0 ? errorMessage : getErrorMessage(hookName, providerName));
63
+ error.name = "ContextError";
64
+ (_a = Error.captureStackTrace) === null || _a === void 0 ? void 0 : _a.call(Error, error, useContext);
65
+ throw error;
66
+ }
67
+ return context;
68
+ }
69
+ Context.displayName = name;
70
+ return [Provider, useContext];
71
+ }
72
+ exports.createContext = createContext;