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