clayui-multi-select 3.165.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/lib/cjs/Labels.js +349 -0
- package/lib/cjs/MultiSelect.js +326 -0
- package/lib/cjs/index.js +27 -0
- package/lib/cjs/types.js +16 -0
- package/lib/esm/Labels.js +324 -0
- package/lib/esm/MultiSelect.js +306 -0
- package/lib/esm/index.js +7 -0
- package/lib/esm/types.js +0 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# `@clayui/multi-select`
|
|
2
|
+
|
|
3
|
+
Multi select is the field type that allows writing text to create "tags" that are represented in the shape of labels.
|
|
4
|
+
|
|
5
|
+
- [Documentation](https://clayui.com/docs/components/multi-select.html)
|
|
6
|
+
- [Changelog](./CHANGELOG.md)
|
|
7
|
+
- [Breaking change schedule](./BREAKING.md)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Run `yarn`
|
|
12
|
+
|
|
13
|
+
```shell
|
|
14
|
+
yarn add @clayui/multi-select
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Contribute
|
|
18
|
+
|
|
19
|
+
We'd love to get contributions from you! Please, check our [Contributing Guidelines](https://github.com/liferay/clay/blob/master/CONTRIBUTING.md) to see how you can help us improve.
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var Labels_exports = {};
|
|
30
|
+
__export(Labels_exports, {
|
|
31
|
+
Labels: () => Labels
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(Labels_exports);
|
|
34
|
+
var import_form = require("@clayui/form");
|
|
35
|
+
var import_icon = __toESM(require("@clayui/icon"));
|
|
36
|
+
var import_label = __toESM(require("@clayui/label"));
|
|
37
|
+
var import_shared = require("@clayui/shared");
|
|
38
|
+
var import_react = __toESM(require("react"));
|
|
39
|
+
const DELIMITER_KEYS = ["Enter", ","];
|
|
40
|
+
const KeysNavigation = [import_shared.Keys.Left, import_shared.Keys.Right, import_shared.Keys.Up, import_shared.Keys.Down];
|
|
41
|
+
const KeysSides = [import_shared.Keys.Left, import_shared.Keys.Right];
|
|
42
|
+
let counterUid = 0;
|
|
43
|
+
const Labels = import_react.default.forwardRef(
|
|
44
|
+
function Labels2({
|
|
45
|
+
allowDuplicateValues = true,
|
|
46
|
+
allowsCustomLabel,
|
|
47
|
+
"aria-activedescendant": activeDescendant,
|
|
48
|
+
ariaDescriptionId,
|
|
49
|
+
closeButtonAriaLabel,
|
|
50
|
+
disabled,
|
|
51
|
+
inputName,
|
|
52
|
+
"insetAfter": _,
|
|
53
|
+
labels,
|
|
54
|
+
lastChangesRef,
|
|
55
|
+
locator,
|
|
56
|
+
onChange,
|
|
57
|
+
onFocusChange,
|
|
58
|
+
onLabelsChange,
|
|
59
|
+
placeholder,
|
|
60
|
+
spritemap,
|
|
61
|
+
suggestionList,
|
|
62
|
+
value,
|
|
63
|
+
...otherProps
|
|
64
|
+
}, ref) {
|
|
65
|
+
const inputRef = (0, import_react.useRef)(null);
|
|
66
|
+
const labelsRef = (0, import_react.useRef)(null);
|
|
67
|
+
const lastItemRef = (0, import_react.useRef)(null);
|
|
68
|
+
const [isLabelFocused, setIsLabelFocused] = (0, import_react.useState)(
|
|
69
|
+
null
|
|
70
|
+
);
|
|
71
|
+
const [lastFocusedItem, setLastFocusedItem] = (0, import_react.useState)(
|
|
72
|
+
null
|
|
73
|
+
);
|
|
74
|
+
const inputElementRef = ref || inputRef;
|
|
75
|
+
const labelId = (0, import_shared.useId)();
|
|
76
|
+
const getSourceItemByLabel = (label) => {
|
|
77
|
+
return suggestionList.find(
|
|
78
|
+
(item) => (0, import_shared.getLocatorValue)({
|
|
79
|
+
item,
|
|
80
|
+
locator: locator.label
|
|
81
|
+
}) === label
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
const getNewItem = (value2) => {
|
|
85
|
+
counterUid++;
|
|
86
|
+
const labelKey = typeof locator.label === "function" ? "label" : locator.label;
|
|
87
|
+
const valueKey = typeof locator.value === "function" ? "value" : locator.value;
|
|
88
|
+
return getSourceItemByLabel(value2) || {
|
|
89
|
+
key: `key_${counterUid}`,
|
|
90
|
+
[labelKey]: value2,
|
|
91
|
+
[valueKey]: value2
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
const onRemove = (0, import_react.useCallback)(
|
|
95
|
+
(label, index) => {
|
|
96
|
+
if (labelsRef.current) {
|
|
97
|
+
const focusableElements = Array.from(
|
|
98
|
+
labelsRef.current.querySelectorAll("button")
|
|
99
|
+
);
|
|
100
|
+
const activeElement = document.activeElement.tagName === "SPAN" ? document.activeElement.querySelector("button") : document.activeElement;
|
|
101
|
+
const position = focusableElements.indexOf(
|
|
102
|
+
activeElement
|
|
103
|
+
);
|
|
104
|
+
const closeElement = focusableElements[focusableElements.length - 1 > position ? position + 1 : position - 1];
|
|
105
|
+
if (closeElement) {
|
|
106
|
+
closeElement.focus();
|
|
107
|
+
setLastFocusedItem(closeElement.getAttribute("id"));
|
|
108
|
+
} else {
|
|
109
|
+
inputElementRef.current?.focus();
|
|
110
|
+
setLastFocusedItem(null);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
lastChangesRef.current = {
|
|
114
|
+
action: "removed",
|
|
115
|
+
label
|
|
116
|
+
};
|
|
117
|
+
onLabelsChange(
|
|
118
|
+
labels.filter((_2, itemIndex) => itemIndex !== index)
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
[labels]
|
|
122
|
+
);
|
|
123
|
+
(0, import_react.useEffect)(() => {
|
|
124
|
+
if (labelsRef.current) {
|
|
125
|
+
const focusableElements = Array.from(
|
|
126
|
+
labelsRef.current.querySelectorAll(
|
|
127
|
+
".label [aria-describedby]"
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
const addedLabel = focusableElements[focusableElements.length - 1];
|
|
131
|
+
if (addedLabel) {
|
|
132
|
+
setLastFocusedItem(addedLabel.getAttribute("id"));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}, [labels]);
|
|
136
|
+
return /* @__PURE__ */ import_react.default.createElement(import_form.ClayInput.GroupItem, null, /* @__PURE__ */ import_react.default.createElement(import_form.ClayInput.Group, null, /* @__PURE__ */ import_react.default.createElement(
|
|
137
|
+
import_form.ClayInput.GroupItem,
|
|
138
|
+
{
|
|
139
|
+
"aria-labelledby": otherProps["aria-labelledby"],
|
|
140
|
+
className: "d-contents",
|
|
141
|
+
onFocus: (event) => setLastFocusedItem(event.target.getAttribute("id")),
|
|
142
|
+
onKeyDown: (event) => {
|
|
143
|
+
if (KeysNavigation.includes(event.key)) {
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
const focusableElements = Array.from(
|
|
146
|
+
event.currentTarget.querySelectorAll(
|
|
147
|
+
KeysSides.includes(event.key) ? "[role=gridcell][tabindex], button" : lastFocusedItem?.includes(
|
|
148
|
+
"span"
|
|
149
|
+
) ? "[role=gridcell][tabindex]" : "button"
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
const position = focusableElements.indexOf(
|
|
153
|
+
document.activeElement
|
|
154
|
+
);
|
|
155
|
+
const key = KeysSides.includes(event.key) ? import_shared.Keys.Left : import_shared.Keys.Up;
|
|
156
|
+
const label = focusableElements[event.key === key ? position - 1 : position + 1];
|
|
157
|
+
if (label) {
|
|
158
|
+
setLastFocusedItem(
|
|
159
|
+
label.getAttribute("id")
|
|
160
|
+
);
|
|
161
|
+
label.focus();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (event.key === import_shared.Keys.Home || event.key === import_shared.Keys.End) {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
const isLabel = lastFocusedItem.includes("span");
|
|
167
|
+
if (isLabel && event.key === import_shared.Keys.Home || !isLabel && event.key === import_shared.Keys.End) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const label = event.currentTarget.querySelector(
|
|
171
|
+
`[id=${lastFocusedItem?.replace(
|
|
172
|
+
isLabel ? "span" : "close",
|
|
173
|
+
event.key === import_shared.Keys.Home ? "span" : "close"
|
|
174
|
+
)}]`
|
|
175
|
+
);
|
|
176
|
+
if (label) {
|
|
177
|
+
setLastFocusedItem(
|
|
178
|
+
label.getAttribute("id")
|
|
179
|
+
);
|
|
180
|
+
label.focus();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
prepend: true,
|
|
185
|
+
ref: (ref2) => {
|
|
186
|
+
labelsRef.current = ref2;
|
|
187
|
+
},
|
|
188
|
+
role: "grid",
|
|
189
|
+
shrink: true
|
|
190
|
+
},
|
|
191
|
+
labels.map((item, i) => {
|
|
192
|
+
const value2 = (0, import_shared.getLocatorValue)({
|
|
193
|
+
item,
|
|
194
|
+
locator: locator.value
|
|
195
|
+
});
|
|
196
|
+
const uid = (0, import_shared.getLocatorValue)({
|
|
197
|
+
item,
|
|
198
|
+
locator: locator.id
|
|
199
|
+
}) ?? value2;
|
|
200
|
+
const id = `${labelId}-label-${uid}-span`;
|
|
201
|
+
const closeId = `${labelId}-label-${uid}-close`;
|
|
202
|
+
const label = (0, import_shared.getLocatorValue)({
|
|
203
|
+
item,
|
|
204
|
+
locator: locator.label
|
|
205
|
+
});
|
|
206
|
+
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, { key: id }, /* @__PURE__ */ import_react.default.createElement(
|
|
207
|
+
import_label.default,
|
|
208
|
+
{
|
|
209
|
+
className: isLabelFocused === id ? "focus" : void 0,
|
|
210
|
+
onKeyDown: ({ key }) => {
|
|
211
|
+
if (key === import_shared.Keys.Backspace) {
|
|
212
|
+
onRemove(label || "", i);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
role: "row",
|
|
216
|
+
spritemap,
|
|
217
|
+
tabIndex: -1,
|
|
218
|
+
withClose: false
|
|
219
|
+
},
|
|
220
|
+
/* @__PURE__ */ import_react.default.createElement(
|
|
221
|
+
import_label.default.ItemExpand,
|
|
222
|
+
{
|
|
223
|
+
"aria-describedby": ariaDescriptionId,
|
|
224
|
+
id,
|
|
225
|
+
onBlur: () => setIsLabelFocused(null),
|
|
226
|
+
onFocus: () => setIsLabelFocused(id),
|
|
227
|
+
role: "gridcell",
|
|
228
|
+
style: { outline: "none" },
|
|
229
|
+
tabIndex: lastFocusedItem === null && i === 0 || lastFocusedItem === id ? 0 : -1
|
|
230
|
+
},
|
|
231
|
+
label
|
|
232
|
+
),
|
|
233
|
+
/* @__PURE__ */ import_react.default.createElement(import_label.default.ItemAfter, { role: "gridcell" }, /* @__PURE__ */ import_react.default.createElement(
|
|
234
|
+
"button",
|
|
235
|
+
{
|
|
236
|
+
"aria-label": (0, import_shared.sub)(
|
|
237
|
+
closeButtonAriaLabel,
|
|
238
|
+
[label || ""]
|
|
239
|
+
),
|
|
240
|
+
className: "close",
|
|
241
|
+
disabled,
|
|
242
|
+
id: closeId,
|
|
243
|
+
onClick: () => onRemove(label || "", i),
|
|
244
|
+
ref: (ref2) => {
|
|
245
|
+
if (i === labels.length - 1) {
|
|
246
|
+
lastItemRef.current = ref2;
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
tabIndex: lastFocusedItem === closeId ? 0 : -1,
|
|
250
|
+
type: "button"
|
|
251
|
+
},
|
|
252
|
+
/* @__PURE__ */ import_react.default.createElement(
|
|
253
|
+
import_icon.default,
|
|
254
|
+
{
|
|
255
|
+
spritemap,
|
|
256
|
+
symbol: "times-small"
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
))
|
|
260
|
+
), inputName && /* @__PURE__ */ import_react.default.createElement(
|
|
261
|
+
"input",
|
|
262
|
+
{
|
|
263
|
+
name: inputName,
|
|
264
|
+
type: "hidden",
|
|
265
|
+
value: value2
|
|
266
|
+
}
|
|
267
|
+
));
|
|
268
|
+
})
|
|
269
|
+
), /* @__PURE__ */ import_react.default.createElement(import_form.ClayInput.GroupItem, { prepend: true }, /* @__PURE__ */ import_react.default.createElement(
|
|
270
|
+
"input",
|
|
271
|
+
{
|
|
272
|
+
...otherProps,
|
|
273
|
+
"aria-activedescendant": activeDescendant,
|
|
274
|
+
className: "form-control-inset",
|
|
275
|
+
disabled,
|
|
276
|
+
onBlur: (event) => {
|
|
277
|
+
if (otherProps.onBlur) {
|
|
278
|
+
otherProps.onBlur(event);
|
|
279
|
+
}
|
|
280
|
+
onFocusChange(false);
|
|
281
|
+
},
|
|
282
|
+
onChange: (event) => {
|
|
283
|
+
const { value: value2 } = event.target;
|
|
284
|
+
event.target.value = allowsCustomLabel ? value2.replace(",", "") : value2;
|
|
285
|
+
onChange(event);
|
|
286
|
+
},
|
|
287
|
+
onFocus: (event) => {
|
|
288
|
+
if (otherProps.onFocus) {
|
|
289
|
+
otherProps.onFocus(event);
|
|
290
|
+
}
|
|
291
|
+
onFocusChange(true);
|
|
292
|
+
},
|
|
293
|
+
onKeyDown: (event) => {
|
|
294
|
+
if (otherProps.onKeyDown) {
|
|
295
|
+
otherProps.onKeyDown(event);
|
|
296
|
+
}
|
|
297
|
+
const { key } = event;
|
|
298
|
+
if (key === import_shared.Keys.Backspace && !value) {
|
|
299
|
+
event.preventDefault();
|
|
300
|
+
}
|
|
301
|
+
if (key === import_shared.Keys.Enter && (activeDescendant || event.defaultPrevented)) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (allowsCustomLabel && value.trim() && DELIMITER_KEYS.includes(key)) {
|
|
305
|
+
if (!allowDuplicateValues && value && labels.find(
|
|
306
|
+
(label) => label.label?.toLowerCase() === `${value}`.toLowerCase()
|
|
307
|
+
)) {
|
|
308
|
+
onChange({ target: { value: "" } });
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
event.preventDefault();
|
|
312
|
+
lastChangesRef.current = {
|
|
313
|
+
action: "added",
|
|
314
|
+
label: value
|
|
315
|
+
};
|
|
316
|
+
onLabelsChange([
|
|
317
|
+
...labels,
|
|
318
|
+
getNewItem(value)
|
|
319
|
+
]);
|
|
320
|
+
onChange({ target: { value: "" } });
|
|
321
|
+
} else if (!value && key === import_shared.Keys.Backspace && inputElementRef.current && lastItemRef.current) {
|
|
322
|
+
inputElementRef.current.blur();
|
|
323
|
+
lastItemRef.current.focus();
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
onPaste: (event) => {
|
|
327
|
+
if (otherProps.onPaste) {
|
|
328
|
+
otherProps.onPaste(event);
|
|
329
|
+
}
|
|
330
|
+
if (event.defaultPrevented) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const pastedText = event.clipboardData.getData("Text");
|
|
334
|
+
const pastedItems = pastedText.split(",").map(
|
|
335
|
+
(itemLabel) => getNewItem(itemLabel.trim())
|
|
336
|
+
).filter(Boolean);
|
|
337
|
+
if (allowsCustomLabel && !!pastedItems.length) {
|
|
338
|
+
event.preventDefault();
|
|
339
|
+
onLabelsChange([...labels, ...pastedItems]);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
placeholder: labels.length ? void 0 : placeholder,
|
|
343
|
+
ref: inputElementRef,
|
|
344
|
+
type: "text",
|
|
345
|
+
value
|
|
346
|
+
}
|
|
347
|
+
))));
|
|
348
|
+
}
|
|
349
|
+
);
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var MultiSelect_exports = {};
|
|
30
|
+
__export(MultiSelect_exports, {
|
|
31
|
+
MultiSelect: () => MultiSelect,
|
|
32
|
+
itemLabelFilter: () => itemLabelFilter
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(MultiSelect_exports);
|
|
35
|
+
var import_autocomplete = __toESM(require("@clayui/autocomplete"));
|
|
36
|
+
var import_button = require("@clayui/button");
|
|
37
|
+
var import_core = require("@clayui/core");
|
|
38
|
+
var import_form = require("@clayui/form");
|
|
39
|
+
var import_shared = require("@clayui/shared");
|
|
40
|
+
var import_classnames = __toESM(require("classnames"));
|
|
41
|
+
var import_react = __toESM(require("react"));
|
|
42
|
+
var import_Labels = require("./Labels");
|
|
43
|
+
const MultiSelect = import_react.default.forwardRef(function MultiSelectInner({
|
|
44
|
+
active: externalActive,
|
|
45
|
+
allowsCustomLabel = true,
|
|
46
|
+
allowDuplicateValues = true,
|
|
47
|
+
alignmentByViewport,
|
|
48
|
+
children,
|
|
49
|
+
clearAllTitle = "Clear All",
|
|
50
|
+
closeButtonAriaLabel = "Remove {0}",
|
|
51
|
+
defaultActive = false,
|
|
52
|
+
defaultItems = [],
|
|
53
|
+
defaultValue = "",
|
|
54
|
+
disabled,
|
|
55
|
+
disabledClearAll,
|
|
56
|
+
displayKeyboardArrowsIndicator = false,
|
|
57
|
+
hotkeysDescription,
|
|
58
|
+
inputName,
|
|
59
|
+
inputValue,
|
|
60
|
+
isLoading: _i,
|
|
61
|
+
isValid = true,
|
|
62
|
+
items: externalItems,
|
|
63
|
+
liveRegion,
|
|
64
|
+
locator = {
|
|
65
|
+
id: "key",
|
|
66
|
+
label: "label",
|
|
67
|
+
value: "value"
|
|
68
|
+
},
|
|
69
|
+
loadingState,
|
|
70
|
+
menuRenderer: MenuRenderer,
|
|
71
|
+
messages = {
|
|
72
|
+
hotkeys: "Press backspace to delete the current row.",
|
|
73
|
+
labelAdded: "Label {0} added to the list",
|
|
74
|
+
labelRemoved: "Label {0} removed to the list",
|
|
75
|
+
listCount: "{0} option available.",
|
|
76
|
+
listCountPlural: "{0} options available.",
|
|
77
|
+
loading: "Loading...",
|
|
78
|
+
notFound: "No results found"
|
|
79
|
+
},
|
|
80
|
+
onActiveChange,
|
|
81
|
+
onChange,
|
|
82
|
+
onClearAllButtonClick,
|
|
83
|
+
onItemsChange,
|
|
84
|
+
onLoadMore,
|
|
85
|
+
placeholder,
|
|
86
|
+
size,
|
|
87
|
+
sourceItems = null,
|
|
88
|
+
spritemap,
|
|
89
|
+
icon,
|
|
90
|
+
value: externalValue,
|
|
91
|
+
...otherProps
|
|
92
|
+
}, ref) {
|
|
93
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
94
|
+
const inputRef = (0, import_react.useRef)(null);
|
|
95
|
+
const lastChangesRef = (0, import_react.useRef)(null);
|
|
96
|
+
const [isFocused, setIsFocused] = (0, import_react.useState)(false);
|
|
97
|
+
const [items, setItems] = (0, import_shared.useControlledState)({
|
|
98
|
+
defaultName: "defaultItems",
|
|
99
|
+
defaultValue: defaultItems,
|
|
100
|
+
handleName: "onItemsChange",
|
|
101
|
+
name: "items",
|
|
102
|
+
onChange: onItemsChange,
|
|
103
|
+
value: externalItems
|
|
104
|
+
});
|
|
105
|
+
const [value, setValue] = (0, import_shared.useControlledState)({
|
|
106
|
+
defaultName: "defaultValue",
|
|
107
|
+
defaultValue,
|
|
108
|
+
handleName: "onChange",
|
|
109
|
+
name: "value",
|
|
110
|
+
onChange,
|
|
111
|
+
value: externalValue ?? inputValue
|
|
112
|
+
});
|
|
113
|
+
const [active, setActive] = (0, import_shared.useControlledState)({
|
|
114
|
+
defaultName: "defaultActive",
|
|
115
|
+
defaultValue: defaultActive,
|
|
116
|
+
handleName: "onActiveChange",
|
|
117
|
+
name: "active",
|
|
118
|
+
onChange: onActiveChange,
|
|
119
|
+
value: externalActive
|
|
120
|
+
});
|
|
121
|
+
const selectedKeys = (0, import_react.useMemo)(() => {
|
|
122
|
+
return items.map(
|
|
123
|
+
(item) => String(
|
|
124
|
+
(0, import_shared.getLocatorValue)({
|
|
125
|
+
item,
|
|
126
|
+
locator: locator.value
|
|
127
|
+
})
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
}, [items, locator.value]);
|
|
131
|
+
const inputElementRef = ref || inputRef;
|
|
132
|
+
const ariaDescriptionId = (0, import_shared.useId)();
|
|
133
|
+
const hasAsyncItems = !!onLoadMore || typeof loadingState === "number";
|
|
134
|
+
const Container = MenuRenderer ? import_shared.FocusScope : import_react.default.Fragment;
|
|
135
|
+
const containerProps = MenuRenderer ? { arrowKeysUpDown: false } : {};
|
|
136
|
+
(0, import_react.useEffect)(() => {
|
|
137
|
+
if (MenuRenderer) {
|
|
138
|
+
console.warn(
|
|
139
|
+
`<ClayMultiSelect />: You are using 'menuRenderer' which is deprecated and missing the new features and improvements that have been implemented in the component try to migrate to the new composition based API to custom list items.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (allowsCustomLabel && (typeof locator.label === "function" || typeof locator.value === "function")) {
|
|
143
|
+
console.warn(
|
|
144
|
+
`<ClayMultiSelect />: Custom labels cannot be created because the expected structure of the label is unknown. Please disable allowsCustomLabel or update the locators to be strings rather than functions.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}, []);
|
|
148
|
+
(0, import_react.useEffect)(() => {
|
|
149
|
+
if (MenuRenderer && sourceItems) {
|
|
150
|
+
setActive(!!value && sourceItems.length !== 0);
|
|
151
|
+
}
|
|
152
|
+
}, [value, sourceItems]);
|
|
153
|
+
const memoizedChildren = (0, import_react.useCallback)(
|
|
154
|
+
(item) => {
|
|
155
|
+
const handleItemClick = (event) => {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
setActive(false);
|
|
158
|
+
if (!allowDuplicateValues && items.find((dropdownItem) => {
|
|
159
|
+
return (0, import_shared.getLocatorValue)({
|
|
160
|
+
item: dropdownItem,
|
|
161
|
+
locator: locator.value
|
|
162
|
+
}) === (0, import_shared.getLocatorValue)({
|
|
163
|
+
item,
|
|
164
|
+
locator: locator.value
|
|
165
|
+
});
|
|
166
|
+
})) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
setItems([...items, item]);
|
|
170
|
+
setValue("");
|
|
171
|
+
};
|
|
172
|
+
if (children && typeof children === "function") {
|
|
173
|
+
const child = children(item);
|
|
174
|
+
return import_react.default.cloneElement(child, {
|
|
175
|
+
onClick: (event) => {
|
|
176
|
+
if (child.props.onClick) {
|
|
177
|
+
child.props.onClick(event);
|
|
178
|
+
}
|
|
179
|
+
if (event.defaultPrevented) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
handleItemClick(event);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
187
|
+
import_autocomplete.Item,
|
|
188
|
+
{
|
|
189
|
+
key: (0, import_shared.getLocatorValue)({
|
|
190
|
+
item,
|
|
191
|
+
locator: locator.value
|
|
192
|
+
}),
|
|
193
|
+
onClick: handleItemClick
|
|
194
|
+
},
|
|
195
|
+
(0, import_shared.getLocatorValue)({
|
|
196
|
+
item,
|
|
197
|
+
locator: locator.label
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
},
|
|
201
|
+
[children, locator, items, setItems, allowDuplicateValues]
|
|
202
|
+
);
|
|
203
|
+
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(Container, { ...containerProps }, /* @__PURE__ */ import_react.default.createElement(
|
|
204
|
+
"div",
|
|
205
|
+
{
|
|
206
|
+
className: (0, import_classnames.default)(
|
|
207
|
+
"form-control form-control-tag-group input-group",
|
|
208
|
+
{
|
|
209
|
+
focus: isFocused && isValid,
|
|
210
|
+
[`form-control-tag-group-${size}`]: size
|
|
211
|
+
}
|
|
212
|
+
),
|
|
213
|
+
ref: containerRef
|
|
214
|
+
},
|
|
215
|
+
/* @__PURE__ */ import_react.default.createElement(
|
|
216
|
+
import_autocomplete.Autocomplete,
|
|
217
|
+
{
|
|
218
|
+
...otherProps,
|
|
219
|
+
UNSAFE_loadingShrink: true,
|
|
220
|
+
active: MenuRenderer ? false : active,
|
|
221
|
+
allowsCustomLabel: typeof locator.label === "function" || typeof locator.value === "function" ? false : allowsCustomLabel,
|
|
222
|
+
allowsCustomValue: true,
|
|
223
|
+
ariaDescriptionId,
|
|
224
|
+
as: import_Labels.Labels,
|
|
225
|
+
closeButtonAriaLabel,
|
|
226
|
+
containerElementRef: containerRef,
|
|
227
|
+
defaultItems: !hasAsyncItems ? sourceItems : void 0,
|
|
228
|
+
disabled,
|
|
229
|
+
filterKey: locator.label,
|
|
230
|
+
inputName,
|
|
231
|
+
items: hasAsyncItems ? sourceItems : void 0,
|
|
232
|
+
labels: items,
|
|
233
|
+
lastChangesRef,
|
|
234
|
+
loadingState,
|
|
235
|
+
locator,
|
|
236
|
+
menuTrigger: "focus",
|
|
237
|
+
messages,
|
|
238
|
+
onActiveChange: MenuRenderer ? () => {
|
|
239
|
+
} : setActive,
|
|
240
|
+
onChange: setValue,
|
|
241
|
+
onFocus: MenuRenderer && sourceItems ? (event) => {
|
|
242
|
+
if (otherProps.onFocus) {
|
|
243
|
+
otherProps.onFocus(event);
|
|
244
|
+
}
|
|
245
|
+
setActive(
|
|
246
|
+
!!value && sourceItems.length !== 0
|
|
247
|
+
);
|
|
248
|
+
} : otherProps.onFocus,
|
|
249
|
+
onFocusChange: setIsFocused,
|
|
250
|
+
onItemsChange: hasAsyncItems ? () => {
|
|
251
|
+
} : void 0,
|
|
252
|
+
onLabelsChange: setItems,
|
|
253
|
+
onLoadMore,
|
|
254
|
+
placeholder,
|
|
255
|
+
ref: inputElementRef,
|
|
256
|
+
selectedKeys,
|
|
257
|
+
spritemap,
|
|
258
|
+
suggestionList: sourceItems ?? [],
|
|
259
|
+
value
|
|
260
|
+
},
|
|
261
|
+
memoizedChildren
|
|
262
|
+
),
|
|
263
|
+
sourceItems && MenuRenderer && !!sourceItems.length && /* @__PURE__ */ import_react.default.createElement(
|
|
264
|
+
import_autocomplete.default.DropDown,
|
|
265
|
+
{
|
|
266
|
+
active,
|
|
267
|
+
alignElementRef: containerRef,
|
|
268
|
+
alignmentByViewport,
|
|
269
|
+
onActiveChange: setActive
|
|
270
|
+
},
|
|
271
|
+
/* @__PURE__ */ import_react.default.createElement(
|
|
272
|
+
MenuRenderer,
|
|
273
|
+
{
|
|
274
|
+
inputValue: value,
|
|
275
|
+
locator,
|
|
276
|
+
onItemClick: (item) => {
|
|
277
|
+
setItems([...items, item]);
|
|
278
|
+
setValue("");
|
|
279
|
+
if (inputElementRef.current) {
|
|
280
|
+
inputElementRef.current.focus();
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
sourceItems,
|
|
284
|
+
value
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
),
|
|
288
|
+
!disabled && !disabledClearAll && (value || !!items.length) ? /* @__PURE__ */ import_react.default.createElement(import_form.ClayInput.GroupItem, { shrink: true }, /* @__PURE__ */ import_react.default.createElement(
|
|
289
|
+
import_button.ClayButtonWithIcon,
|
|
290
|
+
{
|
|
291
|
+
"aria-label": clearAllTitle,
|
|
292
|
+
borderless: true,
|
|
293
|
+
className: "component-action",
|
|
294
|
+
displayType: "secondary",
|
|
295
|
+
onClick: () => {
|
|
296
|
+
if (onClearAllButtonClick) {
|
|
297
|
+
onClearAllButtonClick();
|
|
298
|
+
} else {
|
|
299
|
+
setItems([]);
|
|
300
|
+
setValue("");
|
|
301
|
+
}
|
|
302
|
+
if (inputElementRef.current) {
|
|
303
|
+
inputElementRef.current.focus();
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
outline: true,
|
|
307
|
+
spritemap,
|
|
308
|
+
symbol: "times-circle",
|
|
309
|
+
title: clearAllTitle
|
|
310
|
+
}
|
|
311
|
+
)) : icon,
|
|
312
|
+
/* @__PURE__ */ import_react.default.createElement("div", { className: "sr-only" }, /* @__PURE__ */ import_react.default.createElement("span", { id: ariaDescriptionId }, hotkeysDescription ?? messages.hotkeys), /* @__PURE__ */ import_react.default.createElement("span", { "aria-live": "polite", "aria-relevant": "text" }, lastChangesRef.current ? (0, import_shared.sub)(
|
|
313
|
+
liveRegion ? liveRegion[lastChangesRef.current.action] : lastChangesRef.current.action === "added" ? messages.labelAdded : messages.labelRemoved,
|
|
314
|
+
[lastChangesRef.current.label]
|
|
315
|
+
) : null))
|
|
316
|
+
)), active && displayKeyboardArrowsIndicator && /* @__PURE__ */ import_react.default.createElement(import_shared.ClayPortal, null, /* @__PURE__ */ import_react.default.createElement(
|
|
317
|
+
import_core.KeyboardArrowsIndicator,
|
|
318
|
+
{
|
|
319
|
+
anchorRef: containerRef,
|
|
320
|
+
direction: "vertical"
|
|
321
|
+
}
|
|
322
|
+
)));
|
|
323
|
+
});
|
|
324
|
+
function itemLabelFilter(items, _value, _locator = "label") {
|
|
325
|
+
return items;
|
|
326
|
+
}
|