@stackline/react-multiselect-dropdown 17.0.0 → 17.0.1

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