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