@stackline/react-multiselect-dropdown 18.0.0 → 18.0.1

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