one-design-next 0.0.14 → 0.0.16
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/_genui-types.d.ts +72 -27
- package/dist/action-bar/style/index.css +2 -2
- package/dist/agent-step/style/index.css +1 -1
- package/dist/attachments/style/index.css +5 -5
- package/dist/chat-item/style/index.css +2 -2
- package/dist/composer/clipboard.d.ts +1 -1
- package/dist/composer/editor.d.ts +10 -2
- package/dist/composer/editor.js +176 -34
- package/dist/composer/hooks/useChipManager.d.ts +8 -3
- package/dist/composer/hooks/useChipManager.js +105 -10
- package/dist/composer/index.d.ts +17 -2
- package/dist/composer/index.js +136 -65
- package/dist/composer/inline-ref.d.ts +6 -2
- package/dist/composer/inline-ref.js +10 -3
- package/dist/composer/param-panel.d.ts +14 -0
- package/dist/composer/param-panel.js +1 -0
- package/dist/composer/segments.d.ts +29 -0
- package/dist/composer/segments.js +83 -0
- package/dist/composer/send-meta.d.ts +7 -4
- package/dist/composer/send-meta.js +12 -52
- package/dist/composer/style/index.css +27 -8
- package/dist/composer/utils.d.ts +22 -1
- package/dist/composer/utils.js +213 -45
- package/dist/fab/style/index.css +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/invocation/index.d.ts +7 -2
- package/dist/invocation/index.js +14 -8
- package/dist/invocation/param-popover.d.ts +20 -0
- package/dist/invocation/param-popover.js +112 -0
- package/dist/invocation/style/index.css +96 -9
- package/dist/mention/index.d.ts +1 -6
- package/dist/mention/index.js +11 -8
- package/dist/mention/style/index.css +30 -9
- package/dist/preview-panel/index.js +11 -1
- package/dist/preview-panel/style/index.css +11 -0
- package/dist/skill-slot/index.js +5 -5
- package/dist/skill-slot/style/index.css +51 -27
- package/dist/suggestions/index.js +1 -5
- package/dist/suggestions/style/index.css +6 -7
- package/dist/user-bubble/index.js +9 -4
- package/dist/user-bubble/render-segments.d.ts +9 -0
- package/dist/user-bubble/render-segments.js +42 -0
- package/dist/user-bubble/style/index.css +9 -0
- package/dist/welcome/style/index.css +1 -1
- package/package.json +4 -4
- package/dist/composer/chip.d.ts +0 -36
- package/dist/composer/chip.js +0 -49
package/dist/_genui-types.d.ts
CHANGED
|
@@ -54,24 +54,58 @@ export interface PreviewTab {
|
|
|
54
54
|
content: string;
|
|
55
55
|
type: string;
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* 内联引用三态:
|
|
59
|
+
* - complete:默认稳态(无参数 / 参数已填)
|
|
60
|
+
* - pending:邀请补充参数(业务自定,不强制)
|
|
61
|
+
* - active:参数面板打开(最高优先级,由组件内部根据 `active` prop 控制)
|
|
62
|
+
*/
|
|
63
|
+
export type InlineRefState = 'pending' | 'complete';
|
|
64
|
+
/**
|
|
65
|
+
* Composer 正文内 `/` 触发的内联引用(Invocation 组件数据契约)。
|
|
66
|
+
*
|
|
67
|
+
* 库只关心 UI 渲染必需字段;业务字段(refId、参数、上下文等)放 `payload`,
|
|
68
|
+
* 库**不约定其内部结构**,发送时原样透传。
|
|
69
|
+
*/
|
|
58
70
|
export interface InvocationData {
|
|
71
|
+
/** chip 实例唯一 id(库内部 DOM 锚定、segments 去重用) */
|
|
59
72
|
id: string;
|
|
60
|
-
/**
|
|
73
|
+
/** 业务子类型('skill' / 'command' / 自定义);当前仅 'skill' 使用,预留扩展 */
|
|
61
74
|
kind: string;
|
|
62
|
-
|
|
75
|
+
/** 显示文字 */
|
|
63
76
|
label: string;
|
|
77
|
+
/** 图标 name(可选) */
|
|
64
78
|
icon?: string;
|
|
65
|
-
|
|
79
|
+
/** 视觉态。`active`(参数面板打开)由组件 prop 控制,不进 data。 */
|
|
80
|
+
state?: InlineRefState;
|
|
81
|
+
/** 业务自定义任意字段:refId / 参数 / 上下文等,库不消费 */
|
|
82
|
+
payload?: Record<string, unknown>;
|
|
66
83
|
}
|
|
67
|
-
/** Composer 正文内 `@` 触发的内联引用(Mention
|
|
84
|
+
/** Composer 正文内 `@` 触发的内联引用(Mention 组件数据契约)。结构同 Invocation。 */
|
|
68
85
|
export interface MentionData {
|
|
69
86
|
id: string;
|
|
70
87
|
kind: string;
|
|
71
|
-
refId: string;
|
|
72
88
|
label: string;
|
|
73
89
|
icon?: string;
|
|
74
|
-
|
|
90
|
+
state?: InlineRefState;
|
|
91
|
+
payload?: Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Composer 编辑器内 chip 实例数据(DOM host + Portal 渲染用)。
|
|
95
|
+
* 发送时经 `rawValueToSegments` 转为 `ComposerSegment` 的 invocation / mention。
|
|
96
|
+
*/
|
|
97
|
+
export interface ChipData {
|
|
98
|
+
/** chip 实例唯一 id(与 value marker 中的 id 对应) */
|
|
99
|
+
id: string;
|
|
100
|
+
label: string;
|
|
101
|
+
icon?: string;
|
|
102
|
+
/**
|
|
103
|
+
* 'invocation' = `/` 或 Skill 按钮;'mention' = `@`(预留)。
|
|
104
|
+
* 其他值归 invocation。
|
|
105
|
+
*/
|
|
106
|
+
kind?: 'invocation' | 'mention' | (string & {});
|
|
107
|
+
state?: InlineRefState;
|
|
108
|
+
payload?: Record<string, unknown>;
|
|
75
109
|
}
|
|
76
110
|
export interface SkillItem {
|
|
77
111
|
id: string;
|
|
@@ -79,32 +113,38 @@ export interface SkillItem {
|
|
|
79
113
|
icon: string;
|
|
80
114
|
description?: string;
|
|
81
115
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
|
|
116
|
+
* 插入后的默认视觉态。`'pending'` 时若 Composer 提供 `renderParamPanel`,
|
|
117
|
+
* 插入后会自动打开参数面板。
|
|
118
|
+
*/
|
|
119
|
+
initialState?: InlineRefState;
|
|
120
|
+
/**
|
|
121
|
+
* 禁用态。true 时按钮不可点击、不会触发 onSelect、视觉降级。
|
|
122
|
+
* 典型场景:技能灰度未开放 / 当前会话不支持。
|
|
85
123
|
*/
|
|
86
124
|
disabled?: boolean;
|
|
87
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Composer 发送时的有序内容 IR(与编辑器内出现顺序一致)。
|
|
128
|
+
*
|
|
129
|
+
* 这是 `onSend` 的**主出口**:业务按此数组顺序自定义拼后端(可选用
|
|
130
|
+
* `segmentsToReadableText` 工具函数兜底拼成一句话)。
|
|
131
|
+
*/
|
|
132
|
+
export type ComposerSegment = {
|
|
133
|
+
type: 'text';
|
|
134
|
+
data: {
|
|
135
|
+
text: string;
|
|
136
|
+
};
|
|
137
|
+
} | {
|
|
138
|
+
type: 'invocation';
|
|
139
|
+
data: InvocationData;
|
|
140
|
+
} | {
|
|
141
|
+
type: 'mention';
|
|
142
|
+
data: MentionData;
|
|
143
|
+
};
|
|
144
|
+
/** `onSend(segments, meta?)` 第二参数:非正文的附加数据。 */
|
|
88
145
|
export interface SendMeta {
|
|
89
146
|
attachments?: Attachment[];
|
|
90
147
|
webSearch?: boolean;
|
|
91
|
-
/** `/` 内联引用列表(Invocation),按编辑器内出现顺序。 */
|
|
92
|
-
invocations?: InvocationData[];
|
|
93
|
-
/** `@` 内联引用列表(Mention),按编辑器内出现顺序。 */
|
|
94
|
-
mentions?: MentionData[];
|
|
95
|
-
/**
|
|
96
|
-
* @deprecated 请改用 `invocations` / `mentions`。本字段仍双写一版,便于旧业务迁移。
|
|
97
|
-
* 编辑器内 chip 列表(按出现顺序);`kind: 'skill'` 对应 invocation。
|
|
98
|
-
*/
|
|
99
|
-
chips?: ComposerChipMeta[];
|
|
100
|
-
}
|
|
101
|
-
/** SendMeta.chips 的元素结构;与 components/composer/chip.tsx ChipData 对齐。 */
|
|
102
|
-
export interface ComposerChipMeta {
|
|
103
|
-
id: string;
|
|
104
|
-
skillId: string;
|
|
105
|
-
label: string;
|
|
106
|
-
icon: string;
|
|
107
|
-
kind?: 'skill' | 'invocation' | 'mention' | (string & {});
|
|
108
148
|
}
|
|
109
149
|
export type AgentEvent = {
|
|
110
150
|
kind: 'thinking';
|
|
@@ -140,6 +180,11 @@ export interface ChatMessage {
|
|
|
140
180
|
id: string;
|
|
141
181
|
role: 'user' | 'assistant' | 'system';
|
|
142
182
|
content: string;
|
|
183
|
+
/**
|
|
184
|
+
* 有序内容 IR(与 Composer `onSend` 的 segments 同构)。
|
|
185
|
+
* UserBubble 优先用此字段渲染正文;`content` 仍可保留给 LLM / 日志(如 readableText)。
|
|
186
|
+
*/
|
|
187
|
+
segments?: ComposerSegment[];
|
|
143
188
|
timestamp: number;
|
|
144
189
|
isStreaming?: boolean;
|
|
145
190
|
toolCalls?: ToolCall[];
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
}
|
|
44
44
|
[data-odn-action-bar-toolbar-btn] [data-odn-button]:focus-visible {
|
|
45
45
|
outline: none;
|
|
46
|
-
box-shadow: 0 0 0 2px var(--odn-color-
|
|
46
|
+
box-shadow: 0 0 0 2px var(--odn-color-brand-6);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
[data-odn-action-bar-toolbar-btn] [data-odn-action-bar-feedback=positive][aria-pressed=true] {
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
}
|
|
86
86
|
[data-odn-action-bar-pager-btn]:focus-visible {
|
|
87
87
|
outline: none;
|
|
88
|
-
box-shadow: 0 0 0 2px var(--odn-color-
|
|
88
|
+
box-shadow: 0 0 0 2px var(--odn-color-brand-6);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
[data-odn-action-bar-pager-text] {
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
padding: 20px 20px 20px 24px;
|
|
245
245
|
border-radius: var(--odn-attachments-report-border-radius);
|
|
246
246
|
border: none;
|
|
247
|
-
background: radial-gradient(ellipse 60% 50% at 0% 0%, color-mix(in srgb, var(--odn-color-
|
|
247
|
+
background: radial-gradient(ellipse 60% 50% at 0% 0%, color-mix(in srgb, var(--odn-color-brand-6) 4%, transparent) 0%, transparent 60%), var(--odn-color-bg-elevated);
|
|
248
248
|
box-sizing: border-box;
|
|
249
249
|
font: inherit;
|
|
250
250
|
color: inherit;
|
|
@@ -285,8 +285,8 @@
|
|
|
285
285
|
width: var(--odn-attachments-report-icon-size);
|
|
286
286
|
height: var(--odn-attachments-report-icon-size);
|
|
287
287
|
border-radius: 10px;
|
|
288
|
-
background: var(--odn-color-
|
|
289
|
-
color: var(--odn-color-
|
|
288
|
+
background: var(--odn-color-brand-1);
|
|
289
|
+
color: var(--odn-color-brand-6);
|
|
290
290
|
flex-shrink: 0;
|
|
291
291
|
}
|
|
292
292
|
|
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
position: absolute;
|
|
295
295
|
top: 8px;
|
|
296
296
|
right: 8px;
|
|
297
|
-
color: var(--odn-color-
|
|
297
|
+
color: var(--odn-color-brand-6);
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
[data-odn-attachments-report-body] {
|
|
@@ -342,7 +342,7 @@
|
|
|
342
342
|
[data-odn-attachments-report-thumb-placeholder] {
|
|
343
343
|
position: absolute;
|
|
344
344
|
inset: 0;
|
|
345
|
-
background: linear-gradient(180deg, var(--odn-color-bg-elevated) 0%, var(--odn-color-
|
|
345
|
+
background: linear-gradient(180deg, var(--odn-color-bg-elevated) 0%, var(--odn-color-brand-1) 100%);
|
|
346
346
|
}
|
|
347
347
|
[data-odn-attachments-report-thumb-placeholder]::before, [data-odn-attachments-report-thumb-placeholder]::after {
|
|
348
348
|
content: "";
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
box-shadow: none;
|
|
57
57
|
}
|
|
58
58
|
[data-odn-chat-item-rename-input]::selection {
|
|
59
|
-
background: var(--odn-color-
|
|
59
|
+
background: var(--odn-color-brand-3, rgba(0, 160, 200, 0.25));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
[data-odn-chat-item-btn] {
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
width: 6px;
|
|
126
126
|
height: 6px;
|
|
127
127
|
border-radius: 50%;
|
|
128
|
-
background: var(--odn-color-
|
|
128
|
+
background: var(--odn-color-brand-6);
|
|
129
129
|
flex-shrink: 0;
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChipData } from '
|
|
1
|
+
import type { ChipData } from '../_genui-types';
|
|
2
2
|
import type { ManagedChip } from './hooks/useChipManager';
|
|
3
3
|
export declare const COMPOSER_CLIPBOARD_MIME = "application/x-odn-composer-chips";
|
|
4
4
|
export interface ComposerClipboardPayload {
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
*
|
|
14
14
|
* chip 模型(hybrid):
|
|
15
15
|
* - DOM 由 contenteditable 自治:每个 chip 是 contentEditable=false 的 span(host),带 data-mention-id
|
|
16
|
-
* - 视觉由 React Portal 注入到 host 内:
|
|
16
|
+
* - 视觉由 React Portal 注入到 host 内:ComposerInlineRef 负责
|
|
17
17
|
* - chip 状态(id ↔ host)由 useChipManager 维护
|
|
18
18
|
*/
|
|
19
19
|
/// <reference types="react" />
|
|
20
|
-
import type { ChipData } from '
|
|
20
|
+
import type { ChipData } from '../_genui-types';
|
|
21
21
|
/** Composer editor 触发态对外信号——告诉父组件该弹浮层选 skill 了。 */
|
|
22
22
|
export interface ComposerEditorTriggerInfo {
|
|
23
23
|
/** 触发字符(@ 或 /) */
|
|
@@ -88,6 +88,10 @@ export interface ComposerEditorProps {
|
|
|
88
88
|
onPasteFiles?: (files: File[]) => void;
|
|
89
89
|
/** 复制 / 剪切时回调(选区含 chip 时触发)。 */
|
|
90
90
|
onCopyChips?: (chips: ChipData[]) => void;
|
|
91
|
+
/** 当前打开参数面板的 chip id(驱动 Invocation active 态)。 */
|
|
92
|
+
paramEditChipId?: string | null;
|
|
93
|
+
/** 内联引用(/ skill)被点击;由 Composer 决定是否打开参数面板。 */
|
|
94
|
+
onInlineRefClick?: (chipId: string, anchor: HTMLElement) => void;
|
|
91
95
|
/**
|
|
92
96
|
* 禁用:contenteditable 关闭,editor 不接受输入 / 聚焦 / 粘贴。
|
|
93
97
|
* 已有的文本与 chip 仍渲染(仅冻结,不清空),便于"等接通后继续编辑"。
|
|
@@ -121,6 +125,10 @@ export interface ComposerEditorRef {
|
|
|
121
125
|
* 父组件 onSend 时用,避免自行 parse value marker。
|
|
122
126
|
*/
|
|
123
127
|
getChips: () => ChipData[];
|
|
128
|
+
/** 更新 chip 数据(参数面板保存)。 */
|
|
129
|
+
updateChipData: (id: string, patch: Partial<ChipData>) => void;
|
|
130
|
+
/** 取 chip host 元素(参数面板 anchor)。 */
|
|
131
|
+
getChipHost: (id: string) => HTMLSpanElement | null;
|
|
124
132
|
/** 底层 contenteditable 元素 */
|
|
125
133
|
nativeElement: HTMLDivElement | null;
|
|
126
134
|
}
|
package/dist/composer/editor.js
CHANGED
|
@@ -25,7 +25,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
|
25
25
|
*
|
|
26
26
|
* chip 模型(hybrid):
|
|
27
27
|
* - DOM 由 contenteditable 自治:每个 chip 是 contentEditable=false 的 span(host),带 data-mention-id
|
|
28
|
-
* - 视觉由 React Portal 注入到 host 内:
|
|
28
|
+
* - 视觉由 React Portal 注入到 host 内:ComposerInlineRef 负责
|
|
29
29
|
* - chip 状态(id ↔ host)由 useChipManager 维护
|
|
30
30
|
*/
|
|
31
31
|
|
|
@@ -34,7 +34,7 @@ import { createPortal } from 'react-dom';
|
|
|
34
34
|
import { buildClipboardPayload, COMPOSER_CLIPBOARD_MIME, parseClipboardPayload, plainTextFromClipboardPayload, remapClipboardPayload } from "./clipboard";
|
|
35
35
|
import { useChipManager } from "./hooks/useChipManager";
|
|
36
36
|
import { useChipSelectionMarker } from "./hooks/useChipSelectionMarker";
|
|
37
|
-
import { findChipHostAfterCaret, findChipHostBeforeCaret, getPlainText, getVisibleTextBeforeHost, isEditorEmpty, moveCaretBeforeChip, normalizeEditorDom, probeTrigger, resolveCaretJumpAroundChip, stripInvisibleChars,
|
|
37
|
+
import { ensureTextAnchorAt, findChipHostAfterCaret, findChipHostBeforeCaret, getPlainText, getVisibleTextBeforeHost, isEditorEmpty, moveCaretBeforeChip, normalizeComposerEditorDom, normalizeEditorDom, probeTrigger, resolveCaretJumpAroundChip, stripInvisibleChars, CARET_SPACER } from "./utils";
|
|
38
38
|
import { ComposerInlineRef } from "./inline-ref";
|
|
39
39
|
import ScrollArea from "../scroll-area";
|
|
40
40
|
|
|
@@ -102,6 +102,9 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
102
102
|
onHeightChange = _ref.onHeightChange,
|
|
103
103
|
onPasteFiles = _ref.onPasteFiles,
|
|
104
104
|
onCopyChips = _ref.onCopyChips,
|
|
105
|
+
_ref$paramEditChipId = _ref.paramEditChipId,
|
|
106
|
+
paramEditChipId = _ref$paramEditChipId === void 0 ? null : _ref$paramEditChipId,
|
|
107
|
+
onInlineRefClick = _ref.onInlineRefClick,
|
|
105
108
|
_ref$disabled = _ref.disabled,
|
|
106
109
|
disabled = _ref$disabled === void 0 ? false : _ref$disabled,
|
|
107
110
|
className = _ref.className,
|
|
@@ -122,18 +125,29 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
122
125
|
|
|
123
126
|
/** 当前活动 trigger probe;null 表示无触发态。 */
|
|
124
127
|
var triggerRef = useRef(null);
|
|
128
|
+
/** 最近一次落在 editor 内的 selection(菜单点击导致 blur 后用于恢复插入点)。 */
|
|
129
|
+
var lastEditorRangeRef = useRef(null);
|
|
130
|
+
/** 抑制换行后短窗口内的 selection 二次校正,避免首次 chip 后换行闪烁。 */
|
|
131
|
+
var suppressSelectionNormalizeCountRef = useRef(0);
|
|
132
|
+
/** 用户刚执行“主动换行”时,允许 empty 判定在一次变更内保持非空。 */
|
|
133
|
+
var forceNonEmptyOnceRef = useRef(false);
|
|
125
134
|
|
|
126
135
|
/** 派生:编辑器是否完全空(无文本 + 无 chip),驱动 placeholder。 */
|
|
127
136
|
var _useState = useState(true),
|
|
128
137
|
_useState2 = _slicedToArray(_useState, 2),
|
|
129
138
|
empty = _useState2[0],
|
|
130
139
|
setEmpty = _useState2[1];
|
|
140
|
+
/** 输入模态:键盘操作时置为 keyboard,用于抑制“鼠标隐藏但 hover 仍生效”的视觉残留。 */
|
|
141
|
+
var _useState3 = useState('pointer'),
|
|
142
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
143
|
+
inputModality = _useState4[0],
|
|
144
|
+
setInputModality = _useState4[1];
|
|
131
145
|
|
|
132
146
|
/** ScrollArea root 显式高度。Base UI viewport=100%,root 必须有确定 height。 */
|
|
133
|
-
var
|
|
134
|
-
|
|
135
|
-
scrollHeight =
|
|
136
|
-
setScrollHeight =
|
|
147
|
+
var _useState5 = useState(null),
|
|
148
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
|
149
|
+
scrollHeight = _useState6[0],
|
|
150
|
+
setScrollHeight = _useState6[1];
|
|
137
151
|
|
|
138
152
|
/** onHeightChange 用 ref 稳定,避免 ResizeObserver effect 反复 cleanup/重建。 */
|
|
139
153
|
var onHeightChangeRef = useRef(onHeightChange);
|
|
@@ -173,15 +187,27 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
173
187
|
});
|
|
174
188
|
(_onHeightChangeRef$cu = onHeightChangeRef.current) === null || _onHeightChangeRef$cu === void 0 || _onHeightChangeRef$cu.call(onHeightChangeRef, natural, overflow);
|
|
175
189
|
}, [minHeightPx, maxHeightPx]);
|
|
190
|
+
var shouldShowPlaceholder = useCallback(function (el) {
|
|
191
|
+
if (!isEditorEmpty(el)) return false;
|
|
192
|
+
// placeholder 仅在“单行且空内容”展示
|
|
193
|
+
var lineCount = el.querySelectorAll('br').length + 1;
|
|
194
|
+
return lineCount <= 1;
|
|
195
|
+
}, []);
|
|
176
196
|
var fireChange = useCallback(function () {
|
|
177
197
|
var el = editorRef.current;
|
|
178
198
|
if (!el) return;
|
|
179
199
|
normalizeEditorDom(el);
|
|
180
|
-
|
|
200
|
+
var showPlaceholder = shouldShowPlaceholder(el);
|
|
201
|
+
if (forceNonEmptyOnceRef.current && showPlaceholder) {
|
|
202
|
+
setEmpty(false);
|
|
203
|
+
} else {
|
|
204
|
+
setEmpty(showPlaceholder);
|
|
205
|
+
}
|
|
206
|
+
forceNonEmptyOnceRef.current = false;
|
|
181
207
|
onChange === null || onChange === void 0 || onChange(getPlainText(el));
|
|
182
208
|
// editor.height 锁定时 RO 不触发,主动同步一次,确保升格判定能拿到 scrollHeight
|
|
183
209
|
syncHeight();
|
|
184
|
-
}, [onChange, syncHeight]);
|
|
210
|
+
}, [onChange, shouldShowPlaceholder, syncHeight]);
|
|
185
211
|
|
|
186
212
|
/* ─────────────────────────────────────
|
|
187
213
|
* chip 管理
|
|
@@ -195,6 +221,8 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
195
221
|
insertSerializedAtRange = _useChipManager.insertSerializedAtRange,
|
|
196
222
|
syncChipsAfterDomMutation = _useChipManager.syncChipsAfterDomMutation,
|
|
197
223
|
removeChip = _useChipManager.removeChip,
|
|
224
|
+
updateChipData = _useChipManager.updateChipData,
|
|
225
|
+
getChipHost = _useChipManager.getChipHost,
|
|
198
226
|
resetChips = _useChipManager.resetChips;
|
|
199
227
|
useChipSelectionMarker(editorRef, chips);
|
|
200
228
|
|
|
@@ -254,7 +282,17 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
254
282
|
insertChip: function insertChip(data) {
|
|
255
283
|
var el = editorRef.current;
|
|
256
284
|
if (!el) return;
|
|
257
|
-
|
|
285
|
+
// 优先恢复缓存光标到真实 selection;菜单点击时 selection 已经离开 editor,
|
|
286
|
+
// 直接走 useChipManager 的 fallback 会落到 editor 末尾,导致“飞到第一/末行”。
|
|
287
|
+
var cached = lastEditorRangeRef.current;
|
|
288
|
+
if (cached && cached.startContainer.isConnected && cached.endContainer.isConnected && el.contains(cached.startContainer) && el.contains(cached.endContainer)) {
|
|
289
|
+
var sel = window.getSelection();
|
|
290
|
+
if (sel) {
|
|
291
|
+
sel.removeAllRanges();
|
|
292
|
+
sel.addRange(cached.cloneRange());
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
_insertChip(data, el, cached);
|
|
258
296
|
},
|
|
259
297
|
removeChip: removeChip,
|
|
260
298
|
replaceTriggerWithChip: function replaceTriggerWithChip(data) {
|
|
@@ -262,8 +300,8 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
262
300
|
if (!el) return;
|
|
263
301
|
var probe = triggerRef.current;
|
|
264
302
|
if (!probe) {
|
|
265
|
-
// 无活动 trigger
|
|
266
|
-
_insertChip(data, el);
|
|
303
|
+
// 无活动 trigger,退化为最近光标处插入
|
|
304
|
+
_insertChip(data, el, lastEditorRangeRef.current);
|
|
267
305
|
return;
|
|
268
306
|
}
|
|
269
307
|
// 选中 trigger + query 整段,删掉再插 chip
|
|
@@ -300,11 +338,13 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
300
338
|
return c.data;
|
|
301
339
|
});
|
|
302
340
|
},
|
|
341
|
+
updateChipData: updateChipData,
|
|
342
|
+
getChipHost: getChipHost,
|
|
303
343
|
get nativeElement() {
|
|
304
344
|
return editorRef.current;
|
|
305
345
|
}
|
|
306
346
|
};
|
|
307
|
-
}, [onChange, _insertChip, removeChip, resetChips, onTriggerChange, chips]);
|
|
347
|
+
}, [onChange, _insertChip, removeChip, resetChips, onTriggerChange, chips, updateChipData, getChipHost]);
|
|
308
348
|
|
|
309
349
|
/* ─────────────────────────────────────
|
|
310
350
|
* 监听 selection 变化:方向键 / 鼠标点击移位都要重算 trigger
|
|
@@ -324,6 +364,28 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
324
364
|
}
|
|
325
365
|
return;
|
|
326
366
|
}
|
|
367
|
+
var range = sel.getRangeAt(0);
|
|
368
|
+
// 先缓存 editor 内真实光标;即便本次走 suppress 提前 return,也不能丢失插入锚点
|
|
369
|
+
lastEditorRangeRef.current = range.cloneRange();
|
|
370
|
+
if (suppressSelectionNormalizeCountRef.current > 0) {
|
|
371
|
+
suppressSelectionNormalizeCountRef.current -= 1;
|
|
372
|
+
recomputeTrigger();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (range.collapsed && range.startContainer.nodeType === Node.ELEMENT_NODE) {
|
|
376
|
+
var hostBefore = findChipHostBeforeCaret(range);
|
|
377
|
+
var hostAfter = findChipHostAfterCaret(range);
|
|
378
|
+
if (hostBefore || hostAfter) {
|
|
379
|
+
var _anchor = ensureTextAnchorAt(range.startContainer, range.startOffset);
|
|
380
|
+
if (_anchor) {
|
|
381
|
+
var r = document.createRange();
|
|
382
|
+
r.setStart(_anchor.node, _anchor.offset);
|
|
383
|
+
r.collapse(true);
|
|
384
|
+
sel.removeAllRanges();
|
|
385
|
+
sel.addRange(r);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
327
389
|
recomputeTrigger();
|
|
328
390
|
};
|
|
329
391
|
document.addEventListener('selectionchange', handler);
|
|
@@ -374,7 +436,7 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
374
436
|
if (valueProp !== current) {
|
|
375
437
|
el.textContent = valueProp !== null && valueProp !== void 0 ? valueProp : '';
|
|
376
438
|
resetChips();
|
|
377
|
-
setEmpty(
|
|
439
|
+
setEmpty(shouldShowPlaceholder(el));
|
|
378
440
|
if (triggerRef.current) {
|
|
379
441
|
triggerRef.current = null;
|
|
380
442
|
onTriggerChange === null || onTriggerChange === void 0 || onTriggerChange(null);
|
|
@@ -391,12 +453,15 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
391
453
|
* ───────────────────────────────────── */
|
|
392
454
|
var handleInput = useCallback(function () {
|
|
393
455
|
var el = editorRef.current;
|
|
394
|
-
if (el) {
|
|
395
|
-
|
|
396
|
-
setEmpty(isEditorEmpty(el));
|
|
456
|
+
if (el && !isComposingRef.current) {
|
|
457
|
+
normalizeComposerEditorDom(el);
|
|
397
458
|
}
|
|
398
459
|
requestScrollCaretIntoView();
|
|
399
|
-
if (isComposingRef.current)
|
|
460
|
+
if (isComposingRef.current) {
|
|
461
|
+
// 合成期只负责隐藏 placeholder,不触发归一化提交链路
|
|
462
|
+
setEmpty(false);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
400
465
|
fireChange();
|
|
401
466
|
recomputeTrigger();
|
|
402
467
|
}, [fireChange, recomputeTrigger, requestScrollCaretIntoView]);
|
|
@@ -407,6 +472,8 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
407
472
|
}, []);
|
|
408
473
|
var handleCompositionEnd = useCallback(function () {
|
|
409
474
|
isComposingRef.current = false;
|
|
475
|
+
var el = editorRef.current;
|
|
476
|
+
if (el) normalizeComposerEditorDom(el);
|
|
410
477
|
fireChange();
|
|
411
478
|
recomputeTrigger();
|
|
412
479
|
requestScrollCaretIntoView();
|
|
@@ -418,18 +485,25 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
418
485
|
* - 默认行为 = 自插 <br>(替代浏览器默认的 <div> 包裹,避免破坏 inline 流)
|
|
419
486
|
* ───────────────────────────────────── */
|
|
420
487
|
var insertLineBreak = useCallback(function () {
|
|
421
|
-
var _br$parentNode;
|
|
488
|
+
var _br$parentNode, _tail$textContent;
|
|
422
489
|
var sel = window.getSelection();
|
|
423
490
|
if (!sel || sel.rangeCount === 0) return;
|
|
424
491
|
var range = sel.getRangeAt(0);
|
|
425
492
|
range.deleteContents();
|
|
426
493
|
var br = document.createElement('br');
|
|
494
|
+
br.setAttribute('data-odn-composer-user-break', 'true');
|
|
427
495
|
range.insertNode(br);
|
|
428
|
-
//
|
|
429
|
-
|
|
496
|
+
// 用户主动换行后应立即收起 placeholder
|
|
497
|
+
setEmpty(false);
|
|
498
|
+
forceNonEmptyOnceRef.current = true;
|
|
499
|
+
// 本次主动设 range 会触发 selectionchange,短窗口内跳过二次 caret 校正避免闪烁
|
|
500
|
+
suppressSelectionNormalizeCountRef.current = 2;
|
|
501
|
+
// webkit:行尾插 br 时需要 spacer 让 caret 落点可见
|
|
502
|
+
var tail = document.createTextNode(CARET_SPACER);
|
|
430
503
|
(_br$parentNode = br.parentNode) === null || _br$parentNode === void 0 || _br$parentNode.insertBefore(tail, br.nextSibling);
|
|
431
504
|
var newRange = document.createRange();
|
|
432
|
-
|
|
505
|
+
// 直接落在 spacer 文本内部,避免 element-boundary caret 在首拍被二次改写
|
|
506
|
+
newRange.setStart(tail, ((_tail$textContent = tail.textContent) !== null && _tail$textContent !== void 0 ? _tail$textContent : '').length);
|
|
433
507
|
newRange.collapse(true);
|
|
434
508
|
sel.removeAllRanges();
|
|
435
509
|
sel.addRange(newRange);
|
|
@@ -437,12 +511,13 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
437
511
|
requestScrollCaretIntoView();
|
|
438
512
|
}, [fireChange, requestScrollCaretIntoView]);
|
|
439
513
|
var handleKeyDown = useCallback(function (e) {
|
|
514
|
+
if (inputModality !== 'keyboard') setInputModality('keyboard');
|
|
440
515
|
if (isComposingRef.current || e.nativeEvent.isComposing || e.keyCode === 229) return;
|
|
441
516
|
|
|
442
517
|
/* 触发态优先:activeTrigger 时把导航键先吐给父组件(驱动浮层选择)。
|
|
443
518
|
* 父返回 true 表示已处理,editor 直接结束本次 keydown,不再走任何
|
|
444
519
|
* 自身逻辑(避免 Enter 又触发 onPressEnter / 插换行)。 */
|
|
445
|
-
if (triggerRef.current && onTriggerKeyDown && (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === 'Escape' || e.key === 'Tab')) {
|
|
520
|
+
if (triggerRef.current && onTriggerKeyDown && (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' && !e.shiftKey || e.key === 'Escape' || e.key === 'Tab')) {
|
|
446
521
|
if (onTriggerKeyDown(e) === true) return;
|
|
447
522
|
}
|
|
448
523
|
|
|
@@ -453,16 +528,72 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
453
528
|
if (el && sel && sel.rangeCount > 0) {
|
|
454
529
|
var range = sel.getRangeAt(0);
|
|
455
530
|
if (range.collapsed && el.contains(range.startContainer)) {
|
|
531
|
+
var node = range.startContainer,
|
|
532
|
+
offset = range.startOffset;
|
|
533
|
+
|
|
534
|
+
// 用户换行生成的 "<br> + CARET_SPACER":一次 Backspace 应合并删除,避免看起来“卡一下”
|
|
535
|
+
{
|
|
536
|
+
var spacerText = null;
|
|
537
|
+
var br = null;
|
|
538
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
539
|
+
var _t$textContent;
|
|
540
|
+
var t = node;
|
|
541
|
+
var s = (_t$textContent = t.textContent) !== null && _t$textContent !== void 0 ? _t$textContent : '';
|
|
542
|
+
if (s.startsWith(CARET_SPACER) && offset <= 1 && t.previousSibling instanceof HTMLBRElement) {
|
|
543
|
+
spacerText = t;
|
|
544
|
+
br = t.previousSibling;
|
|
545
|
+
}
|
|
546
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
547
|
+
var _elNode$childNodes, _elNode$childNodes$of, _textContent, _textContent2;
|
|
548
|
+
var elNode = node;
|
|
549
|
+
var prev = (_elNode$childNodes = elNode.childNodes[offset - 1]) !== null && _elNode$childNodes !== void 0 ? _elNode$childNodes : null;
|
|
550
|
+
var at = (_elNode$childNodes$of = elNode.childNodes[offset]) !== null && _elNode$childNodes$of !== void 0 ? _elNode$childNodes$of : null;
|
|
551
|
+
if (prev && prev.nodeType === Node.TEXT_NODE && ((_textContent = prev.textContent) !== null && _textContent !== void 0 ? _textContent : '').startsWith(CARET_SPACER) && prev.previousSibling instanceof HTMLBRElement) {
|
|
552
|
+
spacerText = prev;
|
|
553
|
+
br = prev.previousSibling;
|
|
554
|
+
} else if (prev instanceof HTMLBRElement && at && at.nodeType === Node.TEXT_NODE && ((_textContent2 = at.textContent) !== null && _textContent2 !== void 0 ? _textContent2 : '').startsWith(CARET_SPACER)) {
|
|
555
|
+
spacerText = at;
|
|
556
|
+
br = prev;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (spacerText && br) {
|
|
560
|
+
e.preventDefault();
|
|
561
|
+
var parent = br.parentNode;
|
|
562
|
+
if (parent) {
|
|
563
|
+
var _spacerText$textConte;
|
|
564
|
+
var idx = Array.from(parent.childNodes).indexOf(br);
|
|
565
|
+
var text = (_spacerText$textConte = spacerText.textContent) !== null && _spacerText$textConte !== void 0 ? _spacerText$textConte : '';
|
|
566
|
+
if (text === CARET_SPACER) {
|
|
567
|
+
spacerText.remove();
|
|
568
|
+
} else {
|
|
569
|
+
spacerText.textContent = text.slice(1);
|
|
570
|
+
}
|
|
571
|
+
br.remove();
|
|
572
|
+
var r = document.createRange();
|
|
573
|
+
if (spacerText.isConnected) {
|
|
574
|
+
r.setStart(spacerText, 0);
|
|
575
|
+
} else {
|
|
576
|
+
r.setStart(parent, Math.max(0, idx));
|
|
577
|
+
}
|
|
578
|
+
r.collapse(true);
|
|
579
|
+
sel.removeAllRanges();
|
|
580
|
+
sel.addRange(r);
|
|
581
|
+
fireChange();
|
|
582
|
+
recomputeTrigger();
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
456
587
|
var hostBefore = findChipHostBeforeCaret(range);
|
|
457
588
|
if (hostBefore !== null && hostBefore !== void 0 && hostBefore.dataset.mentionId) {
|
|
458
589
|
var _node$textContent;
|
|
459
|
-
var
|
|
460
|
-
|
|
461
|
-
if (
|
|
590
|
+
var _node = range.startContainer,
|
|
591
|
+
_offset = range.startOffset;
|
|
592
|
+
if (_node.nodeType === Node.TEXT_NODE && _offset === 0 && _node.previousSibling === hostBefore && stripInvisibleChars((_node$textContent = _node.textContent) !== null && _node$textContent !== void 0 ? _node$textContent : '') === '') {
|
|
462
593
|
e.preventDefault();
|
|
463
594
|
moveCaretBeforeChip(hostBefore, {
|
|
464
595
|
removeTrailingEmptyText: true,
|
|
465
|
-
trailingText:
|
|
596
|
+
trailingText: _node
|
|
466
597
|
});
|
|
467
598
|
return;
|
|
468
599
|
}
|
|
@@ -491,11 +622,11 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
491
622
|
var target = resolveCaretJumpAroundChip(_range, e.key === 'ArrowLeft' ? 'left' : 'right');
|
|
492
623
|
if (target) {
|
|
493
624
|
e.preventDefault();
|
|
494
|
-
var
|
|
495
|
-
|
|
496
|
-
|
|
625
|
+
var _r = document.createRange();
|
|
626
|
+
_r.setStart(target.node, target.offset);
|
|
627
|
+
_r.collapse(true);
|
|
497
628
|
_sel.removeAllRanges();
|
|
498
|
-
_sel.addRange(
|
|
629
|
+
_sel.addRange(_r);
|
|
499
630
|
return;
|
|
500
631
|
}
|
|
501
632
|
}
|
|
@@ -503,15 +634,18 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
503
634
|
}
|
|
504
635
|
if (e.key === 'Enter') {
|
|
505
636
|
if (onPressEnter) {
|
|
506
|
-
var
|
|
507
|
-
if (
|
|
637
|
+
var _r2 = onPressEnter(e);
|
|
638
|
+
if (_r2 === false) return;
|
|
508
639
|
}
|
|
509
640
|
// 外部(如浮层)已经 preventDefault → 不再插换行
|
|
510
641
|
if (e.defaultPrevented) return;
|
|
511
642
|
e.preventDefault();
|
|
512
643
|
insertLineBreak();
|
|
513
644
|
}
|
|
514
|
-
}, [onPressEnter, insertLineBreak, removeChip, onTriggerKeyDown]);
|
|
645
|
+
}, [inputModality, onPressEnter, insertLineBreak, removeChip, onTriggerKeyDown, fireChange, recomputeTrigger]);
|
|
646
|
+
var handlePointerInput = useCallback(function () {
|
|
647
|
+
if (inputModality !== 'pointer') setInputModality('pointer');
|
|
648
|
+
}, [inputModality]);
|
|
515
649
|
var writeClipboardPayload = useCallback(function (e, payload) {
|
|
516
650
|
if (!payload) return false;
|
|
517
651
|
e.clipboardData.setData(COMPOSER_CLIPBOARD_MIME, JSON.stringify(payload));
|
|
@@ -622,6 +756,7 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
622
756
|
"aria-disabled": disabled || undefined,
|
|
623
757
|
"data-odn-composer-editor": true,
|
|
624
758
|
"data-odn-composer-editor-disabled": disabled || undefined,
|
|
759
|
+
"data-odn-input-modality": inputModality,
|
|
625
760
|
"data-empty": empty || undefined,
|
|
626
761
|
"data-placeholder": placeholder,
|
|
627
762
|
className: className,
|
|
@@ -629,12 +764,19 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
|
|
|
629
764
|
onCompositionStart: handleCompositionStart,
|
|
630
765
|
onCompositionEnd: handleCompositionEnd,
|
|
631
766
|
onKeyDown: handleKeyDown,
|
|
767
|
+
onMouseMove: handlePointerInput,
|
|
768
|
+
onMouseDown: handlePointerInput,
|
|
769
|
+
onPointerMove: handlePointerInput,
|
|
632
770
|
onCopy: handleCopy,
|
|
633
771
|
onCut: handleCut,
|
|
634
772
|
onPaste: handlePaste
|
|
635
773
|
})), chips.map(function (chip) {
|
|
636
774
|
return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement(ComposerInlineRef, {
|
|
637
|
-
data: chip.data
|
|
775
|
+
data: chip.data,
|
|
776
|
+
active: paramEditChipId === chip.data.id,
|
|
777
|
+
onClick: onInlineRefClick ? function (anchor) {
|
|
778
|
+
return onInlineRefClick(chip.data.id, anchor);
|
|
779
|
+
} : undefined
|
|
638
780
|
}), chip.host, chip.data.id);
|
|
639
781
|
}));
|
|
640
782
|
});
|