one-design-next 0.0.14 → 0.0.15

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.
Files changed (48) hide show
  1. package/dist/_genui-types.d.ts +72 -27
  2. package/dist/action-bar/style/index.css +2 -2
  3. package/dist/agent-step/style/index.css +1 -1
  4. package/dist/attachments/style/index.css +5 -5
  5. package/dist/chat-item/style/index.css +2 -2
  6. package/dist/composer/clipboard.d.ts +1 -1
  7. package/dist/composer/editor.d.ts +10 -2
  8. package/dist/composer/editor.js +176 -34
  9. package/dist/composer/hooks/useChipManager.d.ts +8 -3
  10. package/dist/composer/hooks/useChipManager.js +105 -10
  11. package/dist/composer/index.d.ts +17 -2
  12. package/dist/composer/index.js +136 -65
  13. package/dist/composer/inline-ref.d.ts +6 -2
  14. package/dist/composer/inline-ref.js +10 -3
  15. package/dist/composer/param-panel.d.ts +14 -0
  16. package/dist/composer/param-panel.js +1 -0
  17. package/dist/composer/segments.d.ts +29 -0
  18. package/dist/composer/segments.js +83 -0
  19. package/dist/composer/send-meta.d.ts +7 -4
  20. package/dist/composer/send-meta.js +12 -52
  21. package/dist/composer/style/index.css +27 -8
  22. package/dist/composer/utils.d.ts +22 -1
  23. package/dist/composer/utils.js +213 -45
  24. package/dist/fab/style/index.css +1 -1
  25. package/dist/index.d.ts +4 -2
  26. package/dist/index.js +4 -2
  27. package/dist/invocation/index.d.ts +7 -2
  28. package/dist/invocation/index.js +14 -8
  29. package/dist/invocation/param-popover.d.ts +21 -0
  30. package/dist/invocation/param-popover.js +113 -0
  31. package/dist/invocation/style/index.css +33 -9
  32. package/dist/mention/index.d.ts +1 -6
  33. package/dist/mention/index.js +11 -8
  34. package/dist/mention/style/index.css +30 -9
  35. package/dist/preview-panel/index.js +11 -1
  36. package/dist/preview-panel/style/index.css +11 -0
  37. package/dist/skill-slot/index.js +5 -5
  38. package/dist/skill-slot/style/index.css +51 -27
  39. package/dist/suggestions/index.js +1 -5
  40. package/dist/suggestions/style/index.css +6 -7
  41. package/dist/user-bubble/index.js +9 -4
  42. package/dist/user-bubble/render-segments.d.ts +9 -0
  43. package/dist/user-bubble/render-segments.js +42 -0
  44. package/dist/user-bubble/style/index.css +9 -0
  45. package/dist/welcome/style/index.css +1 -1
  46. package/package.json +4 -4
  47. package/dist/composer/chip.d.ts +0 -36
  48. package/dist/composer/chip.js +0 -49
@@ -54,24 +54,58 @@ export interface PreviewTab {
54
54
  content: string;
55
55
  type: string;
56
56
  }
57
- /** Composer 正文内 `/` 触发的内联引用(Invocation 组件数据契约)。 */
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
- /** 业务类型:skill / command / 自定义 */
73
+ /** 业务子类型('skill' / 'command' / 自定义);当前仅 'skill' 使用,预留扩展 */
61
74
  kind: string;
62
- refId: string;
75
+ /** 显示文字 */
63
76
  label: string;
77
+ /** 图标 name(可选) */
64
78
  icon?: string;
65
- params?: Record<string, unknown>;
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
- params?: Record<string, unknown>;
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
- * 禁用态。设为 true 时按钮不可点击、不会触发 onSelect,视觉降级。
83
- * 用于"该 Skill 处于灰度未开放 / 当前会话不支持"等场景,
84
- * 既保留入口可见性,又避免误触;与 active 互斥(active 不会同时禁用)。
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-cyan-5);
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-cyan-5);
88
+ box-shadow: 0 0 0 2px var(--odn-color-brand-6);
89
89
  }
90
90
 
91
91
  [data-odn-action-bar-pager-text] {
@@ -33,7 +33,7 @@
33
33
  }
34
34
  [data-odn-agent-step-group-trigger]:focus-visible, [data-odn-agent-step-trigger]:focus-visible {
35
35
  outline: none;
36
- box-shadow: 0 0 0 2px var(--odn-color-cyan-5);
36
+ box-shadow: 0 0 0 2px var(--odn-color-brand-6);
37
37
  }
38
38
 
39
39
  [data-odn-agent-step] {
@@ -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-cyan-5) 4%, transparent) 0%, transparent 60%), var(--odn-color-bg-elevated);
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-cyan-1);
289
- color: var(--odn-color-cyan-5);
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-cyan-5);
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-cyan-1) 100%);
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-cyan-3, rgba(0, 160, 200, 0.25));
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-cyan-5);
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 './chip';
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 内:chip.tsx 负责
16
+ * - 视觉由 React Portal 注入到 host 内:ComposerInlineRef 负责
17
17
  * - chip 状态(id ↔ host)由 useChipManager 维护
18
18
  */
19
19
  /// <reference types="react" />
20
- import type { ChipData } from './chip';
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
  }
@@ -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 内:chip.tsx 负责
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, ZWSP } from "./utils";
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 _useState3 = useState(null),
134
- _useState4 = _slicedToArray(_useState3, 2),
135
- scrollHeight = _useState4[0],
136
- setScrollHeight = _useState4[1];
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
- setEmpty(isEditorEmpty(el));
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
- _insertChip(data, el);
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(!valueProp);
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
- normalizeEditorDom(el);
396
- setEmpty(isEditorEmpty(el));
456
+ if (el && !isComposingRef.current) {
457
+ normalizeComposerEditorDom(el);
397
458
  }
398
459
  requestScrollCaretIntoView();
399
- if (isComposingRef.current) return;
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
- // webkit:行尾插 br 时需要 ZWSP 让 caret 落点可见
429
- var tail = document.createTextNode(ZWSP);
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
- newRange.setStartAfter(tail);
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 node = range.startContainer,
460
- offset = range.startOffset;
461
- if (node.nodeType === Node.TEXT_NODE && offset === 0 && node.previousSibling === hostBefore && stripInvisibleChars((_node$textContent = node.textContent) !== null && _node$textContent !== void 0 ? _node$textContent : '') === '') {
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: node
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 r = document.createRange();
495
- r.setStart(target.node, target.offset);
496
- r.collapse(true);
625
+ var _r = document.createRange();
626
+ _r.setStart(target.node, target.offset);
627
+ _r.collapse(true);
497
628
  _sel.removeAllRanges();
498
- _sel.addRange(r);
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 _r = onPressEnter(e);
507
- if (_r === false) return;
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
  });