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
@@ -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 { findChipHostBeforeCaret, getPlainText, isEditorEmpty, probeTrigger, resolveCaretJumpAroundChip, 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,14 +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
- setEmpty(isEditorEmpty(el));
199
+ normalizeEditorDom(el);
200
+ var showPlaceholder = shouldShowPlaceholder(el);
201
+ if (forceNonEmptyOnceRef.current && showPlaceholder) {
202
+ setEmpty(false);
203
+ } else {
204
+ setEmpty(showPlaceholder);
205
+ }
206
+ forceNonEmptyOnceRef.current = false;
180
207
  onChange === null || onChange === void 0 || onChange(getPlainText(el));
181
208
  // editor.height 锁定时 RO 不触发,主动同步一次,确保升格判定能拿到 scrollHeight
182
209
  syncHeight();
183
- }, [onChange, syncHeight]);
210
+ }, [onChange, shouldShowPlaceholder, syncHeight]);
184
211
 
185
212
  /* ─────────────────────────────────────
186
213
  * chip 管理
@@ -194,6 +221,8 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
194
221
  insertSerializedAtRange = _useChipManager.insertSerializedAtRange,
195
222
  syncChipsAfterDomMutation = _useChipManager.syncChipsAfterDomMutation,
196
223
  removeChip = _useChipManager.removeChip,
224
+ updateChipData = _useChipManager.updateChipData,
225
+ getChipHost = _useChipManager.getChipHost,
197
226
  resetChips = _useChipManager.resetChips;
198
227
  useChipSelectionMarker(editorRef, chips);
199
228
 
@@ -253,7 +282,17 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
253
282
  insertChip: function insertChip(data) {
254
283
  var el = editorRef.current;
255
284
  if (!el) return;
256
- _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);
257
296
  },
258
297
  removeChip: removeChip,
259
298
  replaceTriggerWithChip: function replaceTriggerWithChip(data) {
@@ -261,8 +300,8 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
261
300
  if (!el) return;
262
301
  var probe = triggerRef.current;
263
302
  if (!probe) {
264
- // 无活动 trigger,退化为光标处插入
265
- _insertChip(data, el);
303
+ // 无活动 trigger,退化为最近光标处插入
304
+ _insertChip(data, el, lastEditorRangeRef.current);
266
305
  return;
267
306
  }
268
307
  // 选中 trigger + query 整段,删掉再插 chip
@@ -299,11 +338,13 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
299
338
  return c.data;
300
339
  });
301
340
  },
341
+ updateChipData: updateChipData,
342
+ getChipHost: getChipHost,
302
343
  get nativeElement() {
303
344
  return editorRef.current;
304
345
  }
305
346
  };
306
- }, [onChange, _insertChip, removeChip, resetChips, onTriggerChange, chips]);
347
+ }, [onChange, _insertChip, removeChip, resetChips, onTriggerChange, chips, updateChipData, getChipHost]);
307
348
 
308
349
  /* ─────────────────────────────────────
309
350
  * 监听 selection 变化:方向键 / 鼠标点击移位都要重算 trigger
@@ -323,6 +364,28 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
323
364
  }
324
365
  return;
325
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
+ }
326
389
  recomputeTrigger();
327
390
  };
328
391
  document.addEventListener('selectionchange', handler);
@@ -373,7 +436,7 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
373
436
  if (valueProp !== current) {
374
437
  el.textContent = valueProp !== null && valueProp !== void 0 ? valueProp : '';
375
438
  resetChips();
376
- setEmpty(!valueProp);
439
+ setEmpty(shouldShowPlaceholder(el));
377
440
  if (triggerRef.current) {
378
441
  triggerRef.current = null;
379
442
  onTriggerChange === null || onTriggerChange === void 0 || onTriggerChange(null);
@@ -390,9 +453,15 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
390
453
  * ───────────────────────────────────── */
391
454
  var handleInput = useCallback(function () {
392
455
  var el = editorRef.current;
393
- if (el) setEmpty(isEditorEmpty(el));
456
+ if (el && !isComposingRef.current) {
457
+ normalizeComposerEditorDom(el);
458
+ }
394
459
  requestScrollCaretIntoView();
395
- if (isComposingRef.current) return;
460
+ if (isComposingRef.current) {
461
+ // 合成期只负责隐藏 placeholder,不触发归一化提交链路
462
+ setEmpty(false);
463
+ return;
464
+ }
396
465
  fireChange();
397
466
  recomputeTrigger();
398
467
  }, [fireChange, recomputeTrigger, requestScrollCaretIntoView]);
@@ -403,6 +472,8 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
403
472
  }, []);
404
473
  var handleCompositionEnd = useCallback(function () {
405
474
  isComposingRef.current = false;
475
+ var el = editorRef.current;
476
+ if (el) normalizeComposerEditorDom(el);
406
477
  fireChange();
407
478
  recomputeTrigger();
408
479
  requestScrollCaretIntoView();
@@ -414,18 +485,25 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
414
485
  * - 默认行为 = 自插 <br>(替代浏览器默认的 <div> 包裹,避免破坏 inline 流)
415
486
  * ───────────────────────────────────── */
416
487
  var insertLineBreak = useCallback(function () {
417
- var _br$parentNode;
488
+ var _br$parentNode, _tail$textContent;
418
489
  var sel = window.getSelection();
419
490
  if (!sel || sel.rangeCount === 0) return;
420
491
  var range = sel.getRangeAt(0);
421
492
  range.deleteContents();
422
493
  var br = document.createElement('br');
494
+ br.setAttribute('data-odn-composer-user-break', 'true');
423
495
  range.insertNode(br);
424
- // webkit:行尾插 br 时需要 ZWSP 让 caret 落点可见
425
- 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);
426
503
  (_br$parentNode = br.parentNode) === null || _br$parentNode === void 0 || _br$parentNode.insertBefore(tail, br.nextSibling);
427
504
  var newRange = document.createRange();
428
- newRange.setStartAfter(tail);
505
+ // 直接落在 spacer 文本内部,避免 element-boundary caret 在首拍被二次改写
506
+ newRange.setStart(tail, ((_tail$textContent = tail.textContent) !== null && _tail$textContent !== void 0 ? _tail$textContent : '').length);
429
507
  newRange.collapse(true);
430
508
  sel.removeAllRanges();
431
509
  sel.addRange(newRange);
@@ -433,26 +511,101 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
433
511
  requestScrollCaretIntoView();
434
512
  }, [fireChange, requestScrollCaretIntoView]);
435
513
  var handleKeyDown = useCallback(function (e) {
514
+ if (inputModality !== 'keyboard') setInputModality('keyboard');
436
515
  if (isComposingRef.current || e.nativeEvent.isComposing || e.keyCode === 229) return;
437
516
 
438
517
  /* 触发态优先:activeTrigger 时把导航键先吐给父组件(驱动浮层选择)。
439
518
  * 父返回 true 表示已处理,editor 直接结束本次 keydown,不再走任何
440
519
  * 自身逻辑(避免 Enter 又触发 onPressEnter / 插换行)。 */
441
- 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')) {
442
521
  if (onTriggerKeyDown(e) === true) return;
443
522
  }
444
523
 
445
- // Backspace:紧邻 chip 后方时整块删
524
+ // Backspace:chip 边界——空 text 在 chip 后先退回 chip 前;chip 前无正文再整块删
446
525
  if (e.key === 'Backspace') {
526
+ var el = editorRef.current;
447
527
  var sel = window.getSelection();
448
- if (sel && sel.rangeCount > 0) {
528
+ if (el && sel && sel.rangeCount > 0) {
449
529
  var range = sel.getRangeAt(0);
450
- if (range.collapsed) {
451
- var host = findChipHostBeforeCaret(range);
452
- var _id = host === null || host === void 0 ? void 0 : host.dataset.mentionId;
453
- if (_id) {
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
+ }
587
+ var hostBefore = findChipHostBeforeCaret(range);
588
+ if (hostBefore !== null && hostBefore !== void 0 && hostBefore.dataset.mentionId) {
589
+ var _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 : '') === '') {
593
+ e.preventDefault();
594
+ moveCaretBeforeChip(hostBefore, {
595
+ removeTrailingEmptyText: true,
596
+ trailingText: _node
597
+ });
598
+ return;
599
+ }
454
600
  e.preventDefault();
455
- removeChip(_id);
601
+ removeChip(hostBefore.dataset.mentionId);
602
+ return;
603
+ }
604
+ var hostAfter = findChipHostAfterCaret(range);
605
+ var idAfter = hostAfter === null || hostAfter === void 0 ? void 0 : hostAfter.dataset.mentionId;
606
+ if (idAfter && getVisibleTextBeforeHost(el, hostAfter).trim() === '') {
607
+ e.preventDefault();
608
+ removeChip(idAfter);
456
609
  return;
457
610
  }
458
611
  }
@@ -469,11 +622,11 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
469
622
  var target = resolveCaretJumpAroundChip(_range, e.key === 'ArrowLeft' ? 'left' : 'right');
470
623
  if (target) {
471
624
  e.preventDefault();
472
- var r = document.createRange();
473
- r.setStart(target.node, target.offset);
474
- r.collapse(true);
625
+ var _r = document.createRange();
626
+ _r.setStart(target.node, target.offset);
627
+ _r.collapse(true);
475
628
  _sel.removeAllRanges();
476
- _sel.addRange(r);
629
+ _sel.addRange(_r);
477
630
  return;
478
631
  }
479
632
  }
@@ -481,15 +634,18 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
481
634
  }
482
635
  if (e.key === 'Enter') {
483
636
  if (onPressEnter) {
484
- var _r = onPressEnter(e);
485
- if (_r === false) return;
637
+ var _r2 = onPressEnter(e);
638
+ if (_r2 === false) return;
486
639
  }
487
640
  // 外部(如浮层)已经 preventDefault → 不再插换行
488
641
  if (e.defaultPrevented) return;
489
642
  e.preventDefault();
490
643
  insertLineBreak();
491
644
  }
492
- }, [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]);
493
649
  var writeClipboardPayload = useCallback(function (e, payload) {
494
650
  if (!payload) return false;
495
651
  e.clipboardData.setData(COMPOSER_CLIPBOARD_MIME, JSON.stringify(payload));
@@ -600,6 +756,7 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
600
756
  "aria-disabled": disabled || undefined,
601
757
  "data-odn-composer-editor": true,
602
758
  "data-odn-composer-editor-disabled": disabled || undefined,
759
+ "data-odn-input-modality": inputModality,
603
760
  "data-empty": empty || undefined,
604
761
  "data-placeholder": placeholder,
605
762
  className: className,
@@ -607,12 +764,19 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
607
764
  onCompositionStart: handleCompositionStart,
608
765
  onCompositionEnd: handleCompositionEnd,
609
766
  onKeyDown: handleKeyDown,
767
+ onMouseMove: handlePointerInput,
768
+ onMouseDown: handlePointerInput,
769
+ onPointerMove: handlePointerInput,
610
770
  onCopy: handleCopy,
611
771
  onCut: handleCut,
612
772
  onPaste: handlePaste
613
773
  })), chips.map(function (chip) {
614
774
  return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement(ComposerInlineRef, {
615
- 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
616
780
  }), chip.host, chip.data.id);
617
781
  }));
618
782
  });
@@ -7,11 +7,11 @@
7
7
  * - DOM 与 React state 的"双向"维护:DOM 由 contenteditable 自治、视觉由 Portal 注入
8
8
  *
9
9
  * 不负责:
10
- * - chip 视觉渲染(chip.tsx
10
+ * - chip 视觉渲染(inline-ref → Invocation / Mention
11
11
  * - 触发判定(useTriggerMenu,step 4 加)
12
12
  * - onChange 抛出(由调用方 onAfterMutate 回调中处理)
13
13
  */
14
- import type { ChipData } from '../chip';
14
+ import type { ChipData } from '../../_genui-types';
15
15
  /** chip 在 React 状态中的形态:data + 对应的 DOM host。 */
16
16
  export interface ManagedChip {
17
17
  data: ChipData;
@@ -22,10 +22,11 @@ export interface UseChipManagerReturn {
22
22
  chips: ManagedChip[];
23
23
  /**
24
24
  * 在编辑器当前光标位置插入 chip:
25
+ * - 优先使用 preferredRange(菜单点击时缓存的最近编辑器 selection)
25
26
  * - selection 在编辑器内 → 插入光标处
26
27
  * - selection 在编辑器外 / 无 selection → fallback 到末尾
27
28
  */
28
- insertChip: (data: ChipData, editor: HTMLElement) => void;
29
+ insertChip: (data: ChipData, editor: HTMLElement, preferredRange?: Range | null) => void;
29
30
  /** 在指定 range 处插入 chip(调用方负责 deleteContents)。 */
30
31
  insertChipAtRange: (data: ChipData, range: Range, editor: HTMLElement) => HTMLSpanElement;
31
32
  /** 将含 marker 的 raw value + chips 插入 range(粘贴用)。 */
@@ -34,6 +35,10 @@ export interface UseChipManagerReturn {
34
35
  syncChipsAfterDomMutation: (editor: HTMLElement) => void;
35
36
  /** 按 chip id 移除(同时清理伴随的 ZWSP 锚点)。 */
36
37
  removeChip: (id: string) => void;
38
+ /** 更新 chip 数据(参数面板关闭保存等)。 */
39
+ updateChipData: (id: string, patch: Partial<ChipData>) => void;
40
+ /** 按 id 取 chip host(参数面板 anchor)。 */
41
+ getChipHost: (id: string) => HTMLSpanElement | null;
37
42
  /** 清空所有 chip 的 React 状态(DOM 由调用方 editor.innerHTML='' 一并处理)。 */
38
43
  resetChips: () => void;
39
44
  }
@@ -1,3 +1,9 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
5
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
6
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
1
7
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
2
8
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
9
  function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
@@ -17,13 +23,13 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
17
23
  * - DOM 与 React state 的"双向"维护:DOM 由 contenteditable 自治、视觉由 Portal 注入
18
24
  *
19
25
  * 不负责:
20
- * - chip 视觉渲染(chip.tsx
26
+ * - chip 视觉渲染(inline-ref → Invocation / Mention
21
27
  * - 触发判定(useTriggerMenu,step 4 加)
22
28
  * - onChange 抛出(由调用方 onAfterMutate 回调中处理)
23
29
  */
24
30
 
25
31
  import { useCallback, useRef, useState } from 'react';
26
- import { forEachMarkerSegment } from "../utils";
32
+ import { attachCaretSpacerAfter, CARET_SPACER, detachCaretSpacerAfter, ensureTextAnchorAt, forEachMarkerSegment, normalizeComposerEditorDom, ZWSP } from "../utils";
27
33
 
28
34
  /** chip 在 React 状态中的形态:data + 对应的 DOM host。 */
29
35
 
@@ -42,9 +48,11 @@ onAfterMutate) {
42
48
  setChips = _useState2[1];
43
49
  var chipsRef = useRef(chips);
44
50
  chipsRef.current = chips;
45
- var insertChipAtRange = useCallback(function (data, range, _editor) {
51
+ var insertChipAtRange = useCallback(function (data, range, editor) {
46
52
  var host = createChipHost(data);
47
53
  range.insertNode(host);
54
+ attachCaretSpacerAfter(host);
55
+ normalizeComposerEditorDom(editor);
48
56
  var next = [].concat(_toConsumableArray(chipsRef.current), [{
49
57
  data: data,
50
58
  host: host
@@ -53,26 +61,33 @@ onAfterMutate) {
53
61
  setChips(next);
54
62
  return host;
55
63
  }, []);
56
- var insertChip = useCallback(function (data, editor) {
64
+ var insertChip = useCallback(function (data, editor, preferredRange) {
57
65
  var sel = window.getSelection();
58
66
  var range = null;
59
- if (sel && sel.rangeCount > 0 && editor.contains(sel.anchorNode)) {
67
+ if (preferredRange && preferredRange.startContainer.isConnected && preferredRange.endContainer.isConnected && editor.contains(preferredRange.startContainer) && editor.contains(preferredRange.endContainer)) {
68
+ range = preferredRange.cloneRange();
69
+ } else if (sel && sel.rangeCount > 0 && editor.contains(sel.anchorNode)) {
60
70
  range = sel.getRangeAt(0);
61
71
  } else {
62
- var _sel, _sel2;
63
72
  editor.focus();
64
73
  range = document.createRange();
65
74
  range.selectNodeContents(editor);
66
75
  range.collapse(false);
67
- sel = window.getSelection();
68
- (_sel = sel) === null || _sel === void 0 || _sel.removeAllRanges();
69
- (_sel2 = sel) === null || _sel2 === void 0 || _sel2.addRange(range);
70
76
  }
77
+ sel = window.getSelection();
71
78
  if (!range || !sel) return;
79
+ sel.removeAllRanges();
80
+ sel.addRange(range);
72
81
  range.deleteContents();
73
82
  var host = insertChipAtRange(data, range, editor);
83
+ var spacer = host.nextSibling;
74
84
  var newRange = document.createRange();
75
- newRange.setStartAfter(host);
85
+ if ((spacer === null || spacer === void 0 ? void 0 : spacer.nodeType) === Node.TEXT_NODE && (spacer.textContent === CARET_SPACER || spacer.textContent === ZWSP)) {
86
+ newRange.setStart(spacer, 1);
87
+ } else {
88
+ var ensured = attachCaretSpacerAfter(host);
89
+ newRange.setStart(ensured, 1);
90
+ }
76
91
  newRange.collapse(true);
77
92
  sel.removeAllRanges();
78
93
  sel.addRange(newRange);
@@ -96,6 +111,7 @@ onAfterMutate) {
96
111
  if (!data) return;
97
112
  var host = createChipHost(data);
98
113
  frag.appendChild(host);
114
+ frag.appendChild(document.createTextNode(CARET_SPACER));
99
115
  inserted.push({
100
116
  data: data,
101
117
  host: host
@@ -103,6 +119,7 @@ onAfterMutate) {
103
119
  });
104
120
  range.insertNode(frag);
105
121
  range.collapse(false);
122
+ normalizeComposerEditorDom(editor);
106
123
  if (inserted.length > 0) {
107
124
  var next = [].concat(_toConsumableArray(chipsRef.current), inserted);
108
125
  chipsRef.current = next;
@@ -133,14 +150,90 @@ onAfterMutate) {
133
150
  return c.data.id === id;
134
151
  });
135
152
  if (!target) return;
153
+ var editor = target.host.parentElement;
154
+ var parent = target.host.parentNode;
155
+ var hostIndexInParent = parent ? Array.prototype.indexOf.call(parent.childNodes, target.host) : -1;
156
+
157
+ // 主锚点:chip 左侧文本尾(跳过尾部 ZWSP)
158
+ var prev = target.host.previousSibling;
159
+ var textAnchor = prev && prev.nodeType === Node.TEXT_NODE ? function (_prev$textContent) {
160
+ var raw = (_prev$textContent = prev.textContent) !== null && _prev$textContent !== void 0 ? _prev$textContent : '';
161
+ var len = raw.length;
162
+ while (len > 0 && (raw[len - 1] === CARET_SPACER || raw[len - 1] === ZWSP)) len--;
163
+ return {
164
+ node: prev,
165
+ offset: len
166
+ };
167
+ }() : null;
168
+
169
+ // 结构锚点:chip 在父节点中的原位置(删后即为“被删 chip 左侧”)
170
+ var treeAnchor = parent && hostIndexInParent >= 0 ? {
171
+ parent: parent,
172
+ offset: hostIndexInParent
173
+ } : null;
174
+ detachCaretSpacerAfter(target.host);
136
175
  target.host.remove();
176
+ if (editor instanceof HTMLElement) {
177
+ normalizeComposerEditorDom(editor);
178
+ }
137
179
  var next = chipsRef.current.filter(function (c) {
138
180
  return c.data.id !== id;
139
181
  });
140
182
  chipsRef.current = next;
141
183
  setChips(next);
142
184
  onAfterMutate === null || onAfterMutate === void 0 || onAfterMutate();
185
+
186
+ // 关键:在 onAfterMutate(含 fireChange/normalizeEditorDom)之后再恢复 selection
187
+ requestAnimationFrame(function () {
188
+ var _treeAnchor$parent;
189
+ var sel = window.getSelection();
190
+ if (!sel) return;
191
+ var applyAnchor = function applyAnchor(anchor) {
192
+ var _anchor$node$textCont;
193
+ if (!anchor || !anchor.node.isConnected) return false;
194
+ var r = document.createRange();
195
+ var safeOffset = anchor.node.nodeType === Node.TEXT_NODE ? Math.min(anchor.offset, ((_anchor$node$textCont = anchor.node.textContent) !== null && _anchor$node$textCont !== void 0 ? _anchor$node$textCont : '').length) : Math.min(anchor.offset, anchor.node.childNodes.length);
196
+ r.setStart(anchor.node, safeOffset);
197
+ r.collapse(true);
198
+ sel.removeAllRanges();
199
+ sel.addRange(r);
200
+ return true;
201
+ };
202
+ if (applyAnchor(textAnchor)) return;
203
+ if (treeAnchor !== null && treeAnchor !== void 0 && (_treeAnchor$parent = treeAnchor.parent) !== null && _treeAnchor$parent !== void 0 && _treeAnchor$parent.isConnected) {
204
+ var textTreeAnchor = ensureTextAnchorAt(treeAnchor.parent, treeAnchor.offset);
205
+ if (applyAnchor(textTreeAnchor)) return;
206
+ }
207
+ if (editor instanceof HTMLElement) {
208
+ var fallback = ensureTextAnchorAt(editor, 0);
209
+ if (fallback && applyAnchor(fallback)) return;
210
+ var r = document.createRange();
211
+ r.selectNodeContents(editor);
212
+ r.collapse(true);
213
+ sel.removeAllRanges();
214
+ sel.addRange(r);
215
+ }
216
+ });
217
+ }, [onAfterMutate]);
218
+ var updateChipData = useCallback(function (id, patch) {
219
+ var idx = chipsRef.current.findIndex(function (c) {
220
+ return c.data.id === id;
221
+ });
222
+ if (idx === -1) return;
223
+ var next = _toConsumableArray(chipsRef.current);
224
+ next[idx] = _objectSpread(_objectSpread({}, next[idx]), {}, {
225
+ data: _objectSpread(_objectSpread({}, next[idx].data), patch)
226
+ });
227
+ chipsRef.current = next;
228
+ setChips(next);
229
+ onAfterMutate === null || onAfterMutate === void 0 || onAfterMutate();
143
230
  }, [onAfterMutate]);
231
+ var getChipHost = useCallback(function (id) {
232
+ var _chipsRef$current$fin, _chipsRef$current$fin2;
233
+ return (_chipsRef$current$fin = (_chipsRef$current$fin2 = chipsRef.current.find(function (c) {
234
+ return c.data.id === id;
235
+ })) === null || _chipsRef$current$fin2 === void 0 ? void 0 : _chipsRef$current$fin2.host) !== null && _chipsRef$current$fin !== void 0 ? _chipsRef$current$fin : null;
236
+ }, []);
144
237
  var resetChips = useCallback(function () {
145
238
  setChips([]);
146
239
  chipsRef.current = [];
@@ -152,6 +245,8 @@ onAfterMutate) {
152
245
  insertSerializedAtRange: insertSerializedAtRange,
153
246
  syncChipsAfterDomMutation: syncChipsAfterDomMutation,
154
247
  removeChip: removeChip,
248
+ updateChipData: updateChipData,
249
+ getChipHost: getChipHost,
155
250
  resetChips: resetChips
156
251
  };
157
252
  }