@ai-group/chat-sdk 3.7.1-alpha1 → 3.7.1-alpha3
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/cjs/components/FileGallery/index.js +59 -40
- package/dist/cjs/components/FileGallery/index.js.map +2 -2
- package/dist/cjs/components/FileGallery/styles.d.ts +7 -0
- package/dist/cjs/components/FileGallery/styles.js +58 -0
- package/dist/cjs/components/FileGallery/styles.js.map +2 -2
- package/dist/cjs/components/Sources/components/CarouselCard.d.ts +12 -0
- package/dist/cjs/components/Sources/components/CarouselCard.js +130 -0
- package/dist/cjs/components/Sources/components/CarouselCard.js.map +7 -0
- package/dist/cjs/components/Sources/index.d.ts +5 -0
- package/dist/cjs/components/Sources/index.js +162 -0
- package/dist/cjs/components/Sources/index.js.map +7 -0
- package/dist/cjs/components/Sources/styles.d.ts +59 -0
- package/dist/cjs/components/Sources/styles.js +179 -0
- package/dist/cjs/components/Sources/styles.js.map +7 -0
- package/dist/cjs/components/XAdkSender/InlineTagInput.d.ts +2 -1
- package/dist/cjs/components/XAdkSender/InlineTagInput.js +107 -50
- package/dist/cjs/components/XAdkSender/InlineTagInput.js.map +2 -2
- package/dist/cjs/components/XAdkSender/index.js +101 -34
- package/dist/cjs/components/XAdkSender/index.js.map +3 -3
- package/dist/cjs/components/XAdkSender/styles.d.ts +4 -0
- package/dist/cjs/components/XAdkSender/styles.js +34 -6
- package/dist/cjs/components/XAdkSender/styles.js.map +2 -2
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/index.js.map +3 -3
- package/dist/cjs/types/FileGallery.d.ts +3 -0
- package/dist/cjs/types/FileGallery.js.map +1 -1
- package/dist/cjs/types/Sources.d.ts +30 -0
- package/dist/cjs/types/Sources.js +18 -0
- package/dist/cjs/types/Sources.js.map +7 -0
- package/dist/cjs/types/XAdkSender.d.ts +52 -6
- package/dist/cjs/types/XAdkSender.js.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/index.js +3 -1
- package/dist/cjs/types/index.js.map +2 -2
- package/dist/esm/components/FileGallery/index.js +22 -16
- package/dist/esm/components/FileGallery/index.js.map +1 -1
- package/dist/esm/components/FileGallery/styles.d.ts +7 -0
- package/dist/esm/components/FileGallery/styles.js +24 -17
- package/dist/esm/components/FileGallery/styles.js.map +1 -1
- package/dist/esm/components/Sources/components/CarouselCard.d.ts +12 -0
- package/dist/esm/components/Sources/components/CarouselCard.js +107 -0
- package/dist/esm/components/Sources/components/CarouselCard.js.map +1 -0
- package/dist/esm/components/Sources/index.d.ts +5 -0
- package/dist/esm/components/Sources/index.js +127 -0
- package/dist/esm/components/Sources/index.js.map +1 -0
- package/dist/esm/components/Sources/styles.d.ts +59 -0
- package/dist/esm/components/Sources/styles.js +34 -0
- package/dist/esm/components/Sources/styles.js.map +1 -0
- package/dist/esm/components/XAdkSender/InlineTagInput.d.ts +2 -1
- package/dist/esm/components/XAdkSender/InlineTagInput.js +110 -40
- package/dist/esm/components/XAdkSender/InlineTagInput.js.map +1 -1
- package/dist/esm/components/XAdkSender/index.js +129 -38
- package/dist/esm/components/XAdkSender/index.js.map +1 -1
- package/dist/esm/components/XAdkSender/styles.d.ts +4 -0
- package/dist/esm/components/XAdkSender/styles.js +29 -25
- package/dist/esm/components/XAdkSender/styles.js.map +1 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/FileGallery.d.ts +3 -0
- package/dist/esm/types/FileGallery.js.map +1 -1
- package/dist/esm/types/Sources.d.ts +30 -0
- package/dist/esm/types/Sources.js +2 -0
- package/dist/esm/types/Sources.js.map +1 -0
- package/dist/esm/types/XAdkSender.d.ts +52 -6
- package/dist/esm/types/XAdkSender.js.map +1 -1
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/types/index.js.map +1 -1
- package/dist/umd/chat-sdk.min.js +1 -1
- package/package.json +1 -1
|
@@ -29,6 +29,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
29
29
|
var TAG_ATTR = "data-xadk-inline-tag-id";
|
|
30
30
|
var CARET_TEXT = "";
|
|
31
31
|
var createTextNode = (text) => document.createTextNode(text);
|
|
32
|
+
var useIsomorphicLayoutEffect = typeof window === "undefined" ? import_react.useEffect : import_react.useLayoutEffect;
|
|
32
33
|
var normalizeText = (text) => text.replace(/\u200B/g, "").replace(/\u00a0/g, " ");
|
|
33
34
|
var getPlainLength = (text) => normalizeText(text).length;
|
|
34
35
|
var toRawOffset = (text, plainOffset) => {
|
|
@@ -59,6 +60,11 @@ var InlineTagInput = (0, import_react.forwardRef)(
|
|
|
59
60
|
const tagRootMapRef = (0, import_react.useRef)(/* @__PURE__ */ new WeakMap());
|
|
60
61
|
const composingRef = (0, import_react.useRef)(false);
|
|
61
62
|
const lastInsertedCaretRef = (0, import_react.useRef)(null);
|
|
63
|
+
const savedRangeRef = (0, import_react.useRef)(null);
|
|
64
|
+
const lastEmittedStateRef = (0, import_react.useRef)({
|
|
65
|
+
value,
|
|
66
|
+
tagIds: tags.map((tag) => tag.id).join("|")
|
|
67
|
+
});
|
|
62
68
|
(0, import_react.useEffect)(() => {
|
|
63
69
|
latestTagsRef.current = tags;
|
|
64
70
|
}, [tags]);
|
|
@@ -81,9 +87,37 @@ var InlineTagInput = (0, import_react.forwardRef)(
|
|
|
81
87
|
});
|
|
82
88
|
return normalizeText(text);
|
|
83
89
|
}, []);
|
|
90
|
+
const getParts = (0, import_react.useCallback)(() => {
|
|
91
|
+
const editor = editorRef.current;
|
|
92
|
+
if (!editor)
|
|
93
|
+
return [];
|
|
94
|
+
const tagMap = new Map(latestTagsRef.current.map((tag) => [tag.id, tag]));
|
|
95
|
+
const parts = [];
|
|
96
|
+
editor.childNodes.forEach((node) => {
|
|
97
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
98
|
+
const text = normalizeText(node.textContent || "");
|
|
99
|
+
if (text)
|
|
100
|
+
parts.push({ type: "text", text });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (node instanceof HTMLElement && node.hasAttribute(TAG_ATTR)) {
|
|
104
|
+
const tagId = node.dataset.xadkInlineTagId || "";
|
|
105
|
+
const tag = tagMap.get(tagId);
|
|
106
|
+
if (tag)
|
|
107
|
+
parts.push({ type: "tag", tag });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return parts;
|
|
111
|
+
}, []);
|
|
84
112
|
const emitChange = (0, import_react.useCallback)(() => {
|
|
85
|
-
|
|
86
|
-
|
|
113
|
+
const nextValue = getPlainText();
|
|
114
|
+
const nextTags = getOrderedTags();
|
|
115
|
+
lastEmittedStateRef.current = {
|
|
116
|
+
value: nextValue,
|
|
117
|
+
tagIds: nextTags.map((tag) => tag.id).join("|")
|
|
118
|
+
};
|
|
119
|
+
onChange(nextValue);
|
|
120
|
+
onTagsChange(nextTags);
|
|
87
121
|
}, [getOrderedTags, getPlainText, onChange, onTagsChange]);
|
|
88
122
|
const removeTextRange = (0, import_react.useCallback)((node, start, end) => {
|
|
89
123
|
const text = node.textContent || "";
|
|
@@ -169,6 +203,18 @@ var InlineTagInput = (0, import_react.forwardRef)(
|
|
|
169
203
|
range.collapse(true);
|
|
170
204
|
selection == null ? void 0 : selection.removeAllRanges();
|
|
171
205
|
selection == null ? void 0 : selection.addRange(range);
|
|
206
|
+
savedRangeRef.current = range.cloneRange();
|
|
207
|
+
}, []);
|
|
208
|
+
const saveSelectionRange = (0, import_react.useCallback)(() => {
|
|
209
|
+
const editor = editorRef.current;
|
|
210
|
+
const selection = window.getSelection();
|
|
211
|
+
if (!editor || !selection || selection.rangeCount === 0)
|
|
212
|
+
return;
|
|
213
|
+
const range = selection.getRangeAt(0);
|
|
214
|
+
if (!editor.contains(range.startContainer) || !editor.contains(range.endContainer)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
savedRangeRef.current = range.cloneRange();
|
|
172
218
|
}, []);
|
|
173
219
|
const restoreCursorAfterInsert = (0, import_react.useCallback)(
|
|
174
220
|
(node) => {
|
|
@@ -333,7 +379,8 @@ var InlineTagInput = (0, import_react.forwardRef)(
|
|
|
333
379
|
const tagNode = buildTagNode(tag);
|
|
334
380
|
const caretNode = createTextNode(CARET_TEXT);
|
|
335
381
|
const selection = window.getSelection();
|
|
336
|
-
const
|
|
382
|
+
const savedRange = savedRangeRef.current;
|
|
383
|
+
const range = insertRange || ((selection == null ? void 0 : selection.rangeCount) && editor.contains(selection.getRangeAt(0).startContainer) ? selection.getRangeAt(0) : savedRange && editor.contains(savedRange.startContainer) ? savedRange : null);
|
|
337
384
|
if (range && editor.contains(range.startContainer)) {
|
|
338
385
|
const fragment = document.createDocumentFragment();
|
|
339
386
|
fragment.append(tagNode, caretNode);
|
|
@@ -360,45 +407,47 @@ var InlineTagInput = (0, import_react.forwardRef)(
|
|
|
360
407
|
setCursorAtTextEnd
|
|
361
408
|
]
|
|
362
409
|
);
|
|
410
|
+
(0, import_react.useEffect)(() => {
|
|
411
|
+
document.addEventListener("selectionchange", saveSelectionRange);
|
|
412
|
+
return () => {
|
|
413
|
+
document.removeEventListener("selectionchange", saveSelectionRange);
|
|
414
|
+
};
|
|
415
|
+
}, [saveSelectionRange]);
|
|
363
416
|
(0, import_react.useImperativeHandle)(ref, () => ({
|
|
364
417
|
focus: () => {
|
|
365
418
|
var _a;
|
|
366
419
|
return (_a = editorRef.current) == null ? void 0 : _a.focus();
|
|
367
420
|
},
|
|
368
421
|
getText: getPlainText,
|
|
422
|
+
getParts,
|
|
369
423
|
getCursorOffset: getOffsetInEditor,
|
|
370
424
|
insertTag
|
|
371
|
-
}), [getOffsetInEditor, getPlainText, insertTag]);
|
|
372
|
-
(
|
|
373
|
-
const editor = editorRef.current;
|
|
374
|
-
if (!editor || editor.childNodes.length > 0)
|
|
375
|
-
return;
|
|
376
|
-
if (value)
|
|
377
|
-
editor.appendChild(createTextNode(value));
|
|
378
|
-
tags.forEach((tag) => editor.append(buildTagNode(tag), createTextNode(CARET_TEXT)));
|
|
379
|
-
}, []);
|
|
380
|
-
(0, import_react.useEffect)(() => {
|
|
425
|
+
}), [getOffsetInEditor, getParts, getPlainText, insertTag]);
|
|
426
|
+
useIsomorphicLayoutEffect(() => {
|
|
381
427
|
const editor = editorRef.current;
|
|
382
428
|
if (!editor)
|
|
383
429
|
return;
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
removeTagNode(node);
|
|
393
|
-
}
|
|
430
|
+
const incomingTagIds = tags.map((tag) => tag.id).join("|");
|
|
431
|
+
const lastEmittedState = lastEmittedStateRef.current;
|
|
432
|
+
const isInternalControlledEcho = value === lastEmittedState.value && incomingTagIds === lastEmittedState.tagIds;
|
|
433
|
+
if (isInternalControlledEcho && editor.childNodes.length > 0)
|
|
434
|
+
return;
|
|
435
|
+
editor.querySelectorAll(`[${TAG_ATTR}]`).forEach((node) => {
|
|
436
|
+
var _a;
|
|
437
|
+
(_a = tagRootMapRef.current.get(node)) == null ? void 0 : _a.unmount();
|
|
394
438
|
});
|
|
439
|
+
editor.innerHTML = "";
|
|
440
|
+
if (value)
|
|
441
|
+
editor.appendChild(createTextNode(value));
|
|
395
442
|
tags.forEach((tag) => {
|
|
396
|
-
if (existingIds.has(tag.id))
|
|
397
|
-
return;
|
|
398
443
|
editor.append(buildTagNode(tag), createTextNode(CARET_TEXT));
|
|
399
444
|
});
|
|
400
|
-
|
|
401
|
-
|
|
445
|
+
const lastTextNode = Array.from(editor.childNodes).reverse().find((node) => node.nodeType === Node.TEXT_NODE);
|
|
446
|
+
if (document.activeElement === editor && lastTextNode) {
|
|
447
|
+
setCursorAtTextEnd(lastTextNode);
|
|
448
|
+
}
|
|
449
|
+
}, [buildTagNode, setCursorAtTextEnd, tags, value]);
|
|
450
|
+
useIsomorphicLayoutEffect(() => {
|
|
402
451
|
const editor = editorRef.current;
|
|
403
452
|
if (!editor)
|
|
404
453
|
return;
|
|
@@ -431,29 +480,37 @@ var InlineTagInput = (0, import_react.forwardRef)(
|
|
|
431
480
|
}
|
|
432
481
|
onKeyDown == null ? void 0 : onKeyDown(event);
|
|
433
482
|
};
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
{
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
483
|
+
const showPlaceholder = !value && tags.length === 0;
|
|
484
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.inlineInputWrapper, children: [
|
|
485
|
+
showPlaceholder && placeholder && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.inlinePlaceholder, children: placeholder }),
|
|
486
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
487
|
+
"div",
|
|
488
|
+
{
|
|
489
|
+
ref: editorRef,
|
|
490
|
+
className: styles.inlineInput,
|
|
491
|
+
contentEditable: !disabled,
|
|
492
|
+
suppressContentEditableWarning: true,
|
|
493
|
+
role: "textbox",
|
|
494
|
+
"aria-multiline": "true",
|
|
495
|
+
onInput: handleInput,
|
|
496
|
+
onKeyDown: handleKeyDown,
|
|
497
|
+
onKeyUp: (event) => {
|
|
498
|
+
saveSelectionRange();
|
|
499
|
+
onKeyUp == null ? void 0 : onKeyUp(event);
|
|
500
|
+
},
|
|
501
|
+
onMouseUp: saveSelectionRange,
|
|
502
|
+
onFocus: saveSelectionRange,
|
|
503
|
+
onCompositionStart: () => {
|
|
504
|
+
composingRef.current = true;
|
|
505
|
+
},
|
|
506
|
+
onCompositionEnd: () => {
|
|
507
|
+
composingRef.current = false;
|
|
508
|
+
emitChange();
|
|
509
|
+
},
|
|
510
|
+
"data-cursor-offset": getOffsetInEditor()
|
|
511
|
+
}
|
|
512
|
+
)
|
|
513
|
+
] });
|
|
457
514
|
}
|
|
458
515
|
);
|
|
459
516
|
InlineTagInput.displayName = "InlineTagInput";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/components/XAdkSender/InlineTagInput.tsx"],
|
|
4
|
-
"sourcesContent": ["import React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n} from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport type { InlineTagRenderInfo, InputTag } from \"@/types\";\nimport { useStyles } from \"./styles\";\n\nexport interface InlineTagInputRef {\n focus: () => void;\n getText: () => string;\n getCursorOffset: () => number;\n insertTag: (tag: InputTag, replace?: { start: number; end: number }) => void;\n}\n\nexport interface InlineTagInputProps {\n value: string;\n tags: InputTag[];\n placeholder?: string;\n disabled?: boolean;\n onChange: (value: string) => void;\n onTagsChange: (tags: InputTag[]) => void;\n onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;\n onKeyUp?: React.KeyboardEventHandler<HTMLDivElement>;\n renderTag?: (tag: InputTag, info: InlineTagRenderInfo) => React.ReactNode;\n}\n\nconst TAG_ATTR = \"data-xadk-inline-tag-id\";\n// contenteditable 无法把光标放进 contenteditable=false 的标签里,需用零宽字符承接“标签后方”的光标。\nconst CARET_TEXT = \"\\u200B\";\n\nconst createTextNode = (text: string) => document.createTextNode(text);\nconst normalizeText = (text: string) =>\n text.replace(/\\u200B/g, \"\").replace(/\\u00a0/g, \" \");\nconst getPlainLength = (text: string) => normalizeText(text).length;\n// DOM 文本里包含零宽光标锚点,快捷键替换和光标定位都要在“用户可见文本 offset”和 DOM raw offset 之间转换。\nconst toRawOffset = (text: string, plainOffset: number) => {\n let current = 0;\n for (let i = 0; i < text.length; i++) {\n if (current >= plainOffset) return i;\n if (text[i] !== CARET_TEXT) current += 1;\n }\n return text.length;\n};\n\nconst InlineTagInput = forwardRef<InlineTagInputRef, InlineTagInputProps>(\n (\n {\n value,\n tags,\n placeholder,\n disabled,\n onChange,\n onTagsChange,\n onKeyDown,\n onKeyUp,\n renderTag,\n },\n ref,\n ) => {\n const styles = useStyles();\n const editorRef = useRef<HTMLDivElement>(null);\n const latestTagsRef = useRef<InputTag[]>(tags);\n const tagRootMapRef = useRef<WeakMap<Element, Root>>(new WeakMap());\n const composingRef = useRef(false);\n const lastInsertedCaretRef = useRef<Text | null>(null);\n\n useEffect(() => {\n latestTagsRef.current = tags;\n }, [tags]);\n\n const getOrderedTags = useCallback(() => {\n const editor = editorRef.current;\n if (!editor) return [];\n const tagMap = new Map(latestTagsRef.current.map((tag) => [tag.id, tag]));\n return Array.from(editor.querySelectorAll(`[${TAG_ATTR}]`))\n .map((node) => tagMap.get((node as HTMLElement).dataset.xadkInlineTagId || \"\"))\n .filter(Boolean) as InputTag[];\n }, []);\n\n // 对外只暴露用户输入的纯文本,内部零宽锚点不进入 value。\n const getPlainText = useCallback(() => {\n const editor = editorRef.current;\n if (!editor) return \"\";\n let text = \"\";\n editor.childNodes.forEach((node) => {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent || \"\";\n }\n });\n return normalizeText(text);\n }, []);\n\n const emitChange = useCallback(() => {\n onChange(getPlainText());\n onTagsChange(getOrderedTags());\n }, [getOrderedTags, getPlainText, onChange, onTagsChange]);\n\n const removeTextRange = useCallback((node: Text, start: number, end: number) => {\n const text = node.textContent || \"\";\n node.textContent = `${text.slice(0, start)}${text.slice(end)}`;\n if (!node.textContent) node.remove();\n }, []);\n\n const removeTagNode = useCallback(\n (node: Element) => {\n const previous = node.previousSibling;\n const next = node.nextSibling;\n\n tagRootMapRef.current.get(node)?.unmount();\n node.remove();\n\n // 标签后会补一个不可见光标锚点;删除标签时同步清理,避免留下冗余节点。\n if (next?.nodeType === Node.TEXT_NODE) {\n const text = next.textContent || \"\";\n const caretLength = text.match(/^\\u200B+/)?.[0].length || 0;\n if (caretLength > 0) {\n removeTextRange(next as Text, 0, caretLength);\n return;\n }\n }\n\n if (previous?.nodeType === Node.TEXT_NODE) {\n const text = previous.textContent || \"\";\n const spaceLength = text.match(/\\s+$/)?.[0].length || 0;\n if (spaceLength > 0) {\n removeTextRange(previous as Text, text.length - spaceLength, text.length);\n }\n }\n },\n [removeTextRange],\n );\n\n const removeTagById = useCallback(\n (id: string) => {\n const editor = editorRef.current;\n const node = Array.from(editor?.querySelectorAll(`[${TAG_ATTR}]`) || []).find(\n (item) => (item as HTMLElement).dataset.xadkInlineTagId === id,\n );\n if (node) {\n removeTagNode(node);\n }\n emitChange();\n editor?.focus();\n },\n [emitChange, removeTagNode],\n );\n\n // 标签节点本身不可编辑;删除入口通过 onRemove 回到组件内部统一维护 DOM 与 tags。\n const buildTagNode = useCallback(\n (tag: InputTag) => {\n const span = document.createElement(\"span\");\n span.setAttribute(\"contenteditable\", \"false\");\n span.setAttribute(TAG_ATTR, tag.id);\n span.dataset.xadkInlineTagId = tag.id;\n span.className = styles.inlineTag;\n\n if (renderTag) {\n const root = createRoot(span);\n tagRootMapRef.current.set(span, root);\n root.render(\n <>{renderTag(tag, { onRemove: () => removeTagById(tag.id) })}</>,\n );\n } else {\n const label = document.createElement(\"span\");\n label.className = \"xadk-inline-tag-label\";\n label.textContent = tag.label;\n const close = document.createElement(\"span\");\n close.className = \"xadk-inline-tag-close\";\n close.innerHTML = \"×\";\n close.onclick = (event) => {\n event.preventDefault();\n event.stopPropagation();\n removeTagById(tag.id);\n };\n span.append(label, close);\n }\n return span;\n },\n [removeTagById, renderTag, styles.inlineTag],\n );\n\n const setCursorAtTextEnd = useCallback((node: Text) => {\n const range = document.createRange();\n const selection = window.getSelection();\n range.setStart(node, node.textContent?.length || 0);\n range.collapse(true);\n selection?.removeAllRanges();\n selection?.addRange(range);\n }, []);\n\n const restoreCursorAfterInsert = useCallback(\n (node: Text) => {\n const editor = editorRef.current;\n if (!editor || !editor.contains(node)) return;\n editor.focus();\n setCursorAtTextEnd(node);\n },\n [setCursorAtTextEnd],\n );\n\n const isTagNode = (node: Node | null): node is HTMLElement =>\n node instanceof HTMLElement && node.hasAttribute(TAG_ATTR);\n\n const isOnlyCaretText = (text: string) => normalizeText(text) === \"\";\n\n // Backspace/Delete 需要跨过零宽锚点识别相邻标签,不能只看光标所在文本节点。\n const getTagBeforeRange = (range: Range) => {\n const container = range.startContainer;\n if (container.nodeType === Node.TEXT_NODE) {\n const text = container.textContent || \"\";\n const beforeCursor = text.slice(0, range.startOffset);\n if (isOnlyCaretText(beforeCursor) && isTagNode(container.previousSibling)) {\n return container.previousSibling;\n }\n }\n\n if (container instanceof HTMLElement) {\n const previous = container.childNodes[range.startOffset - 1];\n if (isTagNode(previous)) return previous;\n if (\n previous?.nodeType === Node.TEXT_NODE &&\n isOnlyCaretText(previous.textContent || \"\") &&\n isTagNode(previous.previousSibling)\n ) {\n return previous.previousSibling;\n }\n }\n\n return null;\n };\n\n const getTagAfterRange = (range: Range) => {\n const container = range.startContainer;\n if (container.nodeType === Node.TEXT_NODE) {\n const text = container.textContent || \"\";\n const afterCursor = text.slice(range.startOffset);\n if (isOnlyCaretText(afterCursor) && isTagNode(container.nextSibling)) {\n return container.nextSibling;\n }\n }\n\n if (container instanceof HTMLElement) {\n const next = container.childNodes[range.startOffset];\n if (isTagNode(next)) return next;\n if (\n next?.nodeType === Node.TEXT_NODE &&\n isOnlyCaretText(next.textContent || \"\") &&\n isTagNode(next.nextSibling)\n ) {\n return next.nextSibling;\n }\n }\n\n return null;\n };\n\n // 读取光标在纯文本中的位置,供快捷键搜索词和 tag 插入位置复用。\n const getOffsetInEditor = useCallback(() => {\n const editor = editorRef.current;\n const selection = window.getSelection();\n if (!editor || !selection || selection.rangeCount === 0) return 0;\n const range = selection.getRangeAt(0);\n let offset = 0;\n let found = false;\n\n const visit = (node: Node) => {\n if (found) return;\n if (node === range.startContainer) {\n if (node.nodeType === Node.TEXT_NODE) {\n offset += getPlainLength(\n (node.textContent || \"\").slice(0, range.startOffset),\n );\n }\n found = true;\n return;\n }\n if (node instanceof HTMLElement && node.hasAttribute(TAG_ATTR)) return;\n if (node.nodeType === Node.TEXT_NODE) {\n offset += getPlainLength(node.textContent || \"\");\n return;\n }\n node.childNodes.forEach(visit);\n };\n\n editor.childNodes.forEach(visit);\n return offset;\n }, []);\n\n const getNodeIndex = (node: Node) => {\n let index = 0;\n let current = node;\n while (current.previousSibling) {\n index += 1;\n current = current.previousSibling;\n }\n return index;\n };\n\n // 删除快捷键触发片段,例如把 “@文档” 替换成一个结构化 tag,并返回删除位置对应的真实 DOM Range。\n // 注意:纯文本 offset 不包含 tag,标签前后可能都是 offset 0;插入 tag 必须依赖真实 Range,不能再反推 offset。\n const replaceTextRange = useCallback((start: number, end: number) => {\n const editor = editorRef.current;\n if (!editor) return null;\n let offset = 0;\n let insertRange: Range | null = null;\n const nodes = Array.from(editor.childNodes);\n nodes.forEach((node) => {\n if (node.nodeType !== Node.TEXT_NODE) return;\n const text = node.textContent || \"\";\n const nodeStart = offset;\n const plainLength = getPlainLength(text);\n const nodeEnd = offset + plainLength;\n offset = nodeEnd;\n if (nodeEnd <= start || nodeStart >= end) return;\n const rawStart = toRawOffset(text, Math.max(0, start - nodeStart));\n const rawEnd = toRawOffset(text, Math.max(0, end - nodeStart));\n const keepLeft = text.slice(0, rawStart);\n const keepRight = text.slice(rawEnd);\n const parent = node.parentNode;\n const nodeIndex = getNodeIndex(node);\n\n node.textContent = `${keepLeft}${keepRight}`;\n if (!insertRange) {\n insertRange = document.createRange();\n if (node.textContent) {\n insertRange.setStart(node, rawStart);\n } else if (parent) {\n insertRange.setStart(parent, nodeIndex);\n }\n insertRange.collapse(true);\n }\n if (!node.textContent) node.remove();\n });\n\n return insertRange;\n }, []);\n\n // 浏览器拆分 Range 后可能留下空文本节点;清理后才能保证后续 tag 继续 append 在队尾。\n const cleanupCaretTextNodes = useCallback(() => {\n const editor = editorRef.current;\n if (!editor) return;\n Array.from(editor.childNodes).forEach((node) => {\n if (node.nodeType !== Node.TEXT_NODE) return;\n const text = node.textContent || \"\";\n if (!text) {\n node.remove();\n return;\n }\n if (text && normalizeText(text) === \"\") {\n node.textContent = CARET_TEXT;\n }\n });\n }, []);\n\n const insertTag = useCallback(\n (tag: InputTag, replace?: { start: number; end: number }) => {\n const editor = editorRef.current;\n if (!editor) return;\n editor.focus();\n latestTagsRef.current = [\n ...latestTagsRef.current.filter((item) => item.id !== tag.id),\n tag,\n ];\n let insertRange: Range | null = null;\n if (replace) {\n insertRange = replaceTextRange(replace.start, replace.end);\n cleanupCaretTextNodes();\n }\n const tagNode = buildTagNode(tag);\n const caretNode = createTextNode(CARET_TEXT);\n const selection = window.getSelection();\n const range =\n insertRange || (selection?.rangeCount ? selection.getRangeAt(0) : null);\n if (range && editor.contains(range.startContainer)) {\n // 用 DocumentFragment 一次性插入,避免连续 insertNode 导致 tag/caret 顺序被浏览器 Range 状态打乱。\n const fragment = document.createDocumentFragment();\n fragment.append(tagNode, caretNode);\n range.insertNode(fragment);\n } else {\n editor.append(tagNode, caretNode);\n }\n cleanupCaretTextNodes();\n lastInsertedCaretRef.current = caretNode;\n setCursorAtTextEnd(caretNode);\n emitChange();\n\n // 关闭快捷列表和父组件同步 tags/value 会触发一次 React 更新;\n // 下一帧再恢复到刚插入 tag 后的零宽锚点,避免光标被 focus 或重渲染带回旧位置。\n window.requestAnimationFrame(() => {\n if (lastInsertedCaretRef.current === caretNode) {\n restoreCursorAfterInsert(caretNode);\n }\n });\n },\n [\n buildTagNode,\n cleanupCaretTextNodes,\n emitChange,\n replaceTextRange,\n restoreCursorAfterInsert,\n setCursorAtTextEnd,\n ],\n );\n\n useImperativeHandle(ref, () => ({\n focus: () => editorRef.current?.focus(),\n getText: getPlainText,\n getCursorOffset: getOffsetInEditor,\n insertTag,\n }), [getOffsetInEditor, getPlainText, insertTag]);\n\n useEffect(() => {\n const editor = editorRef.current;\n if (!editor || editor.childNodes.length > 0) return;\n if (value) editor.appendChild(createTextNode(value));\n tags.forEach((tag) => editor.append(buildTagNode(tag), createTextNode(CARET_TEXT)));\n }, []);\n\n // 外部受控 tags 变化时,同步补齐或删除 DOM 中的 tag 节点。\n useEffect(() => {\n const editor = editorRef.current;\n if (!editor) return;\n const existingNodes = Array.from(editor.querySelectorAll(`[${TAG_ATTR}]`));\n const nextTagMap = new Map(tags.map((tag) => [tag.id, tag]));\n const existingIds = new Set(\n existingNodes.map((node) => (node as HTMLElement).dataset.xadkInlineTagId || \"\"),\n );\n\n existingNodes.forEach((node) => {\n const tagId = (node as HTMLElement).dataset.xadkInlineTagId || \"\";\n if (!nextTagMap.has(tagId)) {\n removeTagNode(node);\n }\n });\n\n tags.forEach((tag) => {\n if (existingIds.has(tag.id)) return;\n editor.append(buildTagNode(tag), createTextNode(CARET_TEXT));\n });\n }, [buildTagNode, removeTagNode, tags]);\n\n useEffect(() => {\n const editor = editorRef.current;\n if (!editor) return;\n if (!value && tags.length === 0 && editor.childNodes.length > 0) {\n editor.querySelectorAll(`[${TAG_ATTR}]`).forEach((node) => {\n tagRootMapRef.current.get(node)?.unmount();\n });\n editor.innerHTML = \"\";\n }\n }, [tags.length, value]);\n\n const handleInput = () => {\n if (!composingRef.current) emitChange();\n };\n\n const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {\n if (event.key === \"Backspace\" || event.key === \"Delete\") {\n const selection = window.getSelection();\n if (selection?.rangeCount) {\n const range = selection.getRangeAt(0);\n if (range.collapsed) {\n const targetTag =\n event.key === \"Backspace\"\n ? getTagBeforeRange(range)\n : getTagAfterRange(range);\n\n if (targetTag) {\n event.preventDefault();\n removeTagById(targetTag.dataset.xadkInlineTagId || \"\");\n return;\n }\n }\n }\n }\n onKeyDown?.(event);\n };\n\n return (\n <div className={styles.inlineInputWrapper} data-placeholder={placeholder}>\n <div\n ref={editorRef}\n className={styles.inlineInput}\n contentEditable={!disabled}\n suppressContentEditableWarning\n role=\"textbox\"\n aria-multiline=\"true\"\n onInput={handleInput}\n onKeyDown={handleKeyDown}\n onKeyUp={onKeyUp}\n onCompositionStart={() => {\n composingRef.current = true;\n }}\n onCompositionEnd={() => {\n composingRef.current = false;\n emitChange();\n }}\n data-empty={!getPlainText() && getOrderedTags().length === 0}\n data-cursor-offset={getOffsetInEditor()}\n />\n </div>\n );\n },\n);\n\nInlineTagInput.displayName = \"InlineTagInput\";\n\nexport default InlineTagInput;\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import React, {\n forwardRef,\n useCallback,\n useEffect,\n useLayoutEffect,\n useImperativeHandle,\n useRef,\n} from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport type { InlineTagRenderInfo, InputTag, XAdkSenderSubmitPart } from \"@/types\";\nimport { useStyles } from \"./styles\";\n\nexport interface InlineTagInputRef {\n focus: () => void;\n getText: () => string;\n getParts: () => XAdkSenderSubmitPart[];\n getCursorOffset: () => number;\n insertTag: (tag: InputTag, replace?: { start: number; end: number }) => void;\n}\n\nexport interface InlineTagInputProps {\n value: string;\n tags: InputTag[];\n placeholder?: string;\n disabled?: boolean;\n onChange: (value: string) => void;\n onTagsChange: (tags: InputTag[]) => void;\n onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;\n onKeyUp?: React.KeyboardEventHandler<HTMLDivElement>;\n renderTag?: (tag: InputTag, info: InlineTagRenderInfo) => React.ReactNode;\n}\n\nconst TAG_ATTR = \"data-xadk-inline-tag-id\";\n// contenteditable 无法把光标放进 contenteditable=false 的标签里,需用零宽字符承接“标签后方”的光标。\nconst CARET_TEXT = \"\\u200B\";\n\nconst createTextNode = (text: string) => document.createTextNode(text);\nconst useIsomorphicLayoutEffect =\n typeof window === \"undefined\" ? useEffect : useLayoutEffect;\nconst normalizeText = (text: string) =>\n text.replace(/\\u200B/g, \"\").replace(/\\u00a0/g, \" \");\nconst getPlainLength = (text: string) => normalizeText(text).length;\n// DOM 文本里包含零宽光标锚点,快捷键替换和光标定位都要在“用户可见文本 offset”和 DOM raw offset 之间转换。\nconst toRawOffset = (text: string, plainOffset: number) => {\n let current = 0;\n for (let i = 0; i < text.length; i++) {\n if (current >= plainOffset) return i;\n if (text[i] !== CARET_TEXT) current += 1;\n }\n return text.length;\n};\n\nconst InlineTagInput = forwardRef<InlineTagInputRef, InlineTagInputProps>(\n (\n {\n value,\n tags,\n placeholder,\n disabled,\n onChange,\n onTagsChange,\n onKeyDown,\n onKeyUp,\n renderTag,\n },\n ref,\n ) => {\n const styles = useStyles();\n const editorRef = useRef<HTMLDivElement>(null);\n const latestTagsRef = useRef<InputTag[]>(tags);\n const tagRootMapRef = useRef<WeakMap<Element, Root>>(new WeakMap());\n const composingRef = useRef(false);\n const lastInsertedCaretRef = useRef<Text | null>(null);\n const savedRangeRef = useRef<Range | null>(null);\n const lastEmittedStateRef = useRef({\n value,\n tagIds: tags.map((tag) => tag.id).join(\"|\"),\n });\n\n useEffect(() => {\n latestTagsRef.current = tags;\n }, [tags]);\n\n const getOrderedTags = useCallback(() => {\n const editor = editorRef.current;\n if (!editor) return [];\n const tagMap = new Map(latestTagsRef.current.map((tag) => [tag.id, tag]));\n return Array.from(editor.querySelectorAll(`[${TAG_ATTR}]`))\n .map((node) => tagMap.get((node as HTMLElement).dataset.xadkInlineTagId || \"\"))\n .filter(Boolean) as InputTag[];\n }, []);\n\n // 对外只暴露用户输入的纯文本,内部零宽锚点不进入 value。\n const getPlainText = useCallback(() => {\n const editor = editorRef.current;\n if (!editor) return \"\";\n let text = \"\";\n editor.childNodes.forEach((node) => {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent || \"\";\n }\n });\n return normalizeText(text);\n }, []);\n\n // 提交时需要保留文本与 tag 的真实顺序;仅靠 value + tags 无法区分 tag 在句首、句中还是句尾。\n const getParts = useCallback((): XAdkSenderSubmitPart[] => {\n const editor = editorRef.current;\n if (!editor) return [];\n const tagMap = new Map(latestTagsRef.current.map((tag) => [tag.id, tag]));\n const parts: XAdkSenderSubmitPart[] = [];\n\n editor.childNodes.forEach((node) => {\n if (node.nodeType === Node.TEXT_NODE) {\n const text = normalizeText(node.textContent || \"\");\n if (text) parts.push({ type: \"text\", text });\n return;\n }\n\n if (node instanceof HTMLElement && node.hasAttribute(TAG_ATTR)) {\n const tagId = node.dataset.xadkInlineTagId || \"\";\n const tag = tagMap.get(tagId);\n if (tag) parts.push({ type: \"tag\", tag });\n }\n });\n\n return parts;\n }, []);\n\n const emitChange = useCallback(() => {\n const nextValue = getPlainText();\n const nextTags = getOrderedTags();\n lastEmittedStateRef.current = {\n value: nextValue,\n tagIds: nextTags.map((tag) => tag.id).join(\"|\"),\n };\n onChange(nextValue);\n onTagsChange(nextTags);\n }, [getOrderedTags, getPlainText, onChange, onTagsChange]);\n\n const removeTextRange = useCallback((node: Text, start: number, end: number) => {\n const text = node.textContent || \"\";\n node.textContent = `${text.slice(0, start)}${text.slice(end)}`;\n if (!node.textContent) node.remove();\n }, []);\n\n const removeTagNode = useCallback(\n (node: Element) => {\n const previous = node.previousSibling;\n const next = node.nextSibling;\n\n tagRootMapRef.current.get(node)?.unmount();\n node.remove();\n\n // 标签后会补一个不可见光标锚点;删除标签时同步清理,避免留下冗余节点。\n if (next?.nodeType === Node.TEXT_NODE) {\n const text = next.textContent || \"\";\n const caretLength = text.match(/^\\u200B+/)?.[0].length || 0;\n if (caretLength > 0) {\n removeTextRange(next as Text, 0, caretLength);\n return;\n }\n }\n\n if (previous?.nodeType === Node.TEXT_NODE) {\n const text = previous.textContent || \"\";\n const spaceLength = text.match(/\\s+$/)?.[0].length || 0;\n if (spaceLength > 0) {\n removeTextRange(previous as Text, text.length - spaceLength, text.length);\n }\n }\n },\n [removeTextRange],\n );\n\n const removeTagById = useCallback(\n (id: string) => {\n const editor = editorRef.current;\n const node = Array.from(editor?.querySelectorAll(`[${TAG_ATTR}]`) || []).find(\n (item) => (item as HTMLElement).dataset.xadkInlineTagId === id,\n );\n if (node) {\n removeTagNode(node);\n }\n emitChange();\n editor?.focus();\n },\n [emitChange, removeTagNode],\n );\n\n // 标签节点本身不可编辑;删除入口通过 onRemove 回到组件内部统一维护 DOM 与 tags。\n const buildTagNode = useCallback(\n (tag: InputTag) => {\n const span = document.createElement(\"span\");\n span.setAttribute(\"contenteditable\", \"false\");\n span.setAttribute(TAG_ATTR, tag.id);\n span.dataset.xadkInlineTagId = tag.id;\n span.className = styles.inlineTag;\n\n if (renderTag) {\n const root = createRoot(span);\n tagRootMapRef.current.set(span, root);\n root.render(\n <>{renderTag(tag, { onRemove: () => removeTagById(tag.id) })}</>,\n );\n } else {\n const label = document.createElement(\"span\");\n label.className = \"xadk-inline-tag-label\";\n label.textContent = tag.label;\n const close = document.createElement(\"span\");\n close.className = \"xadk-inline-tag-close\";\n close.innerHTML = \"×\";\n close.onclick = (event) => {\n event.preventDefault();\n event.stopPropagation();\n removeTagById(tag.id);\n };\n span.append(label, close);\n }\n return span;\n },\n [removeTagById, renderTag, styles.inlineTag],\n );\n\n const setCursorAtTextEnd = useCallback((node: Text) => {\n const range = document.createRange();\n const selection = window.getSelection();\n range.setStart(node, node.textContent?.length || 0);\n range.collapse(true);\n selection?.removeAllRanges();\n selection?.addRange(range);\n savedRangeRef.current = range.cloneRange();\n }, []);\n\n const saveSelectionRange = useCallback(() => {\n const editor = editorRef.current;\n const selection = window.getSelection();\n if (!editor || !selection || selection.rangeCount === 0) return;\n const range = selection.getRangeAt(0);\n if (!editor.contains(range.startContainer) || !editor.contains(range.endContainer)) {\n return;\n }\n savedRangeRef.current = range.cloneRange();\n }, []);\n\n const restoreCursorAfterInsert = useCallback(\n (node: Text) => {\n const editor = editorRef.current;\n if (!editor || !editor.contains(node)) return;\n editor.focus();\n setCursorAtTextEnd(node);\n },\n [setCursorAtTextEnd],\n );\n\n const isTagNode = (node: Node | null): node is HTMLElement =>\n node instanceof HTMLElement && node.hasAttribute(TAG_ATTR);\n\n const isOnlyCaretText = (text: string) => normalizeText(text) === \"\";\n\n // Backspace/Delete 需要跨过零宽锚点识别相邻标签,不能只看光标所在文本节点。\n const getTagBeforeRange = (range: Range) => {\n const container = range.startContainer;\n if (container.nodeType === Node.TEXT_NODE) {\n const text = container.textContent || \"\";\n const beforeCursor = text.slice(0, range.startOffset);\n if (isOnlyCaretText(beforeCursor) && isTagNode(container.previousSibling)) {\n return container.previousSibling;\n }\n }\n\n if (container instanceof HTMLElement) {\n const previous = container.childNodes[range.startOffset - 1];\n if (isTagNode(previous)) return previous;\n if (\n previous?.nodeType === Node.TEXT_NODE &&\n isOnlyCaretText(previous.textContent || \"\") &&\n isTagNode(previous.previousSibling)\n ) {\n return previous.previousSibling;\n }\n }\n\n return null;\n };\n\n const getTagAfterRange = (range: Range) => {\n const container = range.startContainer;\n if (container.nodeType === Node.TEXT_NODE) {\n const text = container.textContent || \"\";\n const afterCursor = text.slice(range.startOffset);\n if (isOnlyCaretText(afterCursor) && isTagNode(container.nextSibling)) {\n return container.nextSibling;\n }\n }\n\n if (container instanceof HTMLElement) {\n const next = container.childNodes[range.startOffset];\n if (isTagNode(next)) return next;\n if (\n next?.nodeType === Node.TEXT_NODE &&\n isOnlyCaretText(next.textContent || \"\") &&\n isTagNode(next.nextSibling)\n ) {\n return next.nextSibling;\n }\n }\n\n return null;\n };\n\n // 读取光标在纯文本中的位置,供快捷键搜索词和 tag 插入位置复用。\n const getOffsetInEditor = useCallback(() => {\n const editor = editorRef.current;\n const selection = window.getSelection();\n if (!editor || !selection || selection.rangeCount === 0) return 0;\n const range = selection.getRangeAt(0);\n let offset = 0;\n let found = false;\n\n const visit = (node: Node) => {\n if (found) return;\n if (node === range.startContainer) {\n if (node.nodeType === Node.TEXT_NODE) {\n offset += getPlainLength(\n (node.textContent || \"\").slice(0, range.startOffset),\n );\n }\n found = true;\n return;\n }\n if (node instanceof HTMLElement && node.hasAttribute(TAG_ATTR)) return;\n if (node.nodeType === Node.TEXT_NODE) {\n offset += getPlainLength(node.textContent || \"\");\n return;\n }\n node.childNodes.forEach(visit);\n };\n\n editor.childNodes.forEach(visit);\n return offset;\n }, []);\n\n const getNodeIndex = (node: Node) => {\n let index = 0;\n let current = node;\n while (current.previousSibling) {\n index += 1;\n current = current.previousSibling;\n }\n return index;\n };\n\n // 删除快捷键触发片段,例如把 “@文档” 替换成一个结构化 tag,并返回删除位置对应的真实 DOM Range。\n // 注意:纯文本 offset 不包含 tag,标签前后可能都是 offset 0;插入 tag 必须依赖真实 Range,不能再反推 offset。\n const replaceTextRange = useCallback((start: number, end: number) => {\n const editor = editorRef.current;\n if (!editor) return null;\n let offset = 0;\n let insertRange: Range | null = null;\n const nodes = Array.from(editor.childNodes);\n nodes.forEach((node) => {\n if (node.nodeType !== Node.TEXT_NODE) return;\n const text = node.textContent || \"\";\n const nodeStart = offset;\n const plainLength = getPlainLength(text);\n const nodeEnd = offset + plainLength;\n offset = nodeEnd;\n if (nodeEnd <= start || nodeStart >= end) return;\n const rawStart = toRawOffset(text, Math.max(0, start - nodeStart));\n const rawEnd = toRawOffset(text, Math.max(0, end - nodeStart));\n const keepLeft = text.slice(0, rawStart);\n const keepRight = text.slice(rawEnd);\n const parent = node.parentNode;\n const nodeIndex = getNodeIndex(node);\n\n node.textContent = `${keepLeft}${keepRight}`;\n if (!insertRange) {\n insertRange = document.createRange();\n if (node.textContent) {\n insertRange.setStart(node, rawStart);\n } else if (parent) {\n insertRange.setStart(parent, nodeIndex);\n }\n insertRange.collapse(true);\n }\n if (!node.textContent) node.remove();\n });\n\n return insertRange;\n }, []);\n\n // 浏览器拆分 Range 后可能留下空文本节点;清理后才能保证后续 tag 继续 append 在队尾。\n const cleanupCaretTextNodes = useCallback(() => {\n const editor = editorRef.current;\n if (!editor) return;\n Array.from(editor.childNodes).forEach((node) => {\n if (node.nodeType !== Node.TEXT_NODE) return;\n const text = node.textContent || \"\";\n if (!text) {\n node.remove();\n return;\n }\n if (text && normalizeText(text) === \"\") {\n node.textContent = CARET_TEXT;\n }\n });\n }, []);\n\n const insertTag = useCallback(\n (tag: InputTag, replace?: { start: number; end: number }) => {\n const editor = editorRef.current;\n if (!editor) return;\n editor.focus();\n latestTagsRef.current = [\n ...latestTagsRef.current.filter((item) => item.id !== tag.id),\n tag,\n ];\n let insertRange: Range | null = null;\n if (replace) {\n insertRange = replaceTextRange(replace.start, replace.end);\n cleanupCaretTextNodes();\n }\n const tagNode = buildTagNode(tag);\n const caretNode = createTextNode(CARET_TEXT);\n const selection = window.getSelection();\n const savedRange = savedRangeRef.current;\n const range =\n insertRange ||\n (selection?.rangeCount &&\n editor.contains(selection.getRangeAt(0).startContainer)\n ? selection.getRangeAt(0)\n : savedRange && editor.contains(savedRange.startContainer)\n ? savedRange\n : null);\n if (range && editor.contains(range.startContainer)) {\n // 用 DocumentFragment 一次性插入,避免连续 insertNode 导致 tag/caret 顺序被浏览器 Range 状态打乱。\n const fragment = document.createDocumentFragment();\n fragment.append(tagNode, caretNode);\n range.insertNode(fragment);\n } else {\n editor.append(tagNode, caretNode);\n }\n cleanupCaretTextNodes();\n lastInsertedCaretRef.current = caretNode;\n setCursorAtTextEnd(caretNode);\n emitChange();\n\n // 关闭快捷列表和父组件同步 tags/value 会触发一次 React 更新;\n // 下一帧再恢复到刚插入 tag 后的零宽锚点,避免光标被 focus 或重渲染带回旧位置。\n window.requestAnimationFrame(() => {\n if (lastInsertedCaretRef.current === caretNode) {\n restoreCursorAfterInsert(caretNode);\n }\n });\n },\n [\n buildTagNode,\n cleanupCaretTextNodes,\n emitChange,\n replaceTextRange,\n restoreCursorAfterInsert,\n setCursorAtTextEnd,\n ],\n );\n\n useEffect(() => {\n document.addEventListener(\"selectionchange\", saveSelectionRange);\n return () => {\n document.removeEventListener(\"selectionchange\", saveSelectionRange);\n };\n }, [saveSelectionRange]);\n\n useImperativeHandle(ref, () => ({\n focus: () => editorRef.current?.focus(),\n getText: getPlainText,\n getParts,\n getCursorOffset: getOffsetInEditor,\n insertTag,\n }), [getOffsetInEditor, getParts, getPlainText, insertTag]);\n\n useIsomorphicLayoutEffect(() => {\n const editor = editorRef.current;\n if (!editor) return;\n\n const incomingTagIds = tags.map((tag) => tag.id).join(\"|\");\n const lastEmittedState = lastEmittedStateRef.current;\n const isInternalControlledEcho =\n value === lastEmittedState.value &&\n incomingTagIds === lastEmittedState.tagIds;\n\n if (isInternalControlledEcho && editor.childNodes.length > 0) return;\n\n editor.querySelectorAll(`[${TAG_ATTR}]`).forEach((node) => {\n tagRootMapRef.current.get(node)?.unmount();\n });\n editor.innerHTML = \"\";\n\n if (value) editor.appendChild(createTextNode(value));\n tags.forEach((tag) => {\n editor.append(buildTagNode(tag), createTextNode(CARET_TEXT));\n });\n\n // 外部主动写入 value/tags 后,需要在首帧绘制前同步 DOM;若输入区已有焦点,则把光标放在内容末尾。\n const lastTextNode = Array.from(editor.childNodes)\n .reverse()\n .find((node): node is Text => node.nodeType === Node.TEXT_NODE);\n if (document.activeElement === editor && lastTextNode) {\n setCursorAtTextEnd(lastTextNode);\n }\n }, [buildTagNode, setCursorAtTextEnd, tags, value]);\n\n useIsomorphicLayoutEffect(() => {\n const editor = editorRef.current;\n if (!editor) return;\n if (!value && tags.length === 0 && editor.childNodes.length > 0) {\n editor.querySelectorAll(`[${TAG_ATTR}]`).forEach((node) => {\n tagRootMapRef.current.get(node)?.unmount();\n });\n editor.innerHTML = \"\";\n }\n }, [tags.length, value]);\n\n const handleInput = () => {\n if (!composingRef.current) emitChange();\n };\n\n const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {\n if (event.key === \"Backspace\" || event.key === \"Delete\") {\n const selection = window.getSelection();\n if (selection?.rangeCount) {\n const range = selection.getRangeAt(0);\n if (range.collapsed) {\n const targetTag =\n event.key === \"Backspace\"\n ? getTagBeforeRange(range)\n : getTagAfterRange(range);\n\n if (targetTag) {\n event.preventDefault();\n removeTagById(targetTag.dataset.xadkInlineTagId || \"\");\n return;\n }\n }\n }\n }\n onKeyDown?.(event);\n };\n\n // placeholder 只由受控 value/tags 派生,避免从 contenteditable DOM 读取导致外部插入 tag 后状态滞后一帧。\n const showPlaceholder = !value && tags.length === 0;\n\n return (\n <div className={styles.inlineInputWrapper}>\n {showPlaceholder && placeholder && (\n <div className={styles.inlinePlaceholder}>{placeholder}</div>\n )}\n <div\n ref={editorRef}\n className={styles.inlineInput}\n contentEditable={!disabled}\n suppressContentEditableWarning\n role=\"textbox\"\n aria-multiline=\"true\"\n onInput={handleInput}\n onKeyDown={handleKeyDown}\n onKeyUp={(event) => {\n saveSelectionRange();\n onKeyUp?.(event);\n }}\n onMouseUp={saveSelectionRange}\n onFocus={saveSelectionRange}\n onCompositionStart={() => {\n composingRef.current = true;\n }}\n onCompositionEnd={() => {\n composingRef.current = false;\n emitChange();\n }}\n data-cursor-offset={getOffsetInEditor()}\n />\n </div>\n );\n },\n);\n\nInlineTagInput.displayName = \"InlineTagInput\";\n\nexport default InlineTagInput;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -77,7 +77,10 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
77
77
|
tags: controlledTags,
|
|
78
78
|
defaultTags = [],
|
|
79
79
|
onTagsChange,
|
|
80
|
-
onQuickKeyTrigger
|
|
80
|
+
onQuickKeyTrigger,
|
|
81
|
+
submitFormat = "text",
|
|
82
|
+
formatTag,
|
|
83
|
+
transformSubmitData
|
|
81
84
|
}, ref) => {
|
|
82
85
|
const styles = (0, import_styles.useStyles)();
|
|
83
86
|
const isControlled = controlledValue !== void 0;
|
|
@@ -115,6 +118,69 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
115
118
|
const data = (response == null ? void 0 : response.data) ?? response;
|
|
116
119
|
return (Array.isArray(data) ? data[0] : data) ?? {};
|
|
117
120
|
};
|
|
121
|
+
const escapeXml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
122
|
+
const escapeMarkdownLabel = (text) => text.replace(/\\/g, "\\\\").replace(/\]/g, "\\]");
|
|
123
|
+
const getTagSubmitValue = (tag) => {
|
|
124
|
+
var _a, _b;
|
|
125
|
+
return String(tag.value ?? ((_a = tag.data) == null ? void 0 : _a.value) ?? ((_b = tag.data) == null ? void 0 : _b.name) ?? tag.label ?? "");
|
|
126
|
+
};
|
|
127
|
+
const defaultFormatTag = (tag, format, index) => {
|
|
128
|
+
const value2 = getTagSubmitValue(tag);
|
|
129
|
+
if (format === "xml") {
|
|
130
|
+
return `<tag type="${escapeXml(tag.type)}" id="${escapeXml(tag.id)}" label="${escapeXml(tag.label)}" value="${escapeXml(value2)}" />`;
|
|
131
|
+
}
|
|
132
|
+
if (format === "markdown") {
|
|
133
|
+
return `[/${escapeMarkdownLabel(tag.label)}](xadk-tag://${encodeURIComponent(tag.type)}/${encodeURIComponent(tag.id)})`;
|
|
134
|
+
}
|
|
135
|
+
return `/${value2 || `tag-${index + 1}`}`;
|
|
136
|
+
};
|
|
137
|
+
const serializeTag = (tag, format, index) => (formatTag == null ? void 0 : formatTag(tag, { format, index })) ?? defaultFormatTag(tag, format, index);
|
|
138
|
+
const getSubmitParts = (0, import_react.useCallback)(() => {
|
|
139
|
+
var _a;
|
|
140
|
+
if (inlineTags) {
|
|
141
|
+
const parts = ((_a = inlineInputRef.current) == null ? void 0 : _a.getParts()) || [];
|
|
142
|
+
if (parts.length > 0)
|
|
143
|
+
return parts;
|
|
144
|
+
}
|
|
145
|
+
const textParts = value ? [{ type: "text", text: value }] : [];
|
|
146
|
+
const tagParts = tags.map((tag) => ({ type: "tag", tag }));
|
|
147
|
+
return [...tagParts, ...textParts];
|
|
148
|
+
}, [inlineTags, tags, value]);
|
|
149
|
+
const serializeParts = (parts, format) => {
|
|
150
|
+
if (format === "structured") {
|
|
151
|
+
return parts.reduce(
|
|
152
|
+
(content, part) => content + (part.type === "text" ? part.text : ""),
|
|
153
|
+
""
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
let tagIndex = 0;
|
|
157
|
+
return parts.map((part) => {
|
|
158
|
+
if (part.type === "text")
|
|
159
|
+
return part.text;
|
|
160
|
+
const result = serializeTag(part.tag, format, tagIndex);
|
|
161
|
+
tagIndex += 1;
|
|
162
|
+
return result;
|
|
163
|
+
}).join("");
|
|
164
|
+
};
|
|
165
|
+
const createSubmitData = (formattedFiles) => {
|
|
166
|
+
const parts = getSubmitParts();
|
|
167
|
+
const rawText = parts.reduce(
|
|
168
|
+
(content, part) => content + (part.type === "text" ? part.text : ""),
|
|
169
|
+
""
|
|
170
|
+
);
|
|
171
|
+
const standardData = {
|
|
172
|
+
text: serializeParts(parts, submitFormat),
|
|
173
|
+
rawText,
|
|
174
|
+
format: submitFormat,
|
|
175
|
+
parts,
|
|
176
|
+
files: formattedFiles,
|
|
177
|
+
tags
|
|
178
|
+
};
|
|
179
|
+
return {
|
|
180
|
+
...standardData,
|
|
181
|
+
payload: transformSubmitData == null ? void 0 : transformSubmitData(standardData)
|
|
182
|
+
};
|
|
183
|
+
};
|
|
118
184
|
const checkFileType = (file) => {
|
|
119
185
|
if ((allowedFileTypes == null ? void 0 : allowedFileTypes.length) > 0) {
|
|
120
186
|
const ext = (0, import_file.getExt)(file);
|
|
@@ -215,9 +281,21 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
215
281
|
() => ({
|
|
216
282
|
addFiles: (rawFiles) => {
|
|
217
283
|
handleFileSelect(rawFiles);
|
|
284
|
+
},
|
|
285
|
+
insertTag: (tag) => {
|
|
286
|
+
var _a;
|
|
287
|
+
if (inlineTags) {
|
|
288
|
+
(_a = inlineInputRef.current) == null ? void 0 : _a.insertTag(tag);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
setTags([...tags, tag]);
|
|
292
|
+
},
|
|
293
|
+
focus: () => {
|
|
294
|
+
var _a, _b;
|
|
295
|
+
inlineTags ? (_a = inlineInputRef.current) == null ? void 0 : _a.focus() : (_b = textareaRef.current) == null ? void 0 : _b.focus();
|
|
218
296
|
}
|
|
219
297
|
}),
|
|
220
|
-
[handleFileSelect]
|
|
298
|
+
[handleFileSelect, inlineTags, setTags, tags]
|
|
221
299
|
);
|
|
222
300
|
const triggerFileSelect = () => {
|
|
223
301
|
if (fileInputRef.current) {
|
|
@@ -401,10 +479,13 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
401
479
|
const handleQuickKeySelect = (0, import_react.useCallback)(
|
|
402
480
|
(option) => {
|
|
403
481
|
var _a, _b, _c, _d;
|
|
482
|
+
const tagType = (activeQuickKeyConfig == null ? void 0 : activeQuickKeyConfig.tagType) || activeQuickKey || "unknown";
|
|
404
483
|
const newTag = {
|
|
405
484
|
id: generateId(),
|
|
406
485
|
label: option.label,
|
|
407
|
-
type:
|
|
486
|
+
type: tagType,
|
|
487
|
+
value: option.value ?? option.name ?? option.label,
|
|
488
|
+
trigger: activeQuickKey || void 0,
|
|
408
489
|
icon: option.icon,
|
|
409
490
|
data: option
|
|
410
491
|
};
|
|
@@ -502,11 +583,7 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
502
583
|
onOk: () => {
|
|
503
584
|
const formattedFiles = formatFilesForServer(successFiles);
|
|
504
585
|
if (onSubmit) {
|
|
505
|
-
onSubmit(
|
|
506
|
-
text: value,
|
|
507
|
-
files: formattedFiles,
|
|
508
|
-
tags
|
|
509
|
-
});
|
|
586
|
+
onSubmit(createSubmitData(formattedFiles));
|
|
510
587
|
}
|
|
511
588
|
setValue("");
|
|
512
589
|
setTags([]);
|
|
@@ -519,11 +596,7 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
519
596
|
} else {
|
|
520
597
|
const formattedFiles = formatFilesForServer(successFiles);
|
|
521
598
|
if (onSubmit) {
|
|
522
|
-
onSubmit(
|
|
523
|
-
text: value,
|
|
524
|
-
files: formattedFiles,
|
|
525
|
-
tags
|
|
526
|
-
});
|
|
599
|
+
onSubmit(createSubmitData(formattedFiles));
|
|
527
600
|
}
|
|
528
601
|
setValue("");
|
|
529
602
|
setTags([]);
|
|
@@ -583,26 +656,16 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
583
656
|
const renderFileList = () => {
|
|
584
657
|
if (files.length === 0)
|
|
585
658
|
return null;
|
|
586
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
587
|
-
|
|
659
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.contextFiles, children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
660
|
+
import_FileGallery.default,
|
|
588
661
|
{
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
import_FileGallery.default,
|
|
597
|
-
{
|
|
598
|
-
file,
|
|
599
|
-
removable: true,
|
|
600
|
-
onRemove: handleRemoveFile
|
|
601
|
-
},
|
|
602
|
-
file.id
|
|
603
|
-
))
|
|
604
|
-
}
|
|
605
|
-
);
|
|
662
|
+
file,
|
|
663
|
+
size: "small",
|
|
664
|
+
removable: true,
|
|
665
|
+
onRemove: handleRemoveFile
|
|
666
|
+
},
|
|
667
|
+
file.id
|
|
668
|
+
)) });
|
|
606
669
|
};
|
|
607
670
|
const containerClass = `${styles.container} ${isDragOver ? "drag-over" : ""}`;
|
|
608
671
|
const uploading = files.some((f) => f.status === "uploading");
|
|
@@ -681,6 +744,8 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
681
744
|
}
|
|
682
745
|
return slot;
|
|
683
746
|
};
|
|
747
|
+
const headerNode = renderSlot(header, false);
|
|
748
|
+
const fileListNode = allowUpload && files.length > 0 ? renderFileList() : null;
|
|
684
749
|
const defaultSuffix = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.buttonGroup, children: sendButtonRender ? sendButtonRender({
|
|
685
750
|
loading,
|
|
686
751
|
disabled: uploading || disabled,
|
|
@@ -716,8 +781,6 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
716
781
|
onDragLeave: allowUpload && draggable ? handleDragLeave : void 0,
|
|
717
782
|
onDrop: allowUpload && draggable ? handleDrop : void 0,
|
|
718
783
|
children: [
|
|
719
|
-
renderSlot(header, false),
|
|
720
|
-
allowUpload && files.length > 0 && renderFileList(),
|
|
721
784
|
allowUpload && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
722
785
|
"input",
|
|
723
786
|
{
|
|
@@ -735,6 +798,10 @@ var XAdkSender = (0, import_react.forwardRef)(
|
|
|
735
798
|
}
|
|
736
799
|
),
|
|
737
800
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.mainArea, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.senderWrap, children: [
|
|
801
|
+
(headerNode || fileListNode) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.contextBar, children: [
|
|
802
|
+
headerNode && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.contextHeader, children: headerNode }),
|
|
803
|
+
fileListNode
|
|
804
|
+
] }),
|
|
738
805
|
!inlineTags && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_InputTags.default, { tags, onRemove: handleRemoveTag }),
|
|
739
806
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.inputAndButtons, children: [
|
|
740
807
|
renderSlot(prefix, false),
|