one-design-next 0.0.11 → 0.0.13

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 (55) hide show
  1. package/dist/_genui-types.d.ts +45 -10
  2. package/dist/attachments/index.js +67 -10
  3. package/dist/attachments/style/index.css +86 -14
  4. package/dist/collapse/primitive.d.ts +1 -1
  5. package/dist/composer/chip.d.ts +4 -4
  6. package/dist/composer/clipboard.d.ts +12 -0
  7. package/dist/composer/clipboard.js +84 -0
  8. package/dist/composer/editor.d.ts +9 -1
  9. package/dist/composer/editor.js +70 -14
  10. package/dist/composer/hooks/useChipManager.d.ts +6 -1
  11. package/dist/composer/hooks/useChipManager.js +76 -27
  12. package/dist/composer/hooks/useChipSelectionMarker.d.ts +7 -0
  13. package/dist/composer/hooks/useChipSelectionMarker.js +66 -0
  14. package/dist/composer/index.d.ts +58 -1
  15. package/dist/composer/index.js +219 -102
  16. package/dist/composer/inline-ref.d.ts +9 -0
  17. package/dist/composer/inline-ref.js +21 -0
  18. package/dist/composer/send-meta.d.ts +6 -0
  19. package/dist/composer/send-meta.js +67 -0
  20. package/dist/composer/style/index.css +48 -53
  21. package/dist/composer/utils.d.ts +9 -0
  22. package/dist/composer/utils.js +43 -2
  23. package/dist/fab/index.js +3 -16
  24. package/dist/fab/style/index.css +1 -3
  25. package/dist/icon/svg-data.d.ts +4 -0
  26. package/dist/icon/svg-data.js +1 -1
  27. package/dist/icon/types.d.ts +1 -1
  28. package/dist/image/index.d.ts +43 -0
  29. package/dist/image/index.js +51 -0
  30. package/dist/image/style/index.css +59 -0
  31. package/dist/image/style/index.d.ts +2 -0
  32. package/dist/image/style/index.js +2 -0
  33. package/dist/index.d.ts +5 -1
  34. package/dist/index.js +4 -0
  35. package/dist/invocation/index.d.ts +17 -0
  36. package/dist/invocation/index.js +84 -0
  37. package/dist/invocation/style/index.css +58 -0
  38. package/dist/invocation/style/index.d.ts +2 -0
  39. package/dist/invocation/style/index.js +2 -0
  40. package/dist/mention/index.d.ts +17 -0
  41. package/dist/mention/index.js +90 -0
  42. package/dist/mention/style/index.css +58 -0
  43. package/dist/mention/style/index.d.ts +2 -0
  44. package/dist/mention/style/index.js +2 -0
  45. package/dist/message-image/index.d.ts +42 -0
  46. package/dist/message-image/index.js +46 -0
  47. package/dist/message-image/style/index.css +60 -0
  48. package/dist/message-image/style/index.d.ts +2 -0
  49. package/dist/message-image/style/index.js +2 -0
  50. package/dist/preview-panel/index.d.ts +5 -1
  51. package/dist/preview-panel/index.js +27 -2
  52. package/dist/user-bubble/index.d.ts +11 -1
  53. package/dist/user-bubble/index.js +30 -5
  54. package/dist/user-bubble/style/index.css +6 -0
  55. package/package.json +2 -3
@@ -23,21 +23,37 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
23
23
  */
24
24
 
25
25
  import { useCallback, useRef, useState } from 'react';
26
+ import { forEachMarkerSegment } from "../utils";
26
27
 
27
28
  /** chip 在 React 状态中的形态:data + 对应的 DOM host。 */
28
29
 
30
+ function createChipHost(data) {
31
+ var host = document.createElement('span');
32
+ host.dataset.mentionId = data.id;
33
+ host.contentEditable = 'false';
34
+ host.setAttribute('data-odn-composer-chip-host', '');
35
+ return host;
36
+ }
29
37
  export function useChipManager( /** 每次 chip mutation 后调用,给上层(editor)一个抛 onChange 的钩子。 */
30
38
  onAfterMutate) {
31
39
  var _useState = useState([]),
32
40
  _useState2 = _slicedToArray(_useState, 2),
33
41
  chips = _useState2[0],
34
42
  setChips = _useState2[1];
35
-
36
- /** ref 镜像:命令式 API 调用 setState 之后立即再用列表时拿不到最新值,用 ref 兜底。 */
37
43
  var chipsRef = useRef(chips);
38
44
  chipsRef.current = chips;
45
+ var insertChipAtRange = useCallback(function (data, range, _editor) {
46
+ var host = createChipHost(data);
47
+ range.insertNode(host);
48
+ var next = [].concat(_toConsumableArray(chipsRef.current), [{
49
+ data: data,
50
+ host: host
51
+ }]);
52
+ chipsRef.current = next;
53
+ setChips(next);
54
+ return host;
55
+ }, []);
39
56
  var insertChip = useCallback(function (data, editor) {
40
- // ── 定位插入点 ────────────────────────────────
41
57
  var sel = window.getSelection();
42
58
  var range = null;
43
59
  if (sel && sel.rangeCount > 0 && editor.contains(sel.anchorNode)) {
@@ -47,41 +63,70 @@ onAfterMutate) {
47
63
  editor.focus();
48
64
  range = document.createRange();
49
65
  range.selectNodeContents(editor);
50
- range.collapse(false); // 末尾
66
+ range.collapse(false);
51
67
  sel = window.getSelection();
52
68
  (_sel = sel) === null || _sel === void 0 || _sel.removeAllRanges();
53
69
  (_sel2 = sel) === null || _sel2 === void 0 || _sel2.addRange(range);
54
70
  }
55
71
  if (!range || !sel) return;
56
72
  range.deleteContents();
57
-
58
- // ── 创建 host span(contenteditable=false,承载 data-mention-id) ──
59
- // 不在 chip 后塞 ZWSP 锚点:浏览器原生把 contentEditable=false 当原子块,
60
- // Backspace / 方向键已经能正确跨越;多塞一个 ZWSP 反而会撑出多个等价
61
- // caret 位置,导致「字母和 chip 之间卡一下」的体验问题。
62
- var host = document.createElement('span');
63
- host.dataset.mentionId = data.id;
64
- host.contentEditable = 'false';
65
- host.setAttribute('data-odn-composer-chip-host', '');
66
- range.insertNode(host);
67
-
68
- // 光标 → chip 之后;用户继续输入时浏览器会自动建/接续邻接 text node
73
+ var host = insertChipAtRange(data, range, editor);
69
74
  var newRange = document.createRange();
70
75
  newRange.setStartAfter(host);
71
76
  newRange.collapse(true);
72
77
  sel.removeAllRanges();
73
78
  sel.addRange(newRange);
74
- var next = [].concat(_toConsumableArray(chipsRef.current), [{
75
- data: data,
76
- host: host
77
- }]);
78
- setChips(next);
79
-
80
- // 防御 Portal 渲染过程中浏览器把光标弹回 chip 之前的边界
81
79
  requestAnimationFrame(function () {
82
80
  return editor.focus();
83
81
  });
84
82
  onAfterMutate === null || onAfterMutate === void 0 || onAfterMutate();
83
+ }, [insertChipAtRange, onAfterMutate]);
84
+ var insertSerializedAtRange = useCallback(function (value, chipList, range, editor) {
85
+ var chipById = new Map(chipList.map(function (c) {
86
+ return [c.id, c];
87
+ }));
88
+ var frag = document.createDocumentFragment();
89
+ var inserted = [];
90
+ forEachMarkerSegment(value, function (seg) {
91
+ if (seg.type === 'text') {
92
+ if (seg.text) frag.appendChild(document.createTextNode(seg.text));
93
+ return;
94
+ }
95
+ var data = chipById.get(seg.id);
96
+ if (!data) return;
97
+ var host = createChipHost(data);
98
+ frag.appendChild(host);
99
+ inserted.push({
100
+ data: data,
101
+ host: host
102
+ });
103
+ });
104
+ range.insertNode(frag);
105
+ range.collapse(false);
106
+ if (inserted.length > 0) {
107
+ var next = [].concat(_toConsumableArray(chipsRef.current), inserted);
108
+ chipsRef.current = next;
109
+ setChips(next);
110
+ }
111
+ var sel = window.getSelection();
112
+ if (sel) {
113
+ sel.removeAllRanges();
114
+ sel.addRange(range);
115
+ }
116
+ requestAnimationFrame(function () {
117
+ return editor.focus();
118
+ });
119
+ onAfterMutate === null || onAfterMutate === void 0 || onAfterMutate();
120
+ }, [onAfterMutate]);
121
+ var syncChipsAfterDomMutation = useCallback(function (editor) {
122
+ var next = chipsRef.current.filter(function (c) {
123
+ return c.host.isConnected && editor.contains(c.host);
124
+ });
125
+ if (next.length !== chipsRef.current.length) {
126
+ chipsRef.current = next;
127
+ setChips(next);
128
+ onAfterMutate === null || onAfterMutate === void 0 || onAfterMutate();
129
+ }
85
130
  }, [onAfterMutate]);
86
131
  var removeChip = useCallback(function (id) {
87
132
  var target = chipsRef.current.find(function (c) {
@@ -89,19 +134,23 @@ onAfterMutate) {
89
134
  });
90
135
  if (!target) return;
91
136
  target.host.remove();
92
- setChips(chipsRef.current.filter(function (c) {
137
+ var next = chipsRef.current.filter(function (c) {
93
138
  return c.data.id !== id;
94
- }));
139
+ });
140
+ chipsRef.current = next;
141
+ setChips(next);
95
142
  onAfterMutate === null || onAfterMutate === void 0 || onAfterMutate();
96
143
  }, [onAfterMutate]);
97
144
  var resetChips = useCallback(function () {
98
145
  setChips([]);
99
- // 不调 onAfterMutate —— resetChips 通常和 editor.innerHTML='' 配套,
100
- // 由调用方统一抛一次 onChange 即可。
146
+ chipsRef.current = [];
101
147
  }, []);
102
148
  return {
103
149
  chips: chips,
104
150
  insertChip: insertChip,
151
+ insertChipAtRange: insertChipAtRange,
152
+ insertSerializedAtRange: insertSerializedAtRange,
153
+ syncChipsAfterDomMutation: syncChipsAfterDomMutation,
105
154
  removeChip: removeChip,
106
155
  resetChips: resetChips
107
156
  };
@@ -0,0 +1,7 @@
1
+ import { type RefObject } from 'react';
2
+ import type { ManagedChip } from './useChipManager';
3
+ /**
4
+ * chip host 在原生 selection 下无高亮(contentEditable=false + user-select:none),
5
+ * 用 data-selected 自绘与浏览器 selection 一致的浅灰底。
6
+ */
7
+ export declare function useChipSelectionMarker(editorRef: RefObject<HTMLElement | null>, chips: ManagedChip[]): void;
@@ -0,0 +1,66 @@
1
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
2
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
3
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
4
+ import { useEffect, useRef } from 'react';
5
+ /**
6
+ * chip host 在原生 selection 下无高亮(contentEditable=false + user-select:none),
7
+ * 用 data-selected 自绘与浏览器 selection 一致的浅灰底。
8
+ */
9
+ export function useChipSelectionMarker(editorRef, chips) {
10
+ var chipsRef = useRef(chips);
11
+ chipsRef.current = chips;
12
+ useEffect(function () {
13
+ var clearSelected = function clearSelected() {
14
+ var _iterator = _createForOfIteratorHelper(chipsRef.current),
15
+ _step;
16
+ try {
17
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
18
+ var chip = _step.value;
19
+ chip.host.removeAttribute('data-selected');
20
+ }
21
+ } catch (err) {
22
+ _iterator.e(err);
23
+ } finally {
24
+ _iterator.f();
25
+ }
26
+ };
27
+ var handler = function handler() {
28
+ var editor = editorRef.current;
29
+ if (!editor) {
30
+ clearSelected();
31
+ return;
32
+ }
33
+ var sel = window.getSelection();
34
+ if (!sel || sel.rangeCount === 0) {
35
+ clearSelected();
36
+ return;
37
+ }
38
+ var range = sel.getRangeAt(0);
39
+ if (range.collapsed || !editor.contains(range.commonAncestorContainer)) {
40
+ clearSelected();
41
+ return;
42
+ }
43
+ var _iterator2 = _createForOfIteratorHelper(chipsRef.current),
44
+ _step2;
45
+ try {
46
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
47
+ var chip = _step2.value;
48
+ if (range.intersectsNode(chip.host)) {
49
+ chip.host.setAttribute('data-selected', '');
50
+ } else {
51
+ chip.host.removeAttribute('data-selected');
52
+ }
53
+ }
54
+ } catch (err) {
55
+ _iterator2.e(err);
56
+ } finally {
57
+ _iterator2.f();
58
+ }
59
+ };
60
+ document.addEventListener('selectionchange', handler);
61
+ return function () {
62
+ document.removeEventListener('selectionchange', handler);
63
+ clearSelected();
64
+ };
65
+ }, [editorRef]);
66
+ }
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import type { SkillItem, SendMeta } from '../_genui-types';
2
+ import type { Attachment, SkillItem, SendMeta } from '../_genui-types';
3
3
  import './style';
4
4
  export type ComposerTool = 'attachment' | 'webSearch' | 'skill';
5
5
  export type ComposerVariant = 'full' | 'lite';
@@ -36,6 +36,18 @@ export interface ComposerProps {
36
36
  onSend: (text: string, meta?: SendMeta) => void;
37
37
  onStop?: () => void;
38
38
  isGenerating?: boolean;
39
+ /**
40
+ * 整体禁用:输入框不可输入、工具按钮置灰、发送按钮显示但不可点击。
41
+ *
42
+ * 状态优先级(高 → 低):`disabled` > `isGenerating` > `canSend`。
43
+ * - `disabled=true` 时,无论是否在生成中,都强制呈现"发送态"按钮(仅置灰),
44
+ * 不会切换到中止按钮——因为禁用本身意味着"现在什么都不能做"。
45
+ *
46
+ * 典型场景:人工客服尚未接入、会话被风控冻结、配额耗尽等"暂时不可发起新交互"的状态。
47
+ * 此时业务方常配合修改 placeholder(如"等待客服接入…")告知用户。
48
+ * @default false
49
+ */
50
+ disabled?: boolean;
39
51
  placeholder?: string;
40
52
  /** 根节点外边距:drawer = 左右下各 24px(与原先外层 px-6 pb-6 一致),不再包一层 div */
41
53
  spacing?: 'default' | 'drawer';
@@ -76,6 +88,51 @@ export interface ComposerProps {
76
88
  * 类型同步调整为 `HTMLDivElement`,旧业务代码若仅读 `e.shiftKey` 等通用属性无影响。
77
89
  */
78
90
  onEnter?: (e: React.KeyboardEvent<HTMLDivElement>) => void | false;
91
+ /**
92
+ * 受控附件列表。
93
+ * - 传则受控,由业务方决定数组内容;典型搭配 `onAttachFiles` + `onAttachmentsChange` 使用,
94
+ * 把"原生 File → 云端 Attachment"的转换交给业务方(如先上传到企点 / OSS 拿 url)。
95
+ * - 不传则非受控,组件内部自管,沿用 `URL.createObjectURL(f)` 生成 blob url 的旧行为(向后兼容)。
96
+ */
97
+ attachments?: Attachment[];
98
+ /**
99
+ * 附件数组变化时触发(新增、删除、外部回写都会调)。
100
+ * 受控模式下与 `attachments` 配合形成 React 标准受控形态。
101
+ */
102
+ onAttachmentsChange?: (attachments: Attachment[]) => void;
103
+ /**
104
+ * 文件经选择 / 拖入 / 粘贴入口被选中并通过内置校验(数量上限、单文件体积、重名)后触发。
105
+ * - **传入此回调即接管"原生 File → Attachment"全过程**:组件不再做 `URL.createObjectURL`,
106
+ * 也不会自动入栈。业务方负责上传(如调企点接口拿 `qidian_uuid`)后,
107
+ * 通过 `onAttachmentsChange` 把云端结果回写到 `attachments`。
108
+ * - 不传则回落到默认行为:内部生成 blob url 入栈(仅本地预览,无服务端语义)。
109
+ *
110
+ * 强约定:传 `onAttachFiles` 时通常应同时把 `attachments` 受控(否则业务方无路径回写)。
111
+ */
112
+ onAttachFiles?: (files: File[]) => void;
113
+ /**
114
+ * 单条附件被删除时触发,提供下标与被删条目。
115
+ * - 与 `onAttachmentsChange` 同时调(顺序:先 `onAttachmentRemove` 再 `onAttachmentsChange`)。
116
+ * - 适合在此处调"服务端删文件"等需要拿到具体被删条目的接口,
117
+ * 避免业务方从前后两次 `attachments` 数组里 diff 出被删项。
118
+ */
119
+ onAttachmentRemove?: (index: number, attachment: Attachment) => void;
120
+ /**
121
+ * 关闭新附件入口:附件工具按钮置灰、拖入不再高亮、粘贴入文件被忽略。
122
+ * 已存在的附件仍可由用户点击 × 删除。
123
+ * @default false
124
+ */
125
+ attachmentsDisabled?: boolean;
126
+ /**
127
+ * 单次最多附件数量。
128
+ * @default 5
129
+ */
130
+ maxFileCount?: number;
131
+ /**
132
+ * 单个文件最大体积(字节)。
133
+ * @default 20 * 1024 * 1024(20MB)
134
+ */
135
+ maxFileSize?: number;
79
136
  className?: string;
80
137
  style?: React.CSSProperties;
81
138
  }