one-design-next 0.0.12 → 0.0.14

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 +95 -21
  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 +22 -0
  20. package/dist/composer/utils.js +121 -3
  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 { findChipHostAfterCaret, findChipHostBeforeCaret, getPlainText, getVisibleTextBeforeHost, isEditorEmpty, moveCaretBeforeChip, normalizeEditorDom, probeTrigger, resolveCaretJumpAroundChip, stripInvisibleChars, 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,
@@ -173,6 +176,7 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
173
176
  var fireChange = useCallback(function () {
174
177
  var el = editorRef.current;
175
178
  if (!el) return;
179
+ normalizeEditorDom(el);
176
180
  setEmpty(isEditorEmpty(el));
177
181
  onChange === null || onChange === void 0 || onChange(getPlainText(el));
178
182
  // editor.height 锁定时 RO 不触发,主动同步一次,确保升格判定能拿到 scrollHeight
@@ -184,13 +188,15 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
184
188
  * ───────────────────────────────────── */
185
189
  var _useChipManager = useChipManager(function () {
186
190
  fireChange();
187
- // 插入 / 删除 chip 后 caret 位置变了,重算 trigger
188
191
  recomputeTrigger();
189
192
  }),
190
193
  chips = _useChipManager.chips,
191
194
  _insertChip = _useChipManager.insertChip,
195
+ insertSerializedAtRange = _useChipManager.insertSerializedAtRange,
196
+ syncChipsAfterDomMutation = _useChipManager.syncChipsAfterDomMutation,
192
197
  removeChip = _useChipManager.removeChip,
193
198
  resetChips = _useChipManager.resetChips;
199
+ useChipSelectionMarker(editorRef, chips);
194
200
 
195
201
  /* ─────────────────────────────────────
196
202
  * Trigger 探测
@@ -385,7 +391,10 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
385
391
  * ───────────────────────────────────── */
386
392
  var handleInput = useCallback(function () {
387
393
  var el = editorRef.current;
388
- if (el) setEmpty(isEditorEmpty(el));
394
+ if (el) {
395
+ normalizeEditorDom(el);
396
+ setEmpty(isEditorEmpty(el));
397
+ }
389
398
  requestScrollCaretIntoView();
390
399
  if (isComposingRef.current) return;
391
400
  fireChange();
@@ -437,17 +446,35 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
437
446
  if (onTriggerKeyDown(e) === true) return;
438
447
  }
439
448
 
440
- // Backspace:紧邻 chip 后方时整块删
449
+ // Backspace:chip 边界——空 text 在 chip 后先退回 chip 前;chip 前无正文再整块删
441
450
  if (e.key === 'Backspace') {
451
+ var el = editorRef.current;
442
452
  var sel = window.getSelection();
443
- if (sel && sel.rangeCount > 0) {
453
+ if (el && sel && sel.rangeCount > 0) {
444
454
  var range = sel.getRangeAt(0);
445
- if (range.collapsed) {
446
- var host = findChipHostBeforeCaret(range);
447
- var _id = host === null || host === void 0 ? void 0 : host.dataset.mentionId;
448
- if (_id) {
455
+ if (range.collapsed && el.contains(range.startContainer)) {
456
+ var hostBefore = findChipHostBeforeCaret(range);
457
+ if (hostBefore !== null && hostBefore !== void 0 && hostBefore.dataset.mentionId) {
458
+ var _node$textContent;
459
+ var node = range.startContainer,
460
+ offset = range.startOffset;
461
+ if (node.nodeType === Node.TEXT_NODE && offset === 0 && node.previousSibling === hostBefore && stripInvisibleChars((_node$textContent = node.textContent) !== null && _node$textContent !== void 0 ? _node$textContent : '') === '') {
462
+ e.preventDefault();
463
+ moveCaretBeforeChip(hostBefore, {
464
+ removeTrailingEmptyText: true,
465
+ trailingText: node
466
+ });
467
+ return;
468
+ }
449
469
  e.preventDefault();
450
- removeChip(_id);
470
+ removeChip(hostBefore.dataset.mentionId);
471
+ return;
472
+ }
473
+ var hostAfter = findChipHostAfterCaret(range);
474
+ var idAfter = hostAfter === null || hostAfter === void 0 ? void 0 : hostAfter.dataset.mentionId;
475
+ if (idAfter && getVisibleTextBeforeHost(el, hostAfter).trim() === '') {
476
+ e.preventDefault();
477
+ removeChip(idAfter);
451
478
  return;
452
479
  }
453
480
  }
@@ -485,10 +512,45 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
485
512
  insertLineBreak();
486
513
  }
487
514
  }, [onPressEnter, insertLineBreak, removeChip, onTriggerKeyDown]);
515
+ var writeClipboardPayload = useCallback(function (e, payload) {
516
+ if (!payload) return false;
517
+ e.clipboardData.setData(COMPOSER_CLIPBOARD_MIME, JSON.stringify(payload));
518
+ e.clipboardData.setData('text/plain', plainTextFromClipboardPayload(payload));
519
+ onCopyChips === null || onCopyChips === void 0 || onCopyChips(payload.chips);
520
+ return true;
521
+ }, [onCopyChips]);
522
+ var handleCopy = useCallback(function (e) {
523
+ var el = editorRef.current;
524
+ if (!el || disabled) return;
525
+ var sel = window.getSelection();
526
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
527
+ var range = sel.getRangeAt(0);
528
+ if (!el.contains(range.commonAncestorContainer)) return;
529
+ var payload = buildClipboardPayload(range, chips);
530
+ if (!payload) return;
531
+ e.preventDefault();
532
+ writeClipboardPayload(e, payload);
533
+ }, [chips, disabled, writeClipboardPayload]);
534
+ var handleCut = useCallback(function (e) {
535
+ var el = editorRef.current;
536
+ if (!el || disabled) return;
537
+ var sel = window.getSelection();
538
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
539
+ var range = sel.getRangeAt(0);
540
+ if (!el.contains(range.commonAncestorContainer)) return;
541
+ var payload = buildClipboardPayload(range, chips);
542
+ if (!payload) return;
543
+ e.preventDefault();
544
+ if (!writeClipboardPayload(e, payload)) return;
545
+ range.deleteContents();
546
+ syncChipsAfterDomMutation(el);
547
+ fireChange();
548
+ recomputeTrigger();
549
+ requestScrollCaretIntoView();
550
+ }, [chips, disabled, writeClipboardPayload, syncChipsAfterDomMutation, fireChange, recomputeTrigger, requestScrollCaretIntoView]);
488
551
 
489
552
  /* ─────────────────────────────────────
490
- * Paste:纯文本走受控插入;文件交给 onPasteFiles(Composer 转附件)。
491
- * 避免外部富文本污染 DOM;无 handler 时文件粘贴仍 preventDefault 以免整页乱入。
553
+ * Paste:优先私有 mime 还原 chip;纯文本走受控插入;文件交给 onPasteFiles
492
554
  * ───────────────────────────────────── */
493
555
  var handlePaste = useCallback(function (e) {
494
556
  var fileList = e.clipboardData.files;
@@ -514,13 +576,26 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
514
576
  onPasteFiles === null || onPasteFiles === void 0 || onPasteFiles(fromItems);
515
577
  return;
516
578
  }
579
+ var el = editorRef.current;
580
+ if (!el) return;
581
+ var rawPayload = e.clipboardData.getData(COMPOSER_CLIPBOARD_MIME);
582
+ var parsed = rawPayload ? parseClipboardPayload(rawPayload) : null;
517
583
  e.preventDefault();
518
- var text = e.clipboardData.getData('text/plain');
519
- if (!text) return;
520
584
  var sel = window.getSelection();
521
585
  if (!sel || sel.rangeCount === 0) return;
522
586
  var range = sel.getRangeAt(0);
587
+ if (!el.contains(range.commonAncestorContainer)) return;
523
588
  range.deleteContents();
589
+ if (parsed) {
590
+ var remapped = remapClipboardPayload(parsed);
591
+ insertSerializedAtRange(remapped.value, remapped.chips, range, el);
592
+ fireChange();
593
+ recomputeTrigger();
594
+ requestScrollCaretIntoView();
595
+ return;
596
+ }
597
+ var text = e.clipboardData.getData('text/plain');
598
+ if (!text) return;
524
599
  var node = document.createTextNode(text);
525
600
  range.insertNode(node);
526
601
  var newRange = document.createRange();
@@ -530,7 +605,7 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
530
605
  sel.addRange(newRange);
531
606
  fireChange();
532
607
  requestScrollCaretIntoView();
533
- }, [fireChange, requestScrollCaretIntoView, onPasteFiles]);
608
+ }, [fireChange, requestScrollCaretIntoView, onPasteFiles, insertSerializedAtRange, recomputeTrigger]);
534
609
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ScrollArea, {
535
610
  controlRef: scrollRef,
536
611
  autoHide: true,
@@ -554,13 +629,12 @@ export var ComposerEditor = /*#__PURE__*/forwardRef(function ComposerEditor(_ref
554
629
  onCompositionStart: handleCompositionStart,
555
630
  onCompositionEnd: handleCompositionEnd,
556
631
  onKeyDown: handleKeyDown,
632
+ onCopy: handleCopy,
633
+ onCut: handleCut,
557
634
  onPaste: handlePaste
558
635
  })), 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
- }
636
+ return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement(ComposerInlineRef, {
637
+ data: chip.data
564
638
  }), chip.host, chip.data.id);
565
639
  }));
566
640
  });