@enableai-base/ui 1.0.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.
@@ -0,0 +1,1361 @@
1
+ /**
2
+ * @enableai-base/ui v1.0.0
3
+ * (c) 2024-present Enableai
4
+ * @license Proprietary
5
+ **/
6
+ import React, { useState, useEffect, forwardRef, useRef, useCallback, useImperativeHandle, useMemo } from 'react';
7
+ import { Skeleton, Flex, Avatar, Badge, Button, Tag, Tooltip, Checkbox, Radio, Row, ColorPicker, Popover, Input, Space, Spin, Typography, Modal, Upload } from 'antd';
8
+ import { getColorByStr, COLOR_BG_CELL } from '@enableai-base/core';
9
+ import { waitPromise } from '@enableai-sdk-common/core';
10
+ import { CloseOutlined, CaretDownOutlined, LinkOutlined, HolderOutlined, PlusOutlined, EllipsisOutlined, UploadOutlined } from '@ant-design/icons';
11
+ import { getTextColorForBackground } from '@enableai-sdk-common/color';
12
+ import { initEnableAIBaseFontIcon, EnableAIBaseFontIcon } from '@enableai-fonts/font-icon-enableai-base';
13
+ import { getUUID } from '@enableai-sdk-common/id';
14
+ import { useMount } from '@enableai-sdk-common/react-hooks';
15
+ import { cloneDeep } from 'lodash';
16
+ import VirtualList from 'react-tiny-virtual-list';
17
+ import CryptoJS from 'crypto-js';
18
+ import { useSortable, sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable';
19
+ import { CSS } from '@dnd-kit/utilities';
20
+ import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext } from '@dnd-kit/core';
21
+ import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
22
+
23
+ const USER_POPOVER_WIDTH = 160;
24
+ const NAME_FONT_SIZE = 16;
25
+ const UserPopoverWrapper = ({ children }) => /* @__PURE__ */ React.createElement(Flex, { style: { width: USER_POPOVER_WIDTH, justifyContent: "center" } }, children);
26
+ const SkeletonLine = ({ height, width }) => /* @__PURE__ */ React.createElement(
27
+ Skeleton.Button,
28
+ {
29
+ active: true,
30
+ style: {
31
+ width,
32
+ height,
33
+ padding: 0,
34
+ minWidth: 0,
35
+ borderRadius: 2
36
+ }
37
+ }
38
+ );
39
+ const UserLoadingSkeleton = ({ fontSize }) => {
40
+ return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
41
+ "div",
42
+ {
43
+ style: {
44
+ display: "flex",
45
+ gap: 12,
46
+ alignItems: "center",
47
+ marginBottom: 8
48
+ }
49
+ },
50
+ /* @__PURE__ */ React.createElement(Skeleton.Avatar, { size: 48, active: true, shape: "circle" }),
51
+ /* @__PURE__ */ React.createElement(
52
+ "div",
53
+ {
54
+ style: {
55
+ flex: 1,
56
+ display: "flex",
57
+ flexDirection: "column",
58
+ gap: 4,
59
+ overflow: "hidden"
60
+ }
61
+ },
62
+ /* @__PURE__ */ React.createElement(SkeletonLine, { height: NAME_FONT_SIZE, width: 80 }),
63
+ /* @__PURE__ */ React.createElement(SkeletonLine, { height: fontSize, width: 80 }),
64
+ /* @__PURE__ */ React.createElement(SkeletonLine, { height: fontSize, width: 80 })
65
+ )
66
+ ), /* @__PURE__ */ React.createElement(SkeletonLine, { height: fontSize, width: 140 }));
67
+ };
68
+ const AvatarPopoverContent = (props) => {
69
+ const { id, column, fontSize = 14 } = props;
70
+ const [value, setValue] = useState(null);
71
+ const [loading, setLoading] = useState(false);
72
+ useEffect(() => {
73
+ const fetchDetail = async () => {
74
+ if (column && id != null && typeof column.request === "function") {
75
+ try {
76
+ setLoading(true);
77
+ const v = await column.request(id);
78
+ setValue(v);
79
+ } catch (e) {
80
+ console.error("AvatarPopover fetchDetail error:", e);
81
+ setValue(null);
82
+ } finally {
83
+ setLoading(false);
84
+ }
85
+ } else {
86
+ setValue(null);
87
+ setLoading(false);
88
+ }
89
+ };
90
+ fetchDetail();
91
+ }, [id, column]);
92
+ const userContent = loading ? /* @__PURE__ */ React.createElement(UserPopoverWrapper, null, /* @__PURE__ */ React.createElement(UserLoadingSkeleton, { fontSize })) : /* @__PURE__ */ React.createElement(Flex, { style: { minWidth: USER_POPOVER_WIDTH, justifyContent: "center" } }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
93
+ "div",
94
+ {
95
+ style: {
96
+ display: "flex",
97
+ gap: 12,
98
+ alignItems: "center",
99
+ marginBottom: 8
100
+ }
101
+ },
102
+ /* @__PURE__ */ React.createElement(
103
+ Avatar,
104
+ {
105
+ size: 48,
106
+ src: value?.avatar,
107
+ style: { backgroundColor: value?.name === "\u65E0" ? "#D9D9D9" : getColorByStr(value?.name?.slice(-2)) }
108
+ },
109
+ value?.name ? value.name.split(-2) : "\u672A\u77E5"
110
+ ),
111
+ /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement(
112
+ "div",
113
+ {
114
+ style: {
115
+ fontSize: NAME_FONT_SIZE,
116
+ fontWeight: 500,
117
+ marginBottom: 4
118
+ }
119
+ },
120
+ value?.name || "\u672A\u77E5"
121
+ ), /* @__PURE__ */ React.createElement("div", { style: { fontSize, color: "#666", marginBottom: 4 } }, value?.role || "\u6682\u65E0\u89D2\u8272\u4FE1\u606F"), value?.mobile && /* @__PURE__ */ React.createElement("div", { style: { fontSize, color: "#666" } }, value.mobile))
122
+ ), value?.status && /* @__PURE__ */ React.createElement("div", { style: { fontSize, color: "#333", marginLeft: 4 } }, /* @__PURE__ */ React.createElement(
123
+ Badge,
124
+ {
125
+ color: value.status === "\u5728\u7EBF" ? "green" : "gray",
126
+ style: { margin: "0 4px", transform: "scale(1.5)" }
127
+ }
128
+ ), value.status)));
129
+ return userContent;
130
+ };
131
+
132
+ const BlinkButton = forwardRef(function BlinkButton2({ children, style, blinkInterval = 300, blinkTimes = 2, dimOpacity = 0.4, ...props }, ref) {
133
+ const [shine, setShine] = useState(true);
134
+ const aliveRef = useRef(true);
135
+ const runningRef = useRef(false);
136
+ useEffect(() => {
137
+ return () => {
138
+ aliveRef.current = false;
139
+ };
140
+ }, []);
141
+ const blink = useCallback(async () => {
142
+ if (runningRef.current) return;
143
+ runningRef.current = true;
144
+ try {
145
+ for (let i = 0; i < blinkTimes; i++) {
146
+ if (!aliveRef.current) return;
147
+ setShine(false);
148
+ await waitPromise(blinkInterval);
149
+ if (!aliveRef.current) return;
150
+ setShine(true);
151
+ await waitPromise(blinkInterval);
152
+ }
153
+ } finally {
154
+ runningRef.current = false;
155
+ }
156
+ }, [blinkInterval, blinkTimes]);
157
+ useImperativeHandle(ref, () => ({ blink }), [blink]);
158
+ const mergedStyle = useMemo(
159
+ () => ({
160
+ ...style,
161
+ opacity: shine ? 1 : dimOpacity
162
+ }),
163
+ [style, shine, dimOpacity]
164
+ );
165
+ return /* @__PURE__ */ React.createElement(Button, { ...props, style: mergedStyle }, children);
166
+ });
167
+
168
+ const CustomTag = ({
169
+ label,
170
+ maxWidth = "100%",
171
+ color,
172
+ fontSize = 13,
173
+ closable = false,
174
+ onClose,
175
+ style = {},
176
+ className = "",
177
+ ...tagProps
178
+ }) => {
179
+ const textRef = useRef(null);
180
+ const [isOverflowed, setIsOverflowed] = useState(false);
181
+ const [isTooltipVisible, setIsTooltipVisible] = useState(false);
182
+ const fontColor = useMemo(() => getTextColorForBackground(color || "#000000"), [color]);
183
+ useEffect(() => {
184
+ const checkOverflow = () => {
185
+ if (textRef.current) {
186
+ const element = textRef.current;
187
+ const isOverflowing = element.scrollWidth > element.clientWidth;
188
+ setIsOverflowed(isOverflowing);
189
+ }
190
+ };
191
+ checkOverflow();
192
+ window.addEventListener("resize", checkOverflow);
193
+ return () => window.removeEventListener("resize", checkOverflow);
194
+ }, [label, maxWidth]);
195
+ const tagStyle = useMemo(
196
+ () => ({
197
+ maxWidth,
198
+ overflow: "hidden",
199
+ textOverflow: "ellipsis",
200
+ whiteSpace: "nowrap",
201
+ borderRadius: "20px",
202
+ verticalAlign: "middle",
203
+ border: "unset",
204
+ color: fontColor,
205
+ backgroundColor: color,
206
+ ...style
207
+ }),
208
+ [maxWidth, fontColor, color, style]
209
+ );
210
+ const handleTooltipVisibleChange = (visible) => {
211
+ if (isOverflowed) {
212
+ setIsTooltipVisible(visible);
213
+ }
214
+ };
215
+ const tagContent = /* @__PURE__ */ React.createElement(
216
+ Tag,
217
+ {
218
+ closable,
219
+ onClose,
220
+ closeIcon: /* @__PURE__ */ React.createElement(CloseOutlined, { style: { fontSize: 10, color: fontColor } }),
221
+ style: tagStyle,
222
+ className: `custom-tag ${className}`,
223
+ ...tagProps
224
+ },
225
+ /* @__PURE__ */ React.createElement(
226
+ "span",
227
+ {
228
+ ref: textRef,
229
+ style: {
230
+ display: "inline-block",
231
+ maxWidth: `${closable ? "calc(100% - 17px)" : "100%"}`,
232
+ //17是图标宽度+图标间距
233
+ overflow: "hidden",
234
+ textOverflow: "ellipsis",
235
+ fontSize: `${fontSize}px`,
236
+ whiteSpace: "nowrap",
237
+ verticalAlign: "bottom"
238
+ }
239
+ },
240
+ label
241
+ )
242
+ );
243
+ if (isOverflowed) {
244
+ return /* @__PURE__ */ React.createElement(
245
+ Tooltip,
246
+ {
247
+ title: label,
248
+ open: isTooltipVisible,
249
+ onOpenChange: handleTooltipVisibleChange,
250
+ mouseEnterDelay: 0.5,
251
+ styles: { root: { maxWidth: "400px", wordBreak: "break-all" } }
252
+ },
253
+ tagContent
254
+ );
255
+ }
256
+ return tagContent;
257
+ };
258
+
259
+ const ColorCheckboxGroup = ({ value = [], options = [], onChange, disabled }) => {
260
+ const innerValue = value.map((v) => v.value);
261
+ const handleChange = (checkedValues) => {
262
+ const next = options.filter((opt) => checkedValues.includes(opt.value));
263
+ onChange?.(next);
264
+ };
265
+ return /* @__PURE__ */ React.createElement(Checkbox.Group, { value: innerValue, onChange: handleChange, disabled, className: "color-checkbox-group" }, options.map((option) => {
266
+ const checked = innerValue.includes(option.value);
267
+ return /* @__PURE__ */ React.createElement(
268
+ Checkbox,
269
+ {
270
+ key: option.value,
271
+ value: option.value,
272
+ className: `color-checkbox-item ${checked ? "checked" : ""}`
273
+ },
274
+ /* @__PURE__ */ React.createElement(CustomTag, { color: option.color, className: "color-checkbox-tag", label: option.label })
275
+ );
276
+ }));
277
+ };
278
+
279
+ const ColorRadioGroup = ({ value = [], options = [], onChange, disabled }) => {
280
+ const innerValue = value[0]?.value;
281
+ const handleChange = (e) => {
282
+ const selected = options.find((opt) => opt.value === e.target.value);
283
+ onChange?.(selected ? [selected] : []);
284
+ };
285
+ return /* @__PURE__ */ React.createElement(Radio.Group, { value: innerValue, onChange: handleChange, disabled, className: "color-radio-group" }, options.map((option) => {
286
+ const checked = option.value === innerValue;
287
+ return /* @__PURE__ */ React.createElement(Radio, { key: option.value, value: option.value, className: `color-radio-item ${checked ? "checked" : ""}` }, /* @__PURE__ */ React.createElement(CustomTag, { color: option.color, className: "color-radio-tag", label: option.label }));
288
+ }));
289
+ };
290
+
291
+ const ColorPickerButton = ({ color, ...args }) => {
292
+ const iconColor = useMemo(() => color ? getTextColorForBackground(color) : "var(--ant-color-text-base)", [color]);
293
+ return /* @__PURE__ */ React.createElement("div", { ...args }, /* @__PURE__ */ React.createElement("div", { className: `color-picker-button` }, /* @__PURE__ */ React.createElement("span", { className: "universe-icon", style: { backgroundColor: color, color: iconColor } }, /* @__PURE__ */ React.createElement(CaretDownOutlined, null)), /* @__PURE__ */ React.createElement("div", { className: "color-picker-button-mask" }, " ")));
294
+ };
295
+
296
+ const FixedColorPanel = (_, { components: { Presets } }) => {
297
+ return /* @__PURE__ */ React.createElement(Row, { justify: "space-between", wrap: false, className: "color-picker-panel" }, /* @__PURE__ */ React.createElement(Presets, null));
298
+ };
299
+
300
+ const presetColors = [
301
+ {
302
+ label: "group1",
303
+ key: "group1",
304
+ colors: [
305
+ "#ff7570",
306
+ "#ff811a",
307
+ "#ffc60a",
308
+ "#91ad00",
309
+ "#35bd4b",
310
+ "#2dbeab",
311
+ "#25b0e7",
312
+ "#7aa2ff",
313
+ "#eb78b8",
314
+ "#b791fa",
315
+ "#8f959e"
316
+ ]
317
+ },
318
+ {
319
+ label: "group2",
320
+ key: "group2",
321
+ colors: [
322
+ "#fee3e2",
323
+ "#fee7cd",
324
+ "#faedc2",
325
+ "#e3f0a3",
326
+ "#d0f5ce",
327
+ "#c4f2ec",
328
+ "#caeffc",
329
+ "#e0e9ff",
330
+ "#fee2f2",
331
+ "#efe6fe",
332
+ "#eff0f1"
333
+ ]
334
+ },
335
+ {
336
+ label: "group3",
337
+ key: "group3",
338
+ colors: [
339
+ "#fdc6c4",
340
+ "#fec48b",
341
+ "#fcdf7e",
342
+ "#c8dd5f",
343
+ "#95e599",
344
+ "#6fe8d8",
345
+ "#97dcfc",
346
+ "#c2d4ff",
347
+ "#f8c4e1",
348
+ "#dcc9fd",
349
+ "#dee0e3"
350
+ ]
351
+ },
352
+ {
353
+ label: "group4",
354
+ key: "group4",
355
+ colors: [
356
+ "#f54a45",
357
+ "#ed6d0c",
358
+ "#d99904",
359
+ "#7b9207",
360
+ "#32a645",
361
+ "#10a893",
362
+ "#1295ca",
363
+ "#5083fb",
364
+ "#df58a5",
365
+ "#9f6ff1",
366
+ "#646a73"
367
+ ]
368
+ },
369
+ {
370
+ label: "group5",
371
+ key: "group5",
372
+ colors: [
373
+ "#c02a26",
374
+ "#a44904",
375
+ "#865b03",
376
+ "#5c6d08",
377
+ "#1a7526",
378
+ "#067062",
379
+ "#076a94",
380
+ "#1456f0",
381
+ "#b82879",
382
+ "#7a35f0",
383
+ "#373c43"
384
+ ]
385
+ }
386
+ ];
387
+
388
+ const FixedColorPicker = ({ ...args }) => {
389
+ const [clonedPresetColor] = useState(() => cloneDeep(presetColors));
390
+ return /* @__PURE__ */ React.createElement(
391
+ ColorPicker,
392
+ {
393
+ ...{
394
+ placement: "left",
395
+ panelRender: FixedColorPanel,
396
+ presets: clonedPresetColor,
397
+ ...args
398
+ }
399
+ },
400
+ /* @__PURE__ */ React.createElement(ColorPickerButton, { color: args.value })
401
+ );
402
+ };
403
+
404
+ const getRandomColor = () => {
405
+ const colors = presetColors.flatMap((group) => group.colors);
406
+ return colors[Math.floor(Math.random() * colors.length)];
407
+ };
408
+
409
+ const IconFont = ({ name, size = 16, color, className, style }) => {
410
+ const [iconClass, setIconClass] = useState("");
411
+ useEffect(() => {
412
+ initEnableAIBaseFontIcon().then(() => {
413
+ setIconClass(EnableAIBaseFontIcon.get(name));
414
+ });
415
+ }, [name]);
416
+ return /* @__PURE__ */ React.createElement(
417
+ "i",
418
+ {
419
+ className: [iconClass, className].filter(Boolean).join(" "),
420
+ style: { fontSize: size, color, ...style },
421
+ "aria-hidden": "true"
422
+ }
423
+ );
424
+ };
425
+
426
+ function isFocused(el) {
427
+ return !!el && document.activeElement === el;
428
+ }
429
+ function focusToEnd(el) {
430
+ el.focus();
431
+ const selection = window.getSelection();
432
+ if (!selection) return;
433
+ const range = document.createRange();
434
+ range.selectNodeContents(el);
435
+ range.collapse(false);
436
+ selection.removeAllRanges();
437
+ selection.addRange(range);
438
+ }
439
+
440
+ const LinkEdit = ({ value, onChange, disabled, styles, popoverStyles, placeholder }) => {
441
+ const [open, setOpen] = useState(false);
442
+ const [draft, setDraft] = useState(value);
443
+ useEffect(() => {
444
+ setDraft(value);
445
+ }, [value]);
446
+ const updateDraft = (key, v) => {
447
+ setDraft((prev) => ({
448
+ ...prev ?? {},
449
+ [key]: v
450
+ }));
451
+ };
452
+ const confirm = () => {
453
+ dispatchData(draft);
454
+ setOpen(false);
455
+ updateInputTitle(draft?.title ?? "");
456
+ };
457
+ const cancel = () => {
458
+ setDraft(value);
459
+ updateInputTitle(value?.title ?? "");
460
+ setOpen(false);
461
+ };
462
+ const dispatchData = useCallback(
463
+ (data) => {
464
+ const isTitleChange = !Object.is(value?.title, data?.title);
465
+ const isLinkChange = !Object.is(value?.url, data?.url);
466
+ if (isTitleChange || isLinkChange) onChange?.(data);
467
+ },
468
+ [onChange, value?.title, value?.url]
469
+ );
470
+ const inputTitleRef = useRef(value?.title ?? "");
471
+ const [hasTitle, setHasTitle] = useState(!!value?.title);
472
+ const updateInputTitle = useCallback((title, focus = false) => {
473
+ inputTitleRef.current = title;
474
+ setHasTitle(!!title.length);
475
+ if (focus) {
476
+ requestAnimationFrame(() => {
477
+ if (ref1.current && !isFocused(ref1.current)) focusToEnd(ref1.current);
478
+ if (ref2.current && !isFocused(ref2.current)) ref2.current.focus();
479
+ });
480
+ }
481
+ }, []);
482
+ const inputLink = useMemo(() => draft?.url, [draft?.url]);
483
+ const [inputRefreshKey, setInputRefreshKey] = useState(getUUID());
484
+ const editingRef = useRef(false);
485
+ const ref1 = useRef(null);
486
+ const ref2 = useRef(null);
487
+ useEffect(() => {
488
+ if (!editingRef.current) return;
489
+ if (ref1.current && !isFocused(ref1.current)) focusToEnd(ref1.current);
490
+ if (ref2.current) ref2.current.focus();
491
+ }, [hasTitle]);
492
+ const isComposingRef = useRef(false);
493
+ return /* @__PURE__ */ React.createElement(
494
+ Popover,
495
+ {
496
+ open,
497
+ onOpenChange: (v) => {
498
+ if (!v) setOpen(v);
499
+ },
500
+ placement: "bottom",
501
+ trigger: ["click"],
502
+ arrow: false,
503
+ content: /* @__PURE__ */ React.createElement(Flex, { style: { ...popoverStyles }, vertical: true, gap: 24 }, /* @__PURE__ */ React.createElement("div", { className: "link-editor-form-item" }, /* @__PURE__ */ React.createElement("div", { className: "item-label" }, "\u6587\u672C"), /* @__PURE__ */ React.createElement(Input, { value: draft?.title, onChange: (e) => updateDraft("title", e.target.value) })), /* @__PURE__ */ React.createElement("div", { className: "link-editor-form-item" }, /* @__PURE__ */ React.createElement("div", { className: "item-label" }, "\u8D85\u94FE\u63A5"), /* @__PURE__ */ React.createElement(Input, { value: draft?.url, onChange: (e) => updateDraft("url", e.target.value) })), /* @__PURE__ */ React.createElement(Flex, { justify: "flex-end" }, /* @__PURE__ */ React.createElement(Space, null, /* @__PURE__ */ React.createElement(Button, { size: "small", onClick: cancel }, "\u53D6\u6D88"), /* @__PURE__ */ React.createElement(Button, { size: "small", type: "primary", onClick: confirm }, "\u786E\u5B9A"))))
504
+ },
505
+ /* @__PURE__ */ React.createElement(
506
+ Flex,
507
+ {
508
+ align: "center",
509
+ style: {
510
+ backgroundColor: COLOR_BG_CELL,
511
+ borderRadius: 0,
512
+ ...styles
513
+ }
514
+ },
515
+ hasTitle ? /* @__PURE__ */ React.createElement(
516
+ "div",
517
+ {
518
+ className: "link-editor-input content-editable-clear",
519
+ key: inputRefreshKey,
520
+ style: {
521
+ color: `${hasTitle ? "var(--ant-color-link)" : "var(--ant-color-text)"}`
522
+ },
523
+ onClick: (e) => {
524
+ if (e.nativeEvent.target?.nodeName === "DIV") {
525
+ if (ref1.current) {
526
+ editingRef.current = true;
527
+ focusToEnd(ref1.current);
528
+ }
529
+ }
530
+ }
531
+ },
532
+ /* @__PURE__ */ React.createElement(
533
+ "a",
534
+ {
535
+ ref: ref1,
536
+ contentEditable: !disabled,
537
+ suppressContentEditableWarning: true,
538
+ className: "link-editor-input-a content-editable-clear",
539
+ style: {
540
+ color: `${disabled ? "var(--ant-color-text-disabled)" : "var(--ant-color-link)"}`
541
+ },
542
+ onClick: () => window.open(inputLink),
543
+ onCompositionStart: () => {
544
+ isComposingRef.current = true;
545
+ },
546
+ onCompositionEnd: (e) => {
547
+ isComposingRef.current = false;
548
+ const text = e.currentTarget.innerText;
549
+ updateInputTitle(text, true);
550
+ },
551
+ onBlur: (e) => {
552
+ editingRef.current = false;
553
+ const title = e.currentTarget.innerText;
554
+ updateInputTitle(title);
555
+ dispatchData({ ...draft, title });
556
+ setInputRefreshKey(getUUID());
557
+ },
558
+ onInput: (e) => {
559
+ if (isComposingRef.current) return;
560
+ const result = e.currentTarget.innerText;
561
+ updateInputTitle(result, true);
562
+ },
563
+ href: inputLink
564
+ },
565
+ inputTitleRef.current
566
+ )
567
+ ) : /* @__PURE__ */ React.createElement(
568
+ "input",
569
+ {
570
+ disabled,
571
+ placeholder,
572
+ ref: ref2,
573
+ style: { flex: 1 },
574
+ className: "content-editable-clear link-editor-placeholder",
575
+ onCompositionStart: () => {
576
+ isComposingRef.current = true;
577
+ },
578
+ onCompositionEnd: (e) => {
579
+ isComposingRef.current = false;
580
+ const text = e.target.value;
581
+ updateInputTitle(text, true);
582
+ },
583
+ onInput: (e) => {
584
+ const input = e.target.value;
585
+ if (isComposingRef.current) return;
586
+ updateInputTitle(input, true);
587
+ },
588
+ onBlur: (e) => {
589
+ const title = e.currentTarget.innerText;
590
+ updateInputTitle(title);
591
+ dispatchData({ ...draft, title });
592
+ }
593
+ }
594
+ ),
595
+ /* @__PURE__ */ React.createElement(Button, { disabled, type: "text", icon: /* @__PURE__ */ React.createElement(LinkOutlined, null), onClick: () => setOpen((v) => !v) })
596
+ )
597
+ );
598
+ };
599
+
600
+ const OptionItem = ({
601
+ ref,
602
+ onRemove,
603
+ dispatch,
604
+ value,
605
+ onChange,
606
+ onEnterKeyUp,
607
+ style = {},
608
+ disabled
609
+ }) => {
610
+ const inputRef = useRef(null);
611
+ const { color, label } = value;
612
+ const { isDragging, attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: value.value });
613
+ const listStyle = {
614
+ ...style,
615
+ transform: CSS.Translate.toString(transform),
616
+ transition,
617
+ ...isDragging ? { position: "relative", zIndex: 9999 } : {}
618
+ };
619
+ useImperativeHandle(ref, () => {
620
+ return {
621
+ focusInput() {
622
+ inputRef.current?.focus();
623
+ }
624
+ };
625
+ });
626
+ return /* @__PURE__ */ React.createElement(Flex, { className: `option-item`, align: "center", style: listStyle, ref: setNodeRef, gap: "small" }, /* @__PURE__ */ React.createElement(HolderOutlined, { className: "drag-point", ...attributes, ...listeners }), /* @__PURE__ */ React.createElement(
627
+ FixedColorPicker,
628
+ {
629
+ value: color,
630
+ disabled,
631
+ onChange: (colorValue) => {
632
+ const prev = value;
633
+ const next = { ...value, color: colorValue.toHexString() };
634
+ onChange(next);
635
+ if (dispatch) dispatch({ type: "edit-value", next, prev });
636
+ }
637
+ }
638
+ ), /* @__PURE__ */ React.createElement(
639
+ Input,
640
+ {
641
+ ref: inputRef,
642
+ className: "option-item-input",
643
+ value: label,
644
+ disabled,
645
+ onChange: (event) => {
646
+ const prev = value;
647
+ const next = { ...value, label: event.target.value };
648
+ onChange(next);
649
+ if (dispatch) dispatch({ type: "edit-value", next, prev });
650
+ },
651
+ onPressEnter: (e) => {
652
+ e.preventDefault();
653
+ },
654
+ onKeyUp: (e) => {
655
+ if (e.code === "Enter") onEnterKeyUp(e);
656
+ }
657
+ }
658
+ ), !disabled && /* @__PURE__ */ React.createElement(
659
+ Button,
660
+ {
661
+ className: "delete-button",
662
+ type: "text",
663
+ icon: /* @__PURE__ */ React.createElement(CloseOutlined, { className: "delete-icon" }),
664
+ onClick: () => {
665
+ onRemove(value);
666
+ if (dispatch) dispatch({ type: "delete", prev: value, next: null });
667
+ }
668
+ }
669
+ ));
670
+ };
671
+
672
+ const OptionEditor = ({
673
+ initialValues,
674
+ onValuesChange,
675
+ virtualConfig = { height: 600 },
676
+ dispatch,
677
+ style = { width: "100%" },
678
+ disabled
679
+ }) => {
680
+ const lastInputNeedFocus = useRef(false);
681
+ const inputRefs = useRef([]);
682
+ const [virtualValues, setVirtualValues] = useState(
683
+ () => initialValues.map((item, index) => ({ ...item, ordinal: index }))
684
+ );
685
+ const updateVirtualValues = useCallback(
686
+ (values) => {
687
+ setVirtualValues(values);
688
+ if (onValuesChange) onValuesChange(values);
689
+ },
690
+ [onValuesChange]
691
+ );
692
+ const onItemChange = useCallback(
693
+ (value) => {
694
+ const newValues = virtualValues.map((item) => item.value === value.value ? value : item);
695
+ updateVirtualValues(newValues);
696
+ },
697
+ [virtualValues, updateVirtualValues]
698
+ );
699
+ const onItemRemove = useCallback(
700
+ ({ value }) => {
701
+ const newValues = virtualValues.filter((item) => item.value !== value);
702
+ updateVirtualValues(newValues);
703
+ },
704
+ [virtualValues, updateVirtualValues]
705
+ );
706
+ const onAddItem = useCallback(() => {
707
+ const id = getUUID();
708
+ const newValues = [
709
+ ...virtualValues,
710
+ { value: id, color: getRandomColor(), label: ``, ordinal: virtualValues.length }
711
+ ];
712
+ updateVirtualValues(newValues);
713
+ if (dispatch) {
714
+ dispatch({ type: "add", prev: null, next: newValues[newValues.length - 1] });
715
+ }
716
+ }, [virtualValues, updateVirtualValues, dispatch]);
717
+ const onDragEnd = ({ active, over }) => {
718
+ if (active && over && active.id !== over.id) {
719
+ const activeIndex = virtualValues.findIndex((i) => i.value === active.id);
720
+ const overIndex = virtualValues.findIndex((i) => i.value === over.id);
721
+ const newValues = arrayMove(virtualValues, activeIndex, overIndex);
722
+ updateVirtualValues(newValues);
723
+ }
724
+ };
725
+ const sensors = useSensors(
726
+ useSensor(PointerSensor),
727
+ useSensor(KeyboardSensor, {
728
+ // Disable smooth scrolling in Cypress automated tests
729
+ scrollBehavior: "Cypress" in window ? "auto" : void 0,
730
+ coordinateGetter: sortableKeyboardCoordinates
731
+ })
732
+ );
733
+ const onEnterKeyUp = useCallback(
734
+ (value) => {
735
+ const { value: valueId } = value;
736
+ const last = virtualValues[virtualValues.length - 1];
737
+ if (last && last.value === valueId) onAddItem();
738
+ lastInputNeedFocus.current = true;
739
+ },
740
+ [onAddItem, virtualValues]
741
+ );
742
+ useEffect(() => {
743
+ if (lastInputNeedFocus.current) {
744
+ const lastValue = virtualValues[virtualValues.length - 1];
745
+ const lastInput = inputRefs.current[lastValue.ordinal];
746
+ if (lastInput) lastInput.focusInput();
747
+ lastInputNeedFocus.current = false;
748
+ }
749
+ }, [virtualValues]);
750
+ return /* @__PURE__ */ React.createElement(Space, { direction: "vertical", className: "option-editor", style: { ...style } }, /* @__PURE__ */ React.createElement(Flex, { className: `option-editor-title` }, /* @__PURE__ */ React.createElement(Button, { type: "text", onClick: onAddItem, disabled }, /* @__PURE__ */ React.createElement(PlusOutlined, null), "\u6DFB\u52A0\u9009\u9879")), /* @__PURE__ */ React.createElement(
751
+ DndContext,
752
+ {
753
+ onDragEnd,
754
+ sensors: disabled ? [] : sensors,
755
+ modifiers: [restrictToVerticalAxis],
756
+ id: "list-drag-sorting-handler"
757
+ },
758
+ /* @__PURE__ */ React.createElement(SortableContext, { items: virtualValues.map((item) => item.value), strategy: verticalListSortingStrategy }, /* @__PURE__ */ React.createElement(
759
+ VirtualList,
760
+ {
761
+ className: "option-editor-list",
762
+ width: "100%",
763
+ height: virtualConfig.height ?? 600,
764
+ overscanCount: 10,
765
+ itemCount: virtualValues.length,
766
+ itemSize: 40,
767
+ scrollDirection: "vertical",
768
+ renderItem: (item) => {
769
+ const value = virtualValues[item.index];
770
+ return /* @__PURE__ */ React.createElement(
771
+ OptionItem,
772
+ {
773
+ ref: (e) => {
774
+ inputRefs.current[item.index] = e;
775
+ },
776
+ value,
777
+ dispatch,
778
+ onChange: onItemChange,
779
+ onRemove: onItemRemove,
780
+ style: item.style,
781
+ onEnterKeyUp: () => onEnterKeyUp(value),
782
+ key: value.value,
783
+ disabled
784
+ }
785
+ );
786
+ }
787
+ }
788
+ ))
789
+ ));
790
+ };
791
+
792
+ const OptionInput = ({
793
+ option,
794
+ optionPosition,
795
+ inputValue,
796
+ setInputValue,
797
+ updateOptionLabel
798
+ }) => {
799
+ const [isColorPickerOpen, setIsColorPickerOpen] = useState(false);
800
+ const inputRef = useRef(null);
801
+ useMount(() => {
802
+ inputRef.current?.focus();
803
+ });
804
+ return /* @__PURE__ */ React.createElement(
805
+ Input,
806
+ {
807
+ type: "text",
808
+ ref: inputRef,
809
+ onClick: (e) => e.stopPropagation(),
810
+ style: { width: optionPosition.width, height: optionPosition.height },
811
+ suffix: /* @__PURE__ */ React.createElement(
812
+ "span",
813
+ {
814
+ onClick: (e) => {
815
+ e.stopPropagation();
816
+ }
817
+ },
818
+ /* @__PURE__ */ React.createElement(
819
+ FixedColorPicker,
820
+ {
821
+ value: option.color,
822
+ open: isColorPickerOpen,
823
+ rootClassName: "selector-color-picker",
824
+ onOpenChange: (value) => {
825
+ setIsColorPickerOpen(value);
826
+ },
827
+ onChange: (newColor) => {
828
+ setIsColorPickerOpen(false);
829
+ updateOptionLabel(inputValue, newColor.toHexString());
830
+ }
831
+ }
832
+ )
833
+ ),
834
+ value: inputValue,
835
+ onPressEnter: () => updateOptionLabel(inputValue),
836
+ onChange: (e) => setInputValue(e.target.value)
837
+ }
838
+ );
839
+ };
840
+
841
+ const COLOR_BG_OPTION_HOVER = "#1f232914";
842
+ const SelectOptionItem = ({
843
+ editValue,
844
+ option,
845
+ multiple = true,
846
+ onCreate,
847
+ onChange,
848
+ filterOptions,
849
+ onOptionChange,
850
+ onSelect,
851
+ fontSize = 13,
852
+ style
853
+ }) => {
854
+ const [isEdit, setIsEdit] = useState(false);
855
+ const [inputValue, setInputValue] = useState(option.label);
856
+ const [isHovered, setIsHovered] = useState(false);
857
+ const containerRef = useRef(null);
858
+ const [loading, setLoading] = useState(false);
859
+ const loadingRef = useRef(false);
860
+ const isExist = useMemo(
861
+ () => filterOptions.some((item) => inputValue.trim().toLowerCase() === item.label.toLowerCase()),
862
+ [filterOptions, inputValue]
863
+ );
864
+ const [optionPosition, setOptionPosition] = useState({
865
+ x: 0,
866
+ y: 0,
867
+ width: 0,
868
+ height: 0
869
+ });
870
+ const isSelected = useMemo(() => editValue.map((item) => item.value).includes(option.value), [editValue, option]);
871
+ const handleClose = useCallback(() => {
872
+ setIsEdit(false);
873
+ }, []);
874
+ const updateOptionLabel = (newLabel, newColor) => {
875
+ setIsEdit(false);
876
+ onOptionChange?.({ ...option, label: newLabel, color: newColor || option.color });
877
+ };
878
+ const createOptionLabel = () => {
879
+ if (!onCreate) {
880
+ handleItemSelect(option);
881
+ return;
882
+ }
883
+ if (loadingRef.current) return;
884
+ setLoading(true);
885
+ loadingRef.current = true;
886
+ onCreate(option).then((newOption) => {
887
+ handleItemSelect(newOption);
888
+ }).finally(() => {
889
+ setLoading(false);
890
+ loadingRef.current = false;
891
+ });
892
+ };
893
+ const handleItemSelect = useCallback(
894
+ (option2) => {
895
+ if (multiple) {
896
+ const newSelected = isSelected ? editValue : [...editValue, option2];
897
+ onChange?.(newSelected);
898
+ } else {
899
+ onChange?.([option2]);
900
+ }
901
+ onSelect?.(option2);
902
+ },
903
+ [multiple, onSelect, isSelected, editValue, onChange]
904
+ );
905
+ return /* @__PURE__ */ React.createElement(Flex, { style: { padding: "0px 16px 0px 4px", ...style } }, /* @__PURE__ */ React.createElement(
906
+ Flex,
907
+ {
908
+ ref: containerRef,
909
+ className: "option-item",
910
+ onMouseEnter: (e) => {
911
+ setIsHovered(true);
912
+ e.currentTarget.style.backgroundColor = COLOR_BG_OPTION_HOVER;
913
+ },
914
+ onMouseLeave: (e) => {
915
+ setIsHovered(false);
916
+ e.currentTarget.style.backgroundColor = "transparent";
917
+ },
918
+ onClick: () => {
919
+ if (!isExist) return createOptionLabel();
920
+ handleItemSelect(option);
921
+ }
922
+ },
923
+ !isExist ? /* @__PURE__ */ React.createElement(Flex, { gap: 8, style: { width: "100%" } }, /* @__PURE__ */ React.createElement(Spin, { spinning: loading }, /* @__PURE__ */ React.createElement(Typography.Text, null, "\u521B\u5EFA\u9009\u9879"), /* @__PURE__ */ React.createElement(Typography.Text, { strong: true }, option.label))) : /* @__PURE__ */ React.createElement(Flex, { justify: "space-between", align: "center", style: { width: "100%" } }, /* @__PURE__ */ React.createElement(CustomTag, { color: option.color, label: option.label, fontSize }), isHovered && /* @__PURE__ */ React.createElement(
924
+ Button,
925
+ {
926
+ className: "edit-button",
927
+ type: "text",
928
+ icon: /* @__PURE__ */ React.createElement(EllipsisOutlined, { className: "edit-icon" }),
929
+ style: { width: 22, height: 22, marginRight: 8 },
930
+ onClick: (e) => {
931
+ e.stopPropagation();
932
+ setIsEdit(true);
933
+ setOptionPosition({
934
+ x: containerRef.current.getBoundingClientRect().left,
935
+ y: containerRef.current.getBoundingClientRect().top,
936
+ width: containerRef.current.getBoundingClientRect().width,
937
+ height: containerRef.current.getBoundingClientRect().height
938
+ });
939
+ }
940
+ }
941
+ ))
942
+ ), /* @__PURE__ */ React.createElement(
943
+ Modal,
944
+ {
945
+ title: "",
946
+ afterClose: handleClose,
947
+ rootClassName: "option-item-modal",
948
+ open: isEdit,
949
+ footer: null,
950
+ width: optionPosition.width,
951
+ maskTransitionName: "",
952
+ transitionName: "",
953
+ closable: false,
954
+ maskClosable: true,
955
+ onCancel: () => updateOptionLabel(inputValue),
956
+ style: { top: optionPosition.y, left: optionPosition.x, transformOrigin: "top left", margin: "unset" }
957
+ },
958
+ /* @__PURE__ */ React.createElement(
959
+ OptionInput,
960
+ {
961
+ option,
962
+ optionPosition,
963
+ inputValue,
964
+ setInputValue,
965
+ updateOptionLabel
966
+ }
967
+ )
968
+ ));
969
+ };
970
+
971
+ const BORDER_SECONDARY_COLOR = "#f0f0f0";
972
+ const PopoverContent = ({
973
+ editValue,
974
+ popoverRef,
975
+ inputRef,
976
+ open,
977
+ width,
978
+ searchText,
979
+ filteredOptions,
980
+ listHeight,
981
+ itemHeight,
982
+ multiple,
983
+ handleSearch,
984
+ onChange,
985
+ onSelect,
986
+ onCreate,
987
+ onOptionChange,
988
+ fontSize
989
+ }) => {
990
+ const canCreate = useMemo(() => {
991
+ if (!searchText.trim()) return false;
992
+ const lowerSearch = searchText.trim().toLowerCase();
993
+ const res = filteredOptions.some((item) => item.label.toLowerCase() === lowerSearch);
994
+ return !res;
995
+ }, [filteredOptions, searchText]);
996
+ const newOption = useMemo(() => {
997
+ if (canCreate) {
998
+ return { value: getUUID(), label: searchText, color: getRandomColor() };
999
+ }
1000
+ return null;
1001
+ }, [searchText, canCreate]);
1002
+ useEffect(() => {
1003
+ if (open) {
1004
+ inputRef.current?.focus();
1005
+ }
1006
+ }, [open, inputRef]);
1007
+ return /* @__PURE__ */ React.createElement(
1008
+ "div",
1009
+ {
1010
+ ref: popoverRef,
1011
+ className: "select-editor-popover-content",
1012
+ style: {
1013
+ width
1014
+ }
1015
+ },
1016
+ /* @__PURE__ */ React.createElement("div", { style: { border: "none" } }, /* @__PURE__ */ React.createElement(
1017
+ Input,
1018
+ {
1019
+ ref: inputRef,
1020
+ placeholder: "\u67E5\u627E\u6216\u521B\u5EFA\u9009\u9879",
1021
+ autoFocus: true,
1022
+ value: searchText,
1023
+ onChange: handleSearch,
1024
+ contentEditable: true
1025
+ }
1026
+ )),
1027
+ /* @__PURE__ */ React.createElement(
1028
+ "div",
1029
+ {
1030
+ className: "virtual-list-container",
1031
+ style: {
1032
+ height: listHeight,
1033
+ overflow: "hidden",
1034
+ borderTop: `1px solid ${BORDER_SECONDARY_COLOR}`
1035
+ }
1036
+ },
1037
+ /* @__PURE__ */ React.createElement(
1038
+ VirtualList,
1039
+ {
1040
+ width: "100%",
1041
+ height: listHeight,
1042
+ itemCount: !canCreate ? filteredOptions.length : filteredOptions.length + 1,
1043
+ itemSize: itemHeight,
1044
+ renderItem: ({ index, style }) => /* @__PURE__ */ React.createElement(
1045
+ SelectOptionItem,
1046
+ {
1047
+ filterOptions: filteredOptions,
1048
+ editValue,
1049
+ option: filteredOptions[index] ?? newOption,
1050
+ style,
1051
+ key: filteredOptions[index]?.value ?? newOption?.value,
1052
+ multiple,
1053
+ fontSize,
1054
+ onCreate,
1055
+ onChange,
1056
+ onSelect,
1057
+ onOptionChange
1058
+ }
1059
+ ),
1060
+ overscanCount: 5,
1061
+ style: { outline: "none" }
1062
+ }
1063
+ )
1064
+ )
1065
+ );
1066
+ };
1067
+
1068
+ const Select = ({
1069
+ listHeight = 300,
1070
+ itemHeight = 32,
1071
+ emptyText = "\u6682\u65E0\u6570\u636E",
1072
+ multiple = true,
1073
+ options = [],
1074
+ value: editValue = [],
1075
+ placeholder,
1076
+ width,
1077
+ onChange,
1078
+ onSelect,
1079
+ onOptionChange,
1080
+ onOptionCreate,
1081
+ fontSize = 13,
1082
+ onDeselect,
1083
+ tagAreaHeight,
1084
+ tagAreaStyle,
1085
+ showDownIcon = false
1086
+ }) => {
1087
+ const [searchText, setSearchText] = useState("");
1088
+ const popoverRef = useRef(null);
1089
+ const cellRef = useRef(null);
1090
+ const [open, setOpen] = useState(false);
1091
+ const inputRef = useRef(null);
1092
+ const filteredOptions = useMemo(() => {
1093
+ if (!searchText.trim()) return options;
1094
+ const lowerSearch = searchText.toLowerCase();
1095
+ const findOptions = options.filter((item) => item.label.toLowerCase().includes(lowerSearch));
1096
+ return findOptions;
1097
+ }, [options, searchText]);
1098
+ const selectedValue = useMemo(() => {
1099
+ if (!editValue.length) return [];
1100
+ const findItem = (item) => options.find((opt) => opt.value === item.value);
1101
+ return editValue.map((item) => findItem(item)).filter(Boolean);
1102
+ }, [editValue, options]);
1103
+ const handleCreate = async (option) => {
1104
+ const newOption = await onOptionCreate?.(option) ?? option;
1105
+ setSearchText("");
1106
+ return newOption;
1107
+ };
1108
+ const handleSearch = (e) => {
1109
+ setSearchText(e.target.value);
1110
+ };
1111
+ const handleSelect = (option) => {
1112
+ onSelect?.(option);
1113
+ if (inputRef.current) {
1114
+ inputRef.current.focus();
1115
+ }
1116
+ if (cellRef.current && !multiple) {
1117
+ cellRef.current.click();
1118
+ }
1119
+ };
1120
+ const handleDeselect = (option) => {
1121
+ setOpen(false);
1122
+ onDeselect?.(option);
1123
+ const filteredValue = editValue.filter((item) => item.value !== option.value);
1124
+ onChange?.(filteredValue);
1125
+ };
1126
+ useEffect(() => {
1127
+ if (cellRef.current) {
1128
+ cellRef.current.scrollTop = cellRef.current.scrollHeight;
1129
+ }
1130
+ }, [selectedValue]);
1131
+ return /* @__PURE__ */ React.createElement(
1132
+ Popover,
1133
+ {
1134
+ trigger: "click",
1135
+ open,
1136
+ onOpenChange: setOpen,
1137
+ destroyOnHidden: true,
1138
+ content: /* @__PURE__ */ React.createElement(
1139
+ PopoverContent,
1140
+ {
1141
+ editValue: selectedValue,
1142
+ onChange,
1143
+ popoverRef,
1144
+ inputRef,
1145
+ width: width || 163,
1146
+ open,
1147
+ filteredOptions,
1148
+ searchText,
1149
+ handleSearch,
1150
+ listHeight,
1151
+ itemHeight,
1152
+ emptyText,
1153
+ multiple,
1154
+ onSelect: handleSelect,
1155
+ onCreate: handleCreate,
1156
+ onOptionChange,
1157
+ fontSize
1158
+ }
1159
+ ),
1160
+ rootClassName: "multi-select-editor-popover-root",
1161
+ className: "multi-select-editor-popover",
1162
+ arrow: false,
1163
+ align: {
1164
+ points: ["tl", "bl"],
1165
+ // tl(top-left): 将【pop弹窗】左上角 与 Input bl(bottom-left) 左下角对齐
1166
+ offset: [0, 10]
1167
+ }
1168
+ },
1169
+ /* @__PURE__ */ React.createElement(
1170
+ "div",
1171
+ {
1172
+ style: {
1173
+ maxHeight: 80,
1174
+ padding: "4px 0px 4px 8px",
1175
+ overflowY: "hidden",
1176
+ ...tagAreaStyle
1177
+ },
1178
+ className: "tag-area",
1179
+ onClick: (e) => {
1180
+ e.stopPropagation();
1181
+ e.preventDefault();
1182
+ }
1183
+ },
1184
+ /* @__PURE__ */ React.createElement(
1185
+ "div",
1186
+ {
1187
+ ref: cellRef,
1188
+ className: "tag-area-content",
1189
+ style: {
1190
+ minHeight: tagAreaHeight ? tagAreaHeight + 4 - 8 - 4 : "unset",
1191
+ maxHeight: 80 - 8 - 4,
1192
+ overflowY: "auto"
1193
+ }
1194
+ },
1195
+ placeholder && !selectedValue.length ? /* @__PURE__ */ React.createElement("div", { className: "placeholder" }, placeholder) : /* @__PURE__ */ React.createElement(Flex, { gap: 8, align: "flex-start", style: { width: "100%" } }, /* @__PURE__ */ React.createElement(Flex, { gap: 8, wrap: true, style: { width: showDownIcon ? "calc(100% - 32px)" : "100%" } }, selectedValue?.map((selectedItem) => /* @__PURE__ */ React.createElement(
1196
+ CustomTag,
1197
+ {
1198
+ label: selectedItem.label,
1199
+ color: selectedItem.color,
1200
+ key: selectedItem.value,
1201
+ closable: true,
1202
+ onClose: () => handleDeselect(selectedItem),
1203
+ fontSize
1204
+ }
1205
+ ))), showDownIcon && /* @__PURE__ */ React.createElement("div", { className: "icon-occupy" }))
1206
+ ),
1207
+ showDownIcon && /* @__PURE__ */ React.createElement(
1208
+ CaretDownOutlined,
1209
+ {
1210
+ className: "caret-down-icon",
1211
+ style: { position: "absolute", right: 4, top: tagAreaHeight ? tagAreaHeight / 2 - 8 : 0 }
1212
+ }
1213
+ )
1214
+ )
1215
+ );
1216
+ };
1217
+
1218
+ const computeSHA256 = async (file) => {
1219
+ const buffer = await file.arrayBuffer();
1220
+ const wordArray = CryptoJS.lib.WordArray.create(buffer);
1221
+ const hash = CryptoJS.SHA256(wordArray);
1222
+ return hash.toString(CryptoJS.enc.Hex);
1223
+ };
1224
+
1225
+ const UploadListInner = (props) => {
1226
+ const {
1227
+ maxCount = 1,
1228
+ fileList: initialFileList = [],
1229
+ uploadProps,
1230
+ onChange,
1231
+ onPreview,
1232
+ accept,
1233
+ onRemove,
1234
+ beforeUpload,
1235
+ uploadFunction,
1236
+ disabled
1237
+ } = props;
1238
+ const [fileList, setFileList] = useState(initialFileList);
1239
+ return /* @__PURE__ */ React.createElement(
1240
+ Upload,
1241
+ {
1242
+ name: "file",
1243
+ customRequest: async (options) => {
1244
+ const { file: fileInner, onSuccess, onError, onProgress } = options;
1245
+ const file = fileInner;
1246
+ try {
1247
+ const hash = await computeSHA256(file);
1248
+ const path = `upload/${hash}/${file.name}`;
1249
+ await uploadFunction({ file, type: file.type || "application/octet-stream" }, path, ({ percent }) => {
1250
+ onProgress?.({ percent });
1251
+ });
1252
+ onSuccess?.(path);
1253
+ } catch (err) {
1254
+ onError?.(err);
1255
+ }
1256
+ },
1257
+ fileList,
1258
+ onChange: (info) => {
1259
+ const newFileList = info.fileList.map((i) => {
1260
+ if (i.status === "done" && i.response) {
1261
+ return {
1262
+ ...i,
1263
+ url: i.response
1264
+ };
1265
+ }
1266
+ return i;
1267
+ });
1268
+ setFileList(newFileList);
1269
+ onChange?.(info);
1270
+ },
1271
+ onRemove: (file) => {
1272
+ const newFileList = fileList.filter((i) => i.uid !== file.uid);
1273
+ setFileList(newFileList);
1274
+ onRemove?.(file);
1275
+ },
1276
+ onPreview: (file) => {
1277
+ onPreview?.({
1278
+ name: file.name,
1279
+ path: file.url || ""
1280
+ });
1281
+ },
1282
+ maxCount,
1283
+ ...uploadProps,
1284
+ accept,
1285
+ beforeUpload
1286
+ },
1287
+ /* @__PURE__ */ React.createElement(Button, { icon: /* @__PURE__ */ React.createElement(UploadOutlined, null), disabled: disabled ? true : maxCount <= (fileList?.length || 0) }, "\u4E0A\u4F20")
1288
+ );
1289
+ };
1290
+
1291
+ const UploadList = ({
1292
+ onlyOne,
1293
+ value,
1294
+ accept = "",
1295
+ maxCount = 5,
1296
+ onChange,
1297
+ onPreview,
1298
+ uploadFunction,
1299
+ uploadProps,
1300
+ disabled
1301
+ }) => {
1302
+ const fileList = useMemo(() => {
1303
+ if (onlyOne) {
1304
+ if (!value || !value.length) return [];
1305
+ const fileName = value[0].name;
1306
+ return [
1307
+ {
1308
+ uid: "0",
1309
+ name: fileName,
1310
+ status: "done",
1311
+ url: value[0].path
1312
+ }
1313
+ ];
1314
+ } else {
1315
+ if (!value || !value.length) return [];
1316
+ return value.map((i, index) => {
1317
+ const fileName = i.name;
1318
+ return {
1319
+ uid: index.toString(),
1320
+ name: fileName,
1321
+ status: "done",
1322
+ url: i.path
1323
+ };
1324
+ });
1325
+ }
1326
+ }, [value, onlyOne]);
1327
+ const handleChange = (info) => {
1328
+ const { fileList: newFileList } = info;
1329
+ const files = [];
1330
+ newFileList.forEach((i) => {
1331
+ if (i.status === "done" && i.response) {
1332
+ files.push({
1333
+ name: i.name,
1334
+ path: i.response
1335
+ });
1336
+ } else if (i.status === "done" && i.url) {
1337
+ files.push({
1338
+ name: i.name,
1339
+ path: i.url || ""
1340
+ });
1341
+ }
1342
+ return i;
1343
+ });
1344
+ onChange?.(files);
1345
+ };
1346
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1347
+ UploadListInner,
1348
+ {
1349
+ uploadProps,
1350
+ maxCount,
1351
+ fileList,
1352
+ onChange: handleChange,
1353
+ onPreview,
1354
+ uploadFunction,
1355
+ accept,
1356
+ disabled
1357
+ }
1358
+ ));
1359
+ };
1360
+
1361
+ export { AvatarPopoverContent, BORDER_SECONDARY_COLOR, BlinkButton, COLOR_BG_OPTION_HOVER, ColorCheckboxGroup, ColorRadioGroup, CustomTag, FixedColorPicker, IconFont, LinkEdit, OptionEditor, OptionInput, OptionItem, PopoverContent, Select, SelectOptionItem, UploadList, UploadListInner, UserLoadingSkeleton, computeSHA256, focusToEnd, getRandomColor, isFocused };