@navikt/ds-react 5.15.1 → 5.17.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.
- package/_docs.json +44 -1
- package/cjs/form/combobox/Combobox.js +1 -1
- package/cjs/form/combobox/ComboboxProvider.js +2 -1
- package/cjs/form/combobox/ComboboxWrapper.js +1 -1
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +59 -41
- package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +3 -1
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +15 -3
- package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js +52 -32
- package/cjs/form/combobox/Input/Input.js +3 -1
- package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js +3 -1
- package/cjs/util/hooks/descendants/descendant.js +117 -0
- package/cjs/util/hooks/descendants/useDescendant.js +108 -0
- package/cjs/util/hooks/descendants/utils.js +53 -0
- package/esm/form/combobox/Combobox.js +1 -1
- package/esm/form/combobox/Combobox.js.map +1 -1
- package/esm/form/combobox/ComboboxProvider.js +2 -1
- package/esm/form/combobox/ComboboxProvider.js.map +1 -1
- package/esm/form/combobox/ComboboxWrapper.js +1 -1
- package/esm/form/combobox/ComboboxWrapper.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js +59 -41
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +2 -1
- package/esm/form/combobox/FilteredOptions/filtered-options-util.js +3 -1
- package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +15 -3
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.d.ts +2 -4
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js +52 -32
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
- package/esm/form/combobox/Input/Input.js +3 -1
- package/esm/form/combobox/Input/Input.js.map +1 -1
- package/esm/form/combobox/SelectedOptions/selectedOptionsContext.d.ts +5 -2
- package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js +3 -1
- package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
- package/esm/form/combobox/types.d.ts +14 -0
- package/esm/util/hooks/descendants/descendant.d.ts +47 -0
- package/esm/util/hooks/descendants/descendant.js +114 -0
- package/esm/util/hooks/descendants/descendant.js.map +1 -0
- package/esm/util/hooks/descendants/useDescendant.d.ts +14 -0
- package/esm/util/hooks/descendants/useDescendant.js +82 -0
- package/esm/util/hooks/descendants/useDescendant.js.map +1 -0
- package/esm/util/hooks/descendants/utils.d.ts +12 -0
- package/esm/util/hooks/descendants/utils.js +46 -0
- package/esm/util/hooks/descendants/utils.js.map +1 -0
- package/package.json +3 -3
- package/src/form/combobox/Combobox.tsx +1 -1
- package/src/form/combobox/ComboboxProvider.tsx +2 -0
- package/src/form/combobox/ComboboxWrapper.tsx +0 -1
- package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +131 -92
- package/src/form/combobox/FilteredOptions/filtered-options-util.ts +9 -2
- package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +22 -3
- package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +63 -45
- package/src/form/combobox/Input/Input.tsx +3 -1
- package/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx +11 -1
- package/src/form/combobox/combobox.stories.tsx +36 -1
- package/src/form/combobox/combobox.test.tsx +1 -3
- package/src/form/combobox/types.ts +15 -0
- package/src/util/hooks/descendants/descendant.stories.tsx +147 -0
- package/src/util/hooks/descendants/descendant.ts +161 -0
- package/src/util/hooks/descendants/useDescendant.tsx +111 -0
- package/src/util/hooks/descendants/utils.ts +56 -0
package/_docs.json
CHANGED
|
@@ -15085,6 +15085,25 @@
|
|
|
15085
15085
|
"name": "string[]"
|
|
15086
15086
|
}
|
|
15087
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
|
+
},
|
|
15088
15107
|
"shouldAutocomplete": {
|
|
15089
15108
|
"defaultValue": {
|
|
15090
15109
|
"value": "false"
|
|
@@ -15538,6 +15557,25 @@
|
|
|
15538
15557
|
"name": "string[]"
|
|
15539
15558
|
}
|
|
15540
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
|
+
},
|
|
15541
15579
|
"shouldAutocomplete": {
|
|
15542
15580
|
"defaultValue": {
|
|
15543
15581
|
"value": "false"
|
|
@@ -19628,7 +19666,7 @@
|
|
|
19628
19666
|
],
|
|
19629
19667
|
"required": true,
|
|
19630
19668
|
"type": {
|
|
19631
|
-
"name": "Pick<ComboboxProps, \"allowNewValues\" | \"isMultiSelect\" | \"options\" | \"selectedOptions\" | \"onToggleSelected\">"
|
|
19669
|
+
"name": "Pick<ComboboxProps, \"allowNewValues\" | \"isMultiSelect\" | \"options\" | \"selectedOptions\" | \"onToggleSelected\" | \"maxSelected\">"
|
|
19632
19670
|
}
|
|
19633
19671
|
}
|
|
19634
19672
|
}
|
|
@@ -19783,5 +19821,10 @@
|
|
|
19783
19821
|
}
|
|
19784
19822
|
}
|
|
19785
19823
|
}
|
|
19824
|
+
},
|
|
19825
|
+
{
|
|
19826
|
+
"filePath": "src/util/hooks/descendants/useDescendant.tsx",
|
|
19827
|
+
"displayName": "createDescendantContext",
|
|
19828
|
+
"props": {}
|
|
19786
19829
|
}
|
|
19787
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 !==
|
|
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
|
|
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
|
-
|
|
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),
|
|
23
|
-
|
|
24
|
-
react_1.default.createElement(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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 [
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
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
|
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DescendantsManager = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* https://github.com/chakra-ui/chakra-ui/tree/5ec0be610b5a69afba01a9c22365155c1b519136/packages/components/descendant
|
|
6
|
+
*/
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
/**
|
|
9
|
+
* @internal
|
|
10
|
+
*
|
|
11
|
+
* Class to manage descendants and their relative indices in the DOM.
|
|
12
|
+
* It uses `node.compareDocumentPosition(...)` under the hood
|
|
13
|
+
*/
|
|
14
|
+
class DescendantsManager {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.descendants = new Map();
|
|
17
|
+
this.register = (nodeOrOptions) => {
|
|
18
|
+
if (nodeOrOptions == null)
|
|
19
|
+
return;
|
|
20
|
+
if ((0, utils_1.isElement)(nodeOrOptions)) {
|
|
21
|
+
return this.registerNode(nodeOrOptions);
|
|
22
|
+
}
|
|
23
|
+
return (node) => {
|
|
24
|
+
this.registerNode(node, nodeOrOptions);
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
this.unregister = (node) => {
|
|
28
|
+
this.descendants.delete(node);
|
|
29
|
+
const sorted = (0, utils_1.sortNodes)(Array.from(this.descendants.keys()));
|
|
30
|
+
this.assignIndex(sorted);
|
|
31
|
+
};
|
|
32
|
+
this.destroy = () => {
|
|
33
|
+
this.descendants.clear();
|
|
34
|
+
};
|
|
35
|
+
this.assignIndex = (descendants) => {
|
|
36
|
+
this.descendants.forEach((descendant) => {
|
|
37
|
+
const index = descendants.indexOf(descendant.node);
|
|
38
|
+
descendant.index = index;
|
|
39
|
+
descendant.node.dataset["index"] = descendant.index.toString();
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
this.count = () => this.descendants.size;
|
|
43
|
+
this.enabledCount = () => this.enabledValues().length;
|
|
44
|
+
this.values = () => {
|
|
45
|
+
const values = Array.from(this.descendants.values());
|
|
46
|
+
return values.sort((a, b) => a.index - b.index);
|
|
47
|
+
};
|
|
48
|
+
this.enabledValues = () => {
|
|
49
|
+
return this.values().filter((descendant) => !descendant.disabled);
|
|
50
|
+
};
|
|
51
|
+
this.item = (index) => {
|
|
52
|
+
if (this.count() === 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
return this.values()[index];
|
|
55
|
+
};
|
|
56
|
+
this.enabledItem = (index) => {
|
|
57
|
+
if (this.enabledCount() === 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
return this.enabledValues()[index];
|
|
60
|
+
};
|
|
61
|
+
this.first = () => this.item(0);
|
|
62
|
+
this.firstEnabled = () => this.enabledItem(0);
|
|
63
|
+
this.last = () => this.item(this.descendants.size - 1);
|
|
64
|
+
this.lastEnabled = () => {
|
|
65
|
+
const lastIndex = this.enabledValues().length - 1;
|
|
66
|
+
return this.enabledItem(lastIndex);
|
|
67
|
+
};
|
|
68
|
+
this.indexOf = (node) => {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
if (!node)
|
|
71
|
+
return -1;
|
|
72
|
+
return (_b = (_a = this.descendants.get(node)) === null || _a === void 0 ? void 0 : _a.index) !== null && _b !== void 0 ? _b : -1;
|
|
73
|
+
};
|
|
74
|
+
this.enabledIndexOf = (node) => {
|
|
75
|
+
if (node == null)
|
|
76
|
+
return -1;
|
|
77
|
+
return this.enabledValues().findIndex((i) => i.node.isSameNode(node));
|
|
78
|
+
};
|
|
79
|
+
this.next = (index, loop = true) => {
|
|
80
|
+
const next = (0, utils_1.getNextIndex)(index, this.count(), loop);
|
|
81
|
+
return this.item(next);
|
|
82
|
+
};
|
|
83
|
+
this.nextEnabled = (index, loop = true) => {
|
|
84
|
+
const item = this.item(index);
|
|
85
|
+
if (!item)
|
|
86
|
+
return;
|
|
87
|
+
const enabledIndex = this.enabledIndexOf(item.node);
|
|
88
|
+
const nextEnabledIndex = (0, utils_1.getNextIndex)(enabledIndex, this.enabledCount(), loop);
|
|
89
|
+
return this.enabledItem(nextEnabledIndex);
|
|
90
|
+
};
|
|
91
|
+
this.prev = (index, loop = true) => {
|
|
92
|
+
const prev = (0, utils_1.getPrevIndex)(index, this.count() - 1, loop);
|
|
93
|
+
return this.item(prev);
|
|
94
|
+
};
|
|
95
|
+
this.prevEnabled = (index, loop = true) => {
|
|
96
|
+
const item = this.item(index);
|
|
97
|
+
if (!item)
|
|
98
|
+
return;
|
|
99
|
+
const enabledIndex = this.enabledIndexOf(item.node);
|
|
100
|
+
const prevEnabledIndex = (0, utils_1.getPrevIndex)(enabledIndex, this.enabledCount() - 1, loop);
|
|
101
|
+
return this.enabledItem(prevEnabledIndex);
|
|
102
|
+
};
|
|
103
|
+
this.registerNode = (node, options) => {
|
|
104
|
+
if (!node || this.descendants.has(node))
|
|
105
|
+
return;
|
|
106
|
+
const keys = Array.from(this.descendants.keys()).concat(node);
|
|
107
|
+
const sorted = (0, utils_1.sortNodes)(keys);
|
|
108
|
+
if (options === null || options === void 0 ? void 0 : options.disabled) {
|
|
109
|
+
options.disabled = !!options.disabled;
|
|
110
|
+
}
|
|
111
|
+
const descendant = Object.assign({ node, index: -1 }, options);
|
|
112
|
+
this.descendants.set(node, descendant);
|
|
113
|
+
this.assignIndex(sorted);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.DescendantsManager = DescendantsManager;
|