one-design-next 0.0.91 → 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/hover-fill/index.js +9 -16
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 读阶段:只允许读取布局(getBoundingClientRect / getComputedStyle 等)。
|
|
3
|
+
* 返回写阶段回调,或返回 undefined 表示无需写入。
|
|
4
|
+
*/
|
|
5
|
+
type LayoutRead = () => (() => void) | void;
|
|
6
|
+
/**
|
|
7
|
+
* 把「读布局 - 改 DOM」拆成两阶段,同一帧内的所有调用先全部读、再全部写。
|
|
8
|
+
*
|
|
9
|
+
* 长列表里每个实例各自「读 - 写 - 读 - 写」时,每次写都会让布局失效,
|
|
10
|
+
* 下一个读又触发一次完整排版(layout thrashing)。500 项时这是一帧
|
|
11
|
+
* 500 次排版与一次排版的差别。
|
|
12
|
+
*/
|
|
13
|
+
export declare function scheduleLayoutBatch(read: LayoutRead): void;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 读阶段:只允许读取布局(getBoundingClientRect / getComputedStyle 等)。
|
|
3
|
+
* 返回写阶段回调,或返回 undefined 表示无需写入。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
var pending = [];
|
|
7
|
+
var rafId = 0;
|
|
8
|
+
function flush() {
|
|
9
|
+
rafId = 0;
|
|
10
|
+
var batch = pending;
|
|
11
|
+
pending = [];
|
|
12
|
+
|
|
13
|
+
// 阶段一:集中读。期间无任何写入,浏览器只需排版一次。
|
|
14
|
+
var writes = [];
|
|
15
|
+
for (var i = 0; i < batch.length; i++) {
|
|
16
|
+
try {
|
|
17
|
+
var write = batch[i]();
|
|
18
|
+
if (write) writes.push(write);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
console.error('[odn] layout batch read error:', error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 阶段二:集中写。布局失效只发生在全部读取之后。
|
|
26
|
+
for (var _i = 0; _i < writes.length; _i++) {
|
|
27
|
+
try {
|
|
28
|
+
writes[_i]();
|
|
29
|
+
} catch (error) {
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.error('[odn] layout batch write error:', error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 把「读布局 - 改 DOM」拆成两阶段,同一帧内的所有调用先全部读、再全部写。
|
|
38
|
+
*
|
|
39
|
+
* 长列表里每个实例各自「读 - 写 - 读 - 写」时,每次写都会让布局失效,
|
|
40
|
+
* 下一个读又触发一次完整排版(layout thrashing)。500 项时这是一帧
|
|
41
|
+
* 500 次排版与一次排版的差别。
|
|
42
|
+
*/
|
|
43
|
+
export function scheduleLayoutBatch(read) {
|
|
44
|
+
pending.push(read);
|
|
45
|
+
if (rafId) return;
|
|
46
|
+
rafId = requestAnimationFrame(flush);
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type SharedResizeCallback = (entry: ResizeObserverEntry) => void;
|
|
2
|
+
/**
|
|
3
|
+
* 共享 ResizeObserver:模块级单例观察者 + WeakMap 注册表。
|
|
4
|
+
* 大量组件实例共用同一个观察者,避免每个实例各 new 一个,
|
|
5
|
+
* 也避免浏览器每帧遍历成倍增长的观察目标。
|
|
6
|
+
*
|
|
7
|
+
* 返回取消观察的清理函数,应在 effect cleanup 中调用。
|
|
8
|
+
* 非浏览器环境 / 无 ResizeObserver 时安全降级为空操作。
|
|
9
|
+
*/
|
|
10
|
+
export declare function observeResize(target: Element, callback: SharedResizeCallback): () => void;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import canUseDom from "./canUseDom";
|
|
2
|
+
var sharedRO = null;
|
|
3
|
+
/**
|
|
4
|
+
* 同一元素可能被多方观察(如行容器既被标题拟合监听、又被背景同步监听)。
|
|
5
|
+
* 值用 Set 而非单个回调,避免后注册者覆盖先注册者、
|
|
6
|
+
* 以及任一方取消观察时把另一方也一并停掉。
|
|
7
|
+
*/
|
|
8
|
+
var registry = new WeakMap();
|
|
9
|
+
function ensureSharedRO() {
|
|
10
|
+
if (sharedRO) return;
|
|
11
|
+
sharedRO = new ResizeObserver(function (entries) {
|
|
12
|
+
var _loop = function _loop() {
|
|
13
|
+
var entry = entries[i];
|
|
14
|
+
var callbacks = registry.get(entry.target);
|
|
15
|
+
if (!callbacks) return 1; // continue
|
|
16
|
+
callbacks.forEach(function (cb) {
|
|
17
|
+
// 单个回调异常不应中断同批其他实例的派发(共享观察者的隔离保证)
|
|
18
|
+
try {
|
|
19
|
+
cb(entry);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.error('[odn] shared ResizeObserver callback error:', error);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
for (var i = 0; i < entries.length; i++) {
|
|
27
|
+
if (_loop()) continue;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 共享 ResizeObserver:模块级单例观察者 + WeakMap 注册表。
|
|
34
|
+
* 大量组件实例共用同一个观察者,避免每个实例各 new 一个,
|
|
35
|
+
* 也避免浏览器每帧遍历成倍增长的观察目标。
|
|
36
|
+
*
|
|
37
|
+
* 返回取消观察的清理函数,应在 effect cleanup 中调用。
|
|
38
|
+
* 非浏览器环境 / 无 ResizeObserver 时安全降级为空操作。
|
|
39
|
+
*/
|
|
40
|
+
export function observeResize(target, callback) {
|
|
41
|
+
if (!canUseDom() || typeof ResizeObserver === 'undefined') {
|
|
42
|
+
return function () {};
|
|
43
|
+
}
|
|
44
|
+
ensureSharedRO();
|
|
45
|
+
var callbacks = registry.get(target);
|
|
46
|
+
if (!callbacks) {
|
|
47
|
+
callbacks = new Set();
|
|
48
|
+
registry.set(target, callbacks);
|
|
49
|
+
sharedRO.observe(target);
|
|
50
|
+
}
|
|
51
|
+
callbacks.add(callback);
|
|
52
|
+
var stopped = false;
|
|
53
|
+
return function () {
|
|
54
|
+
if (stopped) return;
|
|
55
|
+
stopped = true;
|
|
56
|
+
var current = registry.get(target);
|
|
57
|
+
if (!current) return;
|
|
58
|
+
current.delete(callback);
|
|
59
|
+
// 最后一个观察方撤销时才真正停止观察
|
|
60
|
+
if (current.size === 0) {
|
|
61
|
+
registry.delete(target);
|
|
62
|
+
sharedRO.unobserve(target);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface FitMetrics {
|
|
2
|
+
/** 按字形簇切分的文本段 */
|
|
3
|
+
segments: string[];
|
|
4
|
+
/**
|
|
5
|
+
* prefixWidths[i] = segments[0..i] 拼接后再加省略号的宽度,严格单调递增。
|
|
6
|
+
* 完整文本宽度不放进来(省略号可能比末位字形宽,混入会破坏单调性)。
|
|
7
|
+
*/
|
|
8
|
+
prefixWidths: number[];
|
|
9
|
+
/** 完整文本宽度,不含省略号 */
|
|
10
|
+
fullWidth: number;
|
|
11
|
+
}
|
|
12
|
+
export interface FontSpec {
|
|
13
|
+
/** canvas font 简写,取自 resolveFont */
|
|
14
|
+
font: string;
|
|
15
|
+
/** CSS letter-spacing 的像素值;canvas measureText 不计字间距,需手工补偿 */
|
|
16
|
+
letterSpacing: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 按字形簇切分。emoji、肤色修饰符、ZWJ 序列、带组合符的字母都算一个整体,
|
|
20
|
+
* 截断落在其中任一位置都会产生乱码,所以不能按 UTF-16 码元切。
|
|
21
|
+
* 无 Intl.Segmenter 时退化到码点切分,至少保证代理对不裂。
|
|
22
|
+
*/
|
|
23
|
+
export declare function splitGraphemes(text: string): string[];
|
|
24
|
+
/**
|
|
25
|
+
* getComputedStyle().font 在部分浏览器下返回空串(如 font-family 走 CSS 变量),
|
|
26
|
+
* 此时把空串赋给 canvas 会静默 fallback 到 10px sans-serif,测量整体失真。
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveFont(cs: CSSStyleDeclaration): string;
|
|
29
|
+
/**
|
|
30
|
+
* 预计算所有截断位置的宽度。用 canvas measureText,不触发 DOM 排版,
|
|
31
|
+
* 因此单次挂载即可算完,后续宽度变化只需在结果上二分。
|
|
32
|
+
*/
|
|
33
|
+
export declare function measureFit(text: string, ellipsis: string, spec: FontSpec): FitMetrics;
|
|
34
|
+
export interface FitResult {
|
|
35
|
+
text: string;
|
|
36
|
+
/**
|
|
37
|
+
* 结果是否确实在 available 内。为 false 表示窄到连「一个字形 + 省略号」
|
|
38
|
+
* 都放不下,只能原样返回;调用方此时必须让 CSS text-overflow 接管截断,
|
|
39
|
+
* 否则文本会溢出容器。
|
|
40
|
+
*/
|
|
41
|
+
accurate: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 按可用宽度选出应显示的文本。
|
|
45
|
+
* 极窄时返回原文而非单独的省略号:宽度本身可能是布局未就绪的假值,
|
|
46
|
+
* 裁成「…」后若行宽不再变化就永远恢复不了,交给 CSS text-overflow 兜底更稳。
|
|
47
|
+
*/
|
|
48
|
+
export declare function fitToWidth(metrics: FitMetrics, available: number, text: string, ellipsis: string): FitResult;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import canUseDom from "./canUseDom";
|
|
2
|
+
var sharedCanvas = null;
|
|
3
|
+
function getCtx() {
|
|
4
|
+
if (!canUseDom()) return null;
|
|
5
|
+
if (!sharedCanvas) sharedCanvas = document.createElement('canvas');
|
|
6
|
+
return sharedCanvas.getContext('2d');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 本地 TS lib 目标尚未包含 Intl.Segmenter 的声明,这里按用到的最小面手写。
|
|
11
|
+
* 运行时通过 'Segmenter' in Intl 判定可用性,不可用时降级到码点切分。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
var segmenter = function () {
|
|
15
|
+
if (typeof Intl === 'undefined' || !('Segmenter' in Intl)) return null;
|
|
16
|
+
var Ctor = Intl.Segmenter;
|
|
17
|
+
return new Ctor(undefined, {
|
|
18
|
+
granularity: 'grapheme'
|
|
19
|
+
});
|
|
20
|
+
}();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 按字形簇切分。emoji、肤色修饰符、ZWJ 序列、带组合符的字母都算一个整体,
|
|
24
|
+
* 截断落在其中任一位置都会产生乱码,所以不能按 UTF-16 码元切。
|
|
25
|
+
* 无 Intl.Segmenter 时退化到码点切分,至少保证代理对不裂。
|
|
26
|
+
*/
|
|
27
|
+
export function splitGraphemes(text) {
|
|
28
|
+
if (!text) return [];
|
|
29
|
+
if (segmenter) {
|
|
30
|
+
return Array.from(segmenter.segment(text), function (s) {
|
|
31
|
+
return s.segment;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return Array.from(text);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* getComputedStyle().font 在部分浏览器下返回空串(如 font-family 走 CSS 变量),
|
|
39
|
+
* 此时把空串赋给 canvas 会静默 fallback 到 10px sans-serif,测量整体失真。
|
|
40
|
+
*/
|
|
41
|
+
export function resolveFont(cs) {
|
|
42
|
+
if (cs.font) return cs.font;
|
|
43
|
+
return "".concat(cs.fontStyle, " ").concat(cs.fontWeight, " ").concat(cs.fontSize, " / ").concat(cs.lineHeight, " ").concat(cs.fontFamily);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 预计算所有截断位置的宽度。用 canvas measureText,不触发 DOM 排版,
|
|
48
|
+
* 因此单次挂载即可算完,后续宽度变化只需在结果上二分。
|
|
49
|
+
*/
|
|
50
|
+
export function measureFit(text, ellipsis, spec) {
|
|
51
|
+
var segments = splitGraphemes(text);
|
|
52
|
+
if (!segments.length) {
|
|
53
|
+
return {
|
|
54
|
+
segments: segments,
|
|
55
|
+
prefixWidths: [],
|
|
56
|
+
fullWidth: 0
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
var ctx = getCtx();
|
|
60
|
+
if (!ctx) {
|
|
61
|
+
// 拿不到 canvas 时给出「永远放不下」的度量,调用方据此保留原文
|
|
62
|
+
return {
|
|
63
|
+
segments: segments,
|
|
64
|
+
prefixWidths: [],
|
|
65
|
+
fullWidth: Number.POSITIVE_INFINITY
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
ctx.font = spec.font;
|
|
69
|
+
var letterSpacing = spec.letterSpacing;
|
|
70
|
+
var ellipsisWidth = ctx.measureText(ellipsis).width + letterSpacing * splitGraphemes(ellipsis).length;
|
|
71
|
+
var prefixWidths = [];
|
|
72
|
+
var prefix = '';
|
|
73
|
+
for (var i = 0; i < segments.length - 1; i++) {
|
|
74
|
+
prefix += segments[i];
|
|
75
|
+
prefixWidths.push(ctx.measureText(prefix).width + letterSpacing * (i + 1) + ellipsisWidth);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
segments: segments,
|
|
79
|
+
prefixWidths: prefixWidths,
|
|
80
|
+
fullWidth: ctx.measureText(text).width + letterSpacing * segments.length
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 最后一个 <= available 的下标,-1 表示一个字形加省略号都放不下 */
|
|
85
|
+
function bisect(widths, available) {
|
|
86
|
+
var lo = 0;
|
|
87
|
+
var hi = widths.length - 1;
|
|
88
|
+
var best = -1;
|
|
89
|
+
while (lo <= hi) {
|
|
90
|
+
var mid = lo + hi >> 1;
|
|
91
|
+
if (widths[mid] <= available) {
|
|
92
|
+
best = mid;
|
|
93
|
+
lo = mid + 1;
|
|
94
|
+
} else {
|
|
95
|
+
hi = mid - 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return best;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 按可用宽度选出应显示的文本。
|
|
102
|
+
* 极窄时返回原文而非单独的省略号:宽度本身可能是布局未就绪的假值,
|
|
103
|
+
* 裁成「…」后若行宽不再变化就永远恢复不了,交给 CSS text-overflow 兜底更稳。
|
|
104
|
+
*/
|
|
105
|
+
export function fitToWidth(metrics, available, text, ellipsis) {
|
|
106
|
+
if (!metrics.segments.length) return {
|
|
107
|
+
text: text,
|
|
108
|
+
accurate: true
|
|
109
|
+
};
|
|
110
|
+
if (metrics.fullWidth <= available) return {
|
|
111
|
+
text: text,
|
|
112
|
+
accurate: true
|
|
113
|
+
};
|
|
114
|
+
var best = bisect(metrics.prefixWidths, available);
|
|
115
|
+
if (best < 0) return {
|
|
116
|
+
text: text,
|
|
117
|
+
accurate: false
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
text: metrics.segments.slice(0, best + 1).join('') + ellipsis,
|
|
121
|
+
accurate: true
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -38,9 +38,13 @@ export interface ChatItemProps {
|
|
|
38
38
|
defaultDeleteOpen?: boolean;
|
|
39
39
|
onDeleteOpenChange?: (open: boolean) => void;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
declare function ChatItemInner({ id, title, active, type, status, pinned, time, onClick, onRename, onPin, onDelete, actionMenuZIndex, renaming: renamingProp, defaultRenaming, onRenamingChange, menuOpen: menuOpenProp, defaultMenuOpen, onMenuOpenChange, deleteOpen: deleteOpenProp, defaultDeleteOpen, onDeleteOpenChange, }: ChatItemProps): import("react").JSX.Element;
|
|
42
|
+
/**
|
|
43
|
+
* 会话列表项。
|
|
44
|
+
*
|
|
45
|
+
* memo:列表里单项 hover / 选中会触发父级重渲染,未 memo 时其余项会一并
|
|
46
|
+
* 重跑 render 与相对时间计算。父级需传入稳定的回调引用才能生效。
|
|
47
|
+
*/
|
|
48
|
+
export declare const ChatItem: import("react").MemoExoticComponent<typeof ChatItemInner>;
|
|
45
49
|
export default ChatItem;
|
|
46
50
|
export { formatRelativeTime } from './relative-time';
|
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/hover-fill/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
|
6
6
|
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
|
|
7
7
|
var _excluded = ["bgClassName", "bgStyle", "children", "onMouseEnter", "onMouseLeave", "onMouseDown", "hoverColor", "activeColor", "disabled", "hoverCursor"];
|
|
8
8
|
import React, { forwardRef, useCallback, useContext, useEffect, useRef } from 'react';
|
|
9
|
+
import { observeResize } from "../_util/sharedResizeObserver";
|
|
9
10
|
import { ConfigContext } from "../config-provider";
|
|
10
11
|
import "./style";
|
|
11
12
|
var DEFAULT_HOVER_COLOR = 'rgba(33, 34, 38, 0.05)';
|
|
@@ -66,8 +67,6 @@ var HoverFill = /*#__PURE__*/forwardRef(function (_ref, outerRef) {
|
|
|
66
67
|
y = _root$getBoundingClie.y;
|
|
67
68
|
if (window.odnHoverFillState && window.odnHoverFillState.bgVisible && window.odnHoverFillState.bgNode && window.odnHoverFillState.baseNode && window.odnHoverFillState.baseNode !== root && ((_window$odnHoverFillS3 = window.odnHoverFillState.baseNode) === null || _window$odnHoverFillS3 === void 0 ? void 0 : _window$odnHoverFillS3.parentNode) === root.parentNode && hoverFillConfig.sharable) {
|
|
68
69
|
var _window$odnHoverFillS4 = window.odnHoverFillState,
|
|
69
|
-
bgX = _window$odnHoverFillS4.bgX,
|
|
70
|
-
bgY = _window$odnHoverFillS4.bgY,
|
|
71
70
|
bgScale = _window$odnHoverFillS4.bgScale,
|
|
72
71
|
baseNode = _window$odnHoverFillS4.baseNode;
|
|
73
72
|
var _baseNode$getBounding = baseNode.getBoundingClientRect(),
|
|
@@ -205,6 +204,12 @@ var HoverFill = /*#__PURE__*/forwardRef(function (_ref, outerRef) {
|
|
|
205
204
|
}, 0);
|
|
206
205
|
}, 100);
|
|
207
206
|
};
|
|
207
|
+
var handleMouseUp = function handleMouseUp() {
|
|
208
|
+
window.removeEventListener('mouseup', handleMouseUp, false);
|
|
209
|
+
if (window.odnHoverFillState.bgNode) {
|
|
210
|
+
window.odnHoverFillState.bgNode.style.background = window.odnHoverFillState.bgVisible ? hoverColor : 'transparent';
|
|
211
|
+
}
|
|
212
|
+
};
|
|
208
213
|
var handleMouseDown = function handleMouseDown(e) {
|
|
209
214
|
e.persist();
|
|
210
215
|
if (disabled) {
|
|
@@ -218,12 +223,6 @@ var HoverFill = /*#__PURE__*/forwardRef(function (_ref, outerRef) {
|
|
|
218
223
|
}
|
|
219
224
|
window.addEventListener('mouseup', handleMouseUp, false);
|
|
220
225
|
};
|
|
221
|
-
var handleMouseUp = function handleMouseUp() {
|
|
222
|
-
window.removeEventListener('mouseup', handleMouseUp, false);
|
|
223
|
-
if (window.odnHoverFillState.bgNode) {
|
|
224
|
-
window.odnHoverFillState.bgNode.style.background = window.odnHoverFillState.bgVisible ? hoverColor : 'transparent';
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
226
|
useEffect(function () {
|
|
228
227
|
if (!window.odnHoverFillState) {
|
|
229
228
|
window.odnHoverFillState = {
|
|
@@ -245,16 +244,10 @@ var HoverFill = /*#__PURE__*/forwardRef(function (_ref, outerRef) {
|
|
|
245
244
|
}, []);
|
|
246
245
|
useEffect(function () {
|
|
247
246
|
var root = ref.current;
|
|
248
|
-
if (!root
|
|
247
|
+
if (!root) {
|
|
249
248
|
return;
|
|
250
249
|
}
|
|
251
|
-
|
|
252
|
-
syncBgSize();
|
|
253
|
-
});
|
|
254
|
-
observer.observe(root);
|
|
255
|
-
return function () {
|
|
256
|
-
observer.disconnect();
|
|
257
|
-
};
|
|
250
|
+
return observeResize(root, syncBgSize);
|
|
258
251
|
}, [ref, syncBgSize]);
|
|
259
252
|
return /*#__PURE__*/React.createElement("div", _extends({
|
|
260
253
|
ref: ref,
|