lecom-ui 5.3.85 → 5.3.86
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/components/CustomTagInput/CustomTagInput.js +714 -0
- package/dist/components/MultiSelect/MultiSelect.js +8 -10
- package/dist/components/Textarea/Textarea.js +5 -5
- package/dist/index.d.ts +33 -12
- package/dist/index.js +1 -0
- package/dist/plugin/extend.js +79 -79
- package/dist/style.min.css +1 -1
- package/package.json +131 -131
- package/dist/components/SyntaxHighlighter/SyntaxHighlighter.js +0 -27
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { GripVertical, X } from 'lucide-react';
|
|
3
|
+
import { cn } from '../../lib/utils.js';
|
|
4
|
+
import { Tag } from '../Tag/Tag.js';
|
|
5
|
+
import { textareaVariants } from '../Textarea/Textarea.js';
|
|
6
|
+
import { Typography } from '../Typography/Typography.js';
|
|
7
|
+
|
|
8
|
+
const CustomTagItemComponent = React.memo(function CustomTagItemComponent2({
|
|
9
|
+
item,
|
|
10
|
+
index,
|
|
11
|
+
onRemove,
|
|
12
|
+
onEdit,
|
|
13
|
+
readOnly,
|
|
14
|
+
enableReorder,
|
|
15
|
+
onDragStart,
|
|
16
|
+
onDragOver,
|
|
17
|
+
onDrag,
|
|
18
|
+
onDragEnd,
|
|
19
|
+
isDragging,
|
|
20
|
+
onMouseDown
|
|
21
|
+
}) {
|
|
22
|
+
const [isEditing, setIsEditing] = React.useState(false);
|
|
23
|
+
const [editValue, setEditValue] = React.useState(item.label);
|
|
24
|
+
const inputRef = React.useRef(null);
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (isEditing && inputRef.current) {
|
|
27
|
+
inputRef.current.focus();
|
|
28
|
+
inputRef.current.select();
|
|
29
|
+
}
|
|
30
|
+
}, [isEditing]);
|
|
31
|
+
const handleClick = React.useCallback(() => {
|
|
32
|
+
if (!readOnly) {
|
|
33
|
+
setIsEditing(true);
|
|
34
|
+
setEditValue(item.label);
|
|
35
|
+
}
|
|
36
|
+
}, [readOnly, item.label]);
|
|
37
|
+
const handleRemove = React.useCallback(
|
|
38
|
+
(e) => {
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
onRemove(item.id);
|
|
41
|
+
},
|
|
42
|
+
[item.id, onRemove]
|
|
43
|
+
);
|
|
44
|
+
const handleSave = React.useCallback(() => {
|
|
45
|
+
const trimmedValue = editValue.trim();
|
|
46
|
+
if (trimmedValue && trimmedValue !== item.label) {
|
|
47
|
+
onEdit(item.id, trimmedValue);
|
|
48
|
+
}
|
|
49
|
+
setIsEditing(false);
|
|
50
|
+
}, [editValue, item.id, item.label, onEdit]);
|
|
51
|
+
const handleKeyDown = React.useCallback(
|
|
52
|
+
(e) => {
|
|
53
|
+
if (e.key === "Enter") {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
handleSave();
|
|
56
|
+
} else if (e.key === "Escape") {
|
|
57
|
+
setIsEditing(false);
|
|
58
|
+
setEditValue(item.label);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
[handleSave, item.label]
|
|
62
|
+
);
|
|
63
|
+
const handleBlur = React.useCallback(() => {
|
|
64
|
+
handleSave();
|
|
65
|
+
}, [handleSave]);
|
|
66
|
+
const handleDragStart = React.useCallback(
|
|
67
|
+
(e) => {
|
|
68
|
+
e.stopPropagation();
|
|
69
|
+
onDragStart(index);
|
|
70
|
+
},
|
|
71
|
+
[index, onDragStart]
|
|
72
|
+
);
|
|
73
|
+
const handleDragOver = React.useCallback(
|
|
74
|
+
(e) => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
onDragOver(index);
|
|
78
|
+
},
|
|
79
|
+
[index, onDragOver]
|
|
80
|
+
);
|
|
81
|
+
if (isEditing) {
|
|
82
|
+
return /* @__PURE__ */ React.createElement(Tag, { "data-tag": "true", color: "blue", className: "px-2 py-0 max-w-[15.625rem]" }, /* @__PURE__ */ React.createElement(
|
|
83
|
+
"input",
|
|
84
|
+
{
|
|
85
|
+
ref: inputRef,
|
|
86
|
+
type: "text",
|
|
87
|
+
value: editValue,
|
|
88
|
+
onChange: (e) => setEditValue(e.target.value),
|
|
89
|
+
onKeyDown: handleKeyDown,
|
|
90
|
+
onBlur: handleBlur,
|
|
91
|
+
className: "bg-transparent border-none outline-none text-blue-700 body-small-400 min-w-[60px] w-full",
|
|
92
|
+
onClick: (e) => e.stopPropagation(),
|
|
93
|
+
style: { width: `${Math.min(Math.max(editValue.length * 8, 60), 250)}px` }
|
|
94
|
+
}
|
|
95
|
+
));
|
|
96
|
+
}
|
|
97
|
+
return /* @__PURE__ */ React.createElement(
|
|
98
|
+
Tag,
|
|
99
|
+
{
|
|
100
|
+
"data-tag": "true",
|
|
101
|
+
color: "blue",
|
|
102
|
+
className: cn(
|
|
103
|
+
"cursor-pointer group max-w-[15.625rem]",
|
|
104
|
+
isDragging && "opacity-30 scale-95 transition-all",
|
|
105
|
+
enableReorder && "cursor-grab active:cursor-grabbing"
|
|
106
|
+
),
|
|
107
|
+
draggable: enableReorder && !readOnly,
|
|
108
|
+
onDragStart: handleDragStart,
|
|
109
|
+
onDragOver: handleDragOver,
|
|
110
|
+
onDrag,
|
|
111
|
+
onDragEnd,
|
|
112
|
+
onMouseDown
|
|
113
|
+
},
|
|
114
|
+
enableReorder && !readOnly && /* @__PURE__ */ React.createElement(
|
|
115
|
+
GripVertical,
|
|
116
|
+
{
|
|
117
|
+
className: "w-4 h-4 opacity-100 transition-all cursor-grab flex-shrink-0 stroke-blue-600 fill-none hover:stroke-blue-800 hover:scale-110",
|
|
118
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
119
|
+
strokeWidth: 2
|
|
120
|
+
}
|
|
121
|
+
),
|
|
122
|
+
/* @__PURE__ */ React.createElement(
|
|
123
|
+
Typography,
|
|
124
|
+
{
|
|
125
|
+
variant: "body-small-400",
|
|
126
|
+
className: "truncate flex-1 min-w-0",
|
|
127
|
+
textColor: "text-blue-700",
|
|
128
|
+
onClick: handleClick
|
|
129
|
+
},
|
|
130
|
+
item.label
|
|
131
|
+
),
|
|
132
|
+
!readOnly && /* @__PURE__ */ React.createElement(
|
|
133
|
+
X,
|
|
134
|
+
{
|
|
135
|
+
className: "w-4 h-4 cursor-pointer flex-shrink-0 stroke-blue-600 stroke-2 outline-none hover:stroke-blue-800 hover:scale-110 transition-all",
|
|
136
|
+
onClick: handleRemove,
|
|
137
|
+
"aria-label": `Remover ${item.label}`,
|
|
138
|
+
tabIndex: 0
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
const TagItemWrapper = React.memo(function TagItemWrapper2({
|
|
144
|
+
item,
|
|
145
|
+
index,
|
|
146
|
+
insertAtIndex,
|
|
147
|
+
draggedIndex,
|
|
148
|
+
dragOverIndex,
|
|
149
|
+
readOnly,
|
|
150
|
+
enableReorder,
|
|
151
|
+
inlinePlaceholder,
|
|
152
|
+
onRemove,
|
|
153
|
+
onEdit,
|
|
154
|
+
onDragStart,
|
|
155
|
+
onDragOver,
|
|
156
|
+
onDrag,
|
|
157
|
+
onDragEnd,
|
|
158
|
+
onInlineAdd,
|
|
159
|
+
onInlineCancel,
|
|
160
|
+
onSpacerClick,
|
|
161
|
+
onMouseDown
|
|
162
|
+
}) {
|
|
163
|
+
const showInlineInput = insertAtIndex === index;
|
|
164
|
+
const showDragIndicator = dragOverIndex === index && draggedIndex !== null && draggedIndex !== index;
|
|
165
|
+
const showSpacer = !readOnly && insertAtIndex !== index + 1;
|
|
166
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, showInlineInput && /* @__PURE__ */ React.createElement(
|
|
167
|
+
InlineInput,
|
|
168
|
+
{
|
|
169
|
+
onAdd: onInlineAdd,
|
|
170
|
+
onCancel: onInlineCancel,
|
|
171
|
+
placeholder: inlinePlaceholder
|
|
172
|
+
}
|
|
173
|
+
), /* @__PURE__ */ React.createElement("div", { className: "flex items-start gap-0.5 relative" }, /* @__PURE__ */ React.createElement(
|
|
174
|
+
CustomTagItemComponent,
|
|
175
|
+
{
|
|
176
|
+
item,
|
|
177
|
+
index,
|
|
178
|
+
onRemove,
|
|
179
|
+
onEdit,
|
|
180
|
+
readOnly,
|
|
181
|
+
enableReorder,
|
|
182
|
+
onDragStart,
|
|
183
|
+
onDragOver,
|
|
184
|
+
onDrag,
|
|
185
|
+
onDragEnd,
|
|
186
|
+
isDragging: draggedIndex === index,
|
|
187
|
+
onMouseDown
|
|
188
|
+
}
|
|
189
|
+
), showDragIndicator && /* @__PURE__ */ React.createElement("div", { className: "w-1 h-6 bg-blue-500 rounded-full animate-pulse self-center" }), showSpacer && /* @__PURE__ */ React.createElement(
|
|
190
|
+
"div",
|
|
191
|
+
{
|
|
192
|
+
className: "w-1 h-6 hover:bg-blue-200 rounded cursor-pointer transition-colors self-center",
|
|
193
|
+
onClick: (e) => {
|
|
194
|
+
e.stopPropagation();
|
|
195
|
+
onSpacerClick(index + 1);
|
|
196
|
+
},
|
|
197
|
+
"data-inline-input": "true"
|
|
198
|
+
}
|
|
199
|
+
)));
|
|
200
|
+
});
|
|
201
|
+
const InlineInput = React.memo(function InlineInput2({ onAdd, onCancel, placeholder }) {
|
|
202
|
+
const [value, setValue] = React.useState("");
|
|
203
|
+
const inputRef = React.useRef(null);
|
|
204
|
+
React.useEffect(() => {
|
|
205
|
+
inputRef.current?.focus();
|
|
206
|
+
}, []);
|
|
207
|
+
const handleKeyDown = React.useCallback(
|
|
208
|
+
(e) => {
|
|
209
|
+
if (e.key === "Enter" || e.key === ";") {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
const trimmed = value.trim();
|
|
212
|
+
if (trimmed) {
|
|
213
|
+
onAdd(trimmed);
|
|
214
|
+
setValue("");
|
|
215
|
+
}
|
|
216
|
+
} else if (e.key === "Escape") {
|
|
217
|
+
onCancel();
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
[value, onAdd, onCancel]
|
|
221
|
+
);
|
|
222
|
+
const handleBlur = React.useCallback(() => {
|
|
223
|
+
const trimmed = value.trim();
|
|
224
|
+
if (trimmed) {
|
|
225
|
+
onAdd(trimmed);
|
|
226
|
+
} else {
|
|
227
|
+
onCancel();
|
|
228
|
+
}
|
|
229
|
+
}, [value, onAdd, onCancel]);
|
|
230
|
+
return /* @__PURE__ */ React.createElement(Tag, { "data-tag": "true", color: "blue", className: "px-2 py-0 max-w-[15.625rem]", "data-inline-input": "true" }, /* @__PURE__ */ React.createElement(
|
|
231
|
+
"input",
|
|
232
|
+
{
|
|
233
|
+
ref: inputRef,
|
|
234
|
+
type: "text",
|
|
235
|
+
value,
|
|
236
|
+
onChange: (e) => setValue(e.target.value),
|
|
237
|
+
onKeyDown: handleKeyDown,
|
|
238
|
+
onBlur: handleBlur,
|
|
239
|
+
placeholder,
|
|
240
|
+
className: "bg-transparent border-none outline-none text-blue-700 body-small-400 min-w-[3.75rem] w-full px-1",
|
|
241
|
+
onClick: (e) => e.stopPropagation(),
|
|
242
|
+
style: { width: `${Math.min(Math.max(value.length * 8, 60), 250)}px` }
|
|
243
|
+
}
|
|
244
|
+
));
|
|
245
|
+
});
|
|
246
|
+
function useTagManagement(value, onChange, allowDuplicates) {
|
|
247
|
+
const isDuplicateTag = React.useCallback(
|
|
248
|
+
(label) => value.some(
|
|
249
|
+
(item) => item.label.toLowerCase() === label.toLowerCase()
|
|
250
|
+
),
|
|
251
|
+
[value]
|
|
252
|
+
);
|
|
253
|
+
const createTagItem = React.useCallback(
|
|
254
|
+
(label) => ({
|
|
255
|
+
id: typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
256
|
+
label
|
|
257
|
+
}),
|
|
258
|
+
[]
|
|
259
|
+
);
|
|
260
|
+
const addTagAtPosition = React.useCallback(
|
|
261
|
+
(newItem, atIndex) => {
|
|
262
|
+
if (atIndex !== void 0) {
|
|
263
|
+
const newValue = [...value];
|
|
264
|
+
newValue.splice(atIndex, 0, newItem);
|
|
265
|
+
onChange(newValue);
|
|
266
|
+
} else {
|
|
267
|
+
onChange([...value, newItem]);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
[value, onChange]
|
|
271
|
+
);
|
|
272
|
+
const handleAddTag = React.useCallback(
|
|
273
|
+
(newLabel, atIndex) => {
|
|
274
|
+
const trimmedValue = newLabel.trim();
|
|
275
|
+
if (!trimmedValue) return;
|
|
276
|
+
if (!allowDuplicates && isDuplicateTag(trimmedValue)) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const newItem = createTagItem(trimmedValue);
|
|
280
|
+
addTagAtPosition(newItem, atIndex);
|
|
281
|
+
},
|
|
282
|
+
[allowDuplicates, isDuplicateTag, addTagAtPosition, createTagItem]
|
|
283
|
+
);
|
|
284
|
+
const handleAddMultipleTags = React.useCallback(
|
|
285
|
+
(labels) => {
|
|
286
|
+
const newItems = labels.map((label) => label.trim()).filter((label) => {
|
|
287
|
+
if (!label) return false;
|
|
288
|
+
if (!allowDuplicates && isDuplicateTag(label)) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
}).map((label) => createTagItem(label));
|
|
293
|
+
if (newItems.length > 0) {
|
|
294
|
+
onChange([...value, ...newItems]);
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
[value, onChange, allowDuplicates, isDuplicateTag, createTagItem]
|
|
298
|
+
);
|
|
299
|
+
const handleRemove = React.useCallback(
|
|
300
|
+
(id) => {
|
|
301
|
+
onChange(value.filter((item) => item.id !== id));
|
|
302
|
+
},
|
|
303
|
+
[value, onChange]
|
|
304
|
+
);
|
|
305
|
+
return { handleAddTag, handleAddMultipleTags, handleRemove };
|
|
306
|
+
}
|
|
307
|
+
function useDragAndDrop(value, onChange, containerRef) {
|
|
308
|
+
const [draggedIndex, setDraggedIndex] = React.useState(null);
|
|
309
|
+
const [dragOverIndex, setDragOverIndex] = React.useState(null);
|
|
310
|
+
const scrollIntervalRef = React.useRef(null);
|
|
311
|
+
const isDraggingRef = React.useRef(false);
|
|
312
|
+
const wheelHandlerRef = React.useRef(null);
|
|
313
|
+
const handleMouseDown = React.useCallback((e) => {
|
|
314
|
+
const target = e.target;
|
|
315
|
+
const isDraggableTag = target.closest('[draggable="true"]');
|
|
316
|
+
if (!isDraggableTag) return;
|
|
317
|
+
const handleWheel = (e2) => {
|
|
318
|
+
e2.preventDefault();
|
|
319
|
+
e2.stopImmediatePropagation();
|
|
320
|
+
if (containerRef.current) {
|
|
321
|
+
containerRef.current.scrollTop += e2.deltaY;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
wheelHandlerRef.current = handleWheel;
|
|
325
|
+
document.body.addEventListener("wheel", handleWheel, { passive: false, capture: true });
|
|
326
|
+
const cleanup = () => {
|
|
327
|
+
if (wheelHandlerRef.current) {
|
|
328
|
+
document.body.removeEventListener("wheel", wheelHandlerRef.current, true);
|
|
329
|
+
wheelHandlerRef.current = null;
|
|
330
|
+
}
|
|
331
|
+
document.removeEventListener("mouseup", cleanup);
|
|
332
|
+
document.removeEventListener("dragend", cleanup);
|
|
333
|
+
};
|
|
334
|
+
document.addEventListener("mouseup", cleanup);
|
|
335
|
+
document.addEventListener("dragend", cleanup);
|
|
336
|
+
}, [containerRef]);
|
|
337
|
+
const handleDragStart = React.useCallback((index) => {
|
|
338
|
+
setDraggedIndex(index);
|
|
339
|
+
isDraggingRef.current = true;
|
|
340
|
+
}, []);
|
|
341
|
+
const handleDragOver = React.useCallback(
|
|
342
|
+
(index) => {
|
|
343
|
+
const shouldUpdateDragOver = draggedIndex !== null && draggedIndex !== index;
|
|
344
|
+
if (shouldUpdateDragOver) {
|
|
345
|
+
setDragOverIndex(index);
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
[draggedIndex]
|
|
349
|
+
);
|
|
350
|
+
const handleDrag = React.useCallback(
|
|
351
|
+
(e) => {
|
|
352
|
+
if (!containerRef.current) return;
|
|
353
|
+
const container = containerRef.current;
|
|
354
|
+
const rect = container.getBoundingClientRect();
|
|
355
|
+
const scrollThreshold = 80;
|
|
356
|
+
const maxScrollSpeed = 15;
|
|
357
|
+
const minScrollSpeed = 3;
|
|
358
|
+
const mouseY = e.clientY;
|
|
359
|
+
const distanceFromTop = mouseY - rect.top;
|
|
360
|
+
const distanceFromBottom = rect.bottom - mouseY;
|
|
361
|
+
if (scrollIntervalRef.current) {
|
|
362
|
+
clearInterval(scrollIntervalRef.current);
|
|
363
|
+
scrollIntervalRef.current = null;
|
|
364
|
+
}
|
|
365
|
+
if (distanceFromTop < scrollThreshold && distanceFromTop > 0) {
|
|
366
|
+
const speedFactor = 1 - distanceFromTop / scrollThreshold;
|
|
367
|
+
const scrollSpeed = minScrollSpeed + (maxScrollSpeed - minScrollSpeed) * speedFactor;
|
|
368
|
+
scrollIntervalRef.current = window.setInterval(() => {
|
|
369
|
+
if (container.scrollTop > 0) {
|
|
370
|
+
container.scrollTop -= scrollSpeed;
|
|
371
|
+
}
|
|
372
|
+
}, 16);
|
|
373
|
+
} else if (distanceFromBottom < scrollThreshold && distanceFromBottom > 0) {
|
|
374
|
+
const speedFactor = 1 - distanceFromBottom / scrollThreshold;
|
|
375
|
+
const scrollSpeed = minScrollSpeed + (maxScrollSpeed - minScrollSpeed) * speedFactor;
|
|
376
|
+
scrollIntervalRef.current = window.setInterval(() => {
|
|
377
|
+
if (container.scrollTop < container.scrollHeight - container.clientHeight) {
|
|
378
|
+
container.scrollTop += scrollSpeed;
|
|
379
|
+
}
|
|
380
|
+
}, 16);
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
[containerRef]
|
|
384
|
+
);
|
|
385
|
+
const resetDragState = () => {
|
|
386
|
+
setDraggedIndex(null);
|
|
387
|
+
setDragOverIndex(null);
|
|
388
|
+
isDraggingRef.current = false;
|
|
389
|
+
if (scrollIntervalRef.current) {
|
|
390
|
+
clearInterval(scrollIntervalRef.current);
|
|
391
|
+
scrollIntervalRef.current = null;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
const reorderItems = React.useCallback(
|
|
395
|
+
(fromIndex, toIndex) => {
|
|
396
|
+
const newValue = [...value];
|
|
397
|
+
const [removed] = newValue.splice(fromIndex, 1);
|
|
398
|
+
newValue.splice(toIndex, 0, removed);
|
|
399
|
+
onChange(newValue);
|
|
400
|
+
},
|
|
401
|
+
[value, onChange]
|
|
402
|
+
);
|
|
403
|
+
const handleDragEnd = React.useCallback(() => {
|
|
404
|
+
const canReorder = draggedIndex !== null && dragOverIndex !== null;
|
|
405
|
+
if (canReorder) {
|
|
406
|
+
reorderItems(draggedIndex, dragOverIndex);
|
|
407
|
+
}
|
|
408
|
+
resetDragState();
|
|
409
|
+
}, [draggedIndex, dragOverIndex, reorderItems]);
|
|
410
|
+
return {
|
|
411
|
+
draggedIndex,
|
|
412
|
+
dragOverIndex,
|
|
413
|
+
handleDragStart,
|
|
414
|
+
handleDragOver,
|
|
415
|
+
handleDrag,
|
|
416
|
+
handleDragEnd,
|
|
417
|
+
handleMouseDown
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function useInlineInputHandlers(insertAtIndex, setInsertAtIndex, handleAddTag, readOnly, disabled) {
|
|
421
|
+
const handleInlineAdd = React.useCallback(
|
|
422
|
+
(newLabel) => {
|
|
423
|
+
if (insertAtIndex !== null) {
|
|
424
|
+
handleAddTag(newLabel, insertAtIndex);
|
|
425
|
+
setInsertAtIndex(null);
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
[insertAtIndex, handleAddTag, setInsertAtIndex]
|
|
429
|
+
);
|
|
430
|
+
const handleInlineCancel = React.useCallback(() => {
|
|
431
|
+
setInsertAtIndex(null);
|
|
432
|
+
}, [setInsertAtIndex]);
|
|
433
|
+
const handleSpacerClick = React.useCallback(
|
|
434
|
+
(index) => {
|
|
435
|
+
const canInsert = !readOnly && !disabled;
|
|
436
|
+
if (canInsert) {
|
|
437
|
+
setInsertAtIndex(index);
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
[readOnly, disabled, setInsertAtIndex]
|
|
441
|
+
);
|
|
442
|
+
return { handleInlineAdd, handleInlineCancel, handleSpacerClick };
|
|
443
|
+
}
|
|
444
|
+
function useInputHandlers(inputValue, setInputValue, value, onChange, handleAddTag, handleAddMultipleTags, inputRef) {
|
|
445
|
+
const handleKeyDown = React.useCallback(
|
|
446
|
+
(e) => {
|
|
447
|
+
const isSubmitKey = e.key === "Enter" || e.key === ";";
|
|
448
|
+
const isDeleteKey = e.key === "Backspace";
|
|
449
|
+
if (isSubmitKey) {
|
|
450
|
+
e.preventDefault();
|
|
451
|
+
const currentValue = e.target.textContent || "";
|
|
452
|
+
if (currentValue.trim()) {
|
|
453
|
+
handleAddTag(currentValue.trim());
|
|
454
|
+
if (inputRef.current) {
|
|
455
|
+
inputRef.current.textContent = "";
|
|
456
|
+
}
|
|
457
|
+
setInputValue("");
|
|
458
|
+
}
|
|
459
|
+
} else if (isDeleteKey && !inputValue && value.length > 0) {
|
|
460
|
+
onChange(value.slice(0, -1));
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
[inputValue, value, onChange, handleAddTag, setInputValue, inputRef]
|
|
464
|
+
);
|
|
465
|
+
const handlePaste = React.useCallback(
|
|
466
|
+
(e) => {
|
|
467
|
+
e.preventDefault();
|
|
468
|
+
const pastedText = e.clipboardData.getData("text/plain") || e.clipboardData.getData("text");
|
|
469
|
+
if (!pastedText) return;
|
|
470
|
+
if (pastedText.includes(";")) {
|
|
471
|
+
const items = pastedText.split(";").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
472
|
+
handleAddMultipleTags(items);
|
|
473
|
+
} else {
|
|
474
|
+
const trimmedText = pastedText.trim();
|
|
475
|
+
if (trimmedText) {
|
|
476
|
+
handleAddMultipleTags([trimmedText]);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
setTimeout(() => {
|
|
480
|
+
if (inputRef.current) {
|
|
481
|
+
inputRef.current.textContent = "";
|
|
482
|
+
}
|
|
483
|
+
setInputValue("");
|
|
484
|
+
}, 0);
|
|
485
|
+
},
|
|
486
|
+
[handleAddMultipleTags, setInputValue, inputRef]
|
|
487
|
+
);
|
|
488
|
+
return { handleKeyDown, handlePaste };
|
|
489
|
+
}
|
|
490
|
+
function useTagEditHandler(value, onChange, onTagEdit) {
|
|
491
|
+
const handleEdit = React.useCallback(
|
|
492
|
+
(id, newLabel) => {
|
|
493
|
+
const updatedItems = value.map(
|
|
494
|
+
(item) => item.id === id ? { ...item, label: newLabel } : item
|
|
495
|
+
);
|
|
496
|
+
onChange(updatedItems);
|
|
497
|
+
const editedItem = updatedItems.find((item) => item.id === id);
|
|
498
|
+
if (editedItem && onTagEdit) {
|
|
499
|
+
onTagEdit(editedItem);
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
[value, onChange, onTagEdit]
|
|
503
|
+
);
|
|
504
|
+
return { handleEdit };
|
|
505
|
+
}
|
|
506
|
+
function useContainerClickHandler(disabled, inputRef) {
|
|
507
|
+
const handleContainerClick = React.useCallback(
|
|
508
|
+
(e) => {
|
|
509
|
+
const target = e.target;
|
|
510
|
+
const isClickOnTag = target.closest('[data-tag="true"]');
|
|
511
|
+
const isClickOnInlineInput = target.closest('[data-inline-input="true"]');
|
|
512
|
+
if (isClickOnTag || isClickOnInlineInput) return;
|
|
513
|
+
if (disabled || !inputRef.current) return;
|
|
514
|
+
inputRef.current.focus();
|
|
515
|
+
},
|
|
516
|
+
[disabled, inputRef]
|
|
517
|
+
);
|
|
518
|
+
return { handleContainerClick };
|
|
519
|
+
}
|
|
520
|
+
function useContainerStyles(variant, radius, disabled, className, maxHeight) {
|
|
521
|
+
const textareaRadius = radius === "full" ? "large" : radius;
|
|
522
|
+
const containerClassNames = cn(
|
|
523
|
+
textareaVariants({ variant, radius: textareaRadius }),
|
|
524
|
+
"min-h-10 cursor-text overflow-hidden pr-0.5 py-1",
|
|
525
|
+
disabled && "opacity-50 pointer-events-none cursor-not-allowed",
|
|
526
|
+
className
|
|
527
|
+
);
|
|
528
|
+
const containerStyles = React.useMemo(
|
|
529
|
+
() => ({
|
|
530
|
+
maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight
|
|
531
|
+
}),
|
|
532
|
+
[maxHeight]
|
|
533
|
+
);
|
|
534
|
+
return { containerClassNames, containerStyles };
|
|
535
|
+
}
|
|
536
|
+
function CustomTagInputRenderer({
|
|
537
|
+
value,
|
|
538
|
+
insertAtIndex,
|
|
539
|
+
draggedIndex,
|
|
540
|
+
dragOverIndex,
|
|
541
|
+
readOnly,
|
|
542
|
+
enableReorder,
|
|
543
|
+
inlinePlaceholder,
|
|
544
|
+
inputRef,
|
|
545
|
+
disabled,
|
|
546
|
+
placeholder,
|
|
547
|
+
handleRemove,
|
|
548
|
+
handleEdit,
|
|
549
|
+
handleDragStart,
|
|
550
|
+
handleDragOver,
|
|
551
|
+
handleDrag,
|
|
552
|
+
handleDragEnd,
|
|
553
|
+
handleInlineAdd,
|
|
554
|
+
handleInlineCancel,
|
|
555
|
+
handleSpacerClick,
|
|
556
|
+
handleKeyDown,
|
|
557
|
+
handlePaste,
|
|
558
|
+
setInputValue,
|
|
559
|
+
handleMouseDown
|
|
560
|
+
}) {
|
|
561
|
+
const showFinalInlineInput = insertAtIndex === value.length;
|
|
562
|
+
const inputPlaceholder = value.length === 0 ? placeholder : "";
|
|
563
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, value.map((item, index) => /* @__PURE__ */ React.createElement(
|
|
564
|
+
TagItemWrapper,
|
|
565
|
+
{
|
|
566
|
+
key: item.id,
|
|
567
|
+
item,
|
|
568
|
+
index,
|
|
569
|
+
insertAtIndex,
|
|
570
|
+
draggedIndex,
|
|
571
|
+
dragOverIndex,
|
|
572
|
+
readOnly,
|
|
573
|
+
enableReorder,
|
|
574
|
+
inlinePlaceholder,
|
|
575
|
+
onRemove: handleRemove,
|
|
576
|
+
onEdit: handleEdit,
|
|
577
|
+
onDragStart: handleDragStart,
|
|
578
|
+
onDragOver: handleDragOver,
|
|
579
|
+
onDrag: handleDrag,
|
|
580
|
+
onDragEnd: handleDragEnd,
|
|
581
|
+
onInlineAdd: handleInlineAdd,
|
|
582
|
+
onInlineCancel: handleInlineCancel,
|
|
583
|
+
onSpacerClick: handleSpacerClick,
|
|
584
|
+
onMouseDown: handleMouseDown
|
|
585
|
+
}
|
|
586
|
+
)), showFinalInlineInput && /* @__PURE__ */ React.createElement(
|
|
587
|
+
InlineInput,
|
|
588
|
+
{
|
|
589
|
+
onAdd: handleInlineAdd,
|
|
590
|
+
onCancel: handleInlineCancel,
|
|
591
|
+
placeholder: inlinePlaceholder
|
|
592
|
+
}
|
|
593
|
+
), !readOnly && /* @__PURE__ */ React.createElement(
|
|
594
|
+
"span",
|
|
595
|
+
{
|
|
596
|
+
ref: inputRef,
|
|
597
|
+
contentEditable: !disabled,
|
|
598
|
+
suppressContentEditableWarning: true,
|
|
599
|
+
onInput: (e) => {
|
|
600
|
+
const target = e.target;
|
|
601
|
+
setInputValue(target.textContent || "");
|
|
602
|
+
},
|
|
603
|
+
onKeyDown: handleKeyDown,
|
|
604
|
+
onPaste: handlePaste,
|
|
605
|
+
"data-placeholder": inputPlaceholder,
|
|
606
|
+
className: "bg-transparent border-none outline-none body-small-400 text-grey-800 min-h-[1.5rem] empty:before:content-[attr(data-placeholder)] empty:before:text-grey-400 leading-6",
|
|
607
|
+
style: { flex: "1 1 auto", minWidth: "3.75rem", wordBreak: "break-word" }
|
|
608
|
+
}
|
|
609
|
+
));
|
|
610
|
+
}
|
|
611
|
+
function CustomTagInput(props) {
|
|
612
|
+
const {
|
|
613
|
+
value,
|
|
614
|
+
onChange,
|
|
615
|
+
maxHeight = "200px",
|
|
616
|
+
placeholder = "Digite e pressione Enter para adicionar",
|
|
617
|
+
inlinePlaceholder = "Digite...",
|
|
618
|
+
disabled = false,
|
|
619
|
+
readOnly = false,
|
|
620
|
+
className = "",
|
|
621
|
+
variant = "default",
|
|
622
|
+
radius = "default",
|
|
623
|
+
allowDuplicates = false,
|
|
624
|
+
enableReorder = true,
|
|
625
|
+
onTagEdit,
|
|
626
|
+
...restProps
|
|
627
|
+
} = props;
|
|
628
|
+
const [inputValue, setInputValue] = React.useState("");
|
|
629
|
+
const [insertAtIndex, setInsertAtIndex] = React.useState(null);
|
|
630
|
+
const inputRef = React.useRef(null);
|
|
631
|
+
const containerRef = React.useRef(null);
|
|
632
|
+
const { handleAddTag, handleAddMultipleTags, handleRemove } = useTagManagement(
|
|
633
|
+
value,
|
|
634
|
+
onChange,
|
|
635
|
+
allowDuplicates
|
|
636
|
+
);
|
|
637
|
+
const { draggedIndex, dragOverIndex, handleDragStart, handleDragOver, handleDrag, handleDragEnd, handleMouseDown } = useDragAndDrop(value, onChange, containerRef);
|
|
638
|
+
const { handleKeyDown, handlePaste } = useInputHandlers(
|
|
639
|
+
inputValue,
|
|
640
|
+
setInputValue,
|
|
641
|
+
value,
|
|
642
|
+
onChange,
|
|
643
|
+
handleAddTag,
|
|
644
|
+
handleAddMultipleTags,
|
|
645
|
+
inputRef
|
|
646
|
+
);
|
|
647
|
+
const { handleEdit } = useTagEditHandler(value, onChange, onTagEdit);
|
|
648
|
+
const { handleContainerClick } = useContainerClickHandler(disabled, inputRef);
|
|
649
|
+
const { handleInlineAdd, handleInlineCancel, handleSpacerClick } = useInlineInputHandlers(
|
|
650
|
+
insertAtIndex,
|
|
651
|
+
setInsertAtIndex,
|
|
652
|
+
handleAddTag,
|
|
653
|
+
readOnly,
|
|
654
|
+
disabled
|
|
655
|
+
);
|
|
656
|
+
const { containerClassNames, containerStyles } = useContainerStyles(
|
|
657
|
+
variant,
|
|
658
|
+
radius,
|
|
659
|
+
disabled,
|
|
660
|
+
className,
|
|
661
|
+
maxHeight
|
|
662
|
+
);
|
|
663
|
+
return /* @__PURE__ */ React.createElement(
|
|
664
|
+
"div",
|
|
665
|
+
{
|
|
666
|
+
className: containerClassNames,
|
|
667
|
+
tabIndex: 0,
|
|
668
|
+
role: "textbox",
|
|
669
|
+
"aria-multiline": "true",
|
|
670
|
+
"aria-disabled": disabled,
|
|
671
|
+
"aria-readonly": readOnly,
|
|
672
|
+
...restProps
|
|
673
|
+
},
|
|
674
|
+
/* @__PURE__ */ React.createElement(
|
|
675
|
+
"div",
|
|
676
|
+
{
|
|
677
|
+
ref: containerRef,
|
|
678
|
+
className: "flex flex-wrap content-start items-start w-full h-full gap-1 pr-3 pl-0 py-0 overflow-y-auto overflow-x-hidden [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-grey-300 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb:hover]:bg-grey-400",
|
|
679
|
+
style: containerStyles,
|
|
680
|
+
onClick: handleContainerClick
|
|
681
|
+
},
|
|
682
|
+
/* @__PURE__ */ React.createElement(
|
|
683
|
+
CustomTagInputRenderer,
|
|
684
|
+
{
|
|
685
|
+
value,
|
|
686
|
+
insertAtIndex,
|
|
687
|
+
draggedIndex,
|
|
688
|
+
dragOverIndex,
|
|
689
|
+
readOnly,
|
|
690
|
+
enableReorder,
|
|
691
|
+
inlinePlaceholder,
|
|
692
|
+
inputRef,
|
|
693
|
+
disabled,
|
|
694
|
+
placeholder,
|
|
695
|
+
handleRemove,
|
|
696
|
+
handleEdit,
|
|
697
|
+
handleDragStart,
|
|
698
|
+
handleDragOver,
|
|
699
|
+
handleDrag,
|
|
700
|
+
handleDragEnd,
|
|
701
|
+
handleInlineAdd,
|
|
702
|
+
handleInlineCancel,
|
|
703
|
+
handleSpacerClick,
|
|
704
|
+
handleKeyDown,
|
|
705
|
+
handlePaste,
|
|
706
|
+
setInputValue,
|
|
707
|
+
handleMouseDown
|
|
708
|
+
}
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
export { CustomTagInput };
|