one-design-next 0.0.12 → 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 (48) hide show
  1. package/dist/_genui-types.d.ts +36 -10
  2. package/dist/attachments/index.js +66 -16
  3. package/dist/attachments/style/index.css +86 -37
  4. package/dist/composer/chip.d.ts +4 -4
  5. package/dist/composer/clipboard.d.ts +12 -0
  6. package/dist/composer/clipboard.js +84 -0
  7. package/dist/composer/editor.d.ts +3 -1
  8. package/dist/composer/editor.js +65 -13
  9. package/dist/composer/hooks/useChipManager.d.ts +6 -1
  10. package/dist/composer/hooks/useChipManager.js +76 -27
  11. package/dist/composer/hooks/useChipSelectionMarker.d.ts +7 -0
  12. package/dist/composer/hooks/useChipSelectionMarker.js +66 -0
  13. package/dist/composer/index.js +73 -32
  14. package/dist/composer/inline-ref.d.ts +9 -0
  15. package/dist/composer/inline-ref.js +21 -0
  16. package/dist/composer/send-meta.d.ts +6 -0
  17. package/dist/composer/send-meta.js +67 -0
  18. package/dist/composer/style/index.css +39 -53
  19. package/dist/composer/utils.d.ts +9 -0
  20. package/dist/composer/utils.js +43 -2
  21. package/dist/fab/index.js +3 -16
  22. package/dist/fab/style/index.css +1 -3
  23. package/dist/image/index.d.ts +43 -0
  24. package/dist/image/index.js +51 -0
  25. package/dist/image/style/index.css +59 -0
  26. package/dist/image/style/index.d.ts +2 -0
  27. package/dist/image/style/index.js +2 -0
  28. package/dist/index.d.ts +5 -1
  29. package/dist/index.js +4 -0
  30. package/dist/invocation/index.d.ts +17 -0
  31. package/dist/invocation/index.js +84 -0
  32. package/dist/invocation/style/index.css +58 -0
  33. package/dist/invocation/style/index.d.ts +2 -0
  34. package/dist/invocation/style/index.js +2 -0
  35. package/dist/mention/index.d.ts +17 -0
  36. package/dist/mention/index.js +90 -0
  37. package/dist/mention/style/index.css +58 -0
  38. package/dist/mention/style/index.d.ts +2 -0
  39. package/dist/mention/style/index.js +2 -0
  40. package/dist/message-image/index.d.ts +42 -0
  41. package/dist/message-image/index.js +46 -0
  42. package/dist/message-image/style/index.css +60 -0
  43. package/dist/message-image/style/index.d.ts +2 -0
  44. package/dist/message-image/style/index.js +2 -0
  45. package/dist/user-bubble/index.d.ts +11 -1
  46. package/dist/user-bubble/index.js +30 -5
  47. package/dist/user-bubble/style/index.css +6 -0
  48. package/package.json +2 -2
@@ -25,8 +25,16 @@ export interface Attachment {
25
25
  type: string;
26
26
  url?: string;
27
27
  size?: number;
28
- /** 特殊变体:'report' 渲染为 AI 生成报告的大卡片(含创建时间 + 右侧预览缩略图);未传走普通文件卡片/chip。 */
29
- variant?: 'report';
28
+ /**
29
+ * 特殊变体:
30
+ * - 'report':AI 生成报告的大卡片(含创建时间 + 右侧预览缩略图)。
31
+ * - 'thumb-card':紧凑缩略卡——左侧 32×32 缩略图(image/* + url 时为图本身,
32
+ * 其余类型为分类色 icon)、右侧两行(文件名 + 类型标签)。适合长聊天流回看 /
33
+ * 引用 / 有正文 + 多图列表等"占位省、识别强"的场景。与默认的「图片大预览
34
+ * imageCardInner」并列,由调用方按上下文选择。
35
+ * - 未传:走普通文件卡片(image/* + url 自动展开为内嵌大预览 imageCardInner)。
36
+ */
37
+ variant?: 'report' | 'thumb-card';
30
38
  /** variant='report' 时展示在标题下的创建时间,例如 "03-09 15:46"。 */
31
39
  createdAt?: string;
32
40
  /** variant='report' 时右侧预览缩略图地址。未传时渲染为浅色占位层。 */
@@ -46,6 +54,25 @@ export interface PreviewTab {
46
54
  content: string;
47
55
  type: string;
48
56
  }
57
+ /** Composer 正文内 `/` 触发的内联引用(Invocation 组件数据契约)。 */
58
+ export interface InvocationData {
59
+ id: string;
60
+ /** 业务类型:skill / command / 自定义 */
61
+ kind: string;
62
+ refId: string;
63
+ label: string;
64
+ icon?: string;
65
+ params?: Record<string, unknown>;
66
+ }
67
+ /** Composer 正文内 `@` 触发的内联引用(Mention 组件数据契约)。 */
68
+ export interface MentionData {
69
+ id: string;
70
+ kind: string;
71
+ refId: string;
72
+ label: string;
73
+ icon?: string;
74
+ params?: Record<string, unknown>;
75
+ }
49
76
  export interface SkillItem {
50
77
  id: string;
51
78
  label: string;
@@ -61,14 +88,13 @@ export interface SkillItem {
61
88
  export interface SendMeta {
62
89
  attachments?: Attachment[];
63
90
  webSearch?: boolean;
91
+ /** `/` 内联引用列表(Invocation),按编辑器内出现顺序。 */
92
+ invocations?: InvocationData[];
93
+ /** `@` 内联引用列表(Mention),按编辑器内出现顺序。 */
94
+ mentions?: MentionData[];
64
95
  /**
65
- * 编辑器内插入的 chip 列表(按出现顺序)。
66
- * - 来源包含工具栏按钮、`/` 触发、`@` 触发等
67
- * - 通过 `kind` 字段区分 skill / mention / 自定义类型
68
- * - 业务方按需 filter,例如:`meta.chips?.filter(c => c.kind === 'skill')`
69
- *
70
- * BREAKING(pre-1.0):取代 v0.0.x 的 `skill?: string` 单值字段。
71
- * 老业务迁移:`meta.skill` → `meta.chips?.find(c => c.kind === 'skill')?.label`
96
+ * @deprecated 请改用 `invocations` / `mentions`。本字段仍双写一版,便于旧业务迁移。
97
+ * 编辑器内 chip 列表(按出现顺序);`kind: 'skill'` 对应 invocation。
72
98
  */
73
99
  chips?: ComposerChipMeta[];
74
100
  }
@@ -78,7 +104,7 @@ export interface ComposerChipMeta {
78
104
  skillId: string;
79
105
  label: string;
80
106
  icon: string;
81
- kind?: 'skill' | 'mention' | (string & {});
107
+ kind?: 'skill' | 'invocation' | 'mention' | (string & {});
82
108
  }
83
109
  export type AgentEvent = {
84
110
  kind: 'thinking';
@@ -131,10 +131,68 @@ export function Attachments(_ref) {
131
131
  _ref$compact = _ref.compact,
132
132
  compact = _ref$compact === void 0 ? false : _ref$compact;
133
133
  if (attachments.length === 0) return null;
134
+
135
+ /**
136
+ * thumb-card 渲染:紧凑卡(左缩略图 + 右两行文件名/类型)。
137
+ *
138
+ * 抽成组件内部 helper 而非内联,是为了让 compact 分支与 default 分支共用同一份
139
+ * 渲染逻辑——variant 是「调用方显式 opt-in」的信号,必须优先于 compact 隐式推导:
140
+ * 「有正文 + 附件」场景(compact=true)下,调用方传 thumb-card 就是不想让附件
141
+ * 退化成 chip,而想要"小卡片 + 缩略图"承担引用语义。
142
+ */
143
+ function renderThumbCard(a, i) {
144
+ var meta = getFileMeta(a.type);
145
+ var isImage = a.type.startsWith('image/') && !!a.url;
146
+ var thumbCardInner = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
147
+ "data-odn-attachments-thumb-card-thumb": true,
148
+ style: isImage ? undefined : {
149
+ backgroundColor: meta.bg
150
+ }
151
+ }, isImage ? /*#__PURE__*/React.createElement("img", {
152
+ src: a.url,
153
+ alt: ""
154
+ }) : /*#__PURE__*/React.createElement(Icon, {
155
+ name: meta.icon,
156
+ size: 16,
157
+ style: {
158
+ color: meta.color
159
+ }
160
+ })), /*#__PURE__*/React.createElement("div", {
161
+ "data-odn-attachments-thumb-card-info": true
162
+ }, /*#__PURE__*/React.createElement("div", {
163
+ "data-odn-attachments-thumb-card-name": true
164
+ }, a.name), /*#__PURE__*/React.createElement("div", {
165
+ "data-odn-attachments-thumb-card-type": true
166
+ }, formatType(a.type, a.name))));
167
+ return /*#__PURE__*/React.createElement("div", {
168
+ key: i,
169
+ "data-odn-attachments-item": true,
170
+ "data-odn-attachments-item-thumb-card": ""
171
+ }, onFileClick ? /*#__PURE__*/React.createElement("button", {
172
+ type: "button",
173
+ onClick: function onClick() {
174
+ return onFileClick(a);
175
+ },
176
+ "data-odn-attachments-thumb-card": true,
177
+ "data-odn-attachments-clickable": ""
178
+ }, thumbCardInner) : /*#__PURE__*/React.createElement("div", {
179
+ "data-odn-attachments-thumb-card": true
180
+ }, thumbCardInner), onRemove && /*#__PURE__*/React.createElement("button", {
181
+ onClick: function onClick() {
182
+ return onRemove(i);
183
+ },
184
+ "data-odn-attachments-remove": true
185
+ }, /*#__PURE__*/React.createElement(Icon, {
186
+ name: "x",
187
+ size: 10
188
+ })));
189
+ }
134
190
  if (compact) {
135
191
  return /*#__PURE__*/React.createElement("div", {
136
192
  "data-odn-attachments-compact": true
137
193
  }, attachments.map(function (a, i) {
194
+ // thumb-card 优先于 chip 默认路径——见上方 renderThumbCard 注释。
195
+ if (a.variant === 'thumb-card') return renderThumbCard(a, i);
138
196
  var meta = getFileMeta(a.type);
139
197
  var chip = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Icon, {
140
198
  name: meta.icon,
@@ -252,6 +310,7 @@ export function Attachments(_ref) {
252
310
  size: 10
253
311
  })));
254
312
  }
313
+ if (a.variant === 'thumb-card') return renderThumbCard(a, i);
255
314
  var meta = getFileMeta(a.type);
256
315
  var isImage = a.type.startsWith('image/') && a.url;
257
316
  var fileCardInner = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
@@ -272,30 +331,21 @@ export function Attachments(_ref) {
272
331
  }, a.name), /*#__PURE__*/React.createElement("div", {
273
332
  "data-odn-attachments-meta": true
274
333
  }, formatType(a.type, a.name), a.size ? " \xB7 ".concat(formatSize(a.size)) : '')));
275
- var imageCardInner = /*#__PURE__*/React.createElement("div", {
276
- "data-odn-attachments-img-wrapper": true
277
- }, /*#__PURE__*/React.createElement("img", {
278
- src: a.url,
279
- alt: a.name
280
- }));
281
- return /*#__PURE__*/React.createElement("div", {
334
+ return /*#__PURE__*/React.createElement("div", _extends({
282
335
  key: i,
283
336
  "data-odn-attachments-item": true
284
- }, onFileClick ? /*#__PURE__*/React.createElement("button", _extends({
337
+ }, isImage ? {
338
+ 'data-odn-attachments-item-image-card': ''
339
+ } : {}), onFileClick ? /*#__PURE__*/React.createElement("button", {
285
340
  type: "button",
286
341
  onClick: function onClick() {
287
342
  return onFileClick(a);
288
343
  },
289
- "data-odn-attachments-card": true
290
- }, isImage ? {
291
- 'data-odn-attachments-image-card': ''
292
- } : {}, {
344
+ "data-odn-attachments-card": true,
293
345
  "data-odn-attachments-clickable": ""
294
- }), isImage ? imageCardInner : fileCardInner) : /*#__PURE__*/React.createElement("div", _extends({
346
+ }, fileCardInner) : /*#__PURE__*/React.createElement("div", {
295
347
  "data-odn-attachments-card": true
296
- }, isImage ? {
297
- 'data-odn-attachments-image-card': ''
298
- } : {}), isImage ? imageCardInner : fileCardInner), onRemove && /*#__PURE__*/React.createElement("button", {
348
+ }, fileCardInner), onRemove && /*#__PURE__*/React.createElement("button", {
299
349
  onClick: function onClick() {
300
350
  return onRemove(i);
301
351
  },
@@ -84,43 +84,6 @@
84
84
  border-color: color-mix(in srgb, var(--odn-color-primary) 50%, transparent);
85
85
  }
86
86
 
87
- [data-odn-attachments-img-wrapper] {
88
- height: 0;
89
- min-height: 100%;
90
- aspect-ratio: 1;
91
- border-radius: 3px;
92
- overflow: hidden;
93
- align-self: center;
94
- }
95
- [data-odn-attachments-img-wrapper] img {
96
- width: 100%;
97
- height: 100%;
98
- object-fit: cover;
99
- }
100
-
101
- /** 图片附件卡:仅内嵌预览(非「文件卡」双列),须写在通用 img-wrapper 规则之后以覆盖 grid 方形缩略策略 */
102
- [data-odn-attachments-card][data-odn-attachments-image-card] {
103
- display: block;
104
- padding: 4px;
105
- max-width: min(100%, 280px);
106
- }
107
-
108
- [data-odn-attachments-card][data-odn-attachments-image-card] [data-odn-attachments-img-wrapper] {
109
- height: auto;
110
- min-height: 0;
111
- aspect-ratio: auto;
112
- max-height: 220px;
113
- border-radius: 4px;
114
- align-self: stretch;
115
- }
116
- [data-odn-attachments-card][data-odn-attachments-image-card] [data-odn-attachments-img-wrapper] img {
117
- width: 100%;
118
- height: auto;
119
- max-height: 220px;
120
- object-fit: contain;
121
- vertical-align: middle;
122
- }
123
-
124
87
  [data-odn-attachments-file-icon] {
125
88
  display: flex;
126
89
  align-items: center;
@@ -175,6 +138,92 @@
175
138
  color: var(--odn-color-error);
176
139
  }
177
140
 
141
+ /* ==========================================================================
142
+ * Thumb-card 变体(紧凑缩略卡)
143
+ * 左侧缩略图(image/* + url 时为图本身;其余类型为分类色 icon),
144
+ * 右侧两行(文件名 + 类型标签)。
145
+ *
146
+ * 节奏与默认「文件附件卡」(data-odn-attachments-card) 严格对齐:同样的
147
+ * padding / border-radius / border / 字号,仅 icon 槽内容由「彩色 icon」
148
+ * 替换为「图片缩略 or 彩色 icon」——保证 thumb-card 与 file-card 同行 wrap
149
+ * 时高度 / 圆角 / 边线一致,不会出现错位。
150
+ * ========================================================================== */
151
+ [data-odn-attachments-item-thumb-card] {
152
+ display: inline-flex;
153
+ }
154
+
155
+ [data-odn-attachments-thumb-card] {
156
+ display: grid;
157
+ grid-template-columns: auto 1fr;
158
+ gap: 8px;
159
+ padding: 6px 10px 6px 6px;
160
+ border-radius: 6px;
161
+ border: 1px solid var(--odn-color-black-6);
162
+ background: var(--odn-color-solid-black-1);
163
+ box-sizing: border-box;
164
+ font: inherit;
165
+ color: inherit;
166
+ text-align: left;
167
+ appearance: none;
168
+ transition: border-color 0.15s;
169
+ }
170
+ [data-odn-attachments-thumb-card][data-odn-attachments-clickable] {
171
+ cursor: pointer;
172
+ }
173
+ [data-odn-attachments-thumb-card][data-odn-attachments-clickable]:hover {
174
+ border-color: color-mix(in srgb, var(--odn-color-primary) 50%, transparent);
175
+ }
176
+
177
+ [data-odn-attachments-thumb-card-thumb] {
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ height: 0;
182
+ min-height: 100%;
183
+ aspect-ratio: 1;
184
+ border-radius: 3px;
185
+ overflow: hidden;
186
+ }
187
+ [data-odn-attachments-thumb-card-thumb] img {
188
+ width: 100%;
189
+ height: 100%;
190
+ object-fit: cover;
191
+ display: block;
192
+ }
193
+
194
+ [data-odn-attachments-thumb-card-info] {
195
+ min-width: 0;
196
+ display: flex;
197
+ flex-direction: column;
198
+ justify-content: center;
199
+ gap: 2px;
200
+ }
201
+
202
+ [data-odn-attachments-thumb-card-name] {
203
+ font-size: 12px;
204
+ line-height: normal;
205
+ color: var(--odn-color-black-12);
206
+ overflow: hidden;
207
+ text-overflow: ellipsis;
208
+ white-space: nowrap;
209
+ max-width: 120px;
210
+ }
211
+
212
+ [data-odn-attachments-thumb-card-type] {
213
+ font-size: 10px;
214
+ line-height: 1.25;
215
+ color: var(--odn-color-black-9);
216
+ }
217
+
218
+ [data-odn-attachments-item-image-card] {
219
+ flex-basis: 100%;
220
+ }
221
+
222
+ [data-odn-attachments-card]:not([data-odn-attachments-image-card]),
223
+ [data-odn-attachments-thumb-card] {
224
+ width: 192px;
225
+ }
226
+
178
227
  /* ==========================================================================
179
228
  * Report 变体(AI 生成报告入口卡片)
180
229
  * 设计对齐:豆包报告卡片 —— 左侧图标 + 标题/创建时间,右侧预览缩略图,
@@ -22,11 +22,11 @@ export interface ChipData {
22
22
  /**
23
23
  * chip 语义来源标记,业务方自定义。Composer 内部不消费 kind,
24
24
  * 仅原样透传到 onSend(meta.chips),方便业务方按 kind 分类。
25
- * 约定:'skill' = 工具栏按钮 / `/` 触发选出来的能力;
26
- * 'mention' = `@` 触发的对象引用;
27
- * 其他自定义字符串业务方自行解释。
25
+ * 约定:'invocation' = 工具栏按钮 / `/` 触发(SendMeta.invocations);
26
+ * 'mention' = `@` 触发(SendMeta.mentions);
27
+ * 'skill' 为兼容别名,等同 invocation。
28
28
  */
29
- kind?: 'skill' | 'mention' | (string & {});
29
+ kind?: 'invocation' | 'mention' | 'skill' | (string & {});
30
30
  }
31
31
  export interface ChipProps {
32
32
  data: ChipData;
@@ -0,0 +1,12 @@
1
+ import type { ChipData } from './chip';
2
+ import type { ManagedChip } from './hooks/useChipManager';
3
+ export declare const COMPOSER_CLIPBOARD_MIME = "application/x-odn-composer-chips";
4
+ export interface ComposerClipboardPayload {
5
+ value: string;
6
+ chips: ChipData[];
7
+ }
8
+ export declare function buildClipboardPayload(range: Range, chips: ManagedChip[]): ComposerClipboardPayload | null;
9
+ export declare function parseClipboardPayload(raw: string): ComposerClipboardPayload | null;
10
+ /** 粘贴时重映射 chip id,避免同编辑器内 id 冲突。 */
11
+ export declare function remapClipboardPayload(payload: ComposerClipboardPayload): ComposerClipboardPayload;
12
+ export declare function plainTextFromClipboardPayload(payload: ComposerClipboardPayload): string;
@@ -0,0 +1,84 @@
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 _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
3
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
5
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
6
+ 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; }
7
+ 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; }
8
+ 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; }
9
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
10
+ 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); }
11
+ 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; } } }; }
12
+ 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); }
13
+ 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; }
14
+ import { createMarker, getPlainText, stripMarkers } from "./utils";
15
+ export var COMPOSER_CLIPBOARD_MIME = 'application/x-odn-composer-chips';
16
+ export function buildClipboardPayload(range, chips) {
17
+ var fragment = range.cloneContents();
18
+ var wrapper = document.createElement('div');
19
+ wrapper.appendChild(fragment);
20
+ var value = getPlainText(wrapper);
21
+ var selected = [];
22
+ var _iterator = _createForOfIteratorHelper(chips),
23
+ _step;
24
+ try {
25
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
26
+ var chip = _step.value;
27
+ if (range.intersectsNode(chip.host)) {
28
+ selected.push(chip.data);
29
+ }
30
+ }
31
+ } catch (err) {
32
+ _iterator.e(err);
33
+ } finally {
34
+ _iterator.f();
35
+ }
36
+ if (!value && selected.length === 0) return null;
37
+ return {
38
+ value: value,
39
+ chips: selected
40
+ };
41
+ }
42
+ export function parseClipboardPayload(raw) {
43
+ try {
44
+ var parsed = JSON.parse(raw);
45
+ if (typeof parsed.value !== 'string' || !Array.isArray(parsed.chips)) return null;
46
+ return parsed;
47
+ } catch (_unused) {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /** 粘贴时重映射 chip id,避免同编辑器内 id 冲突。 */
53
+ export function remapClipboardPayload(payload) {
54
+ var idMap = new Map();
55
+ var chips = payload.chips.map(function (chip) {
56
+ var newId = "c_".concat(Date.now(), "_").concat(Math.random().toString(36).slice(2, 8));
57
+ idMap.set(chip.id, newId);
58
+ return _objectSpread(_objectSpread({}, chip), {}, {
59
+ id: newId
60
+ });
61
+ });
62
+ var value = payload.value;
63
+ var _iterator2 = _createForOfIteratorHelper(idMap),
64
+ _step2;
65
+ try {
66
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
67
+ var _step2$value = _slicedToArray(_step2.value, 2),
68
+ oldId = _step2$value[0],
69
+ newId = _step2$value[1];
70
+ value = value.split(createMarker(oldId)).join(createMarker(newId));
71
+ }
72
+ } catch (err) {
73
+ _iterator2.e(err);
74
+ } finally {
75
+ _iterator2.f();
76
+ }
77
+ return {
78
+ value: value,
79
+ chips: chips
80
+ };
81
+ }
82
+ export function plainTextFromClipboardPayload(payload) {
83
+ return stripMarkers(payload.value);
84
+ }
@@ -17,7 +17,7 @@
17
17
  * - chip 状态(id ↔ host)由 useChipManager 维护
18
18
  */
19
19
  /// <reference types="react" />
20
- import { type ChipData } from './chip';
20
+ import type { ChipData } from './chip';
21
21
  /** Composer editor 触发态对外信号——告诉父组件该弹浮层选 skill 了。 */
22
22
  export interface ComposerEditorTriggerInfo {
23
23
  /** 触发字符(@ 或 /) */
@@ -86,6 +86,8 @@ export interface ComposerEditorProps {
86
86
  * 若提供且 `files.length > 0`,编辑器会 `preventDefault` 且不再插入纯文本。
87
87
  */
88
88
  onPasteFiles?: (files: File[]) => void;
89
+ /** 复制 / 剪切时回调(选区含 chip 时触发)。 */
90
+ onCopyChips?: (chips: ChipData[]) => void;
89
91
  /**
90
92
  * 禁用:contenteditable 关闭,editor 不接受输入 / 聚焦 / 粘贴。
91
93
  * 已有的文本与 chip 仍渲染(仅冻结,不清空),便于"等接通后继续编辑"。
@@ -31,9 +31,11 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
31
31
 
32
32
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';
33
33
  import { createPortal } from 'react-dom';
34
- import { findChipHostBeforeCaret, getPlainText, isEditorEmpty, probeTrigger, resolveCaretJumpAroundChip, ZWSP } from "./utils";
34
+ import { buildClipboardPayload, COMPOSER_CLIPBOARD_MIME, parseClipboardPayload, plainTextFromClipboardPayload, remapClipboardPayload } from "./clipboard";
35
35
  import { useChipManager } from "./hooks/useChipManager";
36
- import { Chip } from "./chip";
36
+ import { useChipSelectionMarker } from "./hooks/useChipSelectionMarker";
37
+ import { findChipHostBeforeCaret, getPlainText, isEditorEmpty, probeTrigger, resolveCaretJumpAroundChip, ZWSP } from "./utils";
38
+ import { ComposerInlineRef } from "./inline-ref";
37
39
  import ScrollArea from "../scroll-area";
38
40
 
39
41
  /** Composer editor 触发态对外信号——告诉父组件该弹浮层选 skill 了。 */
@@ -99,6 +101,7 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
99
101
  onTriggerKeyDown = _ref.onTriggerKeyDown,
100
102
  onHeightChange = _ref.onHeightChange,
101
103
  onPasteFiles = _ref.onPasteFiles,
104
+ onCopyChips = _ref.onCopyChips,
102
105
  _ref$disabled = _ref.disabled,
103
106
  disabled = _ref$disabled === void 0 ? false : _ref$disabled,
104
107
  className = _ref.className,
@@ -184,13 +187,15 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
184
187
  * ───────────────────────────────────── */
185
188
  var _useChipManager = useChipManager(function () {
186
189
  fireChange();
187
- // 插入 / 删除 chip 后 caret 位置变了,重算 trigger
188
190
  recomputeTrigger();
189
191
  }),
190
192
  chips = _useChipManager.chips,
191
193
  _insertChip = _useChipManager.insertChip,
194
+ insertSerializedAtRange = _useChipManager.insertSerializedAtRange,
195
+ syncChipsAfterDomMutation = _useChipManager.syncChipsAfterDomMutation,
192
196
  removeChip = _useChipManager.removeChip,
193
197
  resetChips = _useChipManager.resetChips;
198
+ useChipSelectionMarker(editorRef, chips);
194
199
 
195
200
  /* ─────────────────────────────────────
196
201
  * Trigger 探测
@@ -485,10 +490,45 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
485
490
  insertLineBreak();
486
491
  }
487
492
  }, [onPressEnter, insertLineBreak, removeChip, onTriggerKeyDown]);
493
+ var writeClipboardPayload = useCallback(function (e, payload) {
494
+ if (!payload) return false;
495
+ e.clipboardData.setData(COMPOSER_CLIPBOARD_MIME, JSON.stringify(payload));
496
+ e.clipboardData.setData('text/plain', plainTextFromClipboardPayload(payload));
497
+ onCopyChips === null || onCopyChips === void 0 || onCopyChips(payload.chips);
498
+ return true;
499
+ }, [onCopyChips]);
500
+ var handleCopy = useCallback(function (e) {
501
+ var el = editorRef.current;
502
+ if (!el || disabled) return;
503
+ var sel = window.getSelection();
504
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
505
+ var range = sel.getRangeAt(0);
506
+ if (!el.contains(range.commonAncestorContainer)) return;
507
+ var payload = buildClipboardPayload(range, chips);
508
+ if (!payload) return;
509
+ e.preventDefault();
510
+ writeClipboardPayload(e, payload);
511
+ }, [chips, disabled, writeClipboardPayload]);
512
+ var handleCut = useCallback(function (e) {
513
+ var el = editorRef.current;
514
+ if (!el || disabled) return;
515
+ var sel = window.getSelection();
516
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
517
+ var range = sel.getRangeAt(0);
518
+ if (!el.contains(range.commonAncestorContainer)) return;
519
+ var payload = buildClipboardPayload(range, chips);
520
+ if (!payload) return;
521
+ e.preventDefault();
522
+ if (!writeClipboardPayload(e, payload)) return;
523
+ range.deleteContents();
524
+ syncChipsAfterDomMutation(el);
525
+ fireChange();
526
+ recomputeTrigger();
527
+ requestScrollCaretIntoView();
528
+ }, [chips, disabled, writeClipboardPayload, syncChipsAfterDomMutation, fireChange, recomputeTrigger, requestScrollCaretIntoView]);
488
529
 
489
530
  /* ─────────────────────────────────────
490
- * Paste:纯文本走受控插入;文件交给 onPasteFiles(Composer 转附件)。
491
- * 避免外部富文本污染 DOM;无 handler 时文件粘贴仍 preventDefault 以免整页乱入。
531
+ * Paste:优先私有 mime 还原 chip;纯文本走受控插入;文件交给 onPasteFiles
492
532
  * ───────────────────────────────────── */
493
533
  var handlePaste = useCallback(function (e) {
494
534
  var fileList = e.clipboardData.files;
@@ -514,13 +554,26 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
514
554
  onPasteFiles === null || onPasteFiles === void 0 || onPasteFiles(fromItems);
515
555
  return;
516
556
  }
557
+ var el = editorRef.current;
558
+ if (!el) return;
559
+ var rawPayload = e.clipboardData.getData(COMPOSER_CLIPBOARD_MIME);
560
+ var parsed = rawPayload ? parseClipboardPayload(rawPayload) : null;
517
561
  e.preventDefault();
518
- var text = e.clipboardData.getData('text/plain');
519
- if (!text) return;
520
562
  var sel = window.getSelection();
521
563
  if (!sel || sel.rangeCount === 0) return;
522
564
  var range = sel.getRangeAt(0);
565
+ if (!el.contains(range.commonAncestorContainer)) return;
523
566
  range.deleteContents();
567
+ if (parsed) {
568
+ var remapped = remapClipboardPayload(parsed);
569
+ insertSerializedAtRange(remapped.value, remapped.chips, range, el);
570
+ fireChange();
571
+ recomputeTrigger();
572
+ requestScrollCaretIntoView();
573
+ return;
574
+ }
575
+ var text = e.clipboardData.getData('text/plain');
576
+ if (!text) return;
524
577
  var node = document.createTextNode(text);
525
578
  range.insertNode(node);
526
579
  var newRange = document.createRange();
@@ -530,7 +583,7 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
530
583
  sel.addRange(newRange);
531
584
  fireChange();
532
585
  requestScrollCaretIntoView();
533
- }, [fireChange, requestScrollCaretIntoView, onPasteFiles]);
586
+ }, [fireChange, requestScrollCaretIntoView, onPasteFiles, insertSerializedAtRange, recomputeTrigger]);
534
587
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ScrollArea, {
535
588
  controlRef: scrollRef,
536
589
  autoHide: true,
@@ -554,13 +607,12 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
554
607
  onCompositionStart: handleCompositionStart,
555
608
  onCompositionEnd: handleCompositionEnd,
556
609
  onKeyDown: handleKeyDown,
610
+ onCopy: handleCopy,
611
+ onCut: handleCut,
557
612
  onPaste: handlePaste
558
613
  })), chips.map(function (chip) {
559
- return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement(Chip, {
560
- data: chip.data,
561
- onRemove: function onRemove() {
562
- return removeChip(chip.data.id);
563
- }
614
+ return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement(ComposerInlineRef, {
615
+ data: chip.data
564
616
  }), chip.host, chip.data.id);
565
617
  }));
566
618
  });
@@ -24,9 +24,14 @@ export interface UseChipManagerReturn {
24
24
  * 在编辑器当前光标位置插入 chip:
25
25
  * - selection 在编辑器内 → 插入光标处
26
26
  * - selection 在编辑器外 / 无 selection → fallback 到末尾
27
- * 插入后光标自动移到 chip 之后的 ZWSP 锚点之后,方便用户继续输入。
28
27
  */
29
28
  insertChip: (data: ChipData, editor: HTMLElement) => void;
29
+ /** 在指定 range 处插入 chip(调用方负责 deleteContents)。 */
30
+ insertChipAtRange: (data: ChipData, range: Range, editor: HTMLElement) => HTMLSpanElement;
31
+ /** 将含 marker 的 raw value + chips 插入 range(粘贴用)。 */
32
+ insertSerializedAtRange: (value: string, chips: ChipData[], range: Range, editor: HTMLElement) => void;
33
+ /** 移除已从 DOM 摘下的 chip 状态(cut / 选区删除后)。 */
34
+ syncChipsAfterDomMutation: (editor: HTMLElement) => void;
30
35
  /** 按 chip id 移除(同时清理伴随的 ZWSP 锚点)。 */
31
36
  removeChip: (id: string) => void;
32
37
  /** 清空所有 chip 的 React 状态(DOM 由调用方 editor.innerHTML='' 一并处理)。 */