@navikt/ds-react 5.8.0 → 5.9.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 (118) hide show
  1. package/_docs.json +1794 -1749
  2. package/cjs/date/context/useDateInputContext.js +1 -5
  3. package/cjs/date/datepicker/DatePicker.js +26 -25
  4. package/cjs/date/hooks/useDatepicker.js +9 -17
  5. package/cjs/date/hooks/useMonthPicker.js +9 -17
  6. package/cjs/date/hooks/useRangeDatepicker.js +9 -20
  7. package/cjs/date/monthpicker/MonthPicker.js +11 -6
  8. package/cjs/date/{DateInput.js → parts/DateInput.js} +14 -10
  9. package/cjs/date/parts/DateWrapper.js +55 -0
  10. package/cjs/date/utils/labels.js +77 -1
  11. package/cjs/form/combobox/Combobox.js +2 -2
  12. package/cjs/form/combobox/ComboboxProvider.js +1 -2
  13. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +15 -14
  14. package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +24 -0
  15. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +23 -106
  16. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js +55 -0
  17. package/cjs/form/combobox/Input/Input.js +22 -13
  18. package/cjs/form/combobox/customOptionsContext.js +2 -3
  19. package/cjs/modal/Modal.js +4 -1
  20. package/cjs/popover/Popover.js +5 -7
  21. package/cjs/util/useMedia.js +30 -0
  22. package/esm/date/context/useDateInputContext.d.ts +6 -2
  23. package/esm/date/context/useDateInputContext.js +1 -5
  24. package/esm/date/context/useDateInputContext.js.map +1 -1
  25. package/esm/date/datepicker/DatePicker.d.ts +1 -1
  26. package/esm/date/datepicker/DatePicker.js +28 -27
  27. package/esm/date/datepicker/DatePicker.js.map +1 -1
  28. package/esm/date/datepicker/types.d.ts +0 -5
  29. package/esm/date/hooks/useDatepicker.d.ts +8 -5
  30. package/esm/date/hooks/useDatepicker.js +10 -18
  31. package/esm/date/hooks/useDatepicker.js.map +1 -1
  32. package/esm/date/hooks/useMonthPicker.d.ts +7 -4
  33. package/esm/date/hooks/useMonthPicker.js +10 -18
  34. package/esm/date/hooks/useMonthPicker.js.map +1 -1
  35. package/esm/date/hooks/useRangeDatepicker.d.ts +9 -3
  36. package/esm/date/hooks/useRangeDatepicker.js +10 -21
  37. package/esm/date/hooks/useRangeDatepicker.js.map +1 -1
  38. package/esm/date/index.d.ts +1 -1
  39. package/esm/date/index.js.map +1 -1
  40. package/esm/date/monthpicker/MonthPicker.d.ts +1 -1
  41. package/esm/date/monthpicker/MonthPicker.js +13 -8
  42. package/esm/date/monthpicker/MonthPicker.js.map +1 -1
  43. package/esm/date/monthpicker/types.d.ts +0 -5
  44. package/esm/date/{DateInput.d.ts → parts/DateInput.d.ts} +5 -1
  45. package/esm/date/{DateInput.js → parts/DateInput.js} +15 -11
  46. package/esm/date/parts/DateInput.js.map +1 -0
  47. package/esm/date/parts/DateWrapper.d.ts +15 -0
  48. package/esm/date/parts/DateWrapper.js +26 -0
  49. package/esm/date/parts/DateWrapper.js.map +1 -0
  50. package/esm/date/utils/labels.d.ts +2 -0
  51. package/esm/date/utils/labels.js +74 -0
  52. package/esm/date/utils/labels.js.map +1 -1
  53. package/esm/form/combobox/Combobox.js +2 -2
  54. package/esm/form/combobox/Combobox.js.map +1 -1
  55. package/esm/form/combobox/ComboboxProvider.js +1 -2
  56. package/esm/form/combobox/ComboboxProvider.js.map +1 -1
  57. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +15 -14
  58. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  59. package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +12 -0
  60. package/esm/form/combobox/FilteredOptions/filtered-options-util.js +23 -0
  61. package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -0
  62. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +10 -13
  63. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +24 -107
  64. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  65. package/esm/form/combobox/FilteredOptions/useVirtualFocus.d.ts +15 -0
  66. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js +54 -0
  67. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -0
  68. package/esm/form/combobox/Input/Input.js +22 -13
  69. package/esm/form/combobox/Input/Input.js.map +1 -1
  70. package/esm/form/combobox/customOptionsContext.d.ts +4 -1
  71. package/esm/form/combobox/customOptionsContext.js +2 -3
  72. package/esm/form/combobox/customOptionsContext.js.map +1 -1
  73. package/esm/modal/Modal.js +4 -1
  74. package/esm/modal/Modal.js.map +1 -1
  75. package/esm/popover/Popover.d.ts +0 -5
  76. package/esm/popover/Popover.js +5 -7
  77. package/esm/popover/Popover.js.map +1 -1
  78. package/esm/util/useMedia.d.ts +8 -0
  79. package/esm/util/useMedia.js +27 -0
  80. package/esm/util/useMedia.js.map +1 -0
  81. package/package.json +3 -3
  82. package/src/date/context/useDateInputContext.tsx +5 -5
  83. package/src/date/datepicker/DatePicker.tsx +58 -65
  84. package/src/date/datepicker/datepicker.stories.tsx +37 -46
  85. package/src/date/datepicker/types.ts +0 -5
  86. package/src/date/hooks/useDatepicker.tsx +20 -25
  87. package/src/date/hooks/useMonthPicker.tsx +18 -24
  88. package/src/date/hooks/useRangeDatepicker.tsx +27 -30
  89. package/src/date/index.ts +1 -1
  90. package/src/date/monthpicker/MonthPicker.tsx +39 -43
  91. package/src/date/monthpicker/types.ts +0 -5
  92. package/src/date/{DateInput.tsx → parts/DateInput.tsx} +23 -12
  93. package/src/date/parts/DateWrapper.tsx +80 -0
  94. package/src/date/utils/labels.ts +83 -0
  95. package/src/form/combobox/Combobox.tsx +2 -2
  96. package/src/form/combobox/ComboboxProvider.tsx +1 -2
  97. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +28 -16
  98. package/src/form/combobox/FilteredOptions/filtered-options-util.ts +38 -0
  99. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +70 -140
  100. package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +87 -0
  101. package/src/form/combobox/Input/Input.tsx +22 -18
  102. package/src/form/combobox/customOptionsContext.tsx +10 -5
  103. package/src/guide-panel/guidepanel.stories.tsx +2 -2
  104. package/src/modal/Modal.tsx +4 -1
  105. package/src/popover/Popover.tsx +4 -12
  106. package/src/util/__tests__/useMedia.test.tsx +19 -0
  107. package/src/util/useMedia.ts +38 -0
  108. package/cjs/date/hooks/useEscape.js +0 -23
  109. package/cjs/date/hooks/useOutsideClickHandler.js +0 -26
  110. package/esm/date/DateInput.js.map +0 -1
  111. package/esm/date/hooks/useEscape.d.ts +0 -2
  112. package/esm/date/hooks/useEscape.js +0 -20
  113. package/esm/date/hooks/useEscape.js.map +0 -1
  114. package/esm/date/hooks/useOutsideClickHandler.d.ts +0 -1
  115. package/esm/date/hooks/useOutsideClickHandler.js +0 -23
  116. package/esm/date/hooks/useOutsideClickHandler.js.map +0 -1
  117. package/src/date/hooks/useEscape.tsx +0 -30
  118. package/src/date/hooks/useOutsideClickHandler.tsx +0 -34
@@ -11,27 +11,28 @@ const selectedOptionsContext_1 = require("../SelectedOptions/selectedOptionsCont
11
11
  const inputContext_1 = require("../Input/inputContext");
12
12
  const loader_1 = require("../../../loader");
13
13
  const typography_1 = require("../../../typography");
14
+ const filtered_options_util_1 = __importDefault(require("./filtered-options-util"));
14
15
  const FilteredOptions = () => {
15
16
  const { inputProps: { id }, size, value, } = (0, inputContext_1.useInputContext)();
16
- const { allowNewValues, isLoading, isListOpen, filteredOptions, filteredOptionsIndex, filteredOptionsRef, isMouseLastUsedInputDevice, setIsMouseLastUsedInputDevice, isValueNew, setFilteredOptionsIndex, toggleIsListOpen, } = (0, filteredOptionsContext_1.useFilteredOptionsContext)();
17
+ const { allowNewValues, isLoading, isListOpen, filteredOptions, setFilteredOptionsRef, isMouseLastUsedInputDevice, setIsMouseLastUsedInputDevice, isValueNew, toggleIsListOpen, activeDecendantId, virtualFocus, } = (0, filteredOptionsContext_1.useFilteredOptionsContext)();
17
18
  const { isMultiSelect, selectedOptions, toggleOption } = (0, selectedOptionsContext_1.useSelectedOptionsContext)();
18
- return (react_1.default.createElement("ul", { ref: filteredOptionsRef, className: (0, clsx_1.default)("navds-combobox__list", {
19
+ return (react_1.default.createElement("ul", { ref: setFilteredOptionsRef, className: (0, clsx_1.default)("navds-combobox__list", {
19
20
  "navds-combobox__list--closed": !isListOpen,
20
21
  "navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
21
- }), id: `${id}-filtered-options`, role: "listbox", tabIndex: -1 },
22
- isLoading && (react_1.default.createElement("li", { className: "navds-combobox__list-item--loading", role: "option", "aria-selected": false, id: `${id}-is-loading` },
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" },
23
24
  react_1.default.createElement(loader_1.Loader, { "aria-label": "S\u00F8ker..." }))),
24
25
  isValueNew && allowNewValues && (react_1.default.createElement("li", { tabIndex: -1, onMouseMove: () => {
25
- if (filteredOptionsIndex !== -1) {
26
- setFilteredOptionsIndex(-1);
26
+ if (activeDecendantId !== filtered_options_util_1.default.getAddNewOptionId(id)) {
27
+ virtualFocus.moveFocusToElement(filtered_options_util_1.default.getAddNewOptionId(id));
27
28
  setIsMouseLastUsedInputDevice(true);
28
29
  }
29
30
  }, onPointerUp: (event) => {
30
31
  toggleOption(value, event);
31
32
  if (!isMultiSelect && !selectedOptions.includes(value))
32
33
  toggleIsListOpen(false);
33
- }, id: `${id}-combobox-new-option`, className: (0, clsx_1.default)("navds-combobox__list-item__new-option", {
34
- "navds-combobox__list-item__new-option--focus": filteredOptionsIndex === -1,
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),
35
36
  }), role: "option", "aria-selected": false },
36
37
  react_1.default.createElement(aksel_icons_1.PlusIcon, { "aria-hidden": true }),
37
38
  react_1.default.createElement(typography_1.BodyShort, { size: size },
@@ -41,13 +42,13 @@ const FilteredOptions = () => {
41
42
  "\u201C",
42
43
  value,
43
44
  "\u201D")))),
44
- !isLoading && filteredOptions.length === 0 && (react_1.default.createElement("li", { className: "navds-combobox__list-item__no-options", role: "option", "aria-selected": false, id: `${id}-no-hits` }, "Ingen s\u00F8ketreff")),
45
- filteredOptions.map((option, index) => (react_1.default.createElement("li", { className: (0, clsx_1.default)("navds-combobox__list-item", {
46
- "navds-combobox__list-item--focus": index === filteredOptionsIndex,
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),
47
48
  "navds-combobox__list-item--selected": selectedOptions.includes(option),
48
- }), id: `${id}-option-${option.replace(" ", "-")}`, key: option, tabIndex: -1, onMouseMove: () => {
49
- if (filteredOptionsIndex !== index) {
50
- setFilteredOptionsIndex(index);
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));
51
52
  setIsMouseLastUsedInputDevice(true);
52
53
  }
53
54
  }, onPointerUp: (event) => {
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const normalizeText = (text) => typeof text === "string" ? text.toLocaleLowerCase().trim() : "";
4
+ const isPartOfText = (value, text) => normalizeText(text).startsWith(normalizeText(value !== null && value !== void 0 ? value : ""));
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));
7
+ const getFilteredOptionsId = (comboboxId) => `${comboboxId}-filtered-options`;
8
+ const getOptionId = (comboboxId, option) => `${comboboxId.toLocaleLowerCase()}-option-${option
9
+ .replace(" ", "-")
10
+ .toLocaleLowerCase()}`;
11
+ const getAddNewOptionId = (comboboxId) => `${comboboxId}-combobox-new-option`;
12
+ const getIsLoadingId = (comboboxId) => `${comboboxId}-is-loading`;
13
+ const getNoHitsId = (comboboxId) => `${comboboxId}-no-hits`;
14
+ exports.default = {
15
+ normalizeText,
16
+ isPartOfText,
17
+ isValueInList,
18
+ getMatchingValuesFromList,
19
+ getFilteredOptionsId,
20
+ getAddNewOptionId,
21
+ getOptionId,
22
+ getIsLoadingId,
23
+ getNoHitsId,
24
+ };
@@ -33,16 +33,14 @@ const customOptionsContext_1 = require("../customOptionsContext");
33
33
  const inputContext_1 = require("../Input/inputContext");
34
34
  const usePrevious_1 = __importDefault(require("../../../util/usePrevious"));
35
35
  const util_1 = require("../../../util");
36
- const normalizeText = (text) => typeof text === "string" ? `${text}`.toLowerCase().trim() : "";
37
- const isPartOfText = (value, text) => normalizeText(text).startsWith(normalizeText(value !== null && value !== void 0 ? value : ""));
38
- const isValueInList = (value, list) => list === null || list === void 0 ? void 0 : list.find((listItem) => normalizeText(value) === normalizeText(listItem));
39
- const getMatchingValuesFromList = (value, list) => list === null || list === void 0 ? void 0 : list.filter((listItem) => isPartOfText(value, listItem));
36
+ const filtered_options_util_1 = __importDefault(require("./filtered-options-util"));
37
+ const useVirtualFocus_1 = __importDefault(require("./useVirtualFocus"));
40
38
  const FilteredOptionsContext = (0, react_1.createContext)({});
41
- const FilteredOptionsProvider = ({ children, value: props }) => {
39
+ const FilteredOptionsProvider = ({ children, value: props, }) => {
42
40
  const { allowNewValues, filteredOptions: externalFilteredOptions, isListOpen: isExternalListOpen, isLoading, options, } = props;
43
- const filteredOptionsRef = (0, react_1.useRef)(null);
41
+ const [filteredOptionsRef, setFilteredOptionsRef] = (0, react_1.useState)(null);
42
+ const virtualFocus = (0, useVirtualFocus_1.default)(filteredOptionsRef);
44
43
  const { inputProps: { "aria-describedby": partialAriaDescribedBy, id }, value, searchTerm, setValue, setSearchTerm, shouldAutocomplete, } = (0, inputContext_1.useInputContext)();
45
- const [filteredOptionsIndex, setFilteredOptionsIndex] = (0, react_1.useState)(null);
46
44
  const [isInternalListOpen, setInternalListOpen] = (0, react_1.useState)(false);
47
45
  const { customOptions } = (0, customOptionsContext_1.useCustomOptionsContext)();
48
46
  const filteredOptions = (0, react_1.useMemo)(() => {
@@ -50,14 +48,18 @@ const FilteredOptionsProvider = ({ children, value: props }) => {
50
48
  return externalFilteredOptions;
51
49
  }
52
50
  const opts = [...customOptions, ...options];
53
- setFilteredOptionsIndex(null);
54
- return getMatchingValuesFromList(searchTerm, opts);
51
+ return filtered_options_util_1.default.getMatchingValuesFromList(searchTerm, opts);
55
52
  }, [customOptions, externalFilteredOptions, options, searchTerm]);
56
53
  const previousSearchTerm = (0, usePrevious_1.default)(searchTerm);
57
54
  const [isMouseLastUsedInputDevice, setIsMouseLastUsedInputDevice] = (0, react_1.useState)(false);
55
+ const filteredOptionsMap = (0, react_1.useMemo)(() => options.reduce((map, _option) => (Object.assign(Object.assign({}, map), { [filtered_options_util_1.default.getOptionId(id, _option)]: _option })), {
56
+ [filtered_options_util_1.default.getAddNewOptionId(id)]: allowNewValues
57
+ ? value
58
+ : undefined,
59
+ }), [allowNewValues, id, options, value]);
58
60
  (0, util_1.useClientLayoutEffect)(() => {
59
61
  if (shouldAutocomplete &&
60
- normalizeText(searchTerm) !== "" &&
62
+ filtered_options_util_1.default.normalizeText(searchTerm) !== "" &&
61
63
  ((previousSearchTerm === null || previousSearchTerm === void 0 ? void 0 : previousSearchTerm.length) || 0) < searchTerm.length &&
62
64
  filteredOptions.length > 0) {
63
65
  setValue(`${searchTerm}${filteredOptions[0].substring(searchTerm.length)}`);
@@ -75,24 +77,22 @@ const FilteredOptionsProvider = ({ children, value: props }) => {
75
77
  return isExternalListOpen !== null && isExternalListOpen !== void 0 ? isExternalListOpen : isInternalListOpen;
76
78
  }, [isExternalListOpen, isInternalListOpen]);
77
79
  const toggleIsListOpen = (0, react_1.useCallback)((newState) => {
78
- setFilteredOptionsIndex(null);
80
+ virtualFocus.moveFocusToTop();
79
81
  setInternalListOpen((oldState) => newState !== null && newState !== void 0 ? newState : !oldState);
80
- }, []);
81
- const isValueNew = (0, react_1.useMemo)(() => Boolean(value) && !isValueInList(value, filteredOptions), [value, filteredOptions]);
82
- const getMinimumIndex = (0, react_1.useCallback)(() => {
83
- return isValueNew && allowNewValues ? -1 : 0;
84
- }, [allowNewValues, isValueNew]);
82
+ }, [virtualFocus]);
83
+ const isValueNew = (0, react_1.useMemo)(() => Boolean(value) &&
84
+ !filteredOptionsMap[filtered_options_util_1.default.getOptionId(id, value)], [filteredOptionsMap, id, value]);
85
85
  const ariaDescribedBy = (0, react_1.useMemo)(() => {
86
86
  let activeOption;
87
87
  if (!isLoading && filteredOptions.length === 0) {
88
- activeOption = `${id}-no-hits`;
88
+ activeOption = filtered_options_util_1.default.getNoHitsId(id);
89
89
  }
90
90
  else if ((value && value !== "") || isLoading) {
91
91
  if (shouldAutocomplete && filteredOptions[0]) {
92
- activeOption = `${id}-option-${filteredOptions[0].replace(" ", "-")}`;
92
+ activeOption = filtered_options_util_1.default.getOptionId(id, filteredOptions[0]);
93
93
  }
94
94
  else if (isListOpen && isLoading) {
95
- activeOption = `${id}-is-loading`;
95
+ activeOption = filtered_options_util_1.default.getIsLoadingId(id);
96
96
  }
97
97
  }
98
98
  return (0, clsx_1.default)(activeOption, partialAriaDescribedBy) || undefined;
@@ -105,91 +105,12 @@ const FilteredOptionsProvider = ({ children, value: props }) => {
105
105
  filteredOptions,
106
106
  id,
107
107
  ]);
108
- const currentOption = (0, react_1.useMemo)(() => {
109
- if (filteredOptionsIndex == null) {
110
- return null;
111
- }
112
- if (filteredOptionsIndex === -1) {
113
- return value;
114
- }
115
- return filteredOptions[filteredOptionsIndex];
116
- }, [filteredOptionsIndex, filteredOptions, value]);
117
- const resetFilteredOptionsIndex = () => {
118
- setFilteredOptionsIndex(getMinimumIndex());
119
- };
120
- const scrollToOption = (0, react_1.useCallback)((newIndex) => {
121
- if (filteredOptionsRef.current &&
122
- filteredOptionsRef.current.children[newIndex]) {
123
- const child = filteredOptionsRef.current.children[newIndex];
124
- const { top, bottom } = child.getBoundingClientRect();
125
- const parentRect = filteredOptionsRef.current.getBoundingClientRect();
126
- if (top < parentRect.top || bottom > parentRect.bottom) {
127
- child.scrollIntoView({ block: "nearest" });
128
- }
129
- }
130
- }, []);
131
- (0, react_1.useEffect)(() => {
132
- if (filteredOptionsIndex !== null && isListOpen) {
133
- scrollToOption(filteredOptionsIndex);
134
- }
135
- }, [filteredOptionsIndex, isListOpen, scrollToOption]);
136
- const moveFocusToInput = (0, react_1.useCallback)(() => {
137
- setFilteredOptionsIndex(null);
138
- toggleIsListOpen(false);
139
- }, [toggleIsListOpen]);
140
- const moveFocusToEnd = (0, react_1.useCallback)(() => {
141
- const lastIndex = filteredOptions.length - 1;
142
- toggleIsListOpen(true);
143
- setFilteredOptionsIndex(lastIndex);
144
- }, [filteredOptions.length, toggleIsListOpen]);
145
- const moveFocusUp = (0, react_1.useCallback)(() => {
146
- if (filteredOptionsIndex === null) {
147
- return;
148
- }
149
- if (filteredOptionsIndex === getMinimumIndex()) {
150
- toggleIsListOpen(false);
151
- setFilteredOptionsIndex(null);
152
- }
153
- else {
154
- const newIndex = Math.max(getMinimumIndex(), filteredOptionsIndex - 1);
155
- setFilteredOptionsIndex(newIndex);
156
- }
157
- }, [filteredOptionsIndex, getMinimumIndex, toggleIsListOpen]);
158
- const moveFocusDown = (0, react_1.useCallback)(() => {
159
- if (filteredOptionsIndex === null || !isListOpen) {
160
- toggleIsListOpen(true);
161
- if (allowNewValues || filteredOptions.length >= 1) {
162
- setFilteredOptionsIndex(getMinimumIndex());
163
- }
164
- return;
165
- }
166
- const newIndex = Math.min(filteredOptionsIndex + 1, Math.max(getMinimumIndex(), filteredOptions.length - 1));
167
- setFilteredOptionsIndex(newIndex);
168
- }, [
169
- allowNewValues,
170
- filteredOptions.length,
171
- filteredOptionsIndex,
172
- getMinimumIndex,
173
- isListOpen,
174
- toggleIsListOpen,
175
- ]);
176
- const activeDecendantId = (0, react_1.useMemo)(() => {
177
- if (filteredOptionsIndex === null) {
178
- return undefined;
179
- }
180
- else if (filteredOptionsIndex === -1) {
181
- return `${id}-combobox-new-option`;
182
- }
183
- else {
184
- return `${id}-option-${currentOption === null || currentOption === void 0 ? void 0 : currentOption.replace(" ", "-")}`;
185
- }
186
- }, [filteredOptionsIndex, currentOption, id]);
108
+ const currentOption = (0, react_1.useMemo)(() => { var _a; return filteredOptionsMap[((_a = virtualFocus.activeElement) === null || _a === void 0 ? void 0 : _a.getAttribute("id")) || -1]; }, [filteredOptionsMap, virtualFocus]);
109
+ const activeDecendantId = (0, react_1.useMemo)(() => { var _a; return ((_a = virtualFocus.activeElement) === null || _a === void 0 ? void 0 : _a.getAttribute("id")) || undefined; }, [virtualFocus.activeElement]);
187
110
  const filteredOptionsState = {
188
111
  activeDecendantId,
189
112
  allowNewValues,
190
- filteredOptionsRef,
191
- filteredOptionsIndex,
192
- setFilteredOptionsIndex,
113
+ setFilteredOptionsRef,
193
114
  shouldAutocomplete,
194
115
  isListOpen,
195
116
  isLoading,
@@ -199,11 +120,7 @@ const FilteredOptionsProvider = ({ children, value: props }) => {
199
120
  isValueNew,
200
121
  toggleIsListOpen,
201
122
  currentOption,
202
- resetFilteredOptionsIndex,
203
- moveFocusUp,
204
- moveFocusDown,
205
- moveFocusToInput,
206
- moveFocusToEnd,
123
+ virtualFocus,
207
124
  ariaDescribedBy,
208
125
  };
209
126
  return (react_1.default.createElement(FilteredOptionsContext.Provider, { value: filteredOptionsState }, children));
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const react_1 = require("react");
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
+ }
23
+ }
24
+ };
25
+ const _moveFocusAndScrollTo = (_index) => {
26
+ setIndex(_index);
27
+ scrollToOption(_index);
28
+ };
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
+ 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);
40
+ }
41
+ };
42
+ return {
43
+ activeElement,
44
+ getElementById,
45
+ isFocusOnTheTop,
46
+ isFocusOnTheBottom,
47
+ setIndex,
48
+ moveFocusUp,
49
+ moveFocusDown,
50
+ moveFocusToElement,
51
+ moveFocusToTop,
52
+ moveFocusToBottom,
53
+ };
54
+ };
55
+ exports.default = useVirtualFocus;
@@ -43,11 +43,12 @@ const clsx_1 = __importDefault(require("clsx"));
43
43
  const selectedOptionsContext_1 = require("../SelectedOptions/selectedOptionsContext");
44
44
  const filteredOptionsContext_1 = require("../FilteredOptions/filteredOptionsContext");
45
45
  const inputContext_1 = require("./inputContext");
46
+ const filtered_options_util_1 = __importDefault(require("../FilteredOptions/filtered-options-util"));
46
47
  const Input = (0, react_1.forwardRef)((_a, ref) => {
47
48
  var { inputClassName } = _a, rest = __rest(_a, ["inputClassName"]);
48
49
  const { clearInput, inputProps, onChange, size, value } = (0, inputContext_1.useInputContext)();
49
50
  const { selectedOptions, removeSelectedOption, toggleOption, isMultiSelect, } = (0, selectedOptionsContext_1.useSelectedOptionsContext)();
50
- const { activeDecendantId, allowNewValues, currentOption, filteredOptions, isValueNew, toggleIsListOpen, isListOpen, filteredOptionsIndex, moveFocusUp, moveFocusDown, ariaDescribedBy, moveFocusToInput, moveFocusToEnd, setFilteredOptionsIndex, setIsMouseLastUsedInputDevice, shouldAutocomplete, } = (0, filteredOptionsContext_1.useFilteredOptionsContext)();
51
+ const { activeDecendantId, allowNewValues, currentOption, filteredOptions, isValueNew, toggleIsListOpen, isListOpen, ariaDescribedBy, setIsMouseLastUsedInputDevice, shouldAutocomplete, virtualFocus, } = (0, filteredOptionsContext_1.useFilteredOptionsContext)();
51
52
  const onEnter = (0, react_1.useCallback)((event) => {
52
53
  const isTextInSelectedOptions = (text) => {
53
54
  return selectedOptions.find((item) => item.toLocaleLowerCase() === text.toLocaleLowerCase());
@@ -56,8 +57,9 @@ const Input = (0, react_1.forwardRef)((_a, ref) => {
56
57
  event.preventDefault();
57
58
  // Selecting a value from the dropdown / FilteredOptions
58
59
  toggleOption(currentOption, event);
59
- if (!isMultiSelect && !isTextInSelectedOptions(currentOption))
60
+ if (!isMultiSelect && !isTextInSelectedOptions(currentOption)) {
60
61
  toggleIsListOpen(false);
62
+ }
61
63
  }
62
64
  else if (shouldAutocomplete && isTextInSelectedOptions(value)) {
63
65
  event.preventDefault();
@@ -99,10 +101,10 @@ const Input = (0, react_1.forwardRef)((_a, ref) => {
99
101
  onEnter(e);
100
102
  break;
101
103
  case "Home":
102
- moveFocusToInput();
104
+ virtualFocus.moveFocusToTop();
103
105
  break;
104
106
  case "End":
105
- moveFocusToEnd();
107
+ virtualFocus.moveFocusToBottom();
106
108
  break;
107
109
  default:
108
110
  break;
@@ -121,26 +123,32 @@ const Input = (0, react_1.forwardRef)((_a, ref) => {
121
123
  // so we don't interfere with text editing
122
124
  if (e.target.selectionStart === (value === null || value === void 0 ? void 0 : value.length)) {
123
125
  e.preventDefault();
124
- moveFocusDown();
126
+ if (virtualFocus.activeElement === null || !isListOpen) {
127
+ toggleIsListOpen(true);
128
+ }
129
+ virtualFocus.moveFocusDown();
125
130
  }
126
131
  }
127
132
  else if (e.key === "ArrowUp") {
128
133
  // Check that the FilteredOptions list is open and has virtual focus.
129
134
  // Otherwise ignore keystrokes, so it doesn't interfere with text editing
130
- if (isListOpen && filteredOptionsIndex !== null) {
135
+ if (isListOpen && activeDecendantId) {
131
136
  e.preventDefault();
132
- moveFocusUp();
137
+ if (virtualFocus.isFocusOnTheTop) {
138
+ toggleIsListOpen(false);
139
+ }
140
+ virtualFocus.moveFocusUp();
133
141
  }
134
142
  }
135
143
  }, [
136
144
  value,
137
145
  selectedOptions,
138
146
  removeSelectedOption,
139
- moveFocusDown,
140
147
  isListOpen,
141
- filteredOptionsIndex,
142
- moveFocusUp,
148
+ activeDecendantId,
143
149
  setIsMouseLastUsedInputDevice,
150
+ toggleIsListOpen,
151
+ virtualFocus,
144
152
  ]);
145
153
  const onChangeHandler = (0, react_1.useCallback)((event) => {
146
154
  const newValue = event.target.value;
@@ -150,11 +158,12 @@ const Input = (0, react_1.forwardRef)((_a, ref) => {
150
158
  else if (filteredOptions.length === 0) {
151
159
  toggleIsListOpen(false);
152
160
  }
161
+ virtualFocus.moveFocusToTop();
153
162
  onChange(event);
154
- }, [filteredOptions.length, onChange, toggleIsListOpen]);
163
+ }, [filteredOptions.length, virtualFocus, onChange, toggleIsListOpen]);
155
164
  const onBlur = () => {
156
- setFilteredOptionsIndex(-1);
165
+ virtualFocus.moveFocusToTop();
157
166
  };
158
- return (react_1.default.createElement("input", Object.assign({}, rest, (0, util_1.omit)(inputProps, ["aria-invalid"]), { ref: ref, value: value, onChange: onChangeHandler, type: "text", role: "combobox", onBlur: onBlur, onKeyUp: handleKeyUp, onKeyDown: handleKeyDown, "aria-controls": `${inputProps.id}-filtered-options`, "aria-expanded": !!isListOpen, autoComplete: "off", "aria-autocomplete": shouldAutocomplete ? "both" : "list", "aria-activedescendant": activeDecendantId, "aria-describedby": ariaDescribedBy, "aria-invalid": inputProps["aria-invalid"], className: (0, clsx_1.default)(inputClassName, "navds-combobox__input", "navds-body-short", `navds-body-short--${size}`) })));
167
+ return (react_1.default.createElement("input", Object.assign({}, rest, (0, util_1.omit)(inputProps, ["aria-invalid"]), { ref: ref, value: value, onChange: onChangeHandler, type: "text", role: "combobox", onBlur: onBlur, onKeyUp: handleKeyUp, onKeyDown: handleKeyDown, "aria-controls": filtered_options_util_1.default.getFilteredOptionsId(inputProps.id), "aria-expanded": !!isListOpen, autoComplete: "off", "aria-autocomplete": shouldAutocomplete ? "both" : "list", "aria-activedescendant": activeDecendantId, "aria-describedby": ariaDescribedBy, "aria-invalid": inputProps["aria-invalid"], className: (0, clsx_1.default)(inputClassName, "navds-combobox__input", "navds-body-short", `navds-body-short--${size}`) })));
159
168
  });
160
169
  exports.default = Input;
@@ -26,12 +26,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.useCustomOptionsContext = exports.CustomOptionsProvider = void 0;
27
27
  const react_1 = __importStar(require("react"));
28
28
  const inputContext_1 = require("./Input/inputContext");
29
- const selectedOptionsContext_1 = require("./SelectedOptions/selectedOptionsContext");
30
29
  const CustomOptionsContext = (0, react_1.createContext)({});
31
- const CustomOptionsProvider = ({ children }) => {
30
+ const CustomOptionsProvider = ({ children, value, }) => {
32
31
  const [customOptions, setCustomOptions] = (0, react_1.useState)([]);
33
32
  const { focusInput } = (0, inputContext_1.useInputContext)();
34
- const { isMultiSelect } = (0, selectedOptionsContext_1.useSelectedOptionsContext)();
33
+ const { isMultiSelect } = value;
35
34
  const removeCustomOption = (0, react_1.useCallback)((option) => {
36
35
  setCustomOptions((prevCustomOptions) => prevCustomOptions.filter((o) => o !== option));
37
36
  focusInput();
@@ -42,6 +42,7 @@ const react_1 = require("@floating-ui/react");
42
42
  const clsx_1 = __importDefault(require("clsx"));
43
43
  const react_2 = __importStar(require("react"));
44
44
  const react_dom_1 = require("react-dom");
45
+ const context_1 = require("../date/context");
45
46
  const provider_1 = require("../provider");
46
47
  const typography_1 = require("../typography");
47
48
  const util_1 = require("../util");
@@ -105,7 +106,9 @@ exports.Modal = (0, react_2.forwardRef)((_a, ref) => {
105
106
  const ariaLabelId = (0, util_1.useId)();
106
107
  const rootElement = (_b = (0, provider_1.useProvider)()) === null || _b === void 0 ? void 0 : _b.rootElement;
107
108
  const portalNode = (0, react_1.useFloatingPortalNode)({ root: rootElement });
108
- if ((0, react_2.useContext)(ModalContext_1.ModalContext)) {
109
+ const dateContext = (0, react_2.useContext)(context_1.DateContext);
110
+ const modalContext = (0, react_2.useContext)(ModalContext_1.ModalContext);
111
+ if (modalContext && !dateContext) {
109
112
  console.error("Modals should not be nested");
110
113
  }
111
114
  (0, react_2.useEffect)(() => {
@@ -41,6 +41,7 @@ exports.Popover = void 0;
41
41
  const react_1 = require("@floating-ui/react");
42
42
  const clsx_1 = __importDefault(require("clsx"));
43
43
  const react_2 = __importStar(require("react"));
44
+ const context_1 = require("../date/context");
44
45
  const ModalContext_1 = require("../modal/ModalContext");
45
46
  const util_1 = require("../util");
46
47
  const PopoverContent_1 = __importDefault(require("./PopoverContent"));
@@ -65,11 +66,12 @@ const PopoverContent_1 = __importDefault(require("./PopoverContent"));
65
66
  * ```
66
67
  */
67
68
  exports.Popover = (0, react_2.forwardRef)((_a, ref) => {
68
- var { className, children, anchorEl, arrow = true, open, onClose, placement = "top", offset, strategy: userStrategy, bubbleEscape = false, flip: _flip = true } = _a, rest = __rest(_a, ["className", "children", "anchorEl", "arrow", "open", "onClose", "placement", "offset", "strategy", "bubbleEscape", "flip"]);
69
+ var { className, children, anchorEl, arrow = true, open, onClose, placement = "top", offset, strategy: userStrategy, flip: _flip = true } = _a, rest = __rest(_a, ["className", "children", "anchorEl", "arrow", "open", "onClose", "placement", "offset", "strategy", "flip"]);
69
70
  const arrowRef = (0, react_2.useRef)(null);
70
71
  const isInModal = (0, react_2.useContext)(ModalContext_1.ModalContext) !== null;
72
+ const isInDatepicker = (0, react_2.useContext)(context_1.DateContext) !== null;
71
73
  const chosenStrategy = userStrategy !== null && userStrategy !== void 0 ? userStrategy : (isInModal ? "fixed" : "absolute");
72
- const chosenFlip = isInModal ? true : _flip;
74
+ const chosenFlip = isInDatepicker ? false : _flip;
73
75
  const { x, y, strategy, context, update, refs, placement: flPlacement, middlewareData: { arrow: { x: arrowX, y: arrowY } = {} }, } = (0, react_1.useFloating)({
74
76
  strategy: chosenStrategy,
75
77
  placement,
@@ -85,11 +87,7 @@ exports.Popover = (0, react_2.forwardRef)((_a, ref) => {
85
87
  });
86
88
  const { getFloatingProps } = (0, react_1.useInteractions)([
87
89
  (0, react_1.useClick)(context),
88
- (0, react_1.useDismiss)(context, {
89
- bubbles: {
90
- escapeKey: bubbleEscape,
91
- },
92
- }),
90
+ (0, react_1.useDismiss)(context),
93
91
  ]);
94
92
  (0, util_1.useClientLayoutEffect)(() => {
95
93
  refs.setReference(anchorEl);
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useMedia = exports.noMatchMedia = void 0;
4
+ const react_1 = require("react");
5
+ exports.noMatchMedia = typeof window !== "undefined" && window.matchMedia === undefined;
6
+ /**
7
+ * @example useMedia("screen and (min-width: 1024px)")
8
+ * @param media string
9
+ * @param fallback boolean
10
+ * @returns boolean | undefined
11
+ */
12
+ const useMedia = (media, fallback) => {
13
+ const [matches, setMatches] = (0, react_1.useState)(fallback);
14
+ (0, react_1.useEffect)(() => {
15
+ if (exports.noMatchMedia) {
16
+ return;
17
+ }
18
+ const mediaQueryList = window.matchMedia(media);
19
+ setMatches(mediaQueryList.matches);
20
+ const listener = (evt) => {
21
+ setMatches(evt.matches);
22
+ };
23
+ mediaQueryList.addEventListener("change", listener);
24
+ return () => {
25
+ mediaQueryList.removeEventListener("change", listener);
26
+ };
27
+ }, [media]);
28
+ return matches;
29
+ };
30
+ exports.useMedia = useMedia;
@@ -12,7 +12,11 @@ interface DateContextContextProps {
12
12
  * Aria-connected ID
13
13
  */
14
14
  ariaId?: string;
15
+ /**
16
+ * Flag for enabled-check
17
+ */
18
+ defined: boolean;
15
19
  }
16
- export declare const DateContext: import("react").Context<DateContextContextProps>;
17
- export declare const useDateInputContext: () => DateContextContextProps;
20
+ export declare const DateContext: import("react").Context<DateContextContextProps | null>;
21
+ export declare const useDateInputContext: () => DateContextContextProps | null;
18
22
  export {};
@@ -1,9 +1,5 @@
1
1
  import { createContext, useContext } from "react";
2
- export const DateContext = createContext({
3
- open: false,
4
- onOpen: () => null,
5
- ariaId: undefined,
6
- });
2
+ export const DateContext = createContext(null);
7
3
  export const useDateInputContext = () => {
8
4
  const context = useContext(DateContext);
9
5
  if (!context) {
@@ -1 +1 @@
1
- {"version":3,"file":"useDateInputContext.js","sourceRoot":"","sources":["../../../src/date/context/useDateInputContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAiBlD,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAA0B;IAChE,IAAI,EAAE,KAAK;IACX,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI;IAClB,MAAM,EAAE,SAAS;CAClB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,EAAE;IACtC,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAExC,IAAI,CAAC,OAAO,EAAE;QACZ,OAAO,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;KACnE;IAED,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
1
+ {"version":3,"file":"useDateInputContext.js","sourceRoot":"","sources":["../../../src/date/context/useDateInputContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAqBlD,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAiC,IAAI,CAAC,CAAC;AAE/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,EAAE;IACtC,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAExC,IAAI,CAAC,OAAO,EAAE;QACZ,OAAO,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;KACnE;IAED,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { DatePickerInput } from "../DateInput";
2
+ import { DatePickerInput } from "../parts/DateInput";
3
3
  import DatePickerStandalone from "./DatePickerStandalone";
4
4
  import { ConditionalModeProps, DatePickerDefaultProps } from "./types";
5
5
  export type DatePickerProps = DatePickerDefaultProps & ConditionalModeProps;