one-design-next 0.0.13 → 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 +199 -35
  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 +35 -1
  23. package/dist/composer/utils.js +281 -36
  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
@@ -27,11 +27,11 @@
27
27
  transition: border-color 0.15s;
28
28
  }
29
29
  [data-odn-composer-box]:focus-within {
30
- border-color: color-mix(in srgb, var(--odn-color-cyan-5) 50%, transparent);
30
+ border-color: color-mix(in srgb, var(--odn-color-brand-6) 50%, transparent);
31
31
  }
32
32
  [data-odn-composer-box][data-odn-composer-file-drag] {
33
- border-color: color-mix(in srgb, var(--odn-color-cyan-5) 60%, transparent);
34
- background: color-mix(in srgb, var(--odn-color-cyan-5) 6%, var(--odn-color-bg-elevated));
33
+ border-color: color-mix(in srgb, var(--odn-color-brand-6) 60%, transparent);
34
+ background: color-mix(in srgb, var(--odn-color-brand-6) 6%, var(--odn-color-bg-elevated));
35
35
  }
36
36
 
37
37
  /* ------------------------------------------------------------------
@@ -225,6 +225,25 @@
225
225
  vertical-align: baseline;
226
226
  }
227
227
 
228
+ /* editor 内跟随正文 inherit,避免与外层 14/24 双写冲突。
229
+ * 同时收紧 chip 视觉间距:
230
+ * - 取消组件默认 margin(否则 text↔chip / chip↔chip 都会被双边叠加拉宽)
231
+ * - 下调横向 padding,保留可点击热区但减少“透明留白”观感 */
232
+ [data-odn-composer] [data-odn-composer-editor] [data-odn-invocation],
233
+ [data-odn-composer] [data-odn-composer-editor] [data-odn-mention] {
234
+ margin-inline: 1px;
235
+ padding-left: 2px;
236
+ padding-right: 2px;
237
+ font-size: inherit;
238
+ line-height: inherit;
239
+ }
240
+
241
+ /* 键盘输入模态:鼠标会被隐藏,此时不应沿用旧鼠标位置触发 hover 态。 */
242
+ [data-odn-composer] [data-odn-composer-editor][data-odn-input-modality=keyboard] [data-odn-invocation]:hover:not([data-state=active]),
243
+ [data-odn-composer] [data-odn-composer-editor][data-odn-input-modality=keyboard] [data-odn-mention]:hover:not([data-state=active]) {
244
+ background: transparent;
245
+ }
246
+
228
247
  /* chip 在 contentEditable=false 下无原生 selection 高亮,自绘选中态 */
229
248
  [data-odn-composer] [data-odn-composer-chip-host][data-selected] [data-odn-invocation],
230
249
  [data-odn-composer] [data-odn-composer-chip-host][data-selected] [data-odn-mention] {
@@ -303,9 +322,9 @@
303
322
  transition: opacity 0.15s;
304
323
  }
305
324
  [data-odn-composer-tool-btn][data-odn-composer-tool-active] {
306
- color: var(--odn-color-cyan-5);
325
+ color: var(--odn-color-brand-6);
307
326
  font-weight: 600;
308
- background: color-mix(in srgb, var(--odn-color-cyan-5) 10%, transparent);
327
+ background: color-mix(in srgb, var(--odn-color-brand-6) 10%, transparent);
309
328
  }
310
329
  [data-odn-composer-tool-btn][data-odn-composer-tool-active]::before {
311
330
  opacity: 1;
@@ -321,11 +340,11 @@
321
340
  height: 16px;
322
341
  margin-left: 2px;
323
342
  border-radius: 4px;
324
- color: color-mix(in srgb, var(--odn-color-cyan-5) 70%, transparent);
343
+ color: color-mix(in srgb, var(--odn-color-brand-6) 70%, transparent);
325
344
  transition: color 0.15s;
326
345
  }
327
346
  [data-odn-composer-skill-clear]:hover {
328
- color: var(--odn-color-cyan-5);
347
+ color: var(--odn-color-brand-6);
329
348
  }
330
349
  [data-odn-composer-skill-clear] [data-odn-hover-fill-content] {
331
350
  display: inline-flex;
@@ -350,7 +369,7 @@
350
369
  padding: 0;
351
370
  border-radius: 6px;
352
371
  border: none;
353
- background-color: var(--odn-color-cyan-5);
372
+ background-color: var(--odn-color-brand-6);
354
373
  color: #fff;
355
374
  cursor: pointer;
356
375
  transition: opacity 0.15s;
@@ -9,8 +9,10 @@
9
9
  *
10
10
  * @ / / 触发判定(getTriggerContext)放在 step 4 一起加。
11
11
  */
12
- /** Zero Width Space (U+200B):编辑器还会用作光标锚点(chip 后面塞一个)。 */
12
+ /** Zero Width Space (U+200B):仅用于 marker 包裹。 */
13
13
  export declare const ZWSP = "\u200B";
14
+ /** Zero Width No-Break Space (U+FEFF):仅用于 caret 锚点(chip 后 spacer)。 */
15
+ export declare const CARET_SPACER = "\uFEFF";
14
16
  /** Invisible Separator (U+2063):包裹 marker 的 id,业务文本不会出现。 */
15
17
  export declare const SEP = "\u2063";
16
18
  export declare function stripInvisibleChars(s: string): string;
@@ -63,6 +65,25 @@ export declare function getPlainText(editor: HTMLElement, chipIdAttr?: string):
63
65
  * 空格 / 全角空格也算"已输入",只要文本非空就该收起 placeholder。
64
66
  */
65
67
  export declare function isEditorEmpty(editor: HTMLElement, chipIdAttr?: string): boolean;
68
+ export declare const isZwspText: (n: Node | null | undefined) => n is Text;
69
+ /** chip 后插入(或复用)独立 spacer text node,供 caret 落点与方向键 helper 识别。 */
70
+ export declare function attachCaretSpacerAfter(host: HTMLElement): Text;
71
+ /** 删除 chip 时一并移除紧随的 ZWSP 锚点(仅整 node 为 ZWSP 时删,避免误伤合并 text)。 */
72
+ export declare function detachCaretSpacerAfter(host: HTMLElement): void;
73
+ /**
74
+ * 把 element-boundary caret 位置规范为 text-node caret:
75
+ * - 若 offset 左侧已经是 text node,落到该 text 末尾
76
+ * - 否则在 offset 位置插入一个 caret spacer text,并落到 offset=1
77
+ */
78
+ export declare function ensureTextAnchorAt(parent: Node, offset: number): {
79
+ node: Text;
80
+ offset: number;
81
+ } | null;
82
+ /**
83
+ * 清理行首浏览器占位:leading `<br>` / 空 text / 孤立 ZWSP(紧邻 chip 前)。
84
+ * 返回是否改过 DOM;若 caret 落在被删节点上,恢复到 editor 起点或首个 chip 前。
85
+ */
86
+ export declare function normalizeComposerEditorDom(editor: HTMLElement, chipIdAttr?: string): boolean;
66
87
  /**
67
88
  * caret 视觉是否紧邻某个 chip 后方?返回该 chip,否则 null。
68
89
  * 用于 Backspace 整块删 + ArrowLeft 跨过 chip。
@@ -86,6 +107,19 @@ export declare function resolveCaretJumpAroundChip(range: Range, direction: 'lef
86
107
  node: Node;
87
108
  offset: number;
88
109
  } | null;
110
+ export declare function childIndex(node: Node): number;
111
+ /** chip 之前的可见纯文本(不含 marker / 不可见字符)。 */
112
+ export declare function getVisibleTextBeforeHost(editor: HTMLElement, host: HTMLElement, chipIdAttr?: string): string;
113
+ /**
114
+ * 清理 contenteditable 删字后的幽灵节点:空 text、仅 chip 时的占位 <br>。
115
+ * 避免「文字 + invocation」删光文字后出现多余换行,以及 caret 卡在 chip 后误删 invocation。
116
+ */
117
+ export declare function normalizeEditorDom(editor: HTMLElement, _chipIdAttr?: string): void;
118
+ /** caret 落在 chip 后空 text 时,挪到 chip 前(不删 chip)。 */
119
+ export declare function moveCaretBeforeChip(host: HTMLElement, opts?: {
120
+ removeTrailingEmptyText?: boolean;
121
+ trailingText?: Text | null;
122
+ }): void;
89
123
  export interface TriggerProbe {
90
124
  /** 触发字符 */
91
125
  trigger: string;
@@ -17,8 +17,10 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
17
17
  * Marker · 用零宽字符把 mention id 写入纯文本
18
18
  * ──────────────────────────────────────────────────────────────────── */
19
19
 
20
- /** Zero Width Space (U+200B):编辑器还会用作光标锚点(chip 后面塞一个)。 */
20
+ /** Zero Width Space (U+200B):仅用于 marker 包裹。 */
21
21
  export var ZWSP = "\u200B";
22
+ /** Zero Width No-Break Space (U+FEFF):仅用于 caret 锚点(chip 后 spacer)。 */
23
+ export var CARET_SPACER = "\uFEFF";
22
24
 
23
25
  /** Invisible Separator (U+2063):包裹 marker 的 id,业务文本不会出现。 */
24
26
  export var SEP = "\u2063";
@@ -29,11 +31,14 @@ var escapeRe = function escapeRe(s) {
29
31
  };
30
32
  var MARKER_RE = new RegExp("".concat(escapeRe(MARKER_OPEN), "([^").concat(SEP, "]+)").concat(escapeRe(MARKER_CLOSE)), 'g');
31
33
 
32
- /** 从用户可见文本中剔除 ZWSP / SEP(粘贴残留、锚点字符)。 */
33
- var INVISIBLE_RE = new RegExp("[".concat(ZWSP).concat(SEP, "]"), 'g');
34
+ /** 从用户可见文本中剔除 marker/锚点不可见字符。 */
35
+ var INVISIBLE_RE = new RegExp("[".concat(ZWSP).concat(CARET_SPACER).concat(SEP, "]"), 'g');
34
36
  export function stripInvisibleChars(s) {
35
37
  return s.replace(INVISIBLE_RE, '');
36
38
  }
39
+ var isCaretSpacerChar = function isCaretSpacerChar(ch) {
40
+ return ch === CARET_SPACER || ch === ZWSP;
41
+ };
37
42
 
38
43
  /** 把 mention id 包成 marker token,插入 value 的 string 里。 */
39
44
  export function createMarker(id) {
@@ -184,6 +189,143 @@ var kebab = function kebab(s) {
184
189
  return "-".concat(m.toLowerCase());
185
190
  });
186
191
  };
192
+ var makeIsHost = function makeIsHost(chipIdAttr) {
193
+ return function (el) {
194
+ return el instanceof HTMLElement && el.dataset[chipIdAttr] !== undefined;
195
+ };
196
+ };
197
+
198
+ /* ────────────────────────────────────────────────────────────────────
199
+ * DOM 不变量 · chip 后 ZWSP 锚点 + 行首占位清理
200
+ *
201
+ * contentEditable=false 的 chip 顶在行首时,浏览器常会塞占位 <br>,删光前文后
202
+ * invocation 会「掉到第二行」。chip 后固定插 ZWSP(caret 锚点),input 时剥掉
203
+ * chip 前的 leading <br> / 空 text node。
204
+ * ──────────────────────────────────────────────────────────────────── */
205
+
206
+ export var isZwspText = function isZwspText(n) {
207
+ var _n$textContent;
208
+ return !!n && n.nodeType === Node.TEXT_NODE && isCaretSpacerChar((_n$textContent = n.textContent) !== null && _n$textContent !== void 0 ? _n$textContent : '');
209
+ };
210
+
211
+ /** chip 后插入(或复用)独立 spacer text node,供 caret 落点与方向键 helper 识别。 */
212
+ export function attachCaretSpacerAfter(host) {
213
+ var _host$parentNode;
214
+ var next = host.nextSibling;
215
+ if (isZwspText(next)) return next;
216
+ var spacer = document.createTextNode(CARET_SPACER);
217
+ (_host$parentNode = host.parentNode) === null || _host$parentNode === void 0 || _host$parentNode.insertBefore(spacer, host.nextSibling);
218
+ return spacer;
219
+ }
220
+
221
+ /** 删除 chip 时一并移除紧随的 ZWSP 锚点(仅整 node 为 ZWSP 时删,避免误伤合并 text)。 */
222
+ export function detachCaretSpacerAfter(host) {
223
+ var next = host.nextSibling;
224
+ if (isZwspText(next)) next.remove();
225
+ }
226
+
227
+ /**
228
+ * 把 element-boundary caret 位置规范为 text-node caret:
229
+ * - 若 offset 左侧已经是 text node,落到该 text 末尾
230
+ * - 否则在 offset 位置插入一个 caret spacer text,并落到 offset=1
231
+ */
232
+ export function ensureTextAnchorAt(parent, offset) {
233
+ var _parent$childNodes$sa;
234
+ var len = parent.childNodes.length;
235
+ var safeOffset = Math.max(0, Math.min(offset, len));
236
+ var left = parent.childNodes[safeOffset - 1];
237
+ if ((left === null || left === void 0 ? void 0 : left.nodeType) === Node.TEXT_NODE) {
238
+ var _t$textContent;
239
+ var t = left;
240
+ return {
241
+ node: t,
242
+ offset: ((_t$textContent = t.textContent) !== null && _t$textContent !== void 0 ? _t$textContent : '').length
243
+ };
244
+ }
245
+ var ref = (_parent$childNodes$sa = parent.childNodes[safeOffset]) !== null && _parent$childNodes$sa !== void 0 ? _parent$childNodes$sa : null;
246
+ var spacer = document.createTextNode(CARET_SPACER);
247
+ parent.insertBefore(spacer, ref);
248
+ return {
249
+ node: spacer,
250
+ offset: 1
251
+ };
252
+ }
253
+
254
+ /**
255
+ * 清理行首浏览器占位:leading `<br>` / 空 text / 孤立 ZWSP(紧邻 chip 前)。
256
+ * 返回是否改过 DOM;若 caret 落在被删节点上,恢复到 editor 起点或首个 chip 前。
257
+ */
258
+ export function normalizeComposerEditorDom(editor) {
259
+ var chipIdAttr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mentionId';
260
+ var isHost = makeIsHost(chipIdAttr);
261
+ var nodeStartsWithChip = function nodeStartsWithChip(n) {
262
+ if (!n) return false;
263
+ if (isHost(n)) return true;
264
+ if (isZwspText(n) && isHost(n.nextSibling)) return true;
265
+ return false;
266
+ };
267
+ var isRemovableLeading = function isRemovableLeading(n) {
268
+ if (n.nodeType === Node.ELEMENT_NODE && n.tagName === 'BR') return true;
269
+ if (n.nodeType === Node.TEXT_NODE) {
270
+ var _n$textContent2;
271
+ return stripInvisibleChars((_n$textContent2 = n.textContent) !== null && _n$textContent2 !== void 0 ? _n$textContent2 : '') === '';
272
+ }
273
+ return false;
274
+ };
275
+ var sel = window.getSelection();
276
+ var saved = sel && sel.rangeCount > 0 && sel.isCollapsed && editor.contains(sel.anchorNode) ? {
277
+ node: sel.anchorNode,
278
+ offset: sel.anchorOffset
279
+ } : null;
280
+ var mutated = false;
281
+ while (editor.firstChild) {
282
+ var first = editor.firstChild;
283
+ if (isRemovableLeading(first) && nodeStartsWithChip(first.nextSibling)) {
284
+ first.remove();
285
+ mutated = true;
286
+ continue;
287
+ }
288
+ break;
289
+ }
290
+ var chipHosts = Array.from(editor.querySelectorAll("[data-".concat(kebab(chipIdAttr), "]")));
291
+ for (var _i3 = 0, _chipHosts = chipHosts; _i3 < _chipHosts.length; _i3++) {
292
+ var el = _chipHosts[_i3];
293
+ if (!isZwspText(el.nextSibling)) {
294
+ attachCaretSpacerAfter(el);
295
+ mutated = true;
296
+ }
297
+ }
298
+ if (mutated && saved && (!saved.node.isConnected || !editor.contains(saved.node))) {
299
+ restoreCaretAtEditorStart(editor, chipIdAttr);
300
+ }
301
+ return mutated;
302
+ }
303
+ function restoreCaretAtEditorStart(editor, chipIdAttr) {
304
+ var sel = window.getSelection();
305
+ if (!sel) return;
306
+ var isHost = makeIsHost(chipIdAttr);
307
+ var r = document.createRange();
308
+ var first = editor.firstChild;
309
+ if (!first) {
310
+ r.setStart(editor, 0);
311
+ } else if (first.nodeType === Node.TEXT_NODE) {
312
+ var _first$textContent;
313
+ var text = (_first$textContent = first.textContent) !== null && _first$textContent !== void 0 ? _first$textContent : '';
314
+ r.setStart(first, text.length > 0 && isCaretSpacerChar(text[0]) ? 1 : 0);
315
+ } else {
316
+ var anchor = ensureTextAnchorAt(editor, 0);
317
+ if (anchor) {
318
+ r.setStart(anchor.node, anchor.offset);
319
+ } else if (isHost(first)) {
320
+ r.setStart(editor, 0);
321
+ } else {
322
+ r.setStart(editor, 0);
323
+ }
324
+ }
325
+ r.collapse(true);
326
+ sel.removeAllRanges();
327
+ sel.addRange(r);
328
+ }
187
329
 
188
330
  /* ────────────────────────────────────────────────────────────────────
189
331
  * Cursor helpers · chip 边界判定
@@ -201,16 +343,6 @@ var kebab = function kebab(s) {
201
343
  * 多按一次」「两 chip 之间又卡一下」的体验问题。
202
344
  * ──────────────────────────────────────────────────────────────────── */
203
345
 
204
- var isZwspText = function isZwspText(n) {
205
- var _n$textContent;
206
- return !!n && n.nodeType === Node.TEXT_NODE && ((_n$textContent = n.textContent) !== null && _n$textContent !== void 0 ? _n$textContent : '') === ZWSP;
207
- };
208
- var makeIsHost = function makeIsHost(chipIdAttr) {
209
- return function (el) {
210
- return el instanceof HTMLElement && el.dataset[chipIdAttr] !== undefined;
211
- };
212
- };
213
-
214
346
  /**
215
347
  * caret 视觉是否紧邻某个 chip 后方?返回该 chip,否则 null。
216
348
  * 用于 Backspace 整块删 + ArrowLeft 跨过 chip。
@@ -233,7 +365,7 @@ export function findChipHostBeforeCaret(range) {
233
365
  }
234
366
  // (a) caret 紧贴 text 开头的 ZWSP(含 ZWSP 与后续输入合并的场景,如 '\u200babc')
235
367
  // 视觉位置 = chip 之后,应让 Backspace / ← 一次跨过 chip
236
- if (text[0] === ZWSP && (offset === 0 || offset === 1) && isHost(node.previousSibling)) {
368
+ if (isCaretSpacerChar(text[0]) && (offset === 0 || offset === 1) && isHost(node.previousSibling)) {
237
369
  return node.previousSibling;
238
370
  }
239
371
  return null;
@@ -272,9 +404,12 @@ export function findChipHostAfterCaret(range) {
272
404
  return node.nextSibling.nextSibling;
273
405
  }
274
406
  }
275
- // chip 之前的 ZWSP(场景:两 chip 紧贴时,前 chip ZWSP 即在后 chip 之前;
276
- // 也兼容 ZWSP 与文字合并的合并 text node 边界)
277
- if (text[0] === ZWSP && (offset === 0 || offset === 1) && isHost(node.nextSibling)) {
407
+ // 独立 ZWSP 节点:仅 offset=1(紧贴下一 chip 前缘)才算「caret chip 前」
408
+ if (text.length === 1 && isCaretSpacerChar(text[0]) && offset === 1 && isHost(node.nextSibling)) {
409
+ return node.nextSibling;
410
+ }
411
+ // 合并 text(spacer + 正文):offset=1 且下一 sibling 是 chip
412
+ if (text.length > 1 && isCaretSpacerChar(text[0]) && offset === 1 && isHost(node.nextSibling)) {
278
413
  return node.nextSibling;
279
414
  }
280
415
  return null;
@@ -283,10 +418,6 @@ export function findChipHostAfterCaret(range) {
283
418
  var el = node;
284
419
  var next = el.childNodes[offset];
285
420
  if (isHost(next)) return next;
286
- // chip + ZWSP 链:next 是 ZWSP,再后一个是 chip
287
- if (isZwspText(next) && isHost(next.nextSibling)) {
288
- return next.nextSibling;
289
- }
290
421
  }
291
422
  return null;
292
423
  }
@@ -301,16 +432,56 @@ export function findChipHostAfterCaret(range) {
301
432
  * 返回 null 表示无需介入;调用方按浏览器默认处理即可。
302
433
  */
303
434
  export function resolveCaretJumpAroundChip(range, direction) {
435
+ var _node$textContent4;
304
436
  var chipIdAttr = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mentionId';
437
+ var isHost = makeIsHost(chipIdAttr);
438
+ var node = range.startContainer,
439
+ offset = range.startOffset;
440
+
441
+ /* spacer 内微步:仅保留 ArrowRight 的 0→1。
442
+ * ArrowLeft 在 chip 右侧一律直接跨到 chip 左侧,避免任何“卡一下”。 */
443
+ if (node.nodeType === Node.TEXT_NODE && isCaretSpacerChar((_node$textContent4 = node.textContent) !== null && _node$textContent4 !== void 0 ? _node$textContent4 : '')) {
444
+ if (direction === 'right' && offset === 0) {
445
+ return {
446
+ node: node,
447
+ offset: 1
448
+ };
449
+ }
450
+ if (direction === 'left' && offset === 1) {
451
+ var _chip = findChipHostBeforeCaret(range, chipIdAttr);
452
+ if (!_chip) return null;
453
+ var _parent = _chip.parentNode;
454
+ if (!_parent) return null;
455
+ return ensureTextAnchorAt(_parent, childIndex(_chip));
456
+ }
457
+ }
458
+ if (node.nodeType === Node.ELEMENT_NODE) {
459
+ var el = node;
460
+ if (direction === 'right') {
461
+ var next = el.childNodes[offset];
462
+ if (isZwspText(next)) {
463
+ return {
464
+ node: next,
465
+ offset: 0
466
+ };
467
+ }
468
+ } else {
469
+ var prev = el.childNodes[offset - 1];
470
+ var _next = el.childNodes[offset];
471
+ if (isZwspText(prev) && isHost(_next)) {
472
+ return {
473
+ node: prev,
474
+ offset: 1
475
+ };
476
+ }
477
+ }
478
+ }
305
479
  if (direction === 'left') {
306
- var _chip = findChipHostBeforeCaret(range, chipIdAttr);
307
- if (!_chip) return null;
308
- var _parent = _chip.parentNode;
309
- if (!_parent) return null;
310
- return {
311
- node: _parent,
312
- offset: childIndex(_chip)
313
- };
480
+ var _chip2 = findChipHostBeforeCaret(range, chipIdAttr);
481
+ if (!_chip2) return null;
482
+ var _parent2 = _chip2.parentNode;
483
+ if (!_parent2) return null;
484
+ return ensureTextAnchorAt(_parent2, childIndex(_chip2));
314
485
  }
315
486
  var chip = findChipHostAfterCaret(range, chipIdAttr);
316
487
  if (!chip) return null;
@@ -323,12 +494,9 @@ export function resolveCaretJumpAroundChip(range, direction) {
323
494
  }
324
495
  var parent = chip.parentNode;
325
496
  if (!parent) return null;
326
- return {
327
- node: parent,
328
- offset: childIndex(chip) + 1
329
- };
497
+ return ensureTextAnchorAt(parent, childIndex(chip) + 1);
330
498
  }
331
- function childIndex(node) {
499
+ export function childIndex(node) {
332
500
  var i = 0;
333
501
  var cur = node.previousSibling;
334
502
  while (cur) {
@@ -338,6 +506,83 @@ function childIndex(node) {
338
506
  return i;
339
507
  }
340
508
 
509
+ /** chip 之前的可见纯文本(不含 marker / 不可见字符)。 */
510
+ export function getVisibleTextBeforeHost(editor, host) {
511
+ var chipIdAttr = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mentionId';
512
+ var r = document.createRange();
513
+ r.selectNodeContents(editor);
514
+ r.setEndBefore(host);
515
+ var tmp = document.createElement('div');
516
+ tmp.appendChild(r.cloneContents());
517
+ return stripInvisibleChars(stripMarkers(getPlainText(tmp))).replace(/\n/g, '');
518
+ }
519
+
520
+ /**
521
+ * 清理 contenteditable 删字后的幽灵节点:空 text、仅 chip 时的占位 <br>。
522
+ * 避免「文字 + invocation」删光文字后出现多余换行,以及 caret 卡在 chip 后误删 invocation。
523
+ */
524
+ export function normalizeEditorDom(editor) {
525
+ var _chipIdAttr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mentionId';
526
+ var emptyTexts = [];
527
+ var walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT);
528
+ var n;
529
+ while (n = walker.nextNode()) {
530
+ var _t$textContent2;
531
+ var t = n;
532
+ // 仅清理真正的空文本节点;保留 caret spacer,避免首次换行落点被抹掉
533
+ if (((_t$textContent2 = t.textContent) !== null && _t$textContent2 !== void 0 ? _t$textContent2 : '') === '') {
534
+ emptyTexts.push(t);
535
+ }
536
+ }
537
+ emptyTexts.forEach(function (t) {
538
+ return t.remove();
539
+ });
540
+ editor.querySelectorAll('[data-odn-composer-chip-host]').forEach(function (hostEl) {
541
+ var host = hostEl;
542
+ var prev = host.previousSibling;
543
+ while (prev) {
544
+ if (prev.nodeType === Node.TEXT_NODE) {
545
+ var _prev$textContent;
546
+ var _t = stripInvisibleChars((_prev$textContent = prev.textContent) !== null && _prev$textContent !== void 0 ? _prev$textContent : '');
547
+ if (_t !== '') break;
548
+ var rm = prev;
549
+ prev = prev.previousSibling;
550
+ rm.remove();
551
+ continue;
552
+ }
553
+ if (prev.nodeType === Node.ELEMENT_NODE && prev.tagName === 'BR') {
554
+ // 用户主动换行(data-odn-composer-user-break)必须保留,否则会出现
555
+ // 「第二行/第三行换行被吞、chip 插入回到首行」。仅清理浏览器塞的占位 <br>。
556
+ var brEl = prev;
557
+ if (brEl.hasAttribute('data-odn-composer-user-break')) break;
558
+ var _rm = prev;
559
+ prev = prev.previousSibling;
560
+ _rm.remove();
561
+ continue;
562
+ }
563
+ break;
564
+ }
565
+ });
566
+ }
567
+
568
+ /** caret 落在 chip 后空 text 时,挪到 chip 前(不删 chip)。 */
569
+ export function moveCaretBeforeChip(host, opts) {
570
+ var parent = host.parentNode;
571
+ if (!parent) return;
572
+ var sel = window.getSelection();
573
+ if (!sel) return;
574
+ var anchor = ensureTextAnchorAt(parent, childIndex(host));
575
+ if (!anchor) return;
576
+ var r = document.createRange();
577
+ r.setStart(anchor.node, anchor.offset);
578
+ r.collapse(true);
579
+ sel.removeAllRanges();
580
+ sel.addRange(r);
581
+ if (opts !== null && opts !== void 0 && opts.removeTrailingEmptyText && opts.trailingText) {
582
+ opts.trailingText.remove();
583
+ }
584
+ }
585
+
341
586
  /* ────────────────────────────────────────────────────────────────────
342
587
  * Trigger probe · @ / / 触发态判定
343
588
  *
@@ -376,7 +621,7 @@ var isTriggerBoundary = function isTriggerBoundary(node) {
376
621
  return false;
377
622
  };
378
623
  export function probeTrigger(range, triggers) {
379
- var _node$textContent4;
624
+ var _node$textContent5;
380
625
  var maxQueryLen = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 50;
381
626
  if (!range.collapsed) return null;
382
627
 
@@ -401,7 +646,7 @@ export function probeTrigger(range, triggers) {
401
646
  }
402
647
  }
403
648
  if (node.nodeType !== Node.TEXT_NODE) return null;
404
- var text = (_node$textContent4 = node.textContent) !== null && _node$textContent4 !== void 0 ? _node$textContent4 : '';
649
+ var text = (_node$textContent5 = node.textContent) !== null && _node$textContent5 !== void 0 ? _node$textContent5 : '';
405
650
  var triggerOffset = -1;
406
651
  var trigger = '';
407
652
  for (var i = caret - 1; i >= 0 && caret - i <= maxQueryLen + 1; i--) {
@@ -14,7 +14,7 @@
14
14
  backdrop-filter: blur(12px);
15
15
  -webkit-backdrop-filter: blur(12px);
16
16
  -webkit-tap-highlight-color: transparent;
17
- font-family: "PingFang SC", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
17
+ transition: all 200ms cubic-bezier(0.32, 0.72, 0, 1);
18
18
  }
19
19
  [data-odn-fab]:active {
20
20
  transform: scale(0.95);
package/dist/index.d.ts CHANGED
@@ -59,7 +59,8 @@ export { default as AgentThink, type AgentThinkProps } from './agent-think';
59
59
  export { default as Attachments, type AttachmentsProps } from './attachments';
60
60
  export { default as CodeBlock, type CodeBlockProps } from './code-block';
61
61
  export { default as Composer, type ComposerProps, type ComposerRef } from './composer';
62
- export { default as Invocation, type InvocationProps } from './invocation';
62
+ export { default as Invocation, InvocationParamPopover, type InvocationProps, type InvocationParamPopoverProps, } from './invocation';
63
+ export { type ComposerParamPanelContext } from './composer/param-panel';
63
64
  export { default as Mention, type MentionProps } from './mention';
64
65
  export { default as ChatItem, type ChatItemProps } from './chat-item';
65
66
  export { default as SkillSlot, type SkillSlotProps } from './skill-slot';
@@ -72,4 +73,5 @@ export { default as StreamText, type StreamTextProps, type TypographyOverrides }
72
73
  export { default as Suggestions, type SuggestionsProps } from './suggestions';
73
74
  export { default as UserBubble, type UserBubbleProps } from './user-bubble';
74
75
  export { default as Welcome, type WelcomeProps } from './welcome';
75
- export type { ToolCall, ToolStatus, Source, Attachment, PreviewTarget, PreviewTab, SendMeta, ComposerChipMeta, InvocationData, MentionData, SkillItem, AgentEvent, Conversation, MessageSnapshot, ChatMessage } from './_genui-types';
76
+ export type { ToolCall, ToolStatus, Source, Attachment, PreviewTarget, PreviewTab, SendMeta, ComposerSegment, InvocationData, MentionData, InlineRefState, SkillItem, AgentEvent, Conversation, MessageSnapshot, ChatMessage } from './_genui-types';
77
+ export { rawValueToSegments, segmentsToReadableText, segmentsHasContent, type SegmentsToReadableTextOptions, } from './composer/segments';
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ export { default as AgentThink } from "./agent-think";
65
65
  export { default as Attachments } from "./attachments";
66
66
  export { default as CodeBlock } from "./code-block";
67
67
  export { default as Composer } from "./composer";
68
- export { default as Invocation } from "./invocation";
68
+ export { default as Invocation, InvocationParamPopover } from "./invocation";
69
69
  export { default as Mention } from "./mention";
70
70
  export { default as ChatItem } from "./chat-item";
71
71
  export { default as SkillSlot } from "./skill-slot";
@@ -79,4 +79,6 @@ export { default as Suggestions } from "./suggestions";
79
79
  export { default as UserBubble } from "./user-bubble";
80
80
  export { default as Welcome } from "./welcome";
81
81
 
82
- // GenUI shared modules
82
+ // GenUI shared modules
83
+
84
+ export { rawValueToSegments, segmentsToReadableText, segmentsHasContent } from "./composer/segments";
@@ -8,10 +8,15 @@ export interface InvocationProps {
8
8
  * false:UserBubble 等只读态(可 hover 看摘要)。
9
9
  */
10
10
  interactive?: boolean;
11
- /** form popover 打开时的高亮态(L4 params 启用后使用) */
11
+ /**
12
+ * 参数面板打开时高亮(最高优先级,叠浅底色)。
13
+ * 不进 `data.state`,因为它是临时 UI 状态,不属于 invocation 自身语义。
14
+ */
12
15
  active?: boolean;
13
- /** 交互态单击回调 */
16
+ /** 交互态单击;业务接管打开参数面板等行为 */
14
17
  onClick?: (anchor: HTMLElement) => void;
15
18
  }
16
19
  export declare function Invocation({ data, interactive, active, onClick, }: InvocationProps): import("react").JSX.Element;
20
+ export { InvocationParamPopover } from './param-popover';
21
+ export type { InvocationParamPopoverProps } from './param-popover';
17
22
  export default Invocation;
@@ -6,9 +6,9 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
6
6
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
7
  import Popover from "../popover";
8
8
  import "./style";
9
- function formatParamsSummary(params) {
10
- if (!params) return null;
11
- var entries = Object.entries(params).filter(function (_ref) {
9
+ function formatPayloadSummary(payload) {
10
+ if (!payload) return null;
11
+ var entries = Object.entries(payload).filter(function (_ref) {
12
12
  var _ref2 = _slicedToArray(_ref, 2),
13
13
  v = _ref2[1];
14
14
  return v != null && v !== '';
@@ -22,6 +22,7 @@ function formatParamsSummary(params) {
22
22
  }).join(' · ');
23
23
  }
24
24
  export function Invocation(_ref5) {
25
+ var _data$state;
25
26
  var data = _ref5.data,
26
27
  _ref5$interactive = _ref5.interactive,
27
28
  interactive = _ref5$interactive === void 0 ? false : _ref5$interactive,
@@ -29,7 +30,8 @@ export function Invocation(_ref5) {
29
30
  active = _ref5$active === void 0 ? false : _ref5$active,
30
31
  onClick = _ref5.onClick;
31
32
  var clickable = interactive && !!onClick;
32
- var state = active ? 'active' : 'complete';
33
+ /** 状态优先级:active > data.state > 'complete' */
34
+ var state = active ? 'active' : (_data$state = data.state) !== null && _data$state !== void 0 ? _data$state : 'complete';
33
35
  var handleClick = clickable ? function (e) {
34
36
  e.preventDefault();
35
37
  e.stopPropagation();
@@ -52,8 +54,9 @@ export function Invocation(_ref5) {
52
54
  "data-odn-invocation-label": true
53
55
  }, data.label));
54
56
  if (interactive) return core;
55
- var summary = formatParamsSummary(data.params);
56
- if (!summary) return core;
57
+ var summary = formatPayloadSummary(data.payload);
58
+ /** 只读态:有 payload 摘要 → hover;pending 但无 payload → 「未填参数」;否则不弹 */
59
+ if (!summary && data.state !== 'pending') return core;
57
60
  return /*#__PURE__*/React.createElement(Popover, {
58
61
  trigger: "hover",
59
62
  placement: "top",
@@ -77,8 +80,11 @@ function InvocationHoverPopup(_ref6) {
77
80
  }, /*#__PURE__*/React.createElement("span", {
78
81
  "data-odn-invocation-popover-slash": true,
79
82
  "aria-hidden": true
80
- }, "/"), label), /*#__PURE__*/React.createElement("div", {
83
+ }, "/"), label), summary ? /*#__PURE__*/React.createElement("div", {
81
84
  "data-odn-invocation-popover-summary": true
82
- }, summary));
85
+ }, summary) : /*#__PURE__*/React.createElement("div", {
86
+ "data-odn-invocation-popover-empty": true
87
+ }, "\u672A\u586B\u53C2\u6570"));
83
88
  }
89
+ export { InvocationParamPopover } from "./param-popover";
84
90
  export default Invocation;