@stackline/react-multiselect-dropdown 19.0.1 → 19.1.0

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