one-design-next 0.0.90 → 0.0.92
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.
- package/dist/_util/layoutBatch.d.ts +14 -0
- package/dist/_util/layoutBatch.js +47 -0
- package/dist/_util/sharedResizeObserver.d.ts +11 -0
- package/dist/_util/sharedResizeObserver.js +65 -0
- package/dist/_util/textFit.d.ts +48 -0
- package/dist/_util/textFit.js +123 -0
- package/dist/chat-item/index.d.ts +8 -4
- package/dist/chat-item/index.js +156 -120
- package/dist/composer/index.d.ts +4 -3
- package/dist/composer/index.js +55 -56
- package/dist/composer/utils.d.ts +20 -2
- package/dist/composer/utils.js +24 -4
- package/dist/hover-fill/index.js +9 -16
- package/dist/index.d.ts +1 -1
- package/dist/skill-slot/index.d.ts +2 -17
- package/dist/skill-slot/index.js +8 -78
- package/dist/skill-slot/style/index.css +2 -65
- package/package.json +1 -1
package/dist/chat-item/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
|
|
2
2
|
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
|
|
3
|
-
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { scheduleLayoutBatch } from "../_util/layoutBatch";
|
|
5
|
+
import { observeResize } from "../_util/sharedResizeObserver";
|
|
6
|
+
import { fitToWidth, measureFit, resolveFont } from "../_util/textFit";
|
|
4
7
|
import Dialog from "../dialog";
|
|
5
8
|
import Dropdown from "../dropdown";
|
|
6
9
|
import HoverFill from "../hover-fill";
|
|
@@ -15,6 +18,12 @@ var STATUS_ARIA_LABEL = {
|
|
|
15
18
|
};
|
|
16
19
|
var ELLIPSIS = '…';
|
|
17
20
|
|
|
21
|
+
/**
|
|
22
|
+
* 标记「JS 已把标题裁到可用宽度内」,由 useFittedTitle 在写入时同步,
|
|
23
|
+
* 不由 React 渲染:它表达的是某一帧的测量结果,而非「是否启用拟合」。
|
|
24
|
+
*/
|
|
25
|
+
var TITLE_FITTED_ATTR = 'data-odn-chat-item-title-fitted';
|
|
26
|
+
|
|
18
27
|
/** 可用宽度变化小于该值时跳过重算(与二分容差同量级) */
|
|
19
28
|
var FIT_WIDTH_EPS = 0.5;
|
|
20
29
|
/**
|
|
@@ -24,65 +33,85 @@ var FIT_WIDTH_EPS = 0.5;
|
|
|
24
33
|
var FIT_MIN_RELIABLE_WIDTH = 24;
|
|
25
34
|
|
|
26
35
|
/**
|
|
27
|
-
* 有 trailing(客服 icon / status /
|
|
36
|
+
* 有 trailing(客服 icon / status / 时间)时,把标题裁到可用宽度。
|
|
28
37
|
* 解决 CSS text-overflow 按字符边界截断后,省略号右侧留白把 trailing 顶开的问题。
|
|
29
38
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
39
|
+
* 测量走 textFit 的 Canvas 预算:挂载时一次算完所有截断位置的宽度,
|
|
40
|
+
* 之后每次宽度变化只做二分,不再「填字 - 排版 - 量宽」地反复试探。
|
|
41
|
+
*
|
|
42
|
+
* 裁切结果直接写进 titleEl,不走 state:长列表拖宽时零 re-render。
|
|
43
|
+
* 标题本身由 React 渲染,title 变化时 React 会把完整标题写回 DOM,
|
|
44
|
+
* 随后本 effect 重跑重新裁切,顺序天然正确。
|
|
45
|
+
*
|
|
46
|
+
* 性能:RO 只监听整行;回调经 rAF 合并;缓存 maxWidth / trailingWidth / 裁切结果。
|
|
47
|
+
* trailing 宽度变化(时间文案 / 状态 / 让位)由调用方经 layoutKey 触发重测。
|
|
32
48
|
*/
|
|
33
49
|
function useFittedTitle(title, enabled, labelRef, titleRef, /** 右对齐区(状态 / 时间),量宽预留 */
|
|
34
50
|
trailingRef, /** 紧跟标题的类型 icon,量宽预留 */
|
|
35
|
-
typeIconRef, /**
|
|
51
|
+
typeIconRef, /** trailing 宽度 / 字重变化,需强制重测 */
|
|
36
52
|
layoutKey) {
|
|
37
|
-
var _useState = useState(title),
|
|
38
|
-
_useState2 = _slicedToArray(_useState, 2),
|
|
39
|
-
fitted = _useState2[0],
|
|
40
|
-
setFitted = _useState2[1];
|
|
41
53
|
useLayoutEffect(function () {
|
|
42
|
-
var _document$fonts, _label$closest;
|
|
54
|
+
var _measure, _document$fonts, _label$closest;
|
|
55
|
+
var titleEl = titleRef.current;
|
|
56
|
+
if (!titleEl) return;
|
|
57
|
+
|
|
58
|
+
// React 渲染的是完整标题;未启用拟合时保持原样,交给 CSS text-overflow
|
|
43
59
|
if (!enabled) {
|
|
44
|
-
|
|
60
|
+
titleEl.textContent = title;
|
|
61
|
+
titleEl.removeAttribute(TITLE_FITTED_ATTR);
|
|
45
62
|
return;
|
|
46
63
|
}
|
|
47
64
|
var label = labelRef.current;
|
|
48
|
-
|
|
49
|
-
if (!label || !titleEl) return;
|
|
65
|
+
if (!label) return;
|
|
50
66
|
|
|
51
|
-
//
|
|
52
|
-
|
|
67
|
+
// 先回到原文,避免沿用上一会话裁成的「…」,也保证按完整标题取字体
|
|
68
|
+
titleEl.textContent = title;
|
|
69
|
+
var titleCs = getComputedStyle(titleEl);
|
|
70
|
+
var metrics = measureFit(title, ELLIPSIS, {
|
|
71
|
+
font: resolveFont(titleCs),
|
|
72
|
+
letterSpacing: Number.parseFloat(titleCs.letterSpacing) || 0
|
|
73
|
+
});
|
|
53
74
|
var cancelled = false;
|
|
54
|
-
var
|
|
75
|
+
var scheduled = false;
|
|
55
76
|
var lastMaxWidth = Number.NaN;
|
|
56
77
|
var lastTrailingWidth = Number.NaN;
|
|
57
78
|
var lastFitted = title;
|
|
58
|
-
var
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
probe.style.fontFeatureSettings = cs.fontFeatureSettings;
|
|
69
|
-
probe.style.fontVariationSettings = cs.fontVariationSettings;
|
|
79
|
+
var lastFitAccurate = false;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* accurate 表示「当前文本确实在可用宽度内」,此时才挂 fitted 标记让
|
|
83
|
+
* CSS 停止截断(否则省略号右侧会留出字符边界的空白,把 trailing 顶开)。
|
|
84
|
+
* 放弃裁切时必须摘掉标记,把截断交回 CSS,否则标题直接溢出行外。
|
|
85
|
+
*/
|
|
86
|
+
var applyFit = function applyFit(text, accurate) {
|
|
87
|
+
titleEl.textContent = text;
|
|
88
|
+
if (accurate) titleEl.setAttribute(TITLE_FITTED_ATTR, '');else titleEl.removeAttribute(TITLE_FITTED_ATTR);
|
|
70
89
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* gap 与 trailing / typeIcon 的宽度在同一个 effect 周期内是常量:
|
|
93
|
+
* 所有会改变它们的条件(选中、菜单开合、时间文案、状态、类型 icon)
|
|
94
|
+
* 都在 layoutKey 里,变化时 effect 会整体重跑。
|
|
95
|
+
* 拖拽时因此只需读行内标题区的宽度,省掉每帧每项的 getComputedStyle。
|
|
96
|
+
*/
|
|
97
|
+
var gap = Number.NaN;
|
|
98
|
+
var trailingWidth = 0;
|
|
99
|
+
var typeWidth = 0;
|
|
100
|
+
var readStatics = function readStatics() {
|
|
101
|
+
var _trailingRef$current$, _trailingRef$current, _typeIconRef$current$, _typeIconRef$current;
|
|
102
|
+
var cs = getComputedStyle(label);
|
|
103
|
+
gap = Number.parseFloat(cs.columnGap || cs.gap) || 0;
|
|
104
|
+
trailingWidth = (_trailingRef$current$ = (_trailingRef$current = trailingRef.current) === null || _trailingRef$current === void 0 ? void 0 : _trailingRef$current.getBoundingClientRect().width) !== null && _trailingRef$current$ !== void 0 ? _trailingRef$current$ : 0;
|
|
105
|
+
typeWidth = (_typeIconRef$current$ = (_typeIconRef$current = typeIconRef.current) === null || _typeIconRef$current === void 0 ? void 0 : _typeIconRef$current.getBoundingClientRect().width) !== null && _typeIconRef$current$ !== void 0 ? _typeIconRef$current$ : 0;
|
|
74
106
|
};
|
|
75
|
-
|
|
76
|
-
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 只读布局,返回写入回调交由批处理统一执行。
|
|
110
|
+
* 这里不能直接改 DOM:同帧内还有其他实例要读,写入会让它们的读重新排版。
|
|
111
|
+
*/
|
|
112
|
+
var measure = function measure() {
|
|
77
113
|
if (cancelled) return;
|
|
78
|
-
|
|
79
|
-
if (!probe.isConnected) document.body.appendChild(probe);
|
|
80
|
-
var cs = getComputedStyle(label);
|
|
81
|
-
var gap = Number.parseFloat(cs.columnGap || cs.gap) || 0;
|
|
82
|
-
var trailing = trailingRef.current;
|
|
83
|
-
var typeIcon = typeIconRef.current;
|
|
84
|
-
var trailingWidth = (_trailing$getBounding = trailing === null || trailing === void 0 ? void 0 : trailing.getBoundingClientRect().width) !== null && _trailing$getBounding !== void 0 ? _trailing$getBounding : 0;
|
|
85
|
-
var typeWidth = (_typeIcon$getBounding = typeIcon === null || typeIcon === void 0 ? void 0 : typeIcon.getBoundingClientRect().width) !== null && _typeIcon$getBounding !== void 0 ? _typeIcon$getBounding : 0;
|
|
114
|
+
if (!Number.isFinite(gap)) readStatics();
|
|
86
115
|
// 标题两侧可能各有一段 gap:title↔type、type/title↔trailing
|
|
87
116
|
var gapCount = (typeWidth > 0 ? 1 : 0) + (trailingWidth > 0 ? 1 : 0);
|
|
88
117
|
var maxWidth = Math.max(0, label.getBoundingClientRect().width - trailingWidth - typeWidth - gap * gapCount);
|
|
@@ -94,71 +123,58 @@ layoutKey) {
|
|
|
94
123
|
if (maxWidth < FIT_MIN_RELIABLE_WIDTH) {
|
|
95
124
|
lastMaxWidth = Number.NaN;
|
|
96
125
|
lastTrailingWidth = Number.NaN;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
126
|
+
// 此刻 trailing / typeIcon 多半也没量准,下次重读
|
|
127
|
+
gap = Number.NaN;
|
|
128
|
+
if (lastFitted === title && !lastFitAccurate) return;
|
|
129
|
+
lastFitted = title;
|
|
130
|
+
lastFitAccurate = false;
|
|
131
|
+
return function () {
|
|
132
|
+
return applyFit(title, false);
|
|
133
|
+
};
|
|
102
134
|
}
|
|
103
135
|
lastMaxWidth = maxWidth;
|
|
104
136
|
lastTrailingWidth = trailingWidth;
|
|
105
|
-
var
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
next = title;
|
|
110
|
-
while (lo <= hi) {
|
|
111
|
-
var mid = lo + hi >> 1;
|
|
112
|
-
var candidate = "".concat(title.slice(0, mid)).concat(ELLIPSIS);
|
|
113
|
-
if (measure(candidate) <= maxWidth + FIT_WIDTH_EPS) {
|
|
114
|
-
next = candidate;
|
|
115
|
-
lo = mid + 1;
|
|
116
|
-
} else {
|
|
117
|
-
hi = mid - 1;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// 极端窄宽仍放不下「字+…」时交给 CSS ellipsis,保留原文
|
|
121
|
-
if (next === ELLIPSIS || next === title && measure(title) > maxWidth + FIT_WIDTH_EPS) {
|
|
122
|
-
var withEllipsis = "".concat(title.slice(0, 1)).concat(ELLIPSIS);
|
|
123
|
-
next = measure(withEllipsis) <= maxWidth + FIT_WIDTH_EPS ? withEllipsis : title;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (next === lastFitted) return;
|
|
137
|
+
var _fitToWidth = fitToWidth(metrics, maxWidth + FIT_WIDTH_EPS, title, ELLIPSIS),
|
|
138
|
+
next = _fitToWidth.text,
|
|
139
|
+
accurate = _fitToWidth.accurate;
|
|
140
|
+
if (next === lastFitted && accurate === lastFitAccurate) return;
|
|
127
141
|
lastFitted = next;
|
|
128
|
-
|
|
142
|
+
lastFitAccurate = accurate;
|
|
143
|
+
return function () {
|
|
144
|
+
return applyFit(next, accurate);
|
|
145
|
+
};
|
|
129
146
|
};
|
|
130
147
|
var scheduleFit = function scheduleFit() {
|
|
131
|
-
if (cancelled ||
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
if (cancelled || scheduled) return;
|
|
149
|
+
scheduled = true;
|
|
150
|
+
scheduleLayoutBatch(function () {
|
|
151
|
+
scheduled = false;
|
|
152
|
+
return measure();
|
|
135
153
|
});
|
|
136
154
|
};
|
|
137
155
|
|
|
138
|
-
//
|
|
139
|
-
|
|
156
|
+
// 首屏同步裁切,避免首帧闪全标题;后续宽度变化走批处理合并
|
|
157
|
+
(_measure = measure()) === null || _measure === void 0 || _measure();
|
|
140
158
|
void ((_document$fonts = document.fonts) === null || _document$fonts === void 0 ? void 0 : _document$fonts.ready.then(function () {
|
|
141
159
|
if (cancelled) return;
|
|
142
|
-
//
|
|
160
|
+
// 字体就绪后字形宽度可能变,需重新预算并绕过 maxWidth 缓存
|
|
161
|
+
var readyCs = getComputedStyle(titleEl);
|
|
162
|
+
metrics = measureFit(title, ELLIPSIS, {
|
|
163
|
+
font: resolveFont(readyCs),
|
|
164
|
+
letterSpacing: Number.parseFloat(readyCs.letterSpacing) || 0
|
|
165
|
+
});
|
|
143
166
|
lastMaxWidth = Number.NaN;
|
|
167
|
+
gap = Number.NaN;
|
|
144
168
|
scheduleFit();
|
|
145
169
|
}));
|
|
146
170
|
var row = (_label$closest = label.closest('[data-odn-chat-item-btn]')) !== null && _label$closest !== void 0 ? _label$closest : label;
|
|
147
|
-
var
|
|
148
|
-
ro.observe(row);
|
|
149
|
-
ro.observe(label);
|
|
150
|
-
var trailing = trailingRef.current;
|
|
151
|
-
if (trailing) ro.observe(trailing);
|
|
152
|
-
var typeIcon = typeIconRef.current;
|
|
153
|
-
if (typeIcon) ro.observe(typeIcon);
|
|
171
|
+
var stopObserving = observeResize(row, scheduleFit);
|
|
154
172
|
return function () {
|
|
173
|
+
// 批处理队列里可能仍排着本实例的读回调,靠 cancelled 短路即可
|
|
155
174
|
cancelled = true;
|
|
156
|
-
|
|
157
|
-
ro.disconnect();
|
|
158
|
-
probe.remove();
|
|
175
|
+
stopObserving();
|
|
159
176
|
};
|
|
160
177
|
}, [title, enabled, layoutKey, labelRef, titleRef, trailingRef, typeIconRef]);
|
|
161
|
-
return enabled ? fitted : title;
|
|
162
178
|
}
|
|
163
179
|
function ChatItemStatus(_ref) {
|
|
164
180
|
var status = _ref.status;
|
|
@@ -204,18 +220,28 @@ function ChatItemStatus(_ref) {
|
|
|
204
220
|
}));
|
|
205
221
|
}
|
|
206
222
|
function useControllableState(controlled, defaultValue, onChange) {
|
|
207
|
-
var
|
|
208
|
-
|
|
209
|
-
internal =
|
|
210
|
-
setInternal =
|
|
223
|
+
var _useState = useState(defaultValue),
|
|
224
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
225
|
+
internal = _useState2[0],
|
|
226
|
+
setInternal = _useState2[1];
|
|
211
227
|
var value = controlled !== null && controlled !== void 0 ? controlled : internal;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
228
|
+
// 每次渲染新建 setValue 会让下游 memo 组件(Dropdown / 菜单项)全部失效
|
|
229
|
+
var latest = useRef({
|
|
230
|
+
controlled: controlled,
|
|
231
|
+
onChange: onChange
|
|
232
|
+
});
|
|
233
|
+
latest.current = {
|
|
234
|
+
controlled: controlled,
|
|
235
|
+
onChange: onChange
|
|
215
236
|
};
|
|
237
|
+
var setValue = useCallback(function (next) {
|
|
238
|
+
var _latest$current$onCha, _latest$current;
|
|
239
|
+
(_latest$current$onCha = (_latest$current = latest.current).onChange) === null || _latest$current$onCha === void 0 || _latest$current$onCha.call(_latest$current, next);
|
|
240
|
+
if (latest.current.controlled === undefined) setInternal(next);
|
|
241
|
+
}, []);
|
|
216
242
|
return [value, setValue];
|
|
217
243
|
}
|
|
218
|
-
|
|
244
|
+
function ChatItemInner(_ref2) {
|
|
219
245
|
var id = _ref2.id,
|
|
220
246
|
title = _ref2.title,
|
|
221
247
|
active = _ref2.active,
|
|
@@ -246,10 +272,10 @@ export function ChatItem(_ref2) {
|
|
|
246
272
|
_useControllableState2 = _slicedToArray(_useControllableState, 2),
|
|
247
273
|
renaming = _useControllableState2[0],
|
|
248
274
|
setRenaming = _useControllableState2[1];
|
|
249
|
-
var
|
|
250
|
-
|
|
251
|
-
draft =
|
|
252
|
-
setDraft =
|
|
275
|
+
var _useState3 = useState(title),
|
|
276
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
277
|
+
draft = _useState4[0],
|
|
278
|
+
setDraft = _useState4[1];
|
|
253
279
|
var _useControllableState3 = useControllableState(deleteOpenProp, defaultDeleteOpen, onDeleteOpenChange),
|
|
254
280
|
_useControllableState4 = _slicedToArray(_useControllableState3, 2),
|
|
255
281
|
deleteOpen = _useControllableState4[0],
|
|
@@ -258,12 +284,12 @@ export function ChatItem(_ref2) {
|
|
|
258
284
|
_useControllableState6 = _slicedToArray(_useControllableState5, 2),
|
|
259
285
|
menuOpen = _useControllableState6[0],
|
|
260
286
|
setMenuOpen = _useControllableState6[1];
|
|
261
|
-
var
|
|
287
|
+
var _useState5 = useState(function () {
|
|
262
288
|
return Date.now();
|
|
263
289
|
}),
|
|
264
|
-
|
|
265
|
-
now =
|
|
266
|
-
setNow =
|
|
290
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
|
291
|
+
now = _useState6[0],
|
|
292
|
+
setNow = _useState6[1];
|
|
267
293
|
var inputRef = useRef(null);
|
|
268
294
|
var labelRef = useRef(null);
|
|
269
295
|
var titleRef = useRef(null);
|
|
@@ -278,8 +304,9 @@ export function ChatItem(_ref2) {
|
|
|
278
304
|
var showTime = Boolean(timeLabel);
|
|
279
305
|
var showTrailing = showStatus || showTime;
|
|
280
306
|
var fitTitle = showTypeIcon || showTrailing;
|
|
281
|
-
|
|
282
|
-
|
|
307
|
+
useFittedTitle(title, fitTitle, labelRef, titleRef, trailingRef, typeIconRef, // trailing / 字重的全部变化来源。这些都会改标题可用宽度而行宽不变,
|
|
308
|
+
// 提前声明即可,无需为此额外监听 label / trailing / typeIcon 的尺寸。
|
|
309
|
+
"".concat(active ? 1 : 0, ":").concat(menuOpen ? 1 : 0, ":").concat(timeLabel, ":").concat(status !== null && status !== void 0 ? status : '', ":").concat(showTypeIcon ? 1 : 0));
|
|
283
310
|
useEffect(function () {
|
|
284
311
|
if (!renaming) setDraft(title);
|
|
285
312
|
}, [title, renaming]);
|
|
@@ -316,19 +343,21 @@ export function ChatItem(_ref2) {
|
|
|
316
343
|
setDraft(title);
|
|
317
344
|
setRenaming(false);
|
|
318
345
|
};
|
|
319
|
-
var actionMenu =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
346
|
+
var actionMenu = useMemo(function () {
|
|
347
|
+
return [].concat(_toConsumableArray(onRename ? [{
|
|
348
|
+
value: 'rename',
|
|
349
|
+
label: '修改名称',
|
|
350
|
+
icon: 'pencil-line'
|
|
351
|
+
}] : []), _toConsumableArray(onPin ? [{
|
|
352
|
+
value: 'pin',
|
|
353
|
+
label: pinned ? '取消置顶' : '置顶',
|
|
354
|
+
icon: 'arrow-up-to-line'
|
|
355
|
+
}] : []), _toConsumableArray(onDelete ? [{
|
|
356
|
+
value: 'delete',
|
|
357
|
+
label: '删除',
|
|
358
|
+
icon: 'trash-2'
|
|
359
|
+
}] : []));
|
|
360
|
+
}, [onRename, onPin, onDelete, pinned]);
|
|
332
361
|
return /*#__PURE__*/React.createElement(HoverFill, {
|
|
333
362
|
"data-odn-chat-item": true,
|
|
334
363
|
"data-odn-chat-item-id": id,
|
|
@@ -374,9 +403,8 @@ export function ChatItem(_ref2) {
|
|
|
374
403
|
ref: titleRef,
|
|
375
404
|
"data-odn-chat-item-title": true,
|
|
376
405
|
"data-odn-chat-item-title-bold": active ? '' : undefined,
|
|
377
|
-
"data-odn-chat-item-title-fitted": fitTitle ? '' : undefined,
|
|
378
406
|
title: title
|
|
379
|
-
},
|
|
407
|
+
}, title), showTypeIcon ? /*#__PURE__*/React.createElement("span", {
|
|
380
408
|
ref: typeIconRef,
|
|
381
409
|
"data-odn-chat-item-type-icon": true,
|
|
382
410
|
"aria-label": "\u4EBA\u5DE5\u5BA2\u670D\u4F1A\u8BDD",
|
|
@@ -446,6 +474,14 @@ export function ChatItem(_ref2) {
|
|
|
446
474
|
}
|
|
447
475
|
})));
|
|
448
476
|
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* 会话列表项。
|
|
480
|
+
*
|
|
481
|
+
* memo:列表里单项 hover / 选中会触发父级重渲染,未 memo 时其余项会一并
|
|
482
|
+
* 重跑 render 与相对时间计算。父级需传入稳定的回调引用才能生效。
|
|
483
|
+
*/
|
|
484
|
+
export var ChatItem = /*#__PURE__*/memo(ChatItemInner);
|
|
449
485
|
ChatItem.displayName = 'ChatItem';
|
|
450
486
|
export default ChatItem;
|
|
451
487
|
export { formatRelativeTime } from "./relative-time";
|
package/dist/composer/index.d.ts
CHANGED
|
@@ -28,10 +28,10 @@ export interface ComposerToolConfig {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Composer 向内嵌 SkillSlot 透传的配置袋。
|
|
31
|
-
* 技能目录(skills
|
|
32
|
-
* Composer
|
|
31
|
+
* 技能目录(skills)应由「技能管理」模块维护后注入此处;
|
|
32
|
+
* 菜单宽高由 Composer 按输入框与视口计算,不经本袋覆盖。
|
|
33
33
|
*/
|
|
34
|
-
export type ComposerSkillSlotProps = Pick<SkillSlotProps, 'skills' | '
|
|
34
|
+
export type ComposerSkillSlotProps = Pick<SkillSlotProps, 'skills' | 'onManageSkills' | 'skillManageFooter'>;
|
|
35
35
|
export interface ComposerRef {
|
|
36
36
|
/** 聚焦输入框 */
|
|
37
37
|
focus: (options?: FocusOptions) => void;
|
|
@@ -156,6 +156,7 @@ export interface ComposerProps {
|
|
|
156
156
|
/**
|
|
157
157
|
* 向内嵌 SkillSlot 透传的配置袋。
|
|
158
158
|
* 技能目录由业务 / 技能管理模块维护后注入;Composer 只负责触发入口与插入 chip。
|
|
159
|
+
* 菜单宽高由 Composer 按输入框与视口计算,不经本袋覆盖。
|
|
159
160
|
*/
|
|
160
161
|
skillSlotProps?: ComposerSkillSlotProps;
|
|
161
162
|
/**
|
package/dist/composer/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { COMPOSER_EDITOR_MAX_ROWS_PX, ComposerEditor } from "./editor";
|
|
|
20
20
|
import { chipHasEditableParams, isMentionChip } from "./inline-ref";
|
|
21
21
|
import { rawValueToSegments, segmentsHasContent } from "./segments";
|
|
22
22
|
import "./style";
|
|
23
|
-
import { filterSkillsByQuery, resolveComposerSkillMenuWidth, shouldOpenSlashTriggerMenu } from "./utils";
|
|
23
|
+
import { COMPOSER_SKILL_MENU_DENSITY_CAP_PX, filterSkillsByQuery, resolveComposerSkillMenuMaxHeight, resolveComposerSkillMenuWidth, shouldOpenSlashTriggerMenu } from "./utils";
|
|
24
24
|
function createInvocationChipData(skill) {
|
|
25
25
|
return {
|
|
26
26
|
id: "skill-".concat(skill.id, "-").concat(Date.now().toString(36), "-").concat(Math.random().toString(36).slice(2, 6)),
|
|
@@ -92,12 +92,12 @@ var DEFAULT_TOOL_CONFIGS = {
|
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* Composer 向内嵌 SkillSlot 透传的配置袋。
|
|
95
|
-
* 技能目录(skills
|
|
96
|
-
* Composer
|
|
95
|
+
* 技能目录(skills)应由「技能管理」模块维护后注入此处;
|
|
96
|
+
* 菜单宽高由 Composer 按输入框与视口计算,不经本袋覆盖。
|
|
97
97
|
*/
|
|
98
98
|
|
|
99
99
|
export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
100
|
-
var
|
|
100
|
+
var _filteredSkills$lengt, _paramEdit$chipId;
|
|
101
101
|
var valueProp = _ref.value,
|
|
102
102
|
_ref$defaultValue = _ref.defaultValue,
|
|
103
103
|
defaultValue = _ref$defaultValue === void 0 ? '' : _ref$defaultValue,
|
|
@@ -147,11 +147,8 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
147
147
|
className = _ref.className,
|
|
148
148
|
style = _ref.style;
|
|
149
149
|
var skills = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.skills;
|
|
150
|
-
var skillGroups = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.skillGroups;
|
|
151
150
|
var onManageSkills = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.onManageSkills;
|
|
152
151
|
var skillManageFooter = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.skillManageFooter;
|
|
153
|
-
var resolvedMenuMaxHeight = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.menuMaxHeight;
|
|
154
|
-
var showGroupTitles = skillSlotProps === null || skillSlotProps === void 0 ? void 0 : skillSlotProps.showGroupTitles;
|
|
155
152
|
var _useControlledState = useControlledState(defaultValue, valueProp),
|
|
156
153
|
_useControlledState2 = _slicedToArray(_useControlledState, 2),
|
|
157
154
|
text = _useControlledState2[0],
|
|
@@ -216,11 +213,15 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
216
213
|
_useState18 = _slicedToArray(_useState17, 2),
|
|
217
214
|
skillMenuCrossAxis = _useState18[0],
|
|
218
215
|
setSkillMenuCrossAxis = _useState18[1];
|
|
219
|
-
/** composer box
|
|
216
|
+
/** composer box 实宽;技能菜单比它略窄。 */
|
|
220
217
|
var _useState19 = useState(0),
|
|
221
218
|
_useState20 = _slicedToArray(_useState19, 2),
|
|
222
219
|
boxWidth = _useState20[0],
|
|
223
220
|
setBoxWidth = _useState20[1];
|
|
221
|
+
var _useState21 = useState(COMPOSER_SKILL_MENU_DENSITY_CAP_PX),
|
|
222
|
+
_useState22 = _slicedToArray(_useState21, 2),
|
|
223
|
+
skillMenuMaxHeight = _useState22[0],
|
|
224
|
+
setSkillMenuMaxHeight = _useState22[1];
|
|
224
225
|
useLayoutEffect(function () {
|
|
225
226
|
var el = boxRef.current;
|
|
226
227
|
if (!el) return undefined;
|
|
@@ -238,7 +239,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
238
239
|
return ro.disconnect();
|
|
239
240
|
};
|
|
240
241
|
}, []);
|
|
241
|
-
var resolvedMenuWidth = resolveComposerSkillMenuWidth(
|
|
242
|
+
var resolvedMenuWidth = resolveComposerSkillMenuWidth(boxWidth);
|
|
242
243
|
var handleComposerMouseDown = useCallback(function (event) {
|
|
243
244
|
var _editorRef$current;
|
|
244
245
|
if (disabled || event.button !== 0) return;
|
|
@@ -254,10 +255,10 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
254
255
|
preventScroll: true
|
|
255
256
|
});
|
|
256
257
|
}, [disabled]);
|
|
257
|
-
var
|
|
258
|
-
|
|
259
|
-
paramEdit =
|
|
260
|
-
setParamEdit =
|
|
258
|
+
var _useState23 = useState(null),
|
|
259
|
+
_useState24 = _slicedToArray(_useState23, 2),
|
|
260
|
+
paramEdit = _useState24[0],
|
|
261
|
+
setParamEdit = _useState24[1];
|
|
261
262
|
var openParamPanel = useCallback(function (chipId) {
|
|
262
263
|
if (!renderParamPanel) return;
|
|
263
264
|
requestAnimationFrame(function () {
|
|
@@ -598,10 +599,10 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
598
599
|
* 不夺回焦点时 Enter 会再次激活按钮而不是发送 */
|
|
599
600
|
(_editorRef$current15 = editorRef.current) === null || _editorRef$current15 === void 0 || _editorRef$current15.focus();
|
|
600
601
|
};
|
|
601
|
-
var
|
|
602
|
-
|
|
603
|
-
fileDragOver =
|
|
604
|
-
setFileDragOver =
|
|
602
|
+
var _useState25 = useState(false),
|
|
603
|
+
_useState26 = _slicedToArray(_useState25, 2),
|
|
604
|
+
fileDragOver = _useState26[0],
|
|
605
|
+
setFileDragOver = _useState26[1];
|
|
605
606
|
var composerBoxFileDragAttrs = fileDragOver ? {
|
|
606
607
|
'data-odn-composer-file-drag': ''
|
|
607
608
|
} : {};
|
|
@@ -661,40 +662,22 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
661
662
|
return filterSkillsByQuery(skills, slashTriggerQuery);
|
|
662
663
|
}, [skills, slashTriggerActive, slashTriggerQuery]);
|
|
663
664
|
|
|
664
|
-
/* grouped 版本的 trigger query 筛选:按组分别过滤,保留非空组。 */
|
|
665
|
-
var filteredSkillGroups = useMemo(function () {
|
|
666
|
-
if (!skillGroups) return undefined;
|
|
667
|
-
if (!slashTriggerActive) return [];
|
|
668
|
-
return skillGroups.map(function (g) {
|
|
669
|
-
return {
|
|
670
|
-
groupName: g.groupName,
|
|
671
|
-
skills: filterSkillsByQuery(g.skills, slashTriggerQuery)
|
|
672
|
-
};
|
|
673
|
-
}).filter(function (g) {
|
|
674
|
-
return g.skills.length > 0;
|
|
675
|
-
});
|
|
676
|
-
}, [skillGroups, slashTriggerActive, slashTriggerQuery]);
|
|
677
|
-
|
|
678
665
|
/* trigger 浮层:`/` 触发态 + 目录非空 + 过滤后仍有命中。
|
|
679
666
|
* 无匹配时菜单关闭,Enter 不再被当成「选 skill」。`@` 本轮不接。 */
|
|
680
|
-
var hasSkills = !!(skills && skills.length > 0)
|
|
681
|
-
var triggerHitCount =
|
|
682
|
-
return n + g.skills.length;
|
|
683
|
-
}, 0)) !== null && _filteredSkillGroups$ !== void 0 ? _filteredSkillGroups$ : 0 : (_filteredSkills$lengt = filteredSkills === null || filteredSkills === void 0 ? void 0 : filteredSkills.length) !== null && _filteredSkills$lengt !== void 0 ? _filteredSkills$lengt : 0;
|
|
667
|
+
var hasSkills = !!(skills && skills.length > 0);
|
|
668
|
+
var triggerHitCount = (_filteredSkills$lengt = filteredSkills === null || filteredSkills === void 0 ? void 0 : filteredSkills.length) !== null && _filteredSkills$lengt !== void 0 ? _filteredSkills$lengt : 0;
|
|
684
669
|
var triggerMenuVisible = shouldOpenSlashTriggerMenu(triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.trigger, hasSkills ? triggerHitCount : 0);
|
|
685
670
|
|
|
686
671
|
/* query 变化或浮层关闭 → 高亮项重置到「最上方的可用项」(跳过 disabled)。
|
|
687
672
|
* 用 query 而不是 triggerInfo 整体当依赖:rect 变化(光标移位)不应该 reset。 */
|
|
688
673
|
useEffect(function () {
|
|
689
|
-
var _ref3
|
|
690
|
-
var list = (_ref3 =
|
|
691
|
-
return g.skills;
|
|
692
|
-
})) !== null && _ref3 !== void 0 ? _ref3 : [];
|
|
674
|
+
var _ref3;
|
|
675
|
+
var list = (_ref3 = filteredSkills !== null && filteredSkills !== void 0 ? filteredSkills : skills) !== null && _ref3 !== void 0 ? _ref3 : [];
|
|
693
676
|
var first = list.findIndex(function (s) {
|
|
694
677
|
return !s.disabled;
|
|
695
678
|
});
|
|
696
679
|
setTriggerActiveIndex(first === -1 ? 0 : first);
|
|
697
|
-
}, [filteredSkills,
|
|
680
|
+
}, [filteredSkills, skills, triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.query, triggerInfo === null]);
|
|
698
681
|
|
|
699
682
|
/* editor 在 trigger 激活时把 ↑↓Enter Esc Tab 丢过来。返回 true = editor 别管了。
|
|
700
683
|
* - ↑↓ 改高亮 index(夹在 [0, max] 内)
|
|
@@ -718,11 +701,9 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
718
701
|
return true;
|
|
719
702
|
}
|
|
720
703
|
if (e.key === 'Enter' && !e.shiftKey || e.key === 'Tab') {
|
|
721
|
-
var
|
|
704
|
+
var _triggerSkillSlotRef$3, _triggerSkillSlotRef$4;
|
|
722
705
|
e.preventDefault();
|
|
723
|
-
var _skill = (
|
|
724
|
-
return g.skills;
|
|
725
|
-
})[triggerActiveIndex];
|
|
706
|
+
var _skill = (_triggerSkillSlotRef$3 = (_triggerSkillSlotRef$4 = triggerSkillSlotRef.current) === null || _triggerSkillSlotRef$4 === void 0 ? void 0 : _triggerSkillSlotRef$4.getActiveSkill()) !== null && _triggerSkillSlotRef$3 !== void 0 ? _triggerSkillSlotRef$3 : filteredSkills === null || filteredSkills === void 0 ? void 0 : filteredSkills[triggerActiveIndex];
|
|
726
707
|
if (!_skill) return false;
|
|
727
708
|
handleSelectSkill(_skill, 'trigger');
|
|
728
709
|
return true;
|
|
@@ -735,7 +716,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
735
716
|
return true;
|
|
736
717
|
}
|
|
737
718
|
return false;
|
|
738
|
-
}, [filteredSkills,
|
|
719
|
+
}, [filteredSkills, handleSelectSkill, triggerActiveIndex, triggerMenuVisible]);
|
|
739
720
|
|
|
740
721
|
/* ---- Derived ---- */
|
|
741
722
|
var hasUploadingAttachment = attachments.some(function (a) {
|
|
@@ -757,6 +738,30 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
757
738
|
var boxRect = box.getBoundingClientRect();
|
|
758
739
|
el.style.top = "".concat(triggerInfo.rect.top - boxRect.top, "px");
|
|
759
740
|
}, [triggerInfo]);
|
|
741
|
+
useLayoutEffect(function () {
|
|
742
|
+
if (!toolMenuOpen && !triggerMenuVisible) return undefined;
|
|
743
|
+
var sync = function sync() {
|
|
744
|
+
var _skillTriggerRef$curr, _skillTriggerRef$curr2, _ref4, _triggerInfo$rect$top, _triggerAnchorRef$cur;
|
|
745
|
+
var triggerTop = toolMenuOpen ? (_skillTriggerRef$curr = (_skillTriggerRef$curr2 = skillTriggerRef.current) === null || _skillTriggerRef$curr2 === void 0 ? void 0 : _skillTriggerRef$curr2.getBoundingClientRect().top) !== null && _skillTriggerRef$curr !== void 0 ? _skillTriggerRef$curr : null : (_ref4 = (_triggerInfo$rect$top = triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.rect.top) !== null && _triggerInfo$rect$top !== void 0 ? _triggerInfo$rect$top : (_triggerAnchorRef$cur = triggerAnchorRef.current) === null || _triggerAnchorRef$cur === void 0 ? void 0 : _triggerAnchorRef$cur.getBoundingClientRect().top) !== null && _ref4 !== void 0 ? _ref4 : null;
|
|
746
|
+
if (triggerTop === null) return;
|
|
747
|
+
var next = resolveComposerSkillMenuMaxHeight({
|
|
748
|
+
triggerTop: triggerTop,
|
|
749
|
+
viewportHeight: window.innerHeight
|
|
750
|
+
});
|
|
751
|
+
setSkillMenuMaxHeight(function (prev) {
|
|
752
|
+
return prev === next ? prev : next;
|
|
753
|
+
});
|
|
754
|
+
};
|
|
755
|
+
sync();
|
|
756
|
+
window.addEventListener('resize', sync);
|
|
757
|
+
var box = boxRef.current;
|
|
758
|
+
var ro = box && typeof ResizeObserver !== 'undefined' ? new ResizeObserver(sync) : null;
|
|
759
|
+
if (box) ro === null || ro === void 0 || ro.observe(box);
|
|
760
|
+
return function () {
|
|
761
|
+
window.removeEventListener('resize', sync);
|
|
762
|
+
ro === null || ro === void 0 || ro.disconnect();
|
|
763
|
+
};
|
|
764
|
+
}, [toolMenuOpen, triggerMenuVisible, triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.rect.top]);
|
|
760
765
|
useLayoutEffect(function () {
|
|
761
766
|
var box = boxRef.current;
|
|
762
767
|
var trigger = skillTriggerRef.current;
|
|
@@ -907,9 +912,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
907
912
|
}), showLabel && /*#__PURE__*/React.createElement("span", null, label))));
|
|
908
913
|
};
|
|
909
914
|
var renderSkillBtn = function renderSkillBtn() {
|
|
910
|
-
var hasAnySkills = skills && skills.length > 0
|
|
911
|
-
return g.skills.length > 0;
|
|
912
|
-
});
|
|
915
|
+
var hasAnySkills = skills && skills.length > 0;
|
|
913
916
|
if (!hasAnySkills) return null;
|
|
914
917
|
var _resolvedToolConfigs$3 = resolvedToolConfigs.skill,
|
|
915
918
|
icon = _resolvedToolConfigs$3.icon,
|
|
@@ -946,10 +949,8 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
946
949
|
key: toolMenuOpen ? 'open' : 'closed',
|
|
947
950
|
variant: "menu",
|
|
948
951
|
searchable: true,
|
|
949
|
-
skills:
|
|
950
|
-
|
|
951
|
-
showGroupTitles: showGroupTitles,
|
|
952
|
-
menuMaxHeight: resolvedMenuMaxHeight,
|
|
952
|
+
skills: skills,
|
|
953
|
+
menuMaxHeight: skillMenuMaxHeight,
|
|
953
954
|
menuWidth: resolvedMenuWidth,
|
|
954
955
|
onSelect: function onSelect(s) {
|
|
955
956
|
return handleSelectSkill(s, 'btn');
|
|
@@ -1164,12 +1165,10 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
1164
1165
|
popup: /*#__PURE__*/React.createElement(SkillSlot, {
|
|
1165
1166
|
ref: triggerSkillSlotRef,
|
|
1166
1167
|
variant: "menu",
|
|
1167
|
-
skills:
|
|
1168
|
-
skillGroups: filteredSkillGroups,
|
|
1169
|
-
showGroupTitles: showGroupTitles,
|
|
1168
|
+
skills: filteredSkills !== null && filteredSkills !== void 0 ? filteredSkills : [],
|
|
1170
1169
|
activeIndex: triggerActiveIndex,
|
|
1171
1170
|
onActiveIndexChange: setTriggerActiveIndex,
|
|
1172
|
-
menuMaxHeight:
|
|
1171
|
+
menuMaxHeight: skillMenuMaxHeight,
|
|
1173
1172
|
menuWidth: resolvedMenuWidth,
|
|
1174
1173
|
onSelect: function onSelect(s) {
|
|
1175
1174
|
return handleSelectSkill(s, 'trigger');
|