@stackline/react-multiselect-dropdown 19.0.1 → 19.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 = `
@@ -108,6 +301,7 @@ var styles = `
108
301
  display: flex;
109
302
  flex: 1 1 auto;
110
303
  min-width: 0;
304
+ min-height: 1.45em;
111
305
  align-items: center;
112
306
  align-content: center;
113
307
  gap: 8px;
@@ -121,6 +315,7 @@ var styles = `
121
315
  align-self: center;
122
316
  justify-content: flex-start;
123
317
  min-width: 0;
318
+ min-height: 1.45em;
124
319
  max-width: 100%;
125
320
  color: var(--rmsd-muted);
126
321
  font-size: 0.95rem;
@@ -204,33 +399,25 @@ var styles = `
204
399
  display: inline-flex;
205
400
  align-items: center;
206
401
  justify-content: center;
207
- min-width: 0;
208
- min-height: 0;
402
+ flex: 0 0 auto;
403
+ min-width: 24px;
404
+ min-height: 20px;
209
405
  color: var(--rmsd-muted);
210
406
  font-size: 0.8rem;
211
407
  font-weight: 600;
408
+ line-height: 1;
409
+ white-space: nowrap;
410
+ text-align: center;
212
411
  }
213
412
 
214
- .rmsd-root.rmsd-has-overflow:not(.skin-classic) .rmsd-trigger {
413
+ .rmsd-root.rmsd-has-overflow .rmsd-trigger {
215
414
  padding-right: 104px;
216
415
  }
217
416
 
218
- .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 {
219
418
  padding-right: 74px;
220
419
  }
221
420
 
222
- .rmsd-root.rmsd-has-overflow:not(.skin-classic) .rmsd-overflow {
223
- position: absolute;
224
- top: 50%;
225
- right: 76px;
226
- transform: translateY(-50%);
227
- z-index: 1;
228
- }
229
-
230
- .rmsd-root.rmsd-has-overflow:not(.skin-classic):not(.rmsd-has-clear) .rmsd-overflow {
231
- right: 42px;
232
- }
233
-
234
421
  .rmsd-actions {
235
422
  position: absolute;
236
423
  top: 50%;
@@ -795,9 +982,12 @@ var styles = `
795
982
 
796
983
  .theme-classic .rmsd-overflow,
797
984
  .skin-classic .rmsd-overflow {
985
+ min-width: 24px;
986
+ min-height: 20px;
798
987
  color: #333333;
799
988
  font-size: 14px;
800
989
  font-weight: 400;
990
+ line-height: 1;
801
991
  }
802
992
 
803
993
  .theme-classic .rmsd-actions,
@@ -1065,7 +1255,7 @@ var styles = `
1065
1255
 
1066
1256
  @media (max-width: 720px) {
1067
1257
  .rmsd-trigger {
1068
- align-items: flex-start;
1258
+ align-items: center;
1069
1259
  padding-right: 54px;
1070
1260
  }
1071
1261
  }
@@ -1083,56 +1273,30 @@ function ensureDropdownStyles() {
1083
1273
  document.head.appendChild(tag);
1084
1274
  }
1085
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
+
1086
1291
  // src/MultiSelectDropdown.tsx
1087
1292
  var import_jsx_runtime = require("react/jsx-runtime");
1088
- var DEFAULT_SETTINGS = {
1089
- singleSelection: false,
1090
- text: "Select",
1091
- enableCheckAll: true,
1092
- selectAllText: "Select All",
1093
- unSelectAllText: "Unselect All",
1094
- filterSelectAllText: "Select filtered",
1095
- filterUnSelectAllText: "Unselect filtered",
1096
- enableFilterSelectAll: true,
1097
- enableSearchFilter: false,
1098
- searchBy: [],
1099
- maxHeight: 300,
1100
- badgeShowLimit: Number.MAX_SAFE_INTEGER,
1101
- classes: "",
1102
- limitSelection: 0,
1103
- disabled: false,
1104
- searchPlaceholderText: "Search",
1105
- groupBy: "",
1106
- showCheckbox: true,
1107
- noDataLabel: "No Data Available",
1108
- searchAutofocus: true,
1109
- lazyLoading: false,
1110
- labelKey: "itemName",
1111
- primaryKey: "id",
1112
- position: "bottom",
1113
- autoPosition: true,
1114
- loading: false,
1115
- selectGroup: false,
1116
- addNewItemOnFilter: false,
1117
- addNewButtonText: "Add",
1118
- escapeToClose: true,
1119
- clearAll: true,
1120
- closeDropDownOnSelection: false,
1121
- tagToBody: false,
1122
- appendToBody: false,
1123
- theme: "",
1124
- skin: "classic",
1125
- ariaLabel: "Multiselect dropdown",
1126
- listboxAriaLabel: "Dropdown options",
1127
- searchAriaLabel: "Search options",
1128
- clearSearchAriaLabel: "Clear search",
1129
- clearAllAriaLabel: "Clear selected options",
1130
- removeItemAriaLabel: "Remove selected option",
1131
- openDropdownAriaLabel: "Open dropdown",
1132
- closeDropdownAriaLabel: "Close dropdown",
1133
- loadingText: "Loading options"
1134
- };
1135
- 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
+ }
1136
1300
  function StacklineIcon({ name, className = "rmsd-icon" }) {
1137
1301
  if (name === "remove") {
1138
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" }) });
@@ -1145,131 +1309,6 @@ function StacklineIcon({ name, className = "rmsd-icon" }) {
1145
1309
  }
1146
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" }) });
1147
1311
  }
1148
- function isPrimitiveItem(item) {
1149
- return typeof item === "string" || typeof item === "number" || typeof item === "boolean";
1150
- }
1151
- function getLabel(item, settings) {
1152
- if (isPrimitiveItem(item)) {
1153
- return String(item);
1154
- }
1155
- const keys = [settings.labelKey, "itemName", "name", "label", "title", "value"].filter(Boolean);
1156
- for (const key of keys) {
1157
- if (key && item[key] != null) {
1158
- return String(item[key]);
1159
- }
1160
- }
1161
- return JSON.stringify(item);
1162
- }
1163
- function getPrimaryValue(item, settings) {
1164
- if (isPrimitiveItem(item)) {
1165
- return String(item);
1166
- }
1167
- const keys = [settings.primaryKey, "id", "value", "key"].filter(Boolean);
1168
- for (const key of keys) {
1169
- if (key && item[key] != null) {
1170
- return String(item[key]);
1171
- }
1172
- }
1173
- return getLabel(item, settings);
1174
- }
1175
- function itemMatchesQuery(item, query, settings) {
1176
- if (!query.trim()) {
1177
- return true;
1178
- }
1179
- const needle = query.trim().toLowerCase();
1180
- const haystack = /* @__PURE__ */ new Set();
1181
- haystack.add(getLabel(item, settings).toLowerCase());
1182
- if (!isPrimitiveItem(item)) {
1183
- const searchKeys = settings.searchBy.length ? settings.searchBy : [settings.labelKey];
1184
- for (const key of searchKeys) {
1185
- if (key && item[key] != null) {
1186
- haystack.add(String(item[key]).toLowerCase());
1187
- }
1188
- }
1189
- }
1190
- for (const value of haystack) {
1191
- if (value.includes(needle)) {
1192
- return true;
1193
- }
1194
- }
1195
- return false;
1196
- }
1197
- function getGroupName(item, settings) {
1198
- if (!settings.groupBy) {
1199
- return "";
1200
- }
1201
- if (typeof settings.groupBy === "function") {
1202
- return settings.groupBy(item);
1203
- }
1204
- if (!isPrimitiveItem(item)) {
1205
- const groupKey = settings.groupBy;
1206
- const objectItem = item;
1207
- if (groupKey in objectItem) {
1208
- return String(objectItem[groupKey] ?? "");
1209
- }
1210
- }
1211
- return "";
1212
- }
1213
- function mergeUniqueItems(base, extra, settings) {
1214
- const bucket = /* @__PURE__ */ new Map();
1215
- for (const item of [...base, ...extra]) {
1216
- bucket.set(getPrimaryValue(item, settings), item);
1217
- }
1218
- return Array.from(bucket.values());
1219
- }
1220
- function createItemFromQuery(query, settings, sample) {
1221
- if (sample && !isPrimitiveItem(sample)) {
1222
- return {
1223
- [settings.primaryKey]: query.toLowerCase().replace(/\s+/g, "-"),
1224
- [settings.labelKey]: query
1225
- };
1226
- }
1227
- return query;
1228
- }
1229
- function isDisabledItem(item) {
1230
- return !isPrimitiveItem(item) && Boolean(item.disabled);
1231
- }
1232
- function buildGroups(items, settings) {
1233
- if (!settings.groupBy) {
1234
- return [];
1235
- }
1236
- const map = /* @__PURE__ */ new Map();
1237
- for (const item of items) {
1238
- const groupName = getGroupName(item, settings) || "Ungrouped";
1239
- const current = map.get(groupName) || [];
1240
- current.push(item);
1241
- map.set(groupName, current);
1242
- }
1243
- return Array.from(map.entries()).map(([name, groupedItems]) => ({
1244
- name,
1245
- items: groupedItems
1246
- }));
1247
- }
1248
- function useControllableSelection(controlledValue, defaultValue, onChange) {
1249
- const [internalValue, setInternalValue] = (0, import_react.useState)(defaultValue ?? []);
1250
- const isControlled = controlledValue !== void 0;
1251
- const value = isControlled ? controlledValue : internalValue;
1252
- const setValue = (nextValue) => {
1253
- if (!isControlled) {
1254
- setInternalValue(nextValue);
1255
- }
1256
- onChange?.(nextValue);
1257
- };
1258
- return [value, setValue];
1259
- }
1260
- function sanitizeId(value) {
1261
- return value.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 56) || "option";
1262
- }
1263
- function normalizeSkinName(value) {
1264
- return sanitizeId(value.toLowerCase()) || "classic";
1265
- }
1266
- function getVisibleBadgeLimit(selectedCount, rawLimit) {
1267
- if (!Number.isFinite(rawLimit)) {
1268
- return selectedCount;
1269
- }
1270
- const limit = Math.max(0, Math.floor(rawLimit));
1271
- return Math.min(selectedCount, limit);
1272
- }
1273
1312
  function InnerMultiSelectDropdown({
1274
1313
  data,
1275
1314
  settings: incomingSettings,
@@ -1294,41 +1333,43 @@ function InnerMultiSelectDropdown({
1294
1333
  renderItem,
1295
1334
  renderBadge,
1296
1335
  renderSearch,
1297
- renderEmptyState
1336
+ renderEmptyState,
1337
+ slots
1298
1338
  }, ref) {
1299
1339
  ensureDropdownStyles();
1300
- const settings = { ...DEFAULT_SETTINGS, ...incomingSettings };
1340
+ const settings = (0, import_react2.useMemo)(() => resolveDropdownSettings(incomingSettings), [incomingSettings]);
1301
1341
  const [selectedItems, setSelectedItems] = useControllableSelection(
1302
1342
  controlledSelectedItems,
1303
1343
  defaultSelectedItems,
1304
1344
  onChange
1305
1345
  );
1306
- const [isOpen, setIsOpen] = (0, import_react.useState)(false);
1307
- const [filter, setFilter] = (0, import_react.useState)("");
1308
- const [addedItems, setAddedItems] = (0, import_react.useState)([]);
1309
- const [activeDescendantId, setActiveDescendantId] = (0, import_react.useState)(null);
1310
- const [bodyMenuStyle, setBodyMenuStyle] = (0, import_react.useState)();
1311
- const [bodyListMaxHeight, setBodyListMaxHeight] = (0, import_react.useState)();
1312
- 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)(
1313
1353
  settings.position === "top" ? "top" : "bottom"
1314
1354
  );
1315
- const rootRef = (0, import_react.useRef)(null);
1316
- const triggerRef = (0, import_react.useRef)(null);
1317
- const menuRef = (0, import_react.useRef)(null);
1318
- const searchRef = (0, import_react.useRef)(null);
1319
- const listRef = (0, import_react.useRef)(null);
1320
- const lastScrollHeightRef = (0, import_react.useRef)(0);
1321
- const pendingFocusRef = (0, import_react.useRef)(null);
1322
- const instanceIdRef = (0, import_react.useRef)(`rmsd-${Math.random().toString(36).slice(2)}`);
1323
- const allItems = (0, import_react.useMemo)(
1324
- () => mergeUniqueItems(data, addedItems, settings),
1325
- [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]
1326
1367
  );
1327
- const filteredItems = (0, import_react.useMemo)(
1368
+ const filteredItems = (0, import_react2.useMemo)(
1328
1369
  () => allItems.filter((item) => itemMatchesQuery(item, filter, settings)),
1329
1370
  [allItems, filter, settings]
1330
1371
  );
1331
- const groupedItems = (0, import_react.useMemo)(() => buildGroups(filteredItems, settings), [filteredItems, settings]);
1372
+ const groupedItems = (0, import_react2.useMemo)(() => buildGroups(filteredItems, settings), [filteredItems, settings]);
1332
1373
  const listboxId = `${instanceIdRef.current}-listbox`;
1333
1374
  const getOptionId = (item, index, prefix) => `${instanceIdRef.current}-${prefix}-${index}-${sanitizeId(getPrimaryValue(item, settings))}`;
1334
1375
  const isSelected = (item) => selectedItems.some(
@@ -1353,6 +1394,68 @@ function InnerMultiSelectDropdown({
1353
1394
  };
1354
1395
  const focusFirstOption = () => focusOptionByIndex(0);
1355
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
+ };
1356
1459
  const updateSelection = (nextItems) => {
1357
1460
  setSelectedItems(nextItems);
1358
1461
  };
@@ -1386,6 +1489,7 @@ function InnerMultiSelectDropdown({
1386
1489
  const previousItems = selectedItems;
1387
1490
  updateSelection([]);
1388
1491
  onDeSelectAll?.(previousItems);
1492
+ focusAfterSelectionChange();
1389
1493
  };
1390
1494
  const toggleDropdown = () => {
1391
1495
  if (isOpen) {
@@ -1394,25 +1498,34 @@ function InnerMultiSelectDropdown({
1394
1498
  openDropdown("search");
1395
1499
  }
1396
1500
  };
1397
- const removeItem = (item) => {
1501
+ const removeItem = (item, focusTarget = "search") => {
1398
1502
  const nextItems = selectedItems.filter(
1399
1503
  (selectedItem) => getPrimaryValue(selectedItem, settings) !== getPrimaryValue(item, settings)
1400
1504
  );
1401
1505
  updateSelection(nextItems);
1402
1506
  onDeSelect?.(item);
1507
+ focusAfterSelectionChange(focusTarget);
1403
1508
  };
1404
- 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") => {
1405
1517
  if (settings.disabled || isDisabledItem(item)) {
1406
1518
  return;
1407
1519
  }
1408
1520
  if (isSelected(item)) {
1409
- removeItem(item);
1521
+ removeItem(item, focusTarget);
1410
1522
  return;
1411
1523
  }
1412
1524
  if (settings.singleSelection) {
1413
1525
  updateSelection([item]);
1414
1526
  onSelect?.(item);
1415
1527
  closeDropdown(true);
1528
+ focusAfterSelectionChange("trigger");
1416
1529
  return;
1417
1530
  }
1418
1531
  if (settings.limitSelection && selectedItems.length >= settings.limitSelection) {
@@ -1423,7 +1536,10 @@ function InnerMultiSelectDropdown({
1423
1536
  onSelect?.(item);
1424
1537
  if (settings.closeDropDownOnSelection) {
1425
1538
  closeDropdown(true);
1539
+ focusAfterSelectionChange("trigger");
1540
+ return;
1426
1541
  }
1542
+ focusAfterSelectionChange(focusTarget);
1427
1543
  };
1428
1544
  const selectAllItems = (items, filteredSelection = false) => {
1429
1545
  if (settings.singleSelection) {
@@ -1439,6 +1555,7 @@ function InnerMultiSelectDropdown({
1439
1555
  } else {
1440
1556
  onSelectAll?.(nextItems);
1441
1557
  }
1558
+ focusAfterSelectionChange();
1442
1559
  };
1443
1560
  const deSelectAllItems = (items, filteredSelection = false) => {
1444
1561
  const ids = new Set(items.map((item) => getPrimaryValue(item, settings)));
@@ -1449,13 +1566,19 @@ function InnerMultiSelectDropdown({
1449
1566
  } else {
1450
1567
  onDeSelectAll?.(items);
1451
1568
  }
1569
+ focusAfterSelectionChange();
1452
1570
  };
1453
1571
  const handleAddFilterNewItem = async () => {
1454
1572
  const query = filter.trim();
1455
1573
  if (!query) {
1456
1574
  return;
1457
1575
  }
1576
+ const requestId = addRequestIdRef.current + 1;
1577
+ addRequestIdRef.current = requestId;
1458
1578
  const result = await onAddFilterNewItem?.(query);
1579
+ if (requestId !== addRequestIdRef.current) {
1580
+ return;
1581
+ }
1459
1582
  const nextItem = result === void 0 ? createItemFromQuery(query, settings, data[0]) : result;
1460
1583
  setAddedItems((currentItems) => mergeUniqueItems(currentItems, [nextItem], settings));
1461
1584
  if (settings.singleSelection) {
@@ -1464,6 +1587,7 @@ function InnerMultiSelectDropdown({
1464
1587
  updateSelection(mergeUniqueItems(selectedItems, [nextItem], settings));
1465
1588
  }
1466
1589
  setFilter("");
1590
+ focusAfterSelectionChange();
1467
1591
  };
1468
1592
  const toggleGroup = (groupName, items) => {
1469
1593
  const groupItems = items.filter((item) => !isDisabledItem(item));
@@ -1471,10 +1595,12 @@ function InnerMultiSelectDropdown({
1471
1595
  if (allSelected) {
1472
1596
  deSelectAllItems(groupItems, false);
1473
1597
  onGroupDeSelect?.(groupName, groupItems);
1598
+ focusAfterSelectionChange();
1474
1599
  return;
1475
1600
  }
1476
1601
  selectAllItems(groupItems, false);
1477
1602
  onGroupSelect?.(groupName, groupItems);
1603
+ focusAfterSelectionChange();
1478
1604
  };
1479
1605
  const handleListScroll = () => {
1480
1606
  if (!listRef.current || !onScrollToEnd) {
@@ -1503,7 +1629,7 @@ function InnerMultiSelectDropdown({
1503
1629
  }
1504
1630
  return "bottom";
1505
1631
  };
1506
- (0, import_react.useEffect)(() => {
1632
+ (0, import_react2.useEffect)(() => {
1507
1633
  if (!isOpen) {
1508
1634
  return;
1509
1635
  }
@@ -1514,7 +1640,7 @@ function InnerMultiSelectDropdown({
1514
1640
  }
1515
1641
  };
1516
1642
  const handleKeyDown = (event) => {
1517
- if (event.key === "Escape" && settings.escapeToClose) {
1643
+ if (event.key === "Escape" && settings.keyboard.escape) {
1518
1644
  closeDropdown(true);
1519
1645
  }
1520
1646
  };
@@ -1526,7 +1652,7 @@ function InnerMultiSelectDropdown({
1526
1652
  document.removeEventListener("touchstart", handlePointerDown);
1527
1653
  document.removeEventListener("keydown", handleKeyDown);
1528
1654
  };
1529
- }, [isOpen, settings.escapeToClose]);
1655
+ }, [isOpen, settings.keyboard.escape]);
1530
1656
  const updateBodyMenuPosition = () => {
1531
1657
  if (!shouldAppendToBody || !triggerRef.current || typeof window === "undefined") {
1532
1658
  return;
@@ -1586,7 +1712,7 @@ function InnerMultiSelectDropdown({
1586
1712
  selectedItems.length,
1587
1713
  filter
1588
1714
  ]);
1589
- (0, import_react.useEffect)(() => {
1715
+ (0, import_react2.useEffect)(() => {
1590
1716
  if (!isOpen || !shouldAppendToBody || typeof window === "undefined") {
1591
1717
  return;
1592
1718
  }
@@ -1612,7 +1738,7 @@ function InnerMultiSelectDropdown({
1612
1738
  filteredItems.length,
1613
1739
  selectedItems.length
1614
1740
  ]);
1615
- (0, import_react.useEffect)(() => {
1741
+ (0, import_react2.useEffect)(() => {
1616
1742
  if (!isOpen) {
1617
1743
  return;
1618
1744
  }
@@ -1632,10 +1758,10 @@ function InnerMultiSelectDropdown({
1632
1758
  }
1633
1759
  }, 0);
1634
1760
  }, [isOpen, filteredItems.length, settings.enableSearchFilter, settings.searchAutofocus]);
1635
- (0, import_react.useEffect)(() => {
1761
+ (0, import_react2.useEffect)(() => {
1636
1762
  lastScrollHeightRef.current = 0;
1637
1763
  }, [filteredItems.length]);
1638
- (0, import_react.useImperativeHandle)(
1764
+ (0, import_react2.useImperativeHandle)(
1639
1765
  ref,
1640
1766
  () => ({
1641
1767
  openDropdown: () => openDropdown("search"),
@@ -1685,20 +1811,38 @@ function InnerMultiSelectDropdown({
1685
1811
  return `${settings.ariaLabel}: ${selectedItems.map((item) => getLabel(item, settings)).join(", ")}`;
1686
1812
  };
1687
1813
  const stopInlineKey = (event) => {
1688
- if (event.key === "Enter" || event.key === " ") {
1814
+ if (isSpaceKey(event.key) && !settings.keyboard.space) {
1815
+ event.preventDefault();
1689
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;
1690
1829
  }
1830
+ stopInlineKey(event);
1691
1831
  };
1692
1832
  const handleTriggerKeyDown = (event) => {
1693
1833
  if (settings.disabled) {
1694
1834
  return;
1695
1835
  }
1696
- 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)) {
1697
1841
  event.preventDefault();
1698
1842
  toggleDropdown();
1699
1843
  return;
1700
1844
  }
1701
- if (event.key === "ArrowDown") {
1845
+ if (settings.keyboard.arrows && event.key === "ArrowDown") {
1702
1846
  event.preventDefault();
1703
1847
  if (!isOpen) {
1704
1848
  openDropdown("first");
@@ -1707,7 +1851,7 @@ function InnerMultiSelectDropdown({
1707
1851
  }
1708
1852
  return;
1709
1853
  }
1710
- if (event.key === "ArrowUp") {
1854
+ if (settings.keyboard.arrows && event.key === "ArrowUp") {
1711
1855
  event.preventDefault();
1712
1856
  if (!isOpen) {
1713
1857
  openDropdown("last");
@@ -1716,48 +1860,84 @@ function InnerMultiSelectDropdown({
1716
1860
  }
1717
1861
  return;
1718
1862
  }
1719
- if (event.key === "Escape" && isOpen) {
1863
+ if (settings.keyboard.escape && event.key === "Escape" && isOpen) {
1720
1864
  event.preventDefault();
1721
1865
  closeDropdown(true);
1722
1866
  }
1723
1867
  };
1724
1868
  const handleArrowButtonKeyDown = (event) => {
1725
- 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)) {
1726
1875
  event.preventDefault();
1727
1876
  event.stopPropagation();
1728
1877
  toggleDropdown();
1729
1878
  return;
1730
1879
  }
1731
- if (event.key === "ArrowDown") {
1880
+ if (settings.keyboard.arrows && event.key === "ArrowDown") {
1732
1881
  event.preventDefault();
1733
1882
  event.stopPropagation();
1734
- openDropdown("first");
1883
+ if (isOpen) {
1884
+ focusFirstOption();
1885
+ } else {
1886
+ openDropdown("first");
1887
+ }
1735
1888
  return;
1736
1889
  }
1737
- if (event.key === "ArrowUp") {
1890
+ if (settings.keyboard.arrows && event.key === "ArrowUp") {
1738
1891
  event.preventDefault();
1739
1892
  event.stopPropagation();
1740
- openDropdown("last");
1893
+ if (isOpen) {
1894
+ focusLastOption();
1895
+ } else {
1896
+ openDropdown("last");
1897
+ }
1741
1898
  }
1742
1899
  };
1743
1900
  const handleSearchKeyDown = (event) => {
1744
- 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") {
1745
1911
  event.preventDefault();
1746
1912
  focusFirstOption();
1747
1913
  return;
1748
1914
  }
1749
- if (event.key === "Escape" && settings.escapeToClose) {
1915
+ if (settings.keyboard.escape && event.key === "Escape") {
1750
1916
  event.preventDefault();
1751
1917
  closeDropdown(true);
1752
1918
  }
1753
1919
  };
1754
1920
  const handleOptionKeyDown = (event, item, optionIndex) => {
1755
- 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)) {
1756
1930
  event.preventDefault();
1757
- 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
+ }
1758
1938
  return;
1759
1939
  }
1760
- if (event.key === "ArrowDown") {
1940
+ if (settings.keyboard.arrows && event.key === "ArrowDown") {
1761
1941
  event.preventDefault();
1762
1942
  const nextIndex = optionIndex + 1;
1763
1943
  const options = getOptionElements();
@@ -1768,7 +1948,7 @@ function InnerMultiSelectDropdown({
1768
1948
  }
1769
1949
  return;
1770
1950
  }
1771
- if (event.key === "ArrowUp") {
1951
+ if (settings.keyboard.arrows && event.key === "ArrowUp") {
1772
1952
  event.preventDefault();
1773
1953
  if (optionIndex > 0) {
1774
1954
  focusOptionByIndex(optionIndex - 1);
@@ -1779,21 +1959,54 @@ function InnerMultiSelectDropdown({
1779
1959
  }
1780
1960
  return;
1781
1961
  }
1782
- if (event.key === "Home") {
1962
+ if (settings.keyboard.arrows && event.key === "Home") {
1783
1963
  event.preventDefault();
1784
1964
  focusFirstOption();
1785
1965
  return;
1786
1966
  }
1787
- if (event.key === "End") {
1967
+ if (settings.keyboard.arrows && event.key === "End") {
1788
1968
  event.preventDefault();
1789
1969
  focusLastOption();
1790
1970
  return;
1791
1971
  }
1792
- if (event.key === "Escape" && settings.escapeToClose) {
1972
+ if (settings.keyboard.escape && event.key === "Escape") {
1793
1973
  event.preventDefault();
1794
1974
  closeDropdown(true);
1795
1975
  }
1796
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
+ };
1797
2010
  const renderItemNode = (item) => {
1798
2011
  const context = {
1799
2012
  item,
@@ -1809,49 +2022,122 @@ function InnerMultiSelectDropdown({
1809
2022
  !isPrimitiveItem(item) && item.caption ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rmsd-option-hint", children: String(item.caption) }) : null
1810
2023
  ] });
1811
2024
  };
1812
- const renderBadgeNode = (item) => {
2025
+ const renderBadgeLabel = (item) => {
2026
+ const label = getLabel(item, settings);
1813
2027
  const context = {
1814
2028
  item,
1815
- label: getLabel(item, settings),
2029
+ label,
1816
2030
  selected: true,
1817
2031
  disabled: settings.disabled || isDisabledItem(item),
1818
2032
  query: filter,
1819
2033
  toggle: () => selectItem(item),
1820
2034
  remove: () => removeItem(item)
1821
2035
  };
1822
- 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
+ );
1823
2091
  };
1824
- let optionCursor = -1;
2092
+ let enabledOptionCursor = -1;
1825
2093
  const renderOption = (item, prefix, localIndex) => {
1826
2094
  const selected = isSelected(item);
1827
2095
  const disabled = settings.disabled || isDisabledItem(item) || limitReached && !selected;
1828
- optionCursor += 1;
1829
- const optionIndex = optionCursor;
2096
+ const optionIndex = disabled ? -1 : enabledOptionCursor += 1;
1830
2097
  const optionId = getOptionId(item, localIndex, prefix);
1831
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1832
- "div",
1833
- {
1834
- id: optionId,
1835
- className: `rmsd-option${selected ? " rmsd-selected" : ""}${disabled ? " rmsd-disabled" : ""}`,
1836
- role: "option",
1837
- "aria-selected": selected,
1838
- "aria-disabled": disabled,
1839
- tabIndex: disabled ? -1 : 0,
1840
- "data-rmsd-option": "true",
1841
- onFocus: () => setActiveDescendantId(optionId),
1842
- onClick: () => {
1843
- if (!disabled) {
1844
- selectItem(item);
1845
- }
1846
- },
1847
- onKeyDown: (event) => handleOptionKeyDown(event, item, optionIndex),
1848
- children: [
1849
- settings.showCheckbox ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rmsd-checkbox", "data-checked": selected, "aria-hidden": "true" }) : null,
1850
- renderItemNode(item)
1851
- ]
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
+ }
1852
2128
  },
1853
- `${prefix}-${getPrimaryValue(item, settings)}-${localIndex}`
1854
- );
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);
1855
2141
  };
1856
2142
  const handleTriggerClick = (event) => {
1857
2143
  if (event.target.closest("button")) {
@@ -1859,174 +2145,1229 @@ function InnerMultiSelectDropdown({
1859
2145
  }
1860
2146
  toggleDropdown();
1861
2147
  };
1862
- const menu = isOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1863
- "div",
1864
- {
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 = {
1865
2363
  ref: menuRef,
1866
2364
  className: `rmsd-menu rmsd-${effectivePosition} skin-${skinName} theme-${skinName}${skinFallbackClass ? ` ${skinFallbackClass}` : ""}${shouldAppendToBody ? " rmsd-body-overlay" : ""}`,
1867
2365
  style: shouldAppendToBody ? bodyMenuStyle : void 0,
1868
2366
  onMouseDown: (event) => event.stopPropagation(),
1869
- onTouchStart: (event) => event.stopPropagation(),
1870
- children: [
1871
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rmsd-toolbar", children: [
1872
- hasBulkActions ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rmsd-bulk-actions", children: [
1873
- settings.enableCheckAll && !settings.singleSelection ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1874
- "button",
1875
- {
1876
- type: "button",
1877
- className: "rmsd-inline-button rmsd-select-all-button",
1878
- onClick: () => allFilteredSelected ? deSelectAllItems(selectableItems, Boolean(filter.trim())) : selectAllItems(selectableItems, Boolean(filter.trim())),
1879
- disabled: settings.disabled || selectableItems.length === 0,
1880
- children: [
1881
- settings.showCheckbox ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rmsd-checkbox", "data-checked": allFilteredSelected, "aria-hidden": "true" }) : null,
1882
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: allFilteredSelected ? filter.trim() ? settings.filterUnSelectAllText : settings.unSelectAllText : filter.trim() ? settings.filterSelectAllText : settings.selectAllText })
1883
- ]
1884
- }
1885
- ) : null,
1886
- settings.addNewItemOnFilter && filter.trim() ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "rmsd-inline-button rmsd-add-button", onClick: handleAddFilterNewItem, children: [
1887
- settings.addNewButtonText,
1888
- ' "',
1889
- filter.trim(),
1890
- '"'
1891
- ] }) : null
1892
- ] }) : null,
1893
- settings.enableSearchFilter ? renderSearch ? renderSearch({ query: filter, setQuery: setFilter, closeDropdown: () => closeDropdown() }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rmsd-search-shell", children: [
1894
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StacklineIcon, { name: "search", className: "rmsd-search-icon" }),
1895
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1896
- "input",
1897
- {
1898
- ref: searchRef,
1899
- className: "rmsd-search-input",
1900
- value: filter,
1901
- onChange: (event) => setFilter(event.target.value),
1902
- onKeyDown: handleSearchKeyDown,
1903
- placeholder: settings.searchPlaceholderText,
1904
- "aria-label": settings.searchAriaLabel
1905
- }
1906
- ),
1907
- filter ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1908
- "button",
1909
- {
1910
- type: "button",
1911
- className: "rmsd-search-clear",
1912
- "aria-label": settings.clearSearchAriaLabel,
1913
- onKeyDown: stopInlineKey,
1914
- onClick: () => setFilter(""),
1915
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StacklineIcon, { name: "clear" })
1916
- }
1917
- ) : null
1918
- ] }) : null
1919
- ] }),
1920
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1921
- "div",
1922
- {
1923
- className: "rmsd-list",
1924
- ref: listRef,
1925
- style: { maxHeight: shouldAppendToBody ? bodyListMaxHeight ?? settings.maxHeight : settings.maxHeight },
1926
- onScroll: settings.lazyLoading ? handleListScroll : void 0,
1927
- id: listboxId,
1928
- role: "listbox",
1929
- "aria-label": settings.listboxAriaLabel,
1930
- "aria-multiselectable": !settings.singleSelection,
1931
- 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: [
1932
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rmsd-group-header", children: [
1933
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
1934
- group.name,
1935
- " \xB7 ",
1936
- group.items.length
1937
- ] }),
1938
- 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
1939
- ] }),
1940
- group.items.map((item, index) => renderOption(item, `group-${groupIndex}`, index))
1941
- ] }, 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 })
1942
- }
1943
- )
1944
- ]
1945
- }
1946
- ) : null;
1947
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: rootClassName, style, ref: rootRef, "data-open": isOpen, children: [
1948
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1949
- "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,
1950
2376
  {
1951
- ref: triggerRef,
1952
- className: `rmsd-trigger${settings.disabled ? " rmsd-disabled" : ""}`,
1953
- onClick: handleTriggerClick,
1954
- onKeyDown: handleTriggerKeyDown,
1955
- tabIndex: settings.disabled ? -1 : 0,
1956
- role: "combobox",
1957
- "aria-expanded": isOpen,
1958
- "aria-haspopup": "listbox",
1959
- "aria-controls": listboxId,
1960
- "aria-disabled": settings.disabled,
1961
- "aria-activedescendant": activeDescendantId || void 0,
1962
- "aria-label": getTriggerAriaLabel(),
1963
- children: [
1964
- /* @__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: [
1965
- /* @__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: [
1966
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rmsd-badge-label", children: renderBadgeNode(item) }),
1967
- !settings.disabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1968
- "button",
1969
- {
1970
- type: "button",
1971
- className: "rmsd-badge-remove",
1972
- "aria-label": getRemoveItemAriaLabel(item),
1973
- onKeyDown: stopInlineKey,
1974
- onClick: (event) => {
1975
- event.stopPropagation();
1976
- removeItem(item);
1977
- },
1978
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StacklineIcon, { name: "remove" })
1979
- }
1980
- ) : null
1981
- ] }, getPrimaryValue(item, settings))) }),
1982
- hiddenBadgeCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rmsd-overflow", children: [
1983
- "+",
1984
- hiddenBadgeCount
1985
- ] }) : null
1986
- ] }) }),
1987
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rmsd-actions", children: [
1988
- settings.clearAll && selectedItems.length > 0 && !settings.disabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1989
- "button",
1990
- {
1991
- type: "button",
1992
- className: "rmsd-clear",
1993
- "aria-label": settings.clearAllAriaLabel,
1994
- onKeyDown: stopInlineKey,
1995
- onClick: (event) => {
1996
- event.stopPropagation();
1997
- clearSelection();
1998
- },
1999
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StacklineIcon, { name: "remove" })
2000
- }
2001
- ) : null,
2002
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2003
- "button",
2004
- {
2005
- type: "button",
2006
- className: "rmsd-arrow-button",
2007
- disabled: settings.disabled,
2008
- "aria-label": isOpen ? settings.closeDropdownAriaLabel : settings.openDropdownAriaLabel,
2009
- "aria-expanded": isOpen,
2010
- "aria-controls": listboxId,
2011
- onKeyDown: handleArrowButtonKeyDown,
2012
- onClick: (event) => {
2013
- event.stopPropagation();
2014
- toggleDropdown();
2015
- },
2016
- 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" }) })
2017
- }
2018
- )
2019
- ] })
2020
- ]
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();
2021
2430
  }
2022
- ),
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(),
2023
2501
  shouldAppendToBody && menu && typeof document !== "undefined" ? (0, import_react_dom.createPortal)(menu, document.body) : menu
2024
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
+ );
2025
2514
  }
2026
- var ReactMultiSelectDropdown = (0, import_react.forwardRef)(InnerMultiSelectDropdown);
2515
+ var ReactMultiSelectDropdown = (0, import_react2.forwardRef)(InnerMultiSelectDropdown);
2027
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
+ }
2028
3366
  // Annotate the CommonJS export names for ESM import in node:
2029
3367
  0 && (module.exports = {
2030
3368
  MultiSelectDropdown,
2031
- ReactMultiSelectDropdown
3369
+ ReactMultiSelectDropdown,
3370
+ createMultiSelectDropdown,
3371
+ useMultiSelectDropdown,
3372
+ useMultiSelectState
2032
3373
  });