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