@stackline/react-multiselect-dropdown 19.0.2 → 19.1.1

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