@stackline/react-multiselect-dropdown 19.0.1 → 19.1.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/README.md +365 -21
- package/dist/index.cjs +1770 -429
- package/dist/index.d.cts +348 -6
- package/dist/index.d.ts +348 -6
- package/dist/index.js +1756 -411
- package/package.json +42 -3
package/dist/index.js
CHANGED
|
@@ -1,15 +1,206 @@
|
|
|
1
1
|
// src/MultiSelectDropdown.tsx
|
|
2
2
|
import {
|
|
3
|
+
Fragment,
|
|
3
4
|
forwardRef,
|
|
4
5
|
useEffect,
|
|
5
6
|
useImperativeHandle,
|
|
6
7
|
useLayoutEffect,
|
|
7
8
|
useMemo,
|
|
8
9
|
useRef,
|
|
9
|
-
useState
|
|
10
|
+
useState as useState2
|
|
10
11
|
} from "react";
|
|
11
12
|
import { createPortal } from "react-dom";
|
|
12
13
|
|
|
14
|
+
// src/itemUtils.ts
|
|
15
|
+
function isPrimitiveItem(item) {
|
|
16
|
+
return typeof item === "string" || typeof item === "number" || typeof item === "boolean";
|
|
17
|
+
}
|
|
18
|
+
function getLabel(item, settings) {
|
|
19
|
+
if (isPrimitiveItem(item)) {
|
|
20
|
+
return String(item);
|
|
21
|
+
}
|
|
22
|
+
const keys = [settings.labelKey, "itemName", "name", "label", "title", "value"].filter(Boolean);
|
|
23
|
+
for (const key of keys) {
|
|
24
|
+
if (key && item[key] != null) {
|
|
25
|
+
return String(item[key]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return JSON.stringify(item);
|
|
29
|
+
}
|
|
30
|
+
function getPrimaryValue(item, settings) {
|
|
31
|
+
if (isPrimitiveItem(item)) {
|
|
32
|
+
return String(item);
|
|
33
|
+
}
|
|
34
|
+
const keys = [settings.primaryKey, "id", "value", "key"].filter(Boolean);
|
|
35
|
+
for (const key of keys) {
|
|
36
|
+
if (key && item[key] != null) {
|
|
37
|
+
return String(item[key]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return getLabel(item, settings);
|
|
41
|
+
}
|
|
42
|
+
function sanitizeId(value) {
|
|
43
|
+
return value.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 56) || "option";
|
|
44
|
+
}
|
|
45
|
+
function normalizeSkinName(value) {
|
|
46
|
+
return sanitizeId(value.toLowerCase()) || "classic";
|
|
47
|
+
}
|
|
48
|
+
function itemMatchesQuery(item, query, settings) {
|
|
49
|
+
if (!query.trim()) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
const needle = query.trim().toLowerCase();
|
|
53
|
+
const haystack = /* @__PURE__ */ new Set([getLabel(item, settings).toLowerCase()]);
|
|
54
|
+
if (!isPrimitiveItem(item)) {
|
|
55
|
+
const objectItem = item;
|
|
56
|
+
const searchKeys = settings.searchBy.length ? settings.searchBy : [settings.labelKey];
|
|
57
|
+
for (const key of searchKeys) {
|
|
58
|
+
if (key && objectItem[key] != null) {
|
|
59
|
+
haystack.add(String(objectItem[key]).toLowerCase());
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const value of haystack) {
|
|
64
|
+
if (value.includes(needle)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function isDisabledItem(item) {
|
|
71
|
+
return !isPrimitiveItem(item) && Boolean(item.disabled);
|
|
72
|
+
}
|
|
73
|
+
function getGroupName(item, settings) {
|
|
74
|
+
if (!settings.groupBy) {
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
if (typeof settings.groupBy === "function") {
|
|
78
|
+
return settings.groupBy(item);
|
|
79
|
+
}
|
|
80
|
+
if (!isPrimitiveItem(item)) {
|
|
81
|
+
const objectItem = item;
|
|
82
|
+
if (settings.groupBy in objectItem) {
|
|
83
|
+
return String(objectItem[settings.groupBy] ?? "");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
function mergeUniqueItems(base, extra, settings) {
|
|
89
|
+
const itemsById = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const item of [...base, ...extra]) {
|
|
91
|
+
itemsById.set(getPrimaryValue(item, settings), item);
|
|
92
|
+
}
|
|
93
|
+
return Array.from(itemsById.values());
|
|
94
|
+
}
|
|
95
|
+
function createItemFromQuery(query, settings, sample) {
|
|
96
|
+
if (sample && !isPrimitiveItem(sample)) {
|
|
97
|
+
return {
|
|
98
|
+
[settings.primaryKey]: query.toLowerCase().replace(/\s+/g, "-"),
|
|
99
|
+
[settings.labelKey]: query
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return query;
|
|
103
|
+
}
|
|
104
|
+
function buildGroups(items, settings) {
|
|
105
|
+
if (!settings.groupBy) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
const itemsByGroup = /* @__PURE__ */ new Map();
|
|
109
|
+
for (const item of items) {
|
|
110
|
+
const groupName = getGroupName(item, settings) || "Ungrouped";
|
|
111
|
+
const currentItems = itemsByGroup.get(groupName) || [];
|
|
112
|
+
currentItems.push(item);
|
|
113
|
+
itemsByGroup.set(groupName, currentItems);
|
|
114
|
+
}
|
|
115
|
+
return Array.from(itemsByGroup.entries()).map(([name, groupedItems]) => ({
|
|
116
|
+
name,
|
|
117
|
+
items: groupedItems
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
function getVisibleBadgeLimit(selectedCount, rawLimit) {
|
|
121
|
+
if (!Number.isFinite(rawLimit)) {
|
|
122
|
+
return selectedCount;
|
|
123
|
+
}
|
|
124
|
+
return Math.min(selectedCount, Math.max(0, Math.floor(rawLimit)));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/settings.ts
|
|
128
|
+
var DEFAULT_KEYBOARD_SETTINGS = {
|
|
129
|
+
space: true,
|
|
130
|
+
spaceOptionAction: "toggle",
|
|
131
|
+
tab: true,
|
|
132
|
+
arrows: true,
|
|
133
|
+
escape: true,
|
|
134
|
+
backspaceRemovesLastWhenSearchEmpty: false,
|
|
135
|
+
deleteRemovesFocusedBadge: true,
|
|
136
|
+
backspace: false
|
|
137
|
+
};
|
|
138
|
+
var DEFAULT_SETTINGS = {
|
|
139
|
+
singleSelection: false,
|
|
140
|
+
text: "Select",
|
|
141
|
+
enableCheckAll: true,
|
|
142
|
+
selectAllText: "Select All",
|
|
143
|
+
unSelectAllText: "Unselect All",
|
|
144
|
+
filterSelectAllText: "Select filtered",
|
|
145
|
+
filterUnSelectAllText: "Unselect filtered",
|
|
146
|
+
enableFilterSelectAll: true,
|
|
147
|
+
enableSearchFilter: false,
|
|
148
|
+
searchBy: [],
|
|
149
|
+
maxHeight: 300,
|
|
150
|
+
badgeShowLimit: Number.MAX_SAFE_INTEGER,
|
|
151
|
+
classes: "",
|
|
152
|
+
limitSelection: 0,
|
|
153
|
+
disabled: false,
|
|
154
|
+
searchPlaceholderText: "Search",
|
|
155
|
+
groupBy: "",
|
|
156
|
+
showCheckbox: true,
|
|
157
|
+
noDataLabel: "No Data Available",
|
|
158
|
+
searchAutofocus: true,
|
|
159
|
+
lazyLoading: false,
|
|
160
|
+
labelKey: "itemName",
|
|
161
|
+
primaryKey: "id",
|
|
162
|
+
position: "bottom",
|
|
163
|
+
autoPosition: true,
|
|
164
|
+
loading: false,
|
|
165
|
+
selectGroup: false,
|
|
166
|
+
addNewItemOnFilter: false,
|
|
167
|
+
addNewButtonText: "Add",
|
|
168
|
+
escapeToClose: true,
|
|
169
|
+
clearAll: true,
|
|
170
|
+
closeDropDownOnSelection: false,
|
|
171
|
+
tagToBody: false,
|
|
172
|
+
appendToBody: false,
|
|
173
|
+
theme: "",
|
|
174
|
+
skin: "classic",
|
|
175
|
+
ariaLabel: "Multiselect dropdown",
|
|
176
|
+
listboxAriaLabel: "Dropdown options",
|
|
177
|
+
searchAriaLabel: "Search options",
|
|
178
|
+
clearSearchAriaLabel: "Clear search",
|
|
179
|
+
clearAllAriaLabel: "Clear selected options",
|
|
180
|
+
removeItemAriaLabel: "Remove selected option",
|
|
181
|
+
openDropdownAriaLabel: "Open dropdown",
|
|
182
|
+
closeDropdownAriaLabel: "Close dropdown",
|
|
183
|
+
loadingText: "Loading options",
|
|
184
|
+
keyboard: DEFAULT_KEYBOARD_SETTINGS
|
|
185
|
+
};
|
|
186
|
+
function resolveDropdownSettings(incomingSettings) {
|
|
187
|
+
const escapeToClose = incomingSettings?.escapeToClose ?? DEFAULT_SETTINGS.escapeToClose;
|
|
188
|
+
const incomingKeyboard = incomingSettings?.keyboard;
|
|
189
|
+
const keyboard = {
|
|
190
|
+
...DEFAULT_KEYBOARD_SETTINGS,
|
|
191
|
+
...incomingKeyboard,
|
|
192
|
+
backspaceRemovesLastWhenSearchEmpty: incomingKeyboard?.backspaceRemovesLastWhenSearchEmpty ?? incomingKeyboard?.backspace ?? DEFAULT_KEYBOARD_SETTINGS.backspaceRemovesLastWhenSearchEmpty,
|
|
193
|
+
deleteRemovesFocusedBadge: incomingKeyboard?.deleteRemovesFocusedBadge ?? DEFAULT_KEYBOARD_SETTINGS.deleteRemovesFocusedBadge,
|
|
194
|
+
escape: escapeToClose && (incomingKeyboard?.escape ?? DEFAULT_KEYBOARD_SETTINGS.escape)
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
...DEFAULT_SETTINGS,
|
|
198
|
+
...incomingSettings,
|
|
199
|
+
escapeToClose,
|
|
200
|
+
keyboard
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
13
204
|
// src/styles.ts
|
|
14
205
|
var STYLE_ID = "stackline-react-multiselect-dropdown-styles";
|
|
15
206
|
var styles = `
|
|
@@ -89,6 +280,7 @@ var styles = `
|
|
|
89
280
|
display: flex;
|
|
90
281
|
flex: 1 1 auto;
|
|
91
282
|
min-width: 0;
|
|
283
|
+
min-height: 1.45em;
|
|
92
284
|
align-items: center;
|
|
93
285
|
align-content: center;
|
|
94
286
|
gap: 8px;
|
|
@@ -102,6 +294,7 @@ var styles = `
|
|
|
102
294
|
align-self: center;
|
|
103
295
|
justify-content: flex-start;
|
|
104
296
|
min-width: 0;
|
|
297
|
+
min-height: 1.45em;
|
|
105
298
|
max-width: 100%;
|
|
106
299
|
color: var(--rmsd-muted);
|
|
107
300
|
font-size: 0.95rem;
|
|
@@ -185,33 +378,25 @@ var styles = `
|
|
|
185
378
|
display: inline-flex;
|
|
186
379
|
align-items: center;
|
|
187
380
|
justify-content: center;
|
|
188
|
-
|
|
189
|
-
min-
|
|
381
|
+
flex: 0 0 auto;
|
|
382
|
+
min-width: 24px;
|
|
383
|
+
min-height: 20px;
|
|
190
384
|
color: var(--rmsd-muted);
|
|
191
385
|
font-size: 0.8rem;
|
|
192
386
|
font-weight: 600;
|
|
387
|
+
line-height: 1;
|
|
388
|
+
white-space: nowrap;
|
|
389
|
+
text-align: center;
|
|
193
390
|
}
|
|
194
391
|
|
|
195
|
-
.rmsd-root.rmsd-has-overflow
|
|
392
|
+
.rmsd-root.rmsd-has-overflow .rmsd-trigger {
|
|
196
393
|
padding-right: 104px;
|
|
197
394
|
}
|
|
198
395
|
|
|
199
|
-
.rmsd-root.rmsd-has-overflow:not(.
|
|
396
|
+
.rmsd-root.rmsd-has-overflow:not(.rmsd-has-clear) .rmsd-trigger {
|
|
200
397
|
padding-right: 74px;
|
|
201
398
|
}
|
|
202
399
|
|
|
203
|
-
.rmsd-root.rmsd-has-overflow:not(.skin-classic) .rmsd-overflow {
|
|
204
|
-
position: absolute;
|
|
205
|
-
top: 50%;
|
|
206
|
-
right: 76px;
|
|
207
|
-
transform: translateY(-50%);
|
|
208
|
-
z-index: 1;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.rmsd-root.rmsd-has-overflow:not(.skin-classic):not(.rmsd-has-clear) .rmsd-overflow {
|
|
212
|
-
right: 42px;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
400
|
.rmsd-actions {
|
|
216
401
|
position: absolute;
|
|
217
402
|
top: 50%;
|
|
@@ -776,9 +961,12 @@ var styles = `
|
|
|
776
961
|
|
|
777
962
|
.theme-classic .rmsd-overflow,
|
|
778
963
|
.skin-classic .rmsd-overflow {
|
|
964
|
+
min-width: 24px;
|
|
965
|
+
min-height: 20px;
|
|
779
966
|
color: #333333;
|
|
780
967
|
font-size: 14px;
|
|
781
968
|
font-weight: 400;
|
|
969
|
+
line-height: 1;
|
|
782
970
|
}
|
|
783
971
|
|
|
784
972
|
.theme-classic .rmsd-actions,
|
|
@@ -1046,7 +1234,7 @@ var styles = `
|
|
|
1046
1234
|
|
|
1047
1235
|
@media (max-width: 720px) {
|
|
1048
1236
|
.rmsd-trigger {
|
|
1049
|
-
align-items:
|
|
1237
|
+
align-items: center;
|
|
1050
1238
|
padding-right: 54px;
|
|
1051
1239
|
}
|
|
1052
1240
|
}
|
|
@@ -1064,56 +1252,30 @@ function ensureDropdownStyles() {
|
|
|
1064
1252
|
document.head.appendChild(tag);
|
|
1065
1253
|
}
|
|
1066
1254
|
|
|
1255
|
+
// src/useControllableSelection.ts
|
|
1256
|
+
import { useState } from "react";
|
|
1257
|
+
function useControllableSelection(controlledValue, defaultValue, onChange) {
|
|
1258
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? []);
|
|
1259
|
+
const isControlled = controlledValue !== void 0;
|
|
1260
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
1261
|
+
const setValue = (nextValue) => {
|
|
1262
|
+
if (!isControlled) {
|
|
1263
|
+
setInternalValue(nextValue);
|
|
1264
|
+
}
|
|
1265
|
+
onChange?.(nextValue);
|
|
1266
|
+
};
|
|
1267
|
+
return [value, setValue];
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1067
1270
|
// src/MultiSelectDropdown.tsx
|
|
1068
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
1069
|
-
var DEFAULT_SETTINGS = {
|
|
1070
|
-
singleSelection: false,
|
|
1071
|
-
text: "Select",
|
|
1072
|
-
enableCheckAll: true,
|
|
1073
|
-
selectAllText: "Select All",
|
|
1074
|
-
unSelectAllText: "Unselect All",
|
|
1075
|
-
filterSelectAllText: "Select filtered",
|
|
1076
|
-
filterUnSelectAllText: "Unselect filtered",
|
|
1077
|
-
enableFilterSelectAll: true,
|
|
1078
|
-
enableSearchFilter: false,
|
|
1079
|
-
searchBy: [],
|
|
1080
|
-
maxHeight: 300,
|
|
1081
|
-
badgeShowLimit: Number.MAX_SAFE_INTEGER,
|
|
1082
|
-
classes: "",
|
|
1083
|
-
limitSelection: 0,
|
|
1084
|
-
disabled: false,
|
|
1085
|
-
searchPlaceholderText: "Search",
|
|
1086
|
-
groupBy: "",
|
|
1087
|
-
showCheckbox: true,
|
|
1088
|
-
noDataLabel: "No Data Available",
|
|
1089
|
-
searchAutofocus: true,
|
|
1090
|
-
lazyLoading: false,
|
|
1091
|
-
labelKey: "itemName",
|
|
1092
|
-
primaryKey: "id",
|
|
1093
|
-
position: "bottom",
|
|
1094
|
-
autoPosition: true,
|
|
1095
|
-
loading: false,
|
|
1096
|
-
selectGroup: false,
|
|
1097
|
-
addNewItemOnFilter: false,
|
|
1098
|
-
addNewButtonText: "Add",
|
|
1099
|
-
escapeToClose: true,
|
|
1100
|
-
clearAll: true,
|
|
1101
|
-
closeDropDownOnSelection: false,
|
|
1102
|
-
tagToBody: false,
|
|
1103
|
-
appendToBody: false,
|
|
1104
|
-
theme: "",
|
|
1105
|
-
skin: "classic",
|
|
1106
|
-
ariaLabel: "Multiselect dropdown",
|
|
1107
|
-
listboxAriaLabel: "Dropdown options",
|
|
1108
|
-
searchAriaLabel: "Search options",
|
|
1109
|
-
clearSearchAriaLabel: "Clear search",
|
|
1110
|
-
clearAllAriaLabel: "Clear selected options",
|
|
1111
|
-
removeItemAriaLabel: "Remove selected option",
|
|
1112
|
-
openDropdownAriaLabel: "Open dropdown",
|
|
1113
|
-
closeDropdownAriaLabel: "Close dropdown",
|
|
1114
|
-
loadingText: "Loading options"
|
|
1115
|
-
};
|
|
1271
|
+
import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
|
|
1116
1272
|
var useClientLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
1273
|
+
function isSpaceKey(key) {
|
|
1274
|
+
return key === " " || key === "Spacebar";
|
|
1275
|
+
}
|
|
1276
|
+
function renderSlot(Slot, props, fallback) {
|
|
1277
|
+
return Slot ? /* @__PURE__ */ jsx(Fragment2, { children: Slot(props) }) : /* @__PURE__ */ jsx(Fragment2, { children: fallback });
|
|
1278
|
+
}
|
|
1117
1279
|
function StacklineIcon({ name, className = "rmsd-icon" }) {
|
|
1118
1280
|
if (name === "remove") {
|
|
1119
1281
|
return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 47.971 47.971", focusable: "false", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M28.228,23.986L47.092,5.122c1.172-1.171,1.172-3.071,0-4.242c-1.172-1.172-3.07-1.172-4.242,0L23.986,19.744L5.121,0.88c-1.172-1.172-3.07-1.172-4.242,0c-1.172,1.171-1.172,3.071,0,4.242l18.865,18.864L0.879,42.85c-1.172,1.171-1.172,3.071,0,4.242C1.465,47.677,2.233,47.97,3,47.97s1.535-0.293,2.121-0.879l18.865-18.864L42.85,47.091c0.586,0.586,1.354,0.879,2.121,0.879s1.535-0.293,2.121-0.879c1.172-1.171,1.172-3.071,0-4.242L28.228,23.986z" }) });
|
|
@@ -1126,131 +1288,6 @@ function StacklineIcon({ name, className = "rmsd-icon" }) {
|
|
|
1126
1288
|
}
|
|
1127
1289
|
return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 612 612", focusable: "false", "aria-hidden": "true", children: name === "angle-up" ? /* @__PURE__ */ jsx("path", { d: "M604.501,440.509L325.398,134.956c-5.331-5.357-12.423-7.627-19.386-7.27c-6.989-0.357-14.056,1.913-19.387,7.27L7.499,440.509c-9.999,10.024-9.999,26.298,0,36.323s26.223,10.024,36.222,0l262.293-287.164L568.28,476.832c9.999,10.024,26.222,10.024,36.221,0C614.5,466.809,614.5,450.534,604.501,440.509z" }) : /* @__PURE__ */ jsx("path", { d: "M604.501,134.782c-9.999-10.05-26.222-10.05-36.221,0L306.014,422.558L43.721,134.782c-9.999-10.05-26.223-10.05-36.222,0s-9.999,26.35,0,36.399l279.103,306.241c5.331,5.357,12.422,7.652,19.386,7.296c6.988,0.356,14.055-1.939,19.386-7.296l279.128-306.268C614.5,161.106,614.5,144.832,604.501,134.782z" }) });
|
|
1128
1290
|
}
|
|
1129
|
-
function isPrimitiveItem(item) {
|
|
1130
|
-
return typeof item === "string" || typeof item === "number" || typeof item === "boolean";
|
|
1131
|
-
}
|
|
1132
|
-
function getLabel(item, settings) {
|
|
1133
|
-
if (isPrimitiveItem(item)) {
|
|
1134
|
-
return String(item);
|
|
1135
|
-
}
|
|
1136
|
-
const keys = [settings.labelKey, "itemName", "name", "label", "title", "value"].filter(Boolean);
|
|
1137
|
-
for (const key of keys) {
|
|
1138
|
-
if (key && item[key] != null) {
|
|
1139
|
-
return String(item[key]);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
return JSON.stringify(item);
|
|
1143
|
-
}
|
|
1144
|
-
function getPrimaryValue(item, settings) {
|
|
1145
|
-
if (isPrimitiveItem(item)) {
|
|
1146
|
-
return String(item);
|
|
1147
|
-
}
|
|
1148
|
-
const keys = [settings.primaryKey, "id", "value", "key"].filter(Boolean);
|
|
1149
|
-
for (const key of keys) {
|
|
1150
|
-
if (key && item[key] != null) {
|
|
1151
|
-
return String(item[key]);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
return getLabel(item, settings);
|
|
1155
|
-
}
|
|
1156
|
-
function itemMatchesQuery(item, query, settings) {
|
|
1157
|
-
if (!query.trim()) {
|
|
1158
|
-
return true;
|
|
1159
|
-
}
|
|
1160
|
-
const needle = query.trim().toLowerCase();
|
|
1161
|
-
const haystack = /* @__PURE__ */ new Set();
|
|
1162
|
-
haystack.add(getLabel(item, settings).toLowerCase());
|
|
1163
|
-
if (!isPrimitiveItem(item)) {
|
|
1164
|
-
const searchKeys = settings.searchBy.length ? settings.searchBy : [settings.labelKey];
|
|
1165
|
-
for (const key of searchKeys) {
|
|
1166
|
-
if (key && item[key] != null) {
|
|
1167
|
-
haystack.add(String(item[key]).toLowerCase());
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
for (const value of haystack) {
|
|
1172
|
-
if (value.includes(needle)) {
|
|
1173
|
-
return true;
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
return false;
|
|
1177
|
-
}
|
|
1178
|
-
function getGroupName(item, settings) {
|
|
1179
|
-
if (!settings.groupBy) {
|
|
1180
|
-
return "";
|
|
1181
|
-
}
|
|
1182
|
-
if (typeof settings.groupBy === "function") {
|
|
1183
|
-
return settings.groupBy(item);
|
|
1184
|
-
}
|
|
1185
|
-
if (!isPrimitiveItem(item)) {
|
|
1186
|
-
const groupKey = settings.groupBy;
|
|
1187
|
-
const objectItem = item;
|
|
1188
|
-
if (groupKey in objectItem) {
|
|
1189
|
-
return String(objectItem[groupKey] ?? "");
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
return "";
|
|
1193
|
-
}
|
|
1194
|
-
function mergeUniqueItems(base, extra, settings) {
|
|
1195
|
-
const bucket = /* @__PURE__ */ new Map();
|
|
1196
|
-
for (const item of [...base, ...extra]) {
|
|
1197
|
-
bucket.set(getPrimaryValue(item, settings), item);
|
|
1198
|
-
}
|
|
1199
|
-
return Array.from(bucket.values());
|
|
1200
|
-
}
|
|
1201
|
-
function createItemFromQuery(query, settings, sample) {
|
|
1202
|
-
if (sample && !isPrimitiveItem(sample)) {
|
|
1203
|
-
return {
|
|
1204
|
-
[settings.primaryKey]: query.toLowerCase().replace(/\s+/g, "-"),
|
|
1205
|
-
[settings.labelKey]: query
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
return query;
|
|
1209
|
-
}
|
|
1210
|
-
function isDisabledItem(item) {
|
|
1211
|
-
return !isPrimitiveItem(item) && Boolean(item.disabled);
|
|
1212
|
-
}
|
|
1213
|
-
function buildGroups(items, settings) {
|
|
1214
|
-
if (!settings.groupBy) {
|
|
1215
|
-
return [];
|
|
1216
|
-
}
|
|
1217
|
-
const map = /* @__PURE__ */ new Map();
|
|
1218
|
-
for (const item of items) {
|
|
1219
|
-
const groupName = getGroupName(item, settings) || "Ungrouped";
|
|
1220
|
-
const current = map.get(groupName) || [];
|
|
1221
|
-
current.push(item);
|
|
1222
|
-
map.set(groupName, current);
|
|
1223
|
-
}
|
|
1224
|
-
return Array.from(map.entries()).map(([name, groupedItems]) => ({
|
|
1225
|
-
name,
|
|
1226
|
-
items: groupedItems
|
|
1227
|
-
}));
|
|
1228
|
-
}
|
|
1229
|
-
function useControllableSelection(controlledValue, defaultValue, onChange) {
|
|
1230
|
-
const [internalValue, setInternalValue] = useState(defaultValue ?? []);
|
|
1231
|
-
const isControlled = controlledValue !== void 0;
|
|
1232
|
-
const value = isControlled ? controlledValue : internalValue;
|
|
1233
|
-
const setValue = (nextValue) => {
|
|
1234
|
-
if (!isControlled) {
|
|
1235
|
-
setInternalValue(nextValue);
|
|
1236
|
-
}
|
|
1237
|
-
onChange?.(nextValue);
|
|
1238
|
-
};
|
|
1239
|
-
return [value, setValue];
|
|
1240
|
-
}
|
|
1241
|
-
function sanitizeId(value) {
|
|
1242
|
-
return value.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 56) || "option";
|
|
1243
|
-
}
|
|
1244
|
-
function normalizeSkinName(value) {
|
|
1245
|
-
return sanitizeId(value.toLowerCase()) || "classic";
|
|
1246
|
-
}
|
|
1247
|
-
function getVisibleBadgeLimit(selectedCount, rawLimit) {
|
|
1248
|
-
if (!Number.isFinite(rawLimit)) {
|
|
1249
|
-
return selectedCount;
|
|
1250
|
-
}
|
|
1251
|
-
const limit = Math.max(0, Math.floor(rawLimit));
|
|
1252
|
-
return Math.min(selectedCount, limit);
|
|
1253
|
-
}
|
|
1254
1291
|
function InnerMultiSelectDropdown({
|
|
1255
1292
|
data,
|
|
1256
1293
|
settings: incomingSettings,
|
|
@@ -1275,22 +1312,23 @@ function InnerMultiSelectDropdown({
|
|
|
1275
1312
|
renderItem,
|
|
1276
1313
|
renderBadge,
|
|
1277
1314
|
renderSearch,
|
|
1278
|
-
renderEmptyState
|
|
1315
|
+
renderEmptyState,
|
|
1316
|
+
slots
|
|
1279
1317
|
}, ref) {
|
|
1280
1318
|
ensureDropdownStyles();
|
|
1281
|
-
const settings =
|
|
1319
|
+
const settings = useMemo(() => resolveDropdownSettings(incomingSettings), [incomingSettings]);
|
|
1282
1320
|
const [selectedItems, setSelectedItems] = useControllableSelection(
|
|
1283
1321
|
controlledSelectedItems,
|
|
1284
1322
|
defaultSelectedItems,
|
|
1285
1323
|
onChange
|
|
1286
1324
|
);
|
|
1287
|
-
const [isOpen, setIsOpen] =
|
|
1288
|
-
const [filter, setFilter] =
|
|
1289
|
-
const [addedItems, setAddedItems] =
|
|
1290
|
-
const [activeDescendantId, setActiveDescendantId] =
|
|
1291
|
-
const [bodyMenuStyle, setBodyMenuStyle] =
|
|
1292
|
-
const [bodyListMaxHeight, setBodyListMaxHeight] =
|
|
1293
|
-
const [effectivePosition, setEffectivePosition] =
|
|
1325
|
+
const [isOpen, setIsOpen] = useState2(false);
|
|
1326
|
+
const [filter, setFilter] = useState2("");
|
|
1327
|
+
const [addedItems, setAddedItems] = useState2([]);
|
|
1328
|
+
const [activeDescendantId, setActiveDescendantId] = useState2(null);
|
|
1329
|
+
const [bodyMenuStyle, setBodyMenuStyle] = useState2();
|
|
1330
|
+
const [bodyListMaxHeight, setBodyListMaxHeight] = useState2();
|
|
1331
|
+
const [effectivePosition, setEffectivePosition] = useState2(
|
|
1294
1332
|
settings.position === "top" ? "top" : "bottom"
|
|
1295
1333
|
);
|
|
1296
1334
|
const rootRef = useRef(null);
|
|
@@ -1300,10 +1338,11 @@ function InnerMultiSelectDropdown({
|
|
|
1300
1338
|
const listRef = useRef(null);
|
|
1301
1339
|
const lastScrollHeightRef = useRef(0);
|
|
1302
1340
|
const pendingFocusRef = useRef(null);
|
|
1341
|
+
const addRequestIdRef = useRef(0);
|
|
1303
1342
|
const instanceIdRef = useRef(`rmsd-${Math.random().toString(36).slice(2)}`);
|
|
1304
1343
|
const allItems = useMemo(
|
|
1305
|
-
() => mergeUniqueItems(data, addedItems, settings),
|
|
1306
|
-
[addedItems, data, settings]
|
|
1344
|
+
() => mergeUniqueItems(data, [...selectedItems, ...addedItems], settings),
|
|
1345
|
+
[addedItems, data, selectedItems, settings]
|
|
1307
1346
|
);
|
|
1308
1347
|
const filteredItems = useMemo(
|
|
1309
1348
|
() => allItems.filter((item) => itemMatchesQuery(item, filter, settings)),
|
|
@@ -1334,6 +1373,68 @@ function InnerMultiSelectDropdown({
|
|
|
1334
1373
|
};
|
|
1335
1374
|
const focusFirstOption = () => focusOptionByIndex(0);
|
|
1336
1375
|
const focusLastOption = () => focusOptionByIndex(getOptionElements().length - 1);
|
|
1376
|
+
const focusOptionById = (optionId) => {
|
|
1377
|
+
const option = document.getElementById(optionId);
|
|
1378
|
+
if (!option || !listRef.current?.contains(option) || option.getAttribute("aria-disabled") === "true") {
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
option.focus();
|
|
1382
|
+
setActiveDescendantId(option.id || null);
|
|
1383
|
+
option.scrollIntoView({ block: "nearest" });
|
|
1384
|
+
return true;
|
|
1385
|
+
};
|
|
1386
|
+
const focusOptionAfterPointerSelection = (optionId, fallbackIndex) => {
|
|
1387
|
+
window.setTimeout(() => {
|
|
1388
|
+
const focusAfterRender = () => {
|
|
1389
|
+
if (focusOptionById(optionId)) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
focusOptionByIndex(fallbackIndex);
|
|
1393
|
+
};
|
|
1394
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
1395
|
+
window.requestAnimationFrame(focusAfterRender);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
focusAfterRender();
|
|
1399
|
+
}, 0);
|
|
1400
|
+
};
|
|
1401
|
+
const focusOptionAfterKeyboardSelection = (optionId, fallbackIndex, moveToNextOption) => {
|
|
1402
|
+
window.setTimeout(() => {
|
|
1403
|
+
const focusAfterRender = () => {
|
|
1404
|
+
if (moveToNextOption) {
|
|
1405
|
+
const options = getOptionElements();
|
|
1406
|
+
const currentIndex = options.findIndex((option) => option.id === optionId);
|
|
1407
|
+
const nextOption = currentIndex >= 0 ? options[currentIndex + 1] : void 0;
|
|
1408
|
+
if (nextOption) {
|
|
1409
|
+
nextOption.focus();
|
|
1410
|
+
setActiveDescendantId(nextOption.id || null);
|
|
1411
|
+
nextOption.scrollIntoView({ block: "nearest" });
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
if (!focusOptionById(optionId)) {
|
|
1416
|
+
focusOptionByIndex(fallbackIndex);
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
1420
|
+
window.requestAnimationFrame(focusAfterRender);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
focusAfterRender();
|
|
1424
|
+
}, 0);
|
|
1425
|
+
};
|
|
1426
|
+
const focusAfterSelectionChange = (target = "search") => {
|
|
1427
|
+
if (target === "none") {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
window.setTimeout(() => {
|
|
1431
|
+
if (target === "search" && isOpen && settings.enableSearchFilter) {
|
|
1432
|
+
searchRef.current?.focus();
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
triggerRef.current?.focus();
|
|
1436
|
+
}, 0);
|
|
1437
|
+
};
|
|
1337
1438
|
const updateSelection = (nextItems) => {
|
|
1338
1439
|
setSelectedItems(nextItems);
|
|
1339
1440
|
};
|
|
@@ -1367,6 +1468,7 @@ function InnerMultiSelectDropdown({
|
|
|
1367
1468
|
const previousItems = selectedItems;
|
|
1368
1469
|
updateSelection([]);
|
|
1369
1470
|
onDeSelectAll?.(previousItems);
|
|
1471
|
+
focusAfterSelectionChange();
|
|
1370
1472
|
};
|
|
1371
1473
|
const toggleDropdown = () => {
|
|
1372
1474
|
if (isOpen) {
|
|
@@ -1375,25 +1477,34 @@ function InnerMultiSelectDropdown({
|
|
|
1375
1477
|
openDropdown("search");
|
|
1376
1478
|
}
|
|
1377
1479
|
};
|
|
1378
|
-
const removeItem = (item) => {
|
|
1480
|
+
const removeItem = (item, focusTarget = "search") => {
|
|
1379
1481
|
const nextItems = selectedItems.filter(
|
|
1380
1482
|
(selectedItem) => getPrimaryValue(selectedItem, settings) !== getPrimaryValue(item, settings)
|
|
1381
1483
|
);
|
|
1382
1484
|
updateSelection(nextItems);
|
|
1383
1485
|
onDeSelect?.(item);
|
|
1486
|
+
focusAfterSelectionChange(focusTarget);
|
|
1384
1487
|
};
|
|
1385
|
-
const
|
|
1488
|
+
const removeLastSelectedItem = () => {
|
|
1489
|
+
const lastItem = selectedItems[selectedItems.length - 1];
|
|
1490
|
+
if (!lastItem) {
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
removeItem(lastItem);
|
|
1494
|
+
};
|
|
1495
|
+
const selectItem = (item, focusTarget = "search") => {
|
|
1386
1496
|
if (settings.disabled || isDisabledItem(item)) {
|
|
1387
1497
|
return;
|
|
1388
1498
|
}
|
|
1389
1499
|
if (isSelected(item)) {
|
|
1390
|
-
removeItem(item);
|
|
1500
|
+
removeItem(item, focusTarget);
|
|
1391
1501
|
return;
|
|
1392
1502
|
}
|
|
1393
1503
|
if (settings.singleSelection) {
|
|
1394
1504
|
updateSelection([item]);
|
|
1395
1505
|
onSelect?.(item);
|
|
1396
1506
|
closeDropdown(true);
|
|
1507
|
+
focusAfterSelectionChange("trigger");
|
|
1397
1508
|
return;
|
|
1398
1509
|
}
|
|
1399
1510
|
if (settings.limitSelection && selectedItems.length >= settings.limitSelection) {
|
|
@@ -1404,7 +1515,10 @@ function InnerMultiSelectDropdown({
|
|
|
1404
1515
|
onSelect?.(item);
|
|
1405
1516
|
if (settings.closeDropDownOnSelection) {
|
|
1406
1517
|
closeDropdown(true);
|
|
1518
|
+
focusAfterSelectionChange("trigger");
|
|
1519
|
+
return;
|
|
1407
1520
|
}
|
|
1521
|
+
focusAfterSelectionChange(focusTarget);
|
|
1408
1522
|
};
|
|
1409
1523
|
const selectAllItems = (items, filteredSelection = false) => {
|
|
1410
1524
|
if (settings.singleSelection) {
|
|
@@ -1420,6 +1534,7 @@ function InnerMultiSelectDropdown({
|
|
|
1420
1534
|
} else {
|
|
1421
1535
|
onSelectAll?.(nextItems);
|
|
1422
1536
|
}
|
|
1537
|
+
focusAfterSelectionChange();
|
|
1423
1538
|
};
|
|
1424
1539
|
const deSelectAllItems = (items, filteredSelection = false) => {
|
|
1425
1540
|
const ids = new Set(items.map((item) => getPrimaryValue(item, settings)));
|
|
@@ -1430,13 +1545,19 @@ function InnerMultiSelectDropdown({
|
|
|
1430
1545
|
} else {
|
|
1431
1546
|
onDeSelectAll?.(items);
|
|
1432
1547
|
}
|
|
1548
|
+
focusAfterSelectionChange();
|
|
1433
1549
|
};
|
|
1434
1550
|
const handleAddFilterNewItem = async () => {
|
|
1435
1551
|
const query = filter.trim();
|
|
1436
1552
|
if (!query) {
|
|
1437
1553
|
return;
|
|
1438
1554
|
}
|
|
1555
|
+
const requestId = addRequestIdRef.current + 1;
|
|
1556
|
+
addRequestIdRef.current = requestId;
|
|
1439
1557
|
const result = await onAddFilterNewItem?.(query);
|
|
1558
|
+
if (requestId !== addRequestIdRef.current) {
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1440
1561
|
const nextItem = result === void 0 ? createItemFromQuery(query, settings, data[0]) : result;
|
|
1441
1562
|
setAddedItems((currentItems) => mergeUniqueItems(currentItems, [nextItem], settings));
|
|
1442
1563
|
if (settings.singleSelection) {
|
|
@@ -1445,6 +1566,7 @@ function InnerMultiSelectDropdown({
|
|
|
1445
1566
|
updateSelection(mergeUniqueItems(selectedItems, [nextItem], settings));
|
|
1446
1567
|
}
|
|
1447
1568
|
setFilter("");
|
|
1569
|
+
focusAfterSelectionChange();
|
|
1448
1570
|
};
|
|
1449
1571
|
const toggleGroup = (groupName, items) => {
|
|
1450
1572
|
const groupItems = items.filter((item) => !isDisabledItem(item));
|
|
@@ -1452,10 +1574,12 @@ function InnerMultiSelectDropdown({
|
|
|
1452
1574
|
if (allSelected) {
|
|
1453
1575
|
deSelectAllItems(groupItems, false);
|
|
1454
1576
|
onGroupDeSelect?.(groupName, groupItems);
|
|
1577
|
+
focusAfterSelectionChange();
|
|
1455
1578
|
return;
|
|
1456
1579
|
}
|
|
1457
1580
|
selectAllItems(groupItems, false);
|
|
1458
1581
|
onGroupSelect?.(groupName, groupItems);
|
|
1582
|
+
focusAfterSelectionChange();
|
|
1459
1583
|
};
|
|
1460
1584
|
const handleListScroll = () => {
|
|
1461
1585
|
if (!listRef.current || !onScrollToEnd) {
|
|
@@ -1495,7 +1619,7 @@ function InnerMultiSelectDropdown({
|
|
|
1495
1619
|
}
|
|
1496
1620
|
};
|
|
1497
1621
|
const handleKeyDown = (event) => {
|
|
1498
|
-
if (event.key === "Escape" && settings.
|
|
1622
|
+
if (event.key === "Escape" && settings.keyboard.escape) {
|
|
1499
1623
|
closeDropdown(true);
|
|
1500
1624
|
}
|
|
1501
1625
|
};
|
|
@@ -1507,7 +1631,7 @@ function InnerMultiSelectDropdown({
|
|
|
1507
1631
|
document.removeEventListener("touchstart", handlePointerDown);
|
|
1508
1632
|
document.removeEventListener("keydown", handleKeyDown);
|
|
1509
1633
|
};
|
|
1510
|
-
}, [isOpen, settings.
|
|
1634
|
+
}, [isOpen, settings.keyboard.escape]);
|
|
1511
1635
|
const updateBodyMenuPosition = () => {
|
|
1512
1636
|
if (!shouldAppendToBody || !triggerRef.current || typeof window === "undefined") {
|
|
1513
1637
|
return;
|
|
@@ -1666,20 +1790,38 @@ function InnerMultiSelectDropdown({
|
|
|
1666
1790
|
return `${settings.ariaLabel}: ${selectedItems.map((item) => getLabel(item, settings)).join(", ")}`;
|
|
1667
1791
|
};
|
|
1668
1792
|
const stopInlineKey = (event) => {
|
|
1669
|
-
if (event.key
|
|
1793
|
+
if (isSpaceKey(event.key) && !settings.keyboard.space) {
|
|
1794
|
+
event.preventDefault();
|
|
1795
|
+
event.stopPropagation();
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
if (event.key === "Enter" || isSpaceKey(event.key)) {
|
|
1799
|
+
event.stopPropagation();
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
const handleBadgeRemoveKeyDown = (event, item) => {
|
|
1803
|
+
if (settings.keyboard.deleteRemovesFocusedBadge && (event.key === "Backspace" || event.key === "Delete")) {
|
|
1804
|
+
event.preventDefault();
|
|
1670
1805
|
event.stopPropagation();
|
|
1806
|
+
removeItem(item);
|
|
1807
|
+
return;
|
|
1671
1808
|
}
|
|
1809
|
+
stopInlineKey(event);
|
|
1672
1810
|
};
|
|
1673
1811
|
const handleTriggerKeyDown = (event) => {
|
|
1674
1812
|
if (settings.disabled) {
|
|
1675
1813
|
return;
|
|
1676
1814
|
}
|
|
1677
|
-
if (event.key
|
|
1815
|
+
if (isSpaceKey(event.key) && !settings.keyboard.space) {
|
|
1816
|
+
event.preventDefault();
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
if (event.key === "Enter" || isSpaceKey(event.key)) {
|
|
1678
1820
|
event.preventDefault();
|
|
1679
1821
|
toggleDropdown();
|
|
1680
1822
|
return;
|
|
1681
1823
|
}
|
|
1682
|
-
if (event.key === "ArrowDown") {
|
|
1824
|
+
if (settings.keyboard.arrows && event.key === "ArrowDown") {
|
|
1683
1825
|
event.preventDefault();
|
|
1684
1826
|
if (!isOpen) {
|
|
1685
1827
|
openDropdown("first");
|
|
@@ -1688,7 +1830,7 @@ function InnerMultiSelectDropdown({
|
|
|
1688
1830
|
}
|
|
1689
1831
|
return;
|
|
1690
1832
|
}
|
|
1691
|
-
if (event.key === "ArrowUp") {
|
|
1833
|
+
if (settings.keyboard.arrows && event.key === "ArrowUp") {
|
|
1692
1834
|
event.preventDefault();
|
|
1693
1835
|
if (!isOpen) {
|
|
1694
1836
|
openDropdown("last");
|
|
@@ -1697,48 +1839,84 @@ function InnerMultiSelectDropdown({
|
|
|
1697
1839
|
}
|
|
1698
1840
|
return;
|
|
1699
1841
|
}
|
|
1700
|
-
if (event.key === "Escape" && isOpen) {
|
|
1842
|
+
if (settings.keyboard.escape && event.key === "Escape" && isOpen) {
|
|
1701
1843
|
event.preventDefault();
|
|
1702
1844
|
closeDropdown(true);
|
|
1703
1845
|
}
|
|
1704
1846
|
};
|
|
1705
1847
|
const handleArrowButtonKeyDown = (event) => {
|
|
1706
|
-
if (event.key
|
|
1848
|
+
if (isSpaceKey(event.key) && !settings.keyboard.space) {
|
|
1849
|
+
event.preventDefault();
|
|
1850
|
+
event.stopPropagation();
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
if (event.key === "Enter" || isSpaceKey(event.key)) {
|
|
1707
1854
|
event.preventDefault();
|
|
1708
1855
|
event.stopPropagation();
|
|
1709
1856
|
toggleDropdown();
|
|
1710
1857
|
return;
|
|
1711
1858
|
}
|
|
1712
|
-
if (event.key === "ArrowDown") {
|
|
1859
|
+
if (settings.keyboard.arrows && event.key === "ArrowDown") {
|
|
1713
1860
|
event.preventDefault();
|
|
1714
1861
|
event.stopPropagation();
|
|
1715
|
-
|
|
1862
|
+
if (isOpen) {
|
|
1863
|
+
focusFirstOption();
|
|
1864
|
+
} else {
|
|
1865
|
+
openDropdown("first");
|
|
1866
|
+
}
|
|
1716
1867
|
return;
|
|
1717
1868
|
}
|
|
1718
|
-
if (event.key === "ArrowUp") {
|
|
1869
|
+
if (settings.keyboard.arrows && event.key === "ArrowUp") {
|
|
1719
1870
|
event.preventDefault();
|
|
1720
1871
|
event.stopPropagation();
|
|
1721
|
-
|
|
1872
|
+
if (isOpen) {
|
|
1873
|
+
focusLastOption();
|
|
1874
|
+
} else {
|
|
1875
|
+
openDropdown("last");
|
|
1876
|
+
}
|
|
1722
1877
|
}
|
|
1723
1878
|
};
|
|
1724
1879
|
const handleSearchKeyDown = (event) => {
|
|
1725
|
-
if (event.key
|
|
1880
|
+
if (isSpaceKey(event.key) && !settings.keyboard.space) {
|
|
1881
|
+
event.preventDefault();
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
if (settings.keyboard.backspaceRemovesLastWhenSearchEmpty && event.key === "Backspace" && !filter && selectedItems.length > 0 && !settings.singleSelection) {
|
|
1885
|
+
event.preventDefault();
|
|
1886
|
+
removeLastSelectedItem();
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
if (settings.keyboard.arrows && event.key === "ArrowDown") {
|
|
1726
1890
|
event.preventDefault();
|
|
1727
1891
|
focusFirstOption();
|
|
1728
1892
|
return;
|
|
1729
1893
|
}
|
|
1730
|
-
if (event.key === "Escape"
|
|
1894
|
+
if (settings.keyboard.escape && event.key === "Escape") {
|
|
1731
1895
|
event.preventDefault();
|
|
1732
1896
|
closeDropdown(true);
|
|
1733
1897
|
}
|
|
1734
1898
|
};
|
|
1735
1899
|
const handleOptionKeyDown = (event, item, optionIndex) => {
|
|
1736
|
-
if (event.key
|
|
1900
|
+
if (isSpaceKey(event.key) && !settings.keyboard.space) {
|
|
1901
|
+
event.preventDefault();
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
if ((event.key === "Enter" || isSpaceKey(event.key)) && event.repeat) {
|
|
1905
|
+
event.preventDefault();
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
if (event.key === "Enter" || isSpaceKey(event.key)) {
|
|
1737
1909
|
event.preventDefault();
|
|
1738
|
-
|
|
1910
|
+
const willClose = !isSelected(item) && (settings.singleSelection || settings.closeDropDownOnSelection);
|
|
1911
|
+
const currentOptionId = event.currentTarget.id;
|
|
1912
|
+
const moveToNextOption = isSpaceKey(event.key) && settings.keyboard.spaceOptionAction === "toggle-and-next";
|
|
1913
|
+
selectItem(item, willClose ? "trigger" : "none");
|
|
1914
|
+
if (!willClose) {
|
|
1915
|
+
focusOptionAfterKeyboardSelection(currentOptionId, optionIndex, moveToNextOption);
|
|
1916
|
+
}
|
|
1739
1917
|
return;
|
|
1740
1918
|
}
|
|
1741
|
-
if (event.key === "ArrowDown") {
|
|
1919
|
+
if (settings.keyboard.arrows && event.key === "ArrowDown") {
|
|
1742
1920
|
event.preventDefault();
|
|
1743
1921
|
const nextIndex = optionIndex + 1;
|
|
1744
1922
|
const options = getOptionElements();
|
|
@@ -1749,7 +1927,7 @@ function InnerMultiSelectDropdown({
|
|
|
1749
1927
|
}
|
|
1750
1928
|
return;
|
|
1751
1929
|
}
|
|
1752
|
-
if (event.key === "ArrowUp") {
|
|
1930
|
+
if (settings.keyboard.arrows && event.key === "ArrowUp") {
|
|
1753
1931
|
event.preventDefault();
|
|
1754
1932
|
if (optionIndex > 0) {
|
|
1755
1933
|
focusOptionByIndex(optionIndex - 1);
|
|
@@ -1760,21 +1938,54 @@ function InnerMultiSelectDropdown({
|
|
|
1760
1938
|
}
|
|
1761
1939
|
return;
|
|
1762
1940
|
}
|
|
1763
|
-
if (event.key === "Home") {
|
|
1941
|
+
if (settings.keyboard.arrows && event.key === "Home") {
|
|
1764
1942
|
event.preventDefault();
|
|
1765
1943
|
focusFirstOption();
|
|
1766
1944
|
return;
|
|
1767
1945
|
}
|
|
1768
|
-
if (event.key === "End") {
|
|
1946
|
+
if (settings.keyboard.arrows && event.key === "End") {
|
|
1769
1947
|
event.preventDefault();
|
|
1770
1948
|
focusLastOption();
|
|
1771
1949
|
return;
|
|
1772
1950
|
}
|
|
1773
|
-
if (event.key === "Escape"
|
|
1951
|
+
if (settings.keyboard.escape && event.key === "Escape") {
|
|
1774
1952
|
event.preventDefault();
|
|
1775
1953
|
closeDropdown(true);
|
|
1776
1954
|
}
|
|
1777
1955
|
};
|
|
1956
|
+
const slotState = {
|
|
1957
|
+
settings,
|
|
1958
|
+
isOpen,
|
|
1959
|
+
filter,
|
|
1960
|
+
selectedItems,
|
|
1961
|
+
visibleBadges,
|
|
1962
|
+
hiddenBadgeCount,
|
|
1963
|
+
filteredItems,
|
|
1964
|
+
selectableItems,
|
|
1965
|
+
allFilteredSelected,
|
|
1966
|
+
hasFilteredResults,
|
|
1967
|
+
loading: Boolean(loading ?? settings.loading),
|
|
1968
|
+
listboxId,
|
|
1969
|
+
activeDescendantId: activeDescendantId || void 0,
|
|
1970
|
+
label: selectedItems.length ? selectedItems.map((item) => getLabel(item, settings)).join(", ") : settings.text
|
|
1971
|
+
};
|
|
1972
|
+
const slotActions = {
|
|
1973
|
+
openDropdown: () => openDropdown("search"),
|
|
1974
|
+
closeDropdown: () => closeDropdown(),
|
|
1975
|
+
toggleDropdown,
|
|
1976
|
+
clearSelection,
|
|
1977
|
+
selectItem: (item) => selectItem(item),
|
|
1978
|
+
removeItem: (item) => removeItem(item),
|
|
1979
|
+
selectAll: (items = selectableItems) => selectAllItems(items),
|
|
1980
|
+
deSelectAll: (items = selectedItems) => deSelectAllItems(items),
|
|
1981
|
+
toggleGroup,
|
|
1982
|
+
addFilterNewItem: handleAddFilterNewItem,
|
|
1983
|
+
setFilter
|
|
1984
|
+
};
|
|
1985
|
+
const slotBase = {
|
|
1986
|
+
state: slotState,
|
|
1987
|
+
actions: slotActions
|
|
1988
|
+
};
|
|
1778
1989
|
const renderItemNode = (item) => {
|
|
1779
1990
|
const context = {
|
|
1780
1991
|
item,
|
|
@@ -1790,49 +2001,122 @@ function InnerMultiSelectDropdown({
|
|
|
1790
2001
|
!isPrimitiveItem(item) && item.caption ? /* @__PURE__ */ jsx("span", { className: "rmsd-option-hint", children: String(item.caption) }) : null
|
|
1791
2002
|
] });
|
|
1792
2003
|
};
|
|
1793
|
-
const
|
|
2004
|
+
const renderBadgeLabel = (item) => {
|
|
2005
|
+
const label = getLabel(item, settings);
|
|
1794
2006
|
const context = {
|
|
1795
2007
|
item,
|
|
1796
|
-
label
|
|
2008
|
+
label,
|
|
1797
2009
|
selected: true,
|
|
1798
2010
|
disabled: settings.disabled || isDisabledItem(item),
|
|
1799
2011
|
query: filter,
|
|
1800
2012
|
toggle: () => selectItem(item),
|
|
1801
2013
|
remove: () => removeItem(item)
|
|
1802
2014
|
};
|
|
1803
|
-
|
|
2015
|
+
const badgeContent = renderBadge ? renderBadge(item, context) : context.label;
|
|
2016
|
+
const badgeLabelProps = { className: "rmsd-badge-label" };
|
|
2017
|
+
return renderSlot(
|
|
2018
|
+
slots?.BadgeLabel,
|
|
2019
|
+
{ ...slotBase, props: badgeLabelProps, item, label, children: badgeContent },
|
|
2020
|
+
/* @__PURE__ */ jsx("span", { ...badgeLabelProps, children: badgeContent })
|
|
2021
|
+
);
|
|
2022
|
+
};
|
|
2023
|
+
const renderCheckbox = (checked, context) => {
|
|
2024
|
+
if (!settings.showCheckbox) {
|
|
2025
|
+
return null;
|
|
2026
|
+
}
|
|
2027
|
+
const checkboxProps = {
|
|
2028
|
+
className: "rmsd-checkbox",
|
|
2029
|
+
"data-checked": checked,
|
|
2030
|
+
"aria-hidden": true
|
|
2031
|
+
};
|
|
2032
|
+
return renderSlot(
|
|
2033
|
+
slots?.Checkbox,
|
|
2034
|
+
{ ...slotBase, props: checkboxProps, checked, context },
|
|
2035
|
+
/* @__PURE__ */ jsx("span", { ...checkboxProps })
|
|
2036
|
+
);
|
|
2037
|
+
};
|
|
2038
|
+
const renderBadgeRemoveButton = (item) => {
|
|
2039
|
+
const label = getLabel(item, settings);
|
|
2040
|
+
const removeProps = {
|
|
2041
|
+
type: "button",
|
|
2042
|
+
className: "rmsd-badge-remove",
|
|
2043
|
+
"aria-label": getRemoveItemAriaLabel(item),
|
|
2044
|
+
onKeyDown: (event) => handleBadgeRemoveKeyDown(event, item),
|
|
2045
|
+
onClick: (event) => {
|
|
2046
|
+
event.stopPropagation();
|
|
2047
|
+
removeItem(item);
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
const icon = /* @__PURE__ */ jsx(StacklineIcon, { name: "remove" });
|
|
2051
|
+
return renderSlot(
|
|
2052
|
+
slots?.BadgeRemove,
|
|
2053
|
+
{ ...slotBase, props: removeProps, item, label, icon },
|
|
2054
|
+
/* @__PURE__ */ jsx("button", { ...removeProps, children: icon })
|
|
2055
|
+
);
|
|
2056
|
+
};
|
|
2057
|
+
const renderBadgeNode = (item) => {
|
|
2058
|
+
const label = getLabel(item, settings);
|
|
2059
|
+
const badgeProps = { className: "rmsd-badge" };
|
|
2060
|
+
const removeButton = !settings.disabled ? renderBadgeRemoveButton(item) : null;
|
|
2061
|
+
const children = /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
2062
|
+
renderBadgeLabel(item),
|
|
2063
|
+
removeButton
|
|
2064
|
+
] });
|
|
2065
|
+
return renderSlot(
|
|
2066
|
+
slots?.Badge,
|
|
2067
|
+
{ ...slotBase, props: badgeProps, item, label, children, removeButton },
|
|
2068
|
+
/* @__PURE__ */ jsx("span", { ...badgeProps, children })
|
|
2069
|
+
);
|
|
1804
2070
|
};
|
|
1805
|
-
let
|
|
2071
|
+
let enabledOptionCursor = -1;
|
|
1806
2072
|
const renderOption = (item, prefix, localIndex) => {
|
|
1807
2073
|
const selected = isSelected(item);
|
|
1808
2074
|
const disabled = settings.disabled || isDisabledItem(item) || limitReached && !selected;
|
|
1809
|
-
|
|
1810
|
-
const optionIndex = optionCursor;
|
|
2075
|
+
const optionIndex = disabled ? -1 : enabledOptionCursor += 1;
|
|
1811
2076
|
const optionId = getOptionId(item, localIndex, prefix);
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
2077
|
+
const optionKey = `${prefix}-${getPrimaryValue(item, settings)}-${localIndex}`;
|
|
2078
|
+
const optionSlot = {
|
|
2079
|
+
item,
|
|
2080
|
+
id: optionId,
|
|
2081
|
+
key: optionKey,
|
|
2082
|
+
label: getLabel(item, settings),
|
|
2083
|
+
selected,
|
|
2084
|
+
disabled,
|
|
2085
|
+
index: optionIndex,
|
|
2086
|
+
groupName: prefix.startsWith("group-") ? prefix : void 0
|
|
2087
|
+
};
|
|
2088
|
+
const optionProps = {
|
|
2089
|
+
id: optionId,
|
|
2090
|
+
className: `rmsd-option${selected ? " rmsd-selected" : ""}${disabled ? " rmsd-disabled" : ""}`,
|
|
2091
|
+
role: "option",
|
|
2092
|
+
"aria-selected": selected,
|
|
2093
|
+
"aria-checked": selected,
|
|
2094
|
+
"aria-disabled": disabled,
|
|
2095
|
+
tabIndex: disabled || !settings.keyboard.tab ? -1 : 0,
|
|
2096
|
+
"data-rmsd-option": "true",
|
|
2097
|
+
onFocus: () => setActiveDescendantId(optionId),
|
|
2098
|
+
onClick: () => {
|
|
2099
|
+
if (disabled) {
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
const willClose = !isSelected(item) && (settings.singleSelection || settings.closeDropDownOnSelection);
|
|
2103
|
+
selectItem(item, willClose ? "trigger" : "none");
|
|
2104
|
+
if (!willClose) {
|
|
2105
|
+
focusOptionAfterPointerSelection(optionId, optionIndex);
|
|
2106
|
+
}
|
|
1833
2107
|
},
|
|
1834
|
-
|
|
1835
|
-
|
|
2108
|
+
onKeyDown: (event) => handleOptionKeyDown(event, item, optionIndex)
|
|
2109
|
+
};
|
|
2110
|
+
const checkbox = renderCheckbox(selected, "option");
|
|
2111
|
+
const children = /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
2112
|
+
checkbox,
|
|
2113
|
+
renderItemNode(item)
|
|
2114
|
+
] });
|
|
2115
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderSlot(
|
|
2116
|
+
slots?.Option,
|
|
2117
|
+
{ ...slotBase, props: optionProps, option: optionSlot, checkbox, children },
|
|
2118
|
+
/* @__PURE__ */ jsx("div", { ...optionProps, children })
|
|
2119
|
+
) }, optionKey);
|
|
1836
2120
|
};
|
|
1837
2121
|
const handleTriggerClick = (event) => {
|
|
1838
2122
|
if (event.target.closest("button")) {
|
|
@@ -1840,173 +2124,1234 @@ function InnerMultiSelectDropdown({
|
|
|
1840
2124
|
}
|
|
1841
2125
|
toggleDropdown();
|
|
1842
2126
|
};
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
{
|
|
2127
|
+
const renderSelectAllButton = () => {
|
|
2128
|
+
const label = allFilteredSelected ? filter.trim() ? settings.filterUnSelectAllText : settings.unSelectAllText : filter.trim() ? settings.filterSelectAllText : settings.selectAllText;
|
|
2129
|
+
const selectAllProps = {
|
|
2130
|
+
type: "button",
|
|
2131
|
+
className: "rmsd-inline-button rmsd-select-all-button",
|
|
2132
|
+
onClick: () => allFilteredSelected ? deSelectAllItems(selectableItems, Boolean(filter.trim())) : selectAllItems(selectableItems, Boolean(filter.trim())),
|
|
2133
|
+
onKeyDown: stopInlineKey,
|
|
2134
|
+
disabled: settings.disabled || selectableItems.length === 0
|
|
2135
|
+
};
|
|
2136
|
+
const checkbox = renderCheckbox(allFilteredSelected, "selectAll");
|
|
2137
|
+
return renderSlot(
|
|
2138
|
+
slots?.SelectAll,
|
|
2139
|
+
{ ...slotBase, props: selectAllProps, checked: allFilteredSelected, label, checkbox },
|
|
2140
|
+
/* @__PURE__ */ jsxs("button", { ...selectAllProps, children: [
|
|
2141
|
+
checkbox,
|
|
2142
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
2143
|
+
] })
|
|
2144
|
+
);
|
|
2145
|
+
};
|
|
2146
|
+
const renderAddNewItemButton = () => {
|
|
2147
|
+
const query = filter.trim();
|
|
2148
|
+
if (!query) {
|
|
2149
|
+
return null;
|
|
2150
|
+
}
|
|
2151
|
+
const label = `${settings.addNewButtonText} "${query}"`;
|
|
2152
|
+
const addItemProps = {
|
|
2153
|
+
type: "button",
|
|
2154
|
+
className: "rmsd-inline-button rmsd-add-button",
|
|
2155
|
+
onKeyDown: stopInlineKey,
|
|
2156
|
+
onClick: handleAddFilterNewItem
|
|
2157
|
+
};
|
|
2158
|
+
return renderSlot(
|
|
2159
|
+
slots?.AddNewItem,
|
|
2160
|
+
{ ...slotBase, props: addItemProps, query, label },
|
|
2161
|
+
/* @__PURE__ */ jsx("button", { ...addItemProps, children: label })
|
|
2162
|
+
);
|
|
2163
|
+
};
|
|
2164
|
+
const renderSearchNode = () => {
|
|
2165
|
+
if (!settings.enableSearchFilter) {
|
|
2166
|
+
return null;
|
|
2167
|
+
}
|
|
2168
|
+
const searchShellProps = { className: "rmsd-search-shell" };
|
|
2169
|
+
const searchInputProps = {
|
|
2170
|
+
ref: searchRef,
|
|
2171
|
+
className: "rmsd-search-input",
|
|
2172
|
+
value: filter,
|
|
2173
|
+
onChange: (event) => setFilter(event.target.value),
|
|
2174
|
+
onKeyDown: handleSearchKeyDown,
|
|
2175
|
+
placeholder: settings.searchPlaceholderText,
|
|
2176
|
+
"aria-label": settings.searchAriaLabel
|
|
2177
|
+
};
|
|
2178
|
+
const searchClearProps = {
|
|
2179
|
+
type: "button",
|
|
2180
|
+
className: "rmsd-search-clear",
|
|
2181
|
+
"aria-label": settings.clearSearchAriaLabel,
|
|
2182
|
+
onKeyDown: stopInlineKey,
|
|
2183
|
+
onClick: () => setFilter("")
|
|
2184
|
+
};
|
|
2185
|
+
const icon = /* @__PURE__ */ jsx(StacklineIcon, { name: "search", className: "rmsd-search-icon" });
|
|
2186
|
+
const clearIcon = /* @__PURE__ */ jsx(StacklineIcon, { name: "clear" });
|
|
2187
|
+
const fallback = renderSearch ? renderSearch({ query: filter, setQuery: setFilter, closeDropdown: () => closeDropdown() }) : /* @__PURE__ */ jsxs("div", { ...searchShellProps, children: [
|
|
2188
|
+
icon,
|
|
2189
|
+
/* @__PURE__ */ jsx("input", { ...searchInputProps }),
|
|
2190
|
+
filter ? /* @__PURE__ */ jsx("button", { ...searchClearProps, children: clearIcon }) : null
|
|
2191
|
+
] });
|
|
2192
|
+
return renderSlot(
|
|
2193
|
+
slots?.Search,
|
|
2194
|
+
{
|
|
2195
|
+
...slotBase,
|
|
2196
|
+
props: searchShellProps,
|
|
2197
|
+
inputProps: searchInputProps,
|
|
2198
|
+
clearButtonProps: searchClearProps,
|
|
2199
|
+
query: filter,
|
|
2200
|
+
icon,
|
|
2201
|
+
clearIcon
|
|
2202
|
+
},
|
|
2203
|
+
fallback
|
|
2204
|
+
);
|
|
2205
|
+
};
|
|
2206
|
+
const renderGroupAction = (group) => {
|
|
2207
|
+
if (!settings.selectGroup || settings.singleSelection) {
|
|
2208
|
+
return null;
|
|
2209
|
+
}
|
|
2210
|
+
const label = group.selected ? "Unselect" : "Select";
|
|
2211
|
+
const groupActionProps = {
|
|
2212
|
+
type: "button",
|
|
2213
|
+
className: "rmsd-group-action",
|
|
2214
|
+
onKeyDown: stopInlineKey,
|
|
2215
|
+
onClick: () => toggleGroup(group.name, group.items)
|
|
2216
|
+
};
|
|
2217
|
+
return renderSlot(
|
|
2218
|
+
slots?.GroupAction,
|
|
2219
|
+
{ ...slotBase, props: groupActionProps, group, label },
|
|
2220
|
+
/* @__PURE__ */ jsx("button", { ...groupActionProps, children: label })
|
|
2221
|
+
);
|
|
2222
|
+
};
|
|
2223
|
+
const renderGroupNode = (group, groupIndex) => {
|
|
2224
|
+
const enabledItems = group.items.filter((item) => !isDisabledItem(item));
|
|
2225
|
+
const groupSlot = {
|
|
2226
|
+
name: group.name,
|
|
2227
|
+
items: group.items,
|
|
2228
|
+
enabledItems,
|
|
2229
|
+
selected: enabledItems.length > 0 && enabledItems.every((item) => isSelected(item)),
|
|
2230
|
+
disabled: enabledItems.length === 0,
|
|
2231
|
+
index: groupIndex
|
|
2232
|
+
};
|
|
2233
|
+
const action = renderGroupAction(groupSlot);
|
|
2234
|
+
const groupHeaderProps = { className: "rmsd-group-header" };
|
|
2235
|
+
const header = renderSlot(
|
|
2236
|
+
slots?.GroupHeader,
|
|
2237
|
+
{ ...slotBase, props: groupHeaderProps, group: groupSlot, action },
|
|
2238
|
+
/* @__PURE__ */ jsxs("div", { ...groupHeaderProps, children: [
|
|
2239
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2240
|
+
group.name,
|
|
2241
|
+
" \xB7 ",
|
|
2242
|
+
group.items.length
|
|
2243
|
+
] }),
|
|
2244
|
+
action
|
|
2245
|
+
] })
|
|
2246
|
+
);
|
|
2247
|
+
const groupProps = {
|
|
2248
|
+
className: "rmsd-group",
|
|
2249
|
+
role: "group",
|
|
2250
|
+
"aria-label": group.name
|
|
2251
|
+
};
|
|
2252
|
+
const children = group.items.map((item, index) => renderOption(item, `group-${groupIndex}`, index));
|
|
2253
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderSlot(
|
|
2254
|
+
slots?.Group,
|
|
2255
|
+
{ ...slotBase, props: groupProps, group: groupSlot, header, children },
|
|
2256
|
+
/* @__PURE__ */ jsxs("div", { ...groupProps, children: [
|
|
2257
|
+
header,
|
|
2258
|
+
children
|
|
2259
|
+
] })
|
|
2260
|
+
) }, group.name);
|
|
2261
|
+
};
|
|
2262
|
+
const renderOptionListChildren = () => {
|
|
2263
|
+
if (loading ?? settings.loading) {
|
|
2264
|
+
const loadingProps = { className: "rmsd-state", role: "status" };
|
|
2265
|
+
return renderSlot(
|
|
2266
|
+
slots?.LoadingState,
|
|
2267
|
+
{ ...slotBase, props: loadingProps, text: settings.loadingText },
|
|
2268
|
+
/* @__PURE__ */ jsx("div", { ...loadingProps, children: settings.loadingText })
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
if (groupedItems.length > 0) {
|
|
2272
|
+
return groupedItems.map((group, groupIndex) => renderGroupNode(group, groupIndex));
|
|
2273
|
+
}
|
|
2274
|
+
if (hasFilteredResults) {
|
|
2275
|
+
return filteredItems.map((item, index) => renderOption(item, "item", index));
|
|
2276
|
+
}
|
|
2277
|
+
const emptyProps = { className: "rmsd-state" };
|
|
2278
|
+
return renderSlot(
|
|
2279
|
+
slots?.EmptyState,
|
|
2280
|
+
{ ...slotBase, props: emptyProps, query: filter, text: settings.noDataLabel },
|
|
2281
|
+
/* @__PURE__ */ jsx("div", { ...emptyProps, children: renderEmptyState ? renderEmptyState(filter) : settings.noDataLabel })
|
|
2282
|
+
);
|
|
2283
|
+
};
|
|
2284
|
+
const renderOptionList = () => {
|
|
2285
|
+
const listProps = {
|
|
2286
|
+
className: "rmsd-list",
|
|
2287
|
+
ref: listRef,
|
|
2288
|
+
style: {
|
|
2289
|
+
maxHeight: shouldAppendToBody ? bodyListMaxHeight ?? settings.maxHeight : settings.maxHeight
|
|
2290
|
+
},
|
|
2291
|
+
onScroll: settings.lazyLoading ? handleListScroll : void 0,
|
|
2292
|
+
id: listboxId,
|
|
2293
|
+
role: "listbox",
|
|
2294
|
+
"aria-label": settings.listboxAriaLabel,
|
|
2295
|
+
"aria-multiselectable": !settings.singleSelection
|
|
2296
|
+
};
|
|
2297
|
+
const children = renderOptionListChildren();
|
|
2298
|
+
return renderSlot(
|
|
2299
|
+
slots?.OptionList,
|
|
2300
|
+
{ ...slotBase, props: listProps, children },
|
|
2301
|
+
/* @__PURE__ */ jsx("div", { ...listProps, children })
|
|
2302
|
+
);
|
|
2303
|
+
};
|
|
2304
|
+
const renderBulkActions = () => {
|
|
2305
|
+
if (!hasBulkActions) {
|
|
2306
|
+
return null;
|
|
2307
|
+
}
|
|
2308
|
+
const bulkActionsProps = { className: "rmsd-bulk-actions" };
|
|
2309
|
+
const children = /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
2310
|
+
settings.enableCheckAll && !settings.singleSelection ? renderSelectAllButton() : null,
|
|
2311
|
+
settings.addNewItemOnFilter && filter.trim() ? renderAddNewItemButton() : null
|
|
2312
|
+
] });
|
|
2313
|
+
return renderSlot(
|
|
2314
|
+
slots?.BulkActions,
|
|
2315
|
+
{ ...slotBase, props: bulkActionsProps, children },
|
|
2316
|
+
/* @__PURE__ */ jsx("div", { ...bulkActionsProps, children })
|
|
2317
|
+
);
|
|
2318
|
+
};
|
|
2319
|
+
const renderToolbar = () => {
|
|
2320
|
+
const toolbarProps = { className: "rmsd-toolbar" };
|
|
2321
|
+
const children = /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
2322
|
+
renderBulkActions(),
|
|
2323
|
+
renderSearchNode()
|
|
2324
|
+
] });
|
|
2325
|
+
return renderSlot(
|
|
2326
|
+
slots?.Toolbar,
|
|
2327
|
+
{ ...slotBase, props: toolbarProps, children },
|
|
2328
|
+
/* @__PURE__ */ jsx("div", { ...toolbarProps, children })
|
|
2329
|
+
);
|
|
2330
|
+
};
|
|
2331
|
+
const renderMenuFooter = () => {
|
|
2332
|
+
if (!slots?.MenuFooter) {
|
|
2333
|
+
return null;
|
|
2334
|
+
}
|
|
2335
|
+
return renderSlot(slots.MenuFooter, { ...slotBase, props: { className: "rmsd-menu-footer" } }, null);
|
|
2336
|
+
};
|
|
2337
|
+
const renderMenu = () => {
|
|
2338
|
+
if (!isOpen) {
|
|
2339
|
+
return null;
|
|
2340
|
+
}
|
|
2341
|
+
const menuProps = {
|
|
1846
2342
|
ref: menuRef,
|
|
1847
2343
|
className: `rmsd-menu rmsd-${effectivePosition} skin-${skinName} theme-${skinName}${skinFallbackClass ? ` ${skinFallbackClass}` : ""}${shouldAppendToBody ? " rmsd-body-overlay" : ""}`,
|
|
1848
2344
|
style: shouldAppendToBody ? bodyMenuStyle : void 0,
|
|
1849
2345
|
onMouseDown: (event) => event.stopPropagation(),
|
|
1850
|
-
onTouchStart: (event) => event.stopPropagation()
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
onClick: () => allFilteredSelected ? deSelectAllItems(selectableItems, Boolean(filter.trim())) : selectAllItems(selectableItems, Boolean(filter.trim())),
|
|
1860
|
-
disabled: settings.disabled || selectableItems.length === 0,
|
|
1861
|
-
children: [
|
|
1862
|
-
settings.showCheckbox ? /* @__PURE__ */ jsx("span", { className: "rmsd-checkbox", "data-checked": allFilteredSelected, "aria-hidden": "true" }) : null,
|
|
1863
|
-
/* @__PURE__ */ jsx("span", { children: allFilteredSelected ? filter.trim() ? settings.filterUnSelectAllText : settings.unSelectAllText : filter.trim() ? settings.filterSelectAllText : settings.selectAllText })
|
|
1864
|
-
]
|
|
1865
|
-
}
|
|
1866
|
-
) : null,
|
|
1867
|
-
settings.addNewItemOnFilter && filter.trim() ? /* @__PURE__ */ jsxs("button", { type: "button", className: "rmsd-inline-button rmsd-add-button", onClick: handleAddFilterNewItem, children: [
|
|
1868
|
-
settings.addNewButtonText,
|
|
1869
|
-
' "',
|
|
1870
|
-
filter.trim(),
|
|
1871
|
-
'"'
|
|
1872
|
-
] }) : null
|
|
1873
|
-
] }) : null,
|
|
1874
|
-
settings.enableSearchFilter ? renderSearch ? renderSearch({ query: filter, setQuery: setFilter, closeDropdown: () => closeDropdown() }) : /* @__PURE__ */ jsxs("div", { className: "rmsd-search-shell", children: [
|
|
1875
|
-
/* @__PURE__ */ jsx(StacklineIcon, { name: "search", className: "rmsd-search-icon" }),
|
|
1876
|
-
/* @__PURE__ */ jsx(
|
|
1877
|
-
"input",
|
|
1878
|
-
{
|
|
1879
|
-
ref: searchRef,
|
|
1880
|
-
className: "rmsd-search-input",
|
|
1881
|
-
value: filter,
|
|
1882
|
-
onChange: (event) => setFilter(event.target.value),
|
|
1883
|
-
onKeyDown: handleSearchKeyDown,
|
|
1884
|
-
placeholder: settings.searchPlaceholderText,
|
|
1885
|
-
"aria-label": settings.searchAriaLabel
|
|
1886
|
-
}
|
|
1887
|
-
),
|
|
1888
|
-
filter ? /* @__PURE__ */ jsx(
|
|
1889
|
-
"button",
|
|
1890
|
-
{
|
|
1891
|
-
type: "button",
|
|
1892
|
-
className: "rmsd-search-clear",
|
|
1893
|
-
"aria-label": settings.clearSearchAriaLabel,
|
|
1894
|
-
onKeyDown: stopInlineKey,
|
|
1895
|
-
onClick: () => setFilter(""),
|
|
1896
|
-
children: /* @__PURE__ */ jsx(StacklineIcon, { name: "clear" })
|
|
1897
|
-
}
|
|
1898
|
-
) : null
|
|
1899
|
-
] }) : null
|
|
1900
|
-
] }),
|
|
1901
|
-
/* @__PURE__ */ jsx(
|
|
1902
|
-
"div",
|
|
1903
|
-
{
|
|
1904
|
-
className: "rmsd-list",
|
|
1905
|
-
ref: listRef,
|
|
1906
|
-
style: { maxHeight: shouldAppendToBody ? bodyListMaxHeight ?? settings.maxHeight : settings.maxHeight },
|
|
1907
|
-
onScroll: settings.lazyLoading ? handleListScroll : void 0,
|
|
1908
|
-
id: listboxId,
|
|
1909
|
-
role: "listbox",
|
|
1910
|
-
"aria-label": settings.listboxAriaLabel,
|
|
1911
|
-
"aria-multiselectable": !settings.singleSelection,
|
|
1912
|
-
children: loading ?? settings.loading ? /* @__PURE__ */ jsx("div", { className: "rmsd-state", role: "status", children: settings.loadingText }) : groupedItems.length > 0 ? groupedItems.map((group, groupIndex) => /* @__PURE__ */ jsxs("div", { className: "rmsd-group", role: "group", "aria-label": group.name, children: [
|
|
1913
|
-
/* @__PURE__ */ jsxs("div", { className: "rmsd-group-header", children: [
|
|
1914
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
1915
|
-
group.name,
|
|
1916
|
-
" \xB7 ",
|
|
1917
|
-
group.items.length
|
|
1918
|
-
] }),
|
|
1919
|
-
settings.selectGroup && !settings.singleSelection ? /* @__PURE__ */ jsx("button", { type: "button", className: "rmsd-group-action", onClick: () => toggleGroup(group.name, group.items), children: group.items.filter((item) => !isDisabledItem(item)).every((item) => isSelected(item)) ? "Unselect" : "Select" }) : null
|
|
1920
|
-
] }),
|
|
1921
|
-
group.items.map((item, index) => renderOption(item, `group-${groupIndex}`, index))
|
|
1922
|
-
] }, group.name)) : hasFilteredResults ? filteredItems.map((item, index) => renderOption(item, "item", index)) : /* @__PURE__ */ jsx("div", { className: "rmsd-state", children: renderEmptyState ? renderEmptyState(filter) : settings.noDataLabel })
|
|
1923
|
-
}
|
|
1924
|
-
)
|
|
1925
|
-
]
|
|
1926
|
-
}
|
|
1927
|
-
) : null;
|
|
1928
|
-
return /* @__PURE__ */ jsxs("div", { className: rootClassName, style, ref: rootRef, "data-open": isOpen, children: [
|
|
1929
|
-
/* @__PURE__ */ jsxs(
|
|
1930
|
-
"div",
|
|
2346
|
+
onTouchStart: (event) => event.stopPropagation()
|
|
2347
|
+
};
|
|
2348
|
+
const children = /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
2349
|
+
renderToolbar(),
|
|
2350
|
+
renderOptionList(),
|
|
2351
|
+
renderMenuFooter()
|
|
2352
|
+
] });
|
|
2353
|
+
return renderSlot(
|
|
2354
|
+
slots?.Menu,
|
|
1931
2355
|
{
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
}
|
|
1999
|
-
)
|
|
2000
|
-
] })
|
|
2001
|
-
]
|
|
2356
|
+
...slotBase,
|
|
2357
|
+
props: menuProps,
|
|
2358
|
+
children,
|
|
2359
|
+
position: effectivePosition,
|
|
2360
|
+
appendToBody: shouldAppendToBody
|
|
2361
|
+
},
|
|
2362
|
+
/* @__PURE__ */ jsx("div", { ...menuProps, children })
|
|
2363
|
+
);
|
|
2364
|
+
};
|
|
2365
|
+
const renderValue = () => {
|
|
2366
|
+
const valueProps = { className: "rmsd-value" };
|
|
2367
|
+
let children;
|
|
2368
|
+
if (selectedItems.length === 0) {
|
|
2369
|
+
const placeholderProps = { className: "rmsd-placeholder" };
|
|
2370
|
+
children = renderSlot(
|
|
2371
|
+
slots?.Placeholder,
|
|
2372
|
+
{ ...slotBase, props: placeholderProps, text: settings.text },
|
|
2373
|
+
/* @__PURE__ */ jsx("span", { ...placeholderProps, children: settings.text })
|
|
2374
|
+
);
|
|
2375
|
+
} else if (settings.singleSelection) {
|
|
2376
|
+
const item = selectedItems[0];
|
|
2377
|
+
const singleValueProps = { className: "rmsd-single-value" };
|
|
2378
|
+
children = renderSlot(
|
|
2379
|
+
slots?.SingleValue,
|
|
2380
|
+
{ ...slotBase, props: singleValueProps, item, label: getLabel(item, settings) },
|
|
2381
|
+
/* @__PURE__ */ jsx("span", { ...singleValueProps, children: getLabel(item, settings) })
|
|
2382
|
+
);
|
|
2383
|
+
} else {
|
|
2384
|
+
const badgeListProps = { className: "rmsd-badge-list" };
|
|
2385
|
+
const badgeListChildren = visibleBadges.map((item) => /* @__PURE__ */ jsx(Fragment, { children: renderBadgeNode(item) }, getPrimaryValue(item, settings)));
|
|
2386
|
+
children = renderSlot(
|
|
2387
|
+
slots?.BadgeList,
|
|
2388
|
+
{ ...slotBase, props: badgeListProps, items: visibleBadges, children: badgeListChildren },
|
|
2389
|
+
/* @__PURE__ */ jsx("div", { ...badgeListProps, children: badgeListChildren })
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
return renderSlot(
|
|
2393
|
+
slots?.Value,
|
|
2394
|
+
{ ...slotBase, props: valueProps, children },
|
|
2395
|
+
/* @__PURE__ */ jsx("div", { ...valueProps, children })
|
|
2396
|
+
);
|
|
2397
|
+
};
|
|
2398
|
+
const renderActions = () => {
|
|
2399
|
+
const actionsProps = { className: "rmsd-actions" };
|
|
2400
|
+
const overflowProps = { className: "rmsd-overflow" };
|
|
2401
|
+
const clearAllProps = {
|
|
2402
|
+
type: "button",
|
|
2403
|
+
className: "rmsd-clear",
|
|
2404
|
+
"aria-label": settings.clearAllAriaLabel,
|
|
2405
|
+
onKeyDown: stopInlineKey,
|
|
2406
|
+
onClick: (event) => {
|
|
2407
|
+
event.stopPropagation();
|
|
2408
|
+
clearSelection();
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
const arrowProps = {
|
|
2412
|
+
type: "button",
|
|
2413
|
+
className: "rmsd-arrow-button",
|
|
2414
|
+
disabled: settings.disabled,
|
|
2415
|
+
"aria-label": isOpen ? settings.closeDropdownAriaLabel : settings.openDropdownAriaLabel,
|
|
2416
|
+
"aria-expanded": isOpen,
|
|
2417
|
+
"aria-controls": listboxId,
|
|
2418
|
+
onKeyDown: handleArrowButtonKeyDown,
|
|
2419
|
+
onClick: (event) => {
|
|
2420
|
+
event.stopPropagation();
|
|
2421
|
+
toggleDropdown();
|
|
2002
2422
|
}
|
|
2003
|
-
|
|
2423
|
+
};
|
|
2424
|
+
const clearIcon = /* @__PURE__ */ jsx(StacklineIcon, { name: "remove" });
|
|
2425
|
+
const arrowIcon = /* @__PURE__ */ jsx("span", { className: "rmsd-arrow", "aria-hidden": "true", children: /* @__PURE__ */ jsx(StacklineIcon, { name: isOpen ? "angle-up" : "angle-down" }) });
|
|
2426
|
+
const children = /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
2427
|
+
hiddenBadgeCount > 0 ? renderSlot(
|
|
2428
|
+
slots?.OverflowCounter,
|
|
2429
|
+
{ ...slotBase, props: overflowProps, count: hiddenBadgeCount },
|
|
2430
|
+
/* @__PURE__ */ jsxs("span", { ...overflowProps, children: [
|
|
2431
|
+
"+",
|
|
2432
|
+
hiddenBadgeCount
|
|
2433
|
+
] })
|
|
2434
|
+
) : null,
|
|
2435
|
+
settings.clearAll && selectedItems.length > 0 && !settings.disabled ? renderSlot(
|
|
2436
|
+
slots?.ClearAll,
|
|
2437
|
+
{ ...slotBase, props: clearAllProps, icon: clearIcon },
|
|
2438
|
+
/* @__PURE__ */ jsx("button", { ...clearAllProps, children: clearIcon })
|
|
2439
|
+
) : null,
|
|
2440
|
+
renderSlot(
|
|
2441
|
+
slots?.Arrow,
|
|
2442
|
+
{ ...slotBase, props: arrowProps, icon: arrowIcon, direction: isOpen ? "up" : "down" },
|
|
2443
|
+
/* @__PURE__ */ jsx("button", { ...arrowProps, children: arrowIcon })
|
|
2444
|
+
)
|
|
2445
|
+
] });
|
|
2446
|
+
return renderSlot(
|
|
2447
|
+
slots?.Actions,
|
|
2448
|
+
{ ...slotBase, props: actionsProps, children },
|
|
2449
|
+
/* @__PURE__ */ jsx("div", { ...actionsProps, children })
|
|
2450
|
+
);
|
|
2451
|
+
};
|
|
2452
|
+
const renderTrigger = () => {
|
|
2453
|
+
const triggerProps = {
|
|
2454
|
+
ref: triggerRef,
|
|
2455
|
+
className: `rmsd-trigger${settings.disabled ? " rmsd-disabled" : ""}`,
|
|
2456
|
+
onClick: handleTriggerClick,
|
|
2457
|
+
onKeyDown: handleTriggerKeyDown,
|
|
2458
|
+
tabIndex: settings.disabled ? -1 : 0,
|
|
2459
|
+
role: "combobox",
|
|
2460
|
+
"aria-expanded": isOpen,
|
|
2461
|
+
"aria-haspopup": "listbox",
|
|
2462
|
+
"aria-controls": listboxId,
|
|
2463
|
+
"aria-disabled": settings.disabled,
|
|
2464
|
+
"aria-activedescendant": activeDescendantId || void 0,
|
|
2465
|
+
"aria-label": getTriggerAriaLabel()
|
|
2466
|
+
};
|
|
2467
|
+
const children = /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
2468
|
+
renderValue(),
|
|
2469
|
+
renderActions()
|
|
2470
|
+
] });
|
|
2471
|
+
return renderSlot(
|
|
2472
|
+
slots?.Trigger,
|
|
2473
|
+
{ ...slotBase, props: triggerProps, children },
|
|
2474
|
+
/* @__PURE__ */ jsx("div", { ...triggerProps, children })
|
|
2475
|
+
);
|
|
2476
|
+
};
|
|
2477
|
+
const menu = renderMenu();
|
|
2478
|
+
const rootChildren = /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
2479
|
+
renderTrigger(),
|
|
2004
2480
|
shouldAppendToBody && menu && typeof document !== "undefined" ? createPortal(menu, document.body) : menu
|
|
2005
2481
|
] });
|
|
2482
|
+
const rootProps = {
|
|
2483
|
+
className: rootClassName,
|
|
2484
|
+
style,
|
|
2485
|
+
ref: rootRef,
|
|
2486
|
+
"data-open": isOpen
|
|
2487
|
+
};
|
|
2488
|
+
return renderSlot(
|
|
2489
|
+
slots?.Root,
|
|
2490
|
+
{ ...slotBase, props: rootProps, children: rootChildren },
|
|
2491
|
+
/* @__PURE__ */ jsx("div", { ...rootProps, children: rootChildren })
|
|
2492
|
+
);
|
|
2006
2493
|
}
|
|
2007
2494
|
var ReactMultiSelectDropdown = forwardRef(InnerMultiSelectDropdown);
|
|
2008
2495
|
var MultiSelectDropdown = ReactMultiSelectDropdown;
|
|
2496
|
+
|
|
2497
|
+
// src/useMultiSelectDropdown.ts
|
|
2498
|
+
import {
|
|
2499
|
+
useEffect as useEffect2,
|
|
2500
|
+
useId,
|
|
2501
|
+
useMemo as useMemo3,
|
|
2502
|
+
useRef as useRef3,
|
|
2503
|
+
useState as useState4
|
|
2504
|
+
} from "react";
|
|
2505
|
+
|
|
2506
|
+
// src/reactUtils.ts
|
|
2507
|
+
function assignRef(ref, value) {
|
|
2508
|
+
if (typeof ref === "function") {
|
|
2509
|
+
ref(value);
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
if (ref && "current" in ref) {
|
|
2513
|
+
ref.current = value;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
function eventWasPrevented(event) {
|
|
2517
|
+
return Boolean(event.defaultPrevented);
|
|
2518
|
+
}
|
|
2519
|
+
function callAll(ownHandler, userHandler) {
|
|
2520
|
+
return (event) => {
|
|
2521
|
+
userHandler?.(event);
|
|
2522
|
+
if (!eventWasPrevented(event)) {
|
|
2523
|
+
ownHandler?.(event);
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
// src/useMultiSelectState.ts
|
|
2529
|
+
import { useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
|
|
2530
|
+
function useMultiSelectState({
|
|
2531
|
+
data,
|
|
2532
|
+
settings: incomingSettings,
|
|
2533
|
+
selectedItems: controlledSelectedItems,
|
|
2534
|
+
defaultSelectedItems,
|
|
2535
|
+
onChange,
|
|
2536
|
+
onSelect,
|
|
2537
|
+
onDeSelect,
|
|
2538
|
+
onSelectAll,
|
|
2539
|
+
onDeSelectAll,
|
|
2540
|
+
onFilterSelectAll,
|
|
2541
|
+
onFilterDeSelectAll,
|
|
2542
|
+
onAddFilterNewItem,
|
|
2543
|
+
onGroupSelect,
|
|
2544
|
+
onGroupDeSelect,
|
|
2545
|
+
onSelectionShouldClose
|
|
2546
|
+
}) {
|
|
2547
|
+
const [filter, setFilter] = useState3("");
|
|
2548
|
+
const [addedItems, setAddedItems] = useState3([]);
|
|
2549
|
+
const addRequestIdRef = useRef2(0);
|
|
2550
|
+
const settings = useMemo2(() => resolveDropdownSettings(incomingSettings), [incomingSettings]);
|
|
2551
|
+
const [selectedItems, setSelectedItems] = useControllableSelection(
|
|
2552
|
+
controlledSelectedItems,
|
|
2553
|
+
defaultSelectedItems,
|
|
2554
|
+
onChange
|
|
2555
|
+
);
|
|
2556
|
+
const allItems = useMemo2(
|
|
2557
|
+
() => mergeUniqueItems(data, [...selectedItems, ...addedItems], settings),
|
|
2558
|
+
[addedItems, data, selectedItems, settings]
|
|
2559
|
+
);
|
|
2560
|
+
const filteredItems = useMemo2(
|
|
2561
|
+
() => allItems.filter((item) => itemMatchesQuery(item, filter, settings)),
|
|
2562
|
+
[allItems, filter, settings]
|
|
2563
|
+
);
|
|
2564
|
+
const isSelected = (item) => selectedItems.some(
|
|
2565
|
+
(selectedItem) => getPrimaryValue(selectedItem, settings) === getPrimaryValue(item, settings)
|
|
2566
|
+
);
|
|
2567
|
+
const isDisabled = (item) => settings.disabled || isDisabledItem(item);
|
|
2568
|
+
const selectableItems = filteredItems.filter((item) => !isDisabled(item));
|
|
2569
|
+
const allFilteredSelected = selectableItems.length > 0 && selectableItems.every((item) => isSelected(item));
|
|
2570
|
+
const visibleBadgeLimit = getVisibleBadgeLimit(selectedItems.length, settings.badgeShowLimit);
|
|
2571
|
+
const visibleBadges = settings.singleSelection ? selectedItems : selectedItems.slice(0, visibleBadgeLimit);
|
|
2572
|
+
const hiddenBadgeCount = settings.singleSelection ? 0 : Math.max(selectedItems.length - visibleBadges.length, 0);
|
|
2573
|
+
const updateSelection = (nextItems) => {
|
|
2574
|
+
setSelectedItems(nextItems);
|
|
2575
|
+
};
|
|
2576
|
+
const removeItem = (item) => {
|
|
2577
|
+
if (settings.disabled) {
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
const nextItems = selectedItems.filter(
|
|
2581
|
+
(selectedItem) => getPrimaryValue(selectedItem, settings) !== getPrimaryValue(item, settings)
|
|
2582
|
+
);
|
|
2583
|
+
updateSelection(nextItems);
|
|
2584
|
+
onDeSelect?.(item);
|
|
2585
|
+
};
|
|
2586
|
+
const removeLastSelectedItem = () => {
|
|
2587
|
+
const lastItem = selectedItems[selectedItems.length - 1];
|
|
2588
|
+
if (!lastItem || settings.disabled) {
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
removeItem(lastItem);
|
|
2592
|
+
};
|
|
2593
|
+
const selectItem = (item) => {
|
|
2594
|
+
if (settings.disabled || isDisabledItem(item)) {
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
if (isSelected(item)) {
|
|
2598
|
+
removeItem(item);
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
if (settings.singleSelection) {
|
|
2602
|
+
updateSelection([item]);
|
|
2603
|
+
onSelect?.(item);
|
|
2604
|
+
onSelectionShouldClose?.();
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
if (settings.limitSelection && selectedItems.length >= settings.limitSelection) {
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
const nextItems = [...selectedItems, item];
|
|
2611
|
+
updateSelection(nextItems);
|
|
2612
|
+
onSelect?.(item);
|
|
2613
|
+
if (settings.closeDropDownOnSelection) {
|
|
2614
|
+
onSelectionShouldClose?.();
|
|
2615
|
+
}
|
|
2616
|
+
};
|
|
2617
|
+
const selectAll = (items = selectableItems, filteredSelection = false) => {
|
|
2618
|
+
if (settings.disabled || settings.singleSelection) {
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2621
|
+
const selectedIds = new Set(selectedItems.map((item) => getPrimaryValue(item, settings)));
|
|
2622
|
+
const remainingCapacity = settings.limitSelection ? Math.max(settings.limitSelection - selectedItems.length, 0) : Number.MAX_SAFE_INTEGER;
|
|
2623
|
+
const nextItemsToAdd = items.filter((item) => !selectedIds.has(getPrimaryValue(item, settings))).filter((item) => !isDisabledItem(item)).slice(0, remainingCapacity);
|
|
2624
|
+
const nextItems = [...selectedItems, ...nextItemsToAdd];
|
|
2625
|
+
updateSelection(nextItems);
|
|
2626
|
+
if (filteredSelection) {
|
|
2627
|
+
onFilterSelectAll?.(nextItemsToAdd);
|
|
2628
|
+
} else {
|
|
2629
|
+
onSelectAll?.(nextItems);
|
|
2630
|
+
}
|
|
2631
|
+
};
|
|
2632
|
+
const deSelectAll = (items = selectedItems, filteredSelection = false) => {
|
|
2633
|
+
if (settings.disabled) {
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
const itemIds = new Set(items.map((item) => getPrimaryValue(item, settings)));
|
|
2637
|
+
const nextItems = selectedItems.filter((item) => !itemIds.has(getPrimaryValue(item, settings)));
|
|
2638
|
+
updateSelection(nextItems);
|
|
2639
|
+
if (filteredSelection) {
|
|
2640
|
+
onFilterDeSelectAll?.(items);
|
|
2641
|
+
} else {
|
|
2642
|
+
onDeSelectAll?.(items);
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
const clearSelection = () => deSelectAll(selectedItems);
|
|
2646
|
+
const toggleGroup = (groupName, items) => {
|
|
2647
|
+
if (settings.disabled) {
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
const enabledItems = items.filter((item) => !isDisabledItem(item));
|
|
2651
|
+
const allSelected = enabledItems.length > 0 && enabledItems.every((item) => isSelected(item));
|
|
2652
|
+
if (allSelected) {
|
|
2653
|
+
deSelectAll(enabledItems);
|
|
2654
|
+
onGroupDeSelect?.(groupName, enabledItems);
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
selectAll(enabledItems);
|
|
2658
|
+
onGroupSelect?.(groupName, enabledItems);
|
|
2659
|
+
};
|
|
2660
|
+
const addFilterNewItem = async () => {
|
|
2661
|
+
if (settings.disabled) {
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
const query = filter.trim();
|
|
2665
|
+
if (!query) {
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
const requestId = addRequestIdRef.current + 1;
|
|
2669
|
+
addRequestIdRef.current = requestId;
|
|
2670
|
+
const result = await onAddFilterNewItem?.(query);
|
|
2671
|
+
if (requestId !== addRequestIdRef.current) {
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
const nextItem = result === void 0 ? createItemFromQuery(query, settings, data[0]) : result;
|
|
2675
|
+
setAddedItems((currentItems) => mergeUniqueItems(currentItems, [nextItem], settings));
|
|
2676
|
+
if (settings.singleSelection) {
|
|
2677
|
+
updateSelection([nextItem]);
|
|
2678
|
+
} else {
|
|
2679
|
+
updateSelection(mergeUniqueItems(selectedItems, [nextItem], settings));
|
|
2680
|
+
}
|
|
2681
|
+
setFilter("");
|
|
2682
|
+
};
|
|
2683
|
+
return {
|
|
2684
|
+
settings,
|
|
2685
|
+
filter,
|
|
2686
|
+
setFilter,
|
|
2687
|
+
selectedItems,
|
|
2688
|
+
allItems,
|
|
2689
|
+
filteredItems,
|
|
2690
|
+
selectableItems,
|
|
2691
|
+
visibleBadges,
|
|
2692
|
+
hiddenBadgeCount,
|
|
2693
|
+
allFilteredSelected,
|
|
2694
|
+
isSelected,
|
|
2695
|
+
isDisabled,
|
|
2696
|
+
getItemLabel: (item) => getLabel(item, settings),
|
|
2697
|
+
getItemKey: (item) => getPrimaryValue(item, settings),
|
|
2698
|
+
selectItem,
|
|
2699
|
+
removeItem,
|
|
2700
|
+
removeLastSelectedItem,
|
|
2701
|
+
selectAll,
|
|
2702
|
+
deSelectAll,
|
|
2703
|
+
clearSelection,
|
|
2704
|
+
toggleGroup,
|
|
2705
|
+
addFilterNewItem
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
// src/useMultiSelectDropdown.ts
|
|
2710
|
+
function isSpaceKey2(key) {
|
|
2711
|
+
return key === " " || key === "Spacebar";
|
|
2712
|
+
}
|
|
2713
|
+
function useMultiSelectDropdown({
|
|
2714
|
+
id,
|
|
2715
|
+
data,
|
|
2716
|
+
settings: incomingSettings,
|
|
2717
|
+
selectedItems: controlledSelectedItems,
|
|
2718
|
+
defaultSelectedItems,
|
|
2719
|
+
onChange,
|
|
2720
|
+
onSelect,
|
|
2721
|
+
onDeSelect,
|
|
2722
|
+
onSelectAll,
|
|
2723
|
+
onDeSelectAll,
|
|
2724
|
+
onOpen,
|
|
2725
|
+
onClose,
|
|
2726
|
+
onScrollToEnd,
|
|
2727
|
+
onFilterSelectAll,
|
|
2728
|
+
onFilterDeSelectAll,
|
|
2729
|
+
onAddFilterNewItem,
|
|
2730
|
+
onGroupSelect,
|
|
2731
|
+
onGroupDeSelect
|
|
2732
|
+
}) {
|
|
2733
|
+
const reactId = useId();
|
|
2734
|
+
const instanceId = sanitizeId(id || `stackline-multiselect-${reactId}`);
|
|
2735
|
+
const rootRef = useRef3(null);
|
|
2736
|
+
const triggerRef = useRef3(null);
|
|
2737
|
+
const searchRef = useRef3(null);
|
|
2738
|
+
const listboxRef = useRef3(null);
|
|
2739
|
+
const optionRefs = useRef3(/* @__PURE__ */ new Map());
|
|
2740
|
+
const lastScrollHeightRef = useRef3(0);
|
|
2741
|
+
const closeDropdownRef = useRef3(() => void 0);
|
|
2742
|
+
const pendingFocusRef = useRef3("search");
|
|
2743
|
+
const [isOpen, setIsOpen] = useState4(false);
|
|
2744
|
+
const [activeOptionIndex, setActiveOptionIndex] = useState4(-1);
|
|
2745
|
+
const listboxId = `${instanceId}-listbox`;
|
|
2746
|
+
const state = useMultiSelectState({
|
|
2747
|
+
data,
|
|
2748
|
+
settings: incomingSettings,
|
|
2749
|
+
selectedItems: controlledSelectedItems,
|
|
2750
|
+
defaultSelectedItems,
|
|
2751
|
+
onChange,
|
|
2752
|
+
onSelect,
|
|
2753
|
+
onDeSelect,
|
|
2754
|
+
onSelectAll,
|
|
2755
|
+
onDeSelectAll,
|
|
2756
|
+
onFilterSelectAll,
|
|
2757
|
+
onFilterDeSelectAll,
|
|
2758
|
+
onAddFilterNewItem,
|
|
2759
|
+
onGroupSelect,
|
|
2760
|
+
onGroupDeSelect,
|
|
2761
|
+
onSelectionShouldClose: () => closeDropdownRef.current()
|
|
2762
|
+
});
|
|
2763
|
+
const {
|
|
2764
|
+
settings,
|
|
2765
|
+
filter,
|
|
2766
|
+
setFilter,
|
|
2767
|
+
selectedItems,
|
|
2768
|
+
filteredItems,
|
|
2769
|
+
selectableItems,
|
|
2770
|
+
visibleBadges,
|
|
2771
|
+
hiddenBadgeCount,
|
|
2772
|
+
allFilteredSelected,
|
|
2773
|
+
isSelected,
|
|
2774
|
+
isDisabled
|
|
2775
|
+
} = state;
|
|
2776
|
+
const limitReached = Boolean(settings.limitSelection) && selectedItems.length >= settings.limitSelection;
|
|
2777
|
+
const options = useMemo3(() => {
|
|
2778
|
+
let optionIndex = -1;
|
|
2779
|
+
return filteredItems.map((item) => {
|
|
2780
|
+
optionIndex += 1;
|
|
2781
|
+
const groupName = getGroupName(item, settings) || void 0;
|
|
2782
|
+
const selected = selectedItems.some(
|
|
2783
|
+
(selectedItem) => getPrimaryValue(selectedItem, settings) === getPrimaryValue(item, settings)
|
|
2784
|
+
);
|
|
2785
|
+
const disabled = settings.disabled || isDisabledItem(item) || limitReached && !selected;
|
|
2786
|
+
const key = getPrimaryValue(item, settings);
|
|
2787
|
+
return {
|
|
2788
|
+
item,
|
|
2789
|
+
key,
|
|
2790
|
+
id: `${instanceId}-option-${optionIndex}-${sanitizeId(key)}`,
|
|
2791
|
+
label: getLabel(item, settings),
|
|
2792
|
+
selected,
|
|
2793
|
+
disabled,
|
|
2794
|
+
index: optionIndex,
|
|
2795
|
+
groupName
|
|
2796
|
+
};
|
|
2797
|
+
});
|
|
2798
|
+
}, [filteredItems, instanceId, limitReached, selectedItems, settings]);
|
|
2799
|
+
const selectableOptions = options.filter((option) => !option.disabled);
|
|
2800
|
+
const selectedOptions = options.filter((option) => option.selected);
|
|
2801
|
+
const groups = useMemo3(() => {
|
|
2802
|
+
if (!settings.groupBy) {
|
|
2803
|
+
return [];
|
|
2804
|
+
}
|
|
2805
|
+
const bucket = /* @__PURE__ */ new Map();
|
|
2806
|
+
for (const option of options) {
|
|
2807
|
+
const groupName = option.groupName || "Ungrouped";
|
|
2808
|
+
const current = bucket.get(groupName) || [];
|
|
2809
|
+
current.push(option);
|
|
2810
|
+
bucket.set(groupName, current);
|
|
2811
|
+
}
|
|
2812
|
+
return Array.from(bucket.entries()).map(([name, groupOptions]) => {
|
|
2813
|
+
const enabledOptions = groupOptions.filter((option) => !option.disabled);
|
|
2814
|
+
return {
|
|
2815
|
+
name,
|
|
2816
|
+
items: groupOptions,
|
|
2817
|
+
selected: enabledOptions.length > 0 && enabledOptions.every((option) => option.selected),
|
|
2818
|
+
disabled: enabledOptions.length === 0
|
|
2819
|
+
};
|
|
2820
|
+
});
|
|
2821
|
+
}, [options, settings.groupBy]);
|
|
2822
|
+
const activeOption = options.find((option) => option.index === activeOptionIndex && !option.disabled);
|
|
2823
|
+
const activeDescendantId = activeOption?.id;
|
|
2824
|
+
const hasFilteredResults = options.length > 0;
|
|
2825
|
+
const label = selectedItems.length ? selectedItems.map((item) => getLabel(item, settings)).join(", ") : settings.text;
|
|
2826
|
+
const focusOption = (index) => {
|
|
2827
|
+
if (!options.length) {
|
|
2828
|
+
setActiveOptionIndex(-1);
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
const enabledOptions = options.filter((option2) => !option2.disabled);
|
|
2832
|
+
if (!enabledOptions.length) {
|
|
2833
|
+
setActiveOptionIndex(-1);
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
const boundedIndex = Math.max(0, Math.min(index, enabledOptions.length - 1));
|
|
2837
|
+
const option = enabledOptions[boundedIndex];
|
|
2838
|
+
setActiveOptionIndex(option.index);
|
|
2839
|
+
window.setTimeout(() => {
|
|
2840
|
+
optionRefs.current.get(option.id)?.focus();
|
|
2841
|
+
optionRefs.current.get(option.id)?.scrollIntoView({ block: "nearest" });
|
|
2842
|
+
}, 0);
|
|
2843
|
+
};
|
|
2844
|
+
const focusOptionById = (optionId, fallbackIndex, moveToNextOption = false) => {
|
|
2845
|
+
setActiveOptionIndex(fallbackIndex);
|
|
2846
|
+
window.setTimeout(() => {
|
|
2847
|
+
const focusAfterRender = () => {
|
|
2848
|
+
if (moveToNextOption) {
|
|
2849
|
+
const optionNodes = Array.from(
|
|
2850
|
+
listboxRef.current?.querySelectorAll(
|
|
2851
|
+
'[data-headless-option="true"]:not([aria-disabled="true"])'
|
|
2852
|
+
) ?? []
|
|
2853
|
+
);
|
|
2854
|
+
const currentIndex = optionNodes.findIndex((optionNode2) => optionNode2.id === optionId);
|
|
2855
|
+
const nextOptionNode = currentIndex >= 0 ? optionNodes[currentIndex + 1] : void 0;
|
|
2856
|
+
if (nextOptionNode) {
|
|
2857
|
+
const nextOption = options.find((option) => option.id === nextOptionNode.id);
|
|
2858
|
+
nextOptionNode.focus();
|
|
2859
|
+
nextOptionNode.scrollIntoView({ block: "nearest" });
|
|
2860
|
+
setActiveOptionIndex(nextOption?.index ?? fallbackIndex + 1);
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
const optionNode = optionRefs.current.get(optionId);
|
|
2865
|
+
if (optionNode) {
|
|
2866
|
+
optionNode.focus();
|
|
2867
|
+
optionNode.scrollIntoView({ block: "nearest" });
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
focusOption(fallbackIndex);
|
|
2871
|
+
};
|
|
2872
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
2873
|
+
window.requestAnimationFrame(focusAfterRender);
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
focusAfterRender();
|
|
2877
|
+
}, 0);
|
|
2878
|
+
};
|
|
2879
|
+
const openDropdown = (focusTarget = "search") => {
|
|
2880
|
+
if (settings.disabled) {
|
|
2881
|
+
return;
|
|
2882
|
+
}
|
|
2883
|
+
pendingFocusRef.current = focusTarget;
|
|
2884
|
+
setIsOpen((current) => {
|
|
2885
|
+
if (!current) {
|
|
2886
|
+
onOpen?.();
|
|
2887
|
+
}
|
|
2888
|
+
return true;
|
|
2889
|
+
});
|
|
2890
|
+
};
|
|
2891
|
+
const closeDropdown = () => {
|
|
2892
|
+
setIsOpen((current) => {
|
|
2893
|
+
if (current) {
|
|
2894
|
+
onClose?.();
|
|
2895
|
+
}
|
|
2896
|
+
return false;
|
|
2897
|
+
});
|
|
2898
|
+
setActiveOptionIndex(-1);
|
|
2899
|
+
};
|
|
2900
|
+
closeDropdownRef.current = closeDropdown;
|
|
2901
|
+
const toggleDropdown = () => {
|
|
2902
|
+
if (isOpen) {
|
|
2903
|
+
closeDropdown();
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
openDropdown();
|
|
2907
|
+
};
|
|
2908
|
+
const focusAfterSelectionChange = (target = "search") => {
|
|
2909
|
+
if (target === "none") {
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
window.setTimeout(() => {
|
|
2913
|
+
if (target === "search" && isOpen && settings.enableSearchFilter) {
|
|
2914
|
+
searchRef.current?.focus();
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
triggerRef.current?.focus();
|
|
2918
|
+
}, 0);
|
|
2919
|
+
};
|
|
2920
|
+
const selectItem = (item, focusTarget = "search") => {
|
|
2921
|
+
const wasSelected = state.isSelected(item);
|
|
2922
|
+
const willClose = !wasSelected && (settings.singleSelection || settings.closeDropDownOnSelection);
|
|
2923
|
+
state.selectItem(item);
|
|
2924
|
+
focusAfterSelectionChange(willClose ? "trigger" : focusTarget);
|
|
2925
|
+
};
|
|
2926
|
+
const removeItem = (item, focusTarget = "search") => {
|
|
2927
|
+
state.removeItem(item);
|
|
2928
|
+
focusAfterSelectionChange(focusTarget);
|
|
2929
|
+
};
|
|
2930
|
+
const removeLastSelectedItem = () => {
|
|
2931
|
+
state.removeLastSelectedItem();
|
|
2932
|
+
focusAfterSelectionChange();
|
|
2933
|
+
};
|
|
2934
|
+
const selectAll = (items = selectableItems, filteredSelection = false) => {
|
|
2935
|
+
state.selectAll(items, filteredSelection);
|
|
2936
|
+
focusAfterSelectionChange();
|
|
2937
|
+
};
|
|
2938
|
+
const deSelectAll = (items = selectedItems, filteredSelection = false) => {
|
|
2939
|
+
state.deSelectAll(items, filteredSelection);
|
|
2940
|
+
focusAfterSelectionChange();
|
|
2941
|
+
};
|
|
2942
|
+
const clearSelection = () => {
|
|
2943
|
+
state.clearSelection();
|
|
2944
|
+
focusAfterSelectionChange();
|
|
2945
|
+
};
|
|
2946
|
+
const toggleGroup = (groupName, items) => {
|
|
2947
|
+
state.toggleGroup(groupName, items);
|
|
2948
|
+
focusAfterSelectionChange();
|
|
2949
|
+
};
|
|
2950
|
+
const addFilterNewItem = async () => {
|
|
2951
|
+
await state.addFilterNewItem();
|
|
2952
|
+
focusAfterSelectionChange();
|
|
2953
|
+
};
|
|
2954
|
+
const handleListScroll = () => {
|
|
2955
|
+
if (!listboxRef.current || !onScrollToEnd) {
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
const { scrollHeight, scrollTop, clientHeight } = listboxRef.current;
|
|
2959
|
+
if (scrollHeight === lastScrollHeightRef.current && scrollTop + clientHeight < scrollHeight - 12) {
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
if (scrollTop + clientHeight >= scrollHeight - 12) {
|
|
2963
|
+
lastScrollHeightRef.current = scrollHeight;
|
|
2964
|
+
onScrollToEnd({ scrollTop, scrollHeight, clientHeight });
|
|
2965
|
+
}
|
|
2966
|
+
};
|
|
2967
|
+
const handleTriggerKeyDown = (event) => {
|
|
2968
|
+
if (settings.disabled) {
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
if (isSpaceKey2(event.key) && !settings.keyboard.space) {
|
|
2972
|
+
event.preventDefault();
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
if (event.key === "Enter" || isSpaceKey2(event.key)) {
|
|
2976
|
+
event.preventDefault();
|
|
2977
|
+
toggleDropdown();
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
if (settings.keyboard.arrows && event.key === "ArrowDown") {
|
|
2981
|
+
event.preventDefault();
|
|
2982
|
+
if (isOpen) {
|
|
2983
|
+
focusOption(0);
|
|
2984
|
+
} else {
|
|
2985
|
+
openDropdown("first");
|
|
2986
|
+
}
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
if (settings.keyboard.arrows && event.key === "ArrowUp") {
|
|
2990
|
+
event.preventDefault();
|
|
2991
|
+
if (isOpen) {
|
|
2992
|
+
focusOption(selectableOptions.length - 1);
|
|
2993
|
+
} else {
|
|
2994
|
+
openDropdown("last");
|
|
2995
|
+
}
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
if (settings.keyboard.escape && event.key === "Escape" && isOpen) {
|
|
2999
|
+
event.preventDefault();
|
|
3000
|
+
closeDropdown();
|
|
3001
|
+
}
|
|
3002
|
+
};
|
|
3003
|
+
const handleSearchKeyDown = (event) => {
|
|
3004
|
+
if (isSpaceKey2(event.key) && !settings.keyboard.space) {
|
|
3005
|
+
event.preventDefault();
|
|
3006
|
+
return;
|
|
3007
|
+
}
|
|
3008
|
+
if (settings.keyboard.backspaceRemovesLastWhenSearchEmpty && event.key === "Backspace" && !filter && selectedItems.length > 0 && !settings.singleSelection) {
|
|
3009
|
+
event.preventDefault();
|
|
3010
|
+
removeLastSelectedItem();
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
if (settings.keyboard.arrows && event.key === "ArrowDown") {
|
|
3014
|
+
event.preventDefault();
|
|
3015
|
+
focusOption(0);
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
if (settings.keyboard.escape && event.key === "Escape") {
|
|
3019
|
+
event.preventDefault();
|
|
3020
|
+
closeDropdown();
|
|
3021
|
+
triggerRef.current?.focus();
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
const handleOptionKeyDown = (event, option) => {
|
|
3025
|
+
if (isSpaceKey2(event.key) && !settings.keyboard.space) {
|
|
3026
|
+
event.preventDefault();
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
if ((event.key === "Enter" || isSpaceKey2(event.key)) && event.repeat) {
|
|
3030
|
+
event.preventDefault();
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
if (event.key === "Enter" || isSpaceKey2(event.key)) {
|
|
3034
|
+
event.preventDefault();
|
|
3035
|
+
const enabledOptions2 = options.filter((currentOption) => !currentOption.disabled);
|
|
3036
|
+
const currentEnabledIndex2 = enabledOptions2.findIndex((currentOption) => currentOption.id === option.id);
|
|
3037
|
+
const willClose = !state.isSelected(option.item) && (settings.singleSelection || settings.closeDropDownOnSelection);
|
|
3038
|
+
const moveToNextOption = isSpaceKey2(event.key) && settings.keyboard.spaceOptionAction === "toggle-and-next";
|
|
3039
|
+
selectItem(option.item, willClose ? "trigger" : "none");
|
|
3040
|
+
if (!willClose) {
|
|
3041
|
+
focusOptionById(option.id, Math.max(0, currentEnabledIndex2), moveToNextOption);
|
|
3042
|
+
}
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3045
|
+
const enabledOptions = options.filter((currentOption) => !currentOption.disabled);
|
|
3046
|
+
const currentEnabledIndex = enabledOptions.findIndex((currentOption) => currentOption.id === option.id);
|
|
3047
|
+
if (settings.keyboard.arrows && event.key === "ArrowDown") {
|
|
3048
|
+
event.preventDefault();
|
|
3049
|
+
if (currentEnabledIndex < enabledOptions.length - 1) {
|
|
3050
|
+
focusOption(currentEnabledIndex + 1);
|
|
3051
|
+
} else if (settings.lazyLoading) {
|
|
3052
|
+
handleListScroll();
|
|
3053
|
+
}
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
if (settings.keyboard.arrows && event.key === "ArrowUp") {
|
|
3057
|
+
event.preventDefault();
|
|
3058
|
+
if (currentEnabledIndex > 0) {
|
|
3059
|
+
focusOption(currentEnabledIndex - 1);
|
|
3060
|
+
} else if (settings.enableSearchFilter) {
|
|
3061
|
+
searchRef.current?.focus();
|
|
3062
|
+
} else {
|
|
3063
|
+
triggerRef.current?.focus();
|
|
3064
|
+
}
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
if (settings.keyboard.arrows && event.key === "Home") {
|
|
3068
|
+
event.preventDefault();
|
|
3069
|
+
focusOption(0);
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
if (settings.keyboard.arrows && event.key === "End") {
|
|
3073
|
+
event.preventDefault();
|
|
3074
|
+
focusOption(enabledOptions.length - 1);
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
if (settings.keyboard.escape && event.key === "Escape") {
|
|
3078
|
+
event.preventDefault();
|
|
3079
|
+
closeDropdown();
|
|
3080
|
+
triggerRef.current?.focus();
|
|
3081
|
+
}
|
|
3082
|
+
};
|
|
3083
|
+
const handleInlineButtonKeyDown = (event) => {
|
|
3084
|
+
if (isSpaceKey2(event.key) && !settings.keyboard.space) {
|
|
3085
|
+
event.preventDefault();
|
|
3086
|
+
event.stopPropagation();
|
|
3087
|
+
return;
|
|
3088
|
+
}
|
|
3089
|
+
if (event.key === "Enter" || isSpaceKey2(event.key)) {
|
|
3090
|
+
event.stopPropagation();
|
|
3091
|
+
}
|
|
3092
|
+
};
|
|
3093
|
+
const handleRemoveButtonKeyDown = (event, item) => {
|
|
3094
|
+
if (settings.keyboard.deleteRemovesFocusedBadge && (event.key === "Backspace" || event.key === "Delete")) {
|
|
3095
|
+
event.preventDefault();
|
|
3096
|
+
event.stopPropagation();
|
|
3097
|
+
removeItem(item);
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
handleInlineButtonKeyDown(event);
|
|
3101
|
+
};
|
|
3102
|
+
const getOptionFromItem = (optionOrItem) => {
|
|
3103
|
+
if (optionOrItem && typeof optionOrItem === "object" && "item" in optionOrItem && "id" in optionOrItem && "index" in optionOrItem) {
|
|
3104
|
+
return optionOrItem;
|
|
3105
|
+
}
|
|
3106
|
+
const item = optionOrItem;
|
|
3107
|
+
const key = getPrimaryValue(item, settings);
|
|
3108
|
+
const existingOption = options.find((option) => option.key === key);
|
|
3109
|
+
if (existingOption) {
|
|
3110
|
+
return existingOption;
|
|
3111
|
+
}
|
|
3112
|
+
const selected = isSelected(item);
|
|
3113
|
+
return {
|
|
3114
|
+
item,
|
|
3115
|
+
key,
|
|
3116
|
+
id: `${instanceId}-option-manual-${sanitizeId(key)}`,
|
|
3117
|
+
label: getLabel(item, settings),
|
|
3118
|
+
selected,
|
|
3119
|
+
disabled: settings.disabled || isDisabledItem(item) || limitReached && !selected,
|
|
3120
|
+
index: -1,
|
|
3121
|
+
groupName: getGroupName(item, settings) || void 0
|
|
3122
|
+
};
|
|
3123
|
+
};
|
|
3124
|
+
useEffect2(() => {
|
|
3125
|
+
if (!isOpen) {
|
|
3126
|
+
return;
|
|
3127
|
+
}
|
|
3128
|
+
const handlePointerDown = (event) => {
|
|
3129
|
+
const target = event.target;
|
|
3130
|
+
if (!rootRef.current?.contains(target) && !listboxRef.current?.contains(target)) {
|
|
3131
|
+
closeDropdown();
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
const handleKeyDown = (event) => {
|
|
3135
|
+
if (event.key === "Escape" && settings.keyboard.escape) {
|
|
3136
|
+
closeDropdown();
|
|
3137
|
+
}
|
|
3138
|
+
};
|
|
3139
|
+
document.addEventListener("mousedown", handlePointerDown);
|
|
3140
|
+
document.addEventListener("touchstart", handlePointerDown);
|
|
3141
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
3142
|
+
return () => {
|
|
3143
|
+
document.removeEventListener("mousedown", handlePointerDown);
|
|
3144
|
+
document.removeEventListener("touchstart", handlePointerDown);
|
|
3145
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
3146
|
+
};
|
|
3147
|
+
}, [isOpen, settings.keyboard.escape]);
|
|
3148
|
+
useEffect2(() => {
|
|
3149
|
+
lastScrollHeightRef.current = 0;
|
|
3150
|
+
}, [options.length]);
|
|
3151
|
+
useEffect2(() => {
|
|
3152
|
+
if (!isOpen) {
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
const pendingFocus = pendingFocusRef.current;
|
|
3156
|
+
pendingFocusRef.current = "search";
|
|
3157
|
+
window.setTimeout(() => {
|
|
3158
|
+
if (pendingFocus === "first") {
|
|
3159
|
+
focusOption(0);
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
if (pendingFocus === "last") {
|
|
3163
|
+
focusOption(selectableOptions.length - 1);
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
if (settings.enableSearchFilter && settings.searchAutofocus) {
|
|
3167
|
+
searchRef.current?.focus();
|
|
3168
|
+
}
|
|
3169
|
+
}, 0);
|
|
3170
|
+
}, [
|
|
3171
|
+
isOpen,
|
|
3172
|
+
options.length,
|
|
3173
|
+
selectableOptions.length,
|
|
3174
|
+
settings.enableSearchFilter,
|
|
3175
|
+
settings.searchAutofocus
|
|
3176
|
+
]);
|
|
3177
|
+
return {
|
|
3178
|
+
settings,
|
|
3179
|
+
isOpen,
|
|
3180
|
+
filter,
|
|
3181
|
+
setFilter,
|
|
3182
|
+
selectedItems,
|
|
3183
|
+
selectedOptions,
|
|
3184
|
+
options,
|
|
3185
|
+
groups,
|
|
3186
|
+
visibleOptions: options,
|
|
3187
|
+
visibleBadges,
|
|
3188
|
+
hiddenBadgeCount,
|
|
3189
|
+
allFilteredSelected,
|
|
3190
|
+
hasFilteredResults,
|
|
3191
|
+
activeDescendantId,
|
|
3192
|
+
listboxId,
|
|
3193
|
+
label,
|
|
3194
|
+
openDropdown,
|
|
3195
|
+
closeDropdown,
|
|
3196
|
+
toggleDropdown,
|
|
3197
|
+
clearSelection,
|
|
3198
|
+
selectItem,
|
|
3199
|
+
removeItem,
|
|
3200
|
+
selectAll,
|
|
3201
|
+
deSelectAll,
|
|
3202
|
+
toggleGroup,
|
|
3203
|
+
addFilterNewItem,
|
|
3204
|
+
isSelected,
|
|
3205
|
+
isDisabled,
|
|
3206
|
+
getItemLabel: (item) => getLabel(item, settings),
|
|
3207
|
+
getItemKey: (item) => getPrimaryValue(item, settings),
|
|
3208
|
+
getRootProps: (props = {}) => ({
|
|
3209
|
+
...props,
|
|
3210
|
+
ref: (node) => {
|
|
3211
|
+
rootRef.current = node;
|
|
3212
|
+
assignRef(props.ref, node);
|
|
3213
|
+
}
|
|
3214
|
+
}),
|
|
3215
|
+
getTriggerProps: (props = {}) => ({
|
|
3216
|
+
type: "button",
|
|
3217
|
+
...props,
|
|
3218
|
+
ref: (node) => {
|
|
3219
|
+
triggerRef.current = node;
|
|
3220
|
+
assignRef(props.ref, node);
|
|
3221
|
+
},
|
|
3222
|
+
disabled: settings.disabled || props.disabled,
|
|
3223
|
+
role: "combobox",
|
|
3224
|
+
"aria-expanded": isOpen,
|
|
3225
|
+
"aria-haspopup": "listbox",
|
|
3226
|
+
"aria-controls": listboxId,
|
|
3227
|
+
"aria-disabled": settings.disabled || void 0,
|
|
3228
|
+
"aria-activedescendant": activeDescendantId,
|
|
3229
|
+
"aria-label": selectedItems.length ? `${settings.ariaLabel}: ${selectedItems.map((item) => getLabel(item, settings)).join(", ")}` : settings.ariaLabel,
|
|
3230
|
+
onClick: callAll(() => toggleDropdown(), props.onClick),
|
|
3231
|
+
onKeyDown: callAll((event) => {
|
|
3232
|
+
handleTriggerKeyDown(event);
|
|
3233
|
+
}, props.onKeyDown)
|
|
3234
|
+
}),
|
|
3235
|
+
getListboxProps: (props = {}) => ({
|
|
3236
|
+
...props,
|
|
3237
|
+
ref: (node) => {
|
|
3238
|
+
listboxRef.current = node;
|
|
3239
|
+
assignRef(props.ref, node);
|
|
3240
|
+
},
|
|
3241
|
+
id: props.id || listboxId,
|
|
3242
|
+
role: "listbox",
|
|
3243
|
+
"aria-label": props["aria-label"] || settings.listboxAriaLabel,
|
|
3244
|
+
"aria-multiselectable": !settings.singleSelection,
|
|
3245
|
+
onScroll: callAll(() => {
|
|
3246
|
+
if (settings.lazyLoading) {
|
|
3247
|
+
handleListScroll();
|
|
3248
|
+
}
|
|
3249
|
+
}, props.onScroll)
|
|
3250
|
+
}),
|
|
3251
|
+
getOptionProps: (optionOrItem, props = {}) => {
|
|
3252
|
+
const option = getOptionFromItem(optionOrItem);
|
|
3253
|
+
return {
|
|
3254
|
+
...props,
|
|
3255
|
+
ref: (node) => {
|
|
3256
|
+
if (node) {
|
|
3257
|
+
optionRefs.current.set(option.id, node);
|
|
3258
|
+
} else {
|
|
3259
|
+
optionRefs.current.delete(option.id);
|
|
3260
|
+
}
|
|
3261
|
+
assignRef(props.ref, node);
|
|
3262
|
+
},
|
|
3263
|
+
id: props.id || option.id,
|
|
3264
|
+
role: "option",
|
|
3265
|
+
tabIndex: option.disabled || !settings.keyboard.tab ? -1 : props.tabIndex ?? 0,
|
|
3266
|
+
"aria-selected": option.selected,
|
|
3267
|
+
"aria-checked": option.selected,
|
|
3268
|
+
"aria-disabled": option.disabled || void 0,
|
|
3269
|
+
"data-headless-option": "true",
|
|
3270
|
+
onClick: callAll((event) => {
|
|
3271
|
+
if (option.disabled) {
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
const willClose = !state.isSelected(option.item) && (settings.singleSelection || settings.closeDropDownOnSelection);
|
|
3275
|
+
selectItem(option.item, willClose ? "trigger" : "none");
|
|
3276
|
+
if (!willClose) {
|
|
3277
|
+
setActiveOptionIndex(option.index);
|
|
3278
|
+
focusOptionById(option.id, option.index);
|
|
3279
|
+
}
|
|
3280
|
+
}, props.onClick),
|
|
3281
|
+
onFocus: callAll(() => setActiveOptionIndex(option.index), props.onFocus),
|
|
3282
|
+
onKeyDown: callAll((event) => {
|
|
3283
|
+
handleOptionKeyDown(event, option);
|
|
3284
|
+
}, props.onKeyDown)
|
|
3285
|
+
};
|
|
3286
|
+
},
|
|
3287
|
+
getSearchInputProps: (props = {}) => ({
|
|
3288
|
+
type: "search",
|
|
3289
|
+
...props,
|
|
3290
|
+
ref: (node) => {
|
|
3291
|
+
searchRef.current = node;
|
|
3292
|
+
assignRef(props.ref, node);
|
|
3293
|
+
},
|
|
3294
|
+
value: props.value ?? filter,
|
|
3295
|
+
placeholder: props.placeholder ?? settings.searchPlaceholderText,
|
|
3296
|
+
"aria-label": props["aria-label"] ?? settings.searchAriaLabel,
|
|
3297
|
+
onChange: callAll((event) => {
|
|
3298
|
+
setFilter(event.currentTarget.value);
|
|
3299
|
+
}, props.onChange),
|
|
3300
|
+
onKeyDown: callAll((event) => {
|
|
3301
|
+
handleSearchKeyDown(event);
|
|
3302
|
+
}, props.onKeyDown)
|
|
3303
|
+
}),
|
|
3304
|
+
getClearAllButtonProps: (props = {}) => ({
|
|
3305
|
+
type: "button",
|
|
3306
|
+
...props,
|
|
3307
|
+
disabled: settings.disabled || selectedItems.length === 0 || props.disabled,
|
|
3308
|
+
"aria-label": props["aria-label"] ?? settings.clearAllAriaLabel,
|
|
3309
|
+
onKeyDown: callAll((event) => {
|
|
3310
|
+
handleInlineButtonKeyDown(event);
|
|
3311
|
+
}, props.onKeyDown),
|
|
3312
|
+
onClick: callAll(() => clearSelection(), props.onClick)
|
|
3313
|
+
}),
|
|
3314
|
+
getRemoveButtonProps: (item, props = {}) => ({
|
|
3315
|
+
type: "button",
|
|
3316
|
+
...props,
|
|
3317
|
+
disabled: settings.disabled || props.disabled,
|
|
3318
|
+
"aria-label": props["aria-label"] ?? (typeof settings.removeItemAriaLabel === "function" ? settings.removeItemAriaLabel(item) : `${settings.removeItemAriaLabel}: ${getLabel(item, settings)}`),
|
|
3319
|
+
onKeyDown: callAll((event) => {
|
|
3320
|
+
handleRemoveButtonKeyDown(event, item);
|
|
3321
|
+
}, props.onKeyDown),
|
|
3322
|
+
onClick: callAll(() => removeItem(item), props.onClick)
|
|
3323
|
+
})
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
// src/createMultiSelectDropdown.ts
|
|
3328
|
+
function createMultiSelectDropdown() {
|
|
3329
|
+
const TypedDropdown = MultiSelectDropdown;
|
|
3330
|
+
function useDropdown(props) {
|
|
3331
|
+
return useMultiSelectDropdown(props);
|
|
3332
|
+
}
|
|
3333
|
+
function useSelectionState(props) {
|
|
3334
|
+
return useMultiSelectState(props);
|
|
3335
|
+
}
|
|
3336
|
+
function defineSettings(settings) {
|
|
3337
|
+
return settings;
|
|
3338
|
+
}
|
|
3339
|
+
function defineSlots(slots) {
|
|
3340
|
+
return slots;
|
|
3341
|
+
}
|
|
3342
|
+
return {
|
|
3343
|
+
Dropdown: TypedDropdown,
|
|
3344
|
+
MultiSelectDropdown: TypedDropdown,
|
|
3345
|
+
useDropdown,
|
|
3346
|
+
useSelectionState,
|
|
3347
|
+
defineSettings,
|
|
3348
|
+
defineSlots
|
|
3349
|
+
};
|
|
3350
|
+
}
|
|
2009
3351
|
export {
|
|
2010
3352
|
MultiSelectDropdown,
|
|
2011
|
-
ReactMultiSelectDropdown
|
|
3353
|
+
ReactMultiSelectDropdown,
|
|
3354
|
+
createMultiSelectDropdown,
|
|
3355
|
+
useMultiSelectDropdown,
|
|
3356
|
+
useMultiSelectState
|
|
2012
3357
|
};
|