Skip to content

Commit 6f165e2

Browse files
authored
perf(ui): defer tool status width measurement (#26282)
1 parent cef0c8a commit 6f165e2

1 file changed

Lines changed: 61 additions & 63 deletions

File tree

packages/ui/src/components/tool-status-title.tsx

Lines changed: 61 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Show, createEffect, createMemo, on, onCleanup, onMount } from "solid-js"
1+
import { Show, createEffect, createMemo, on, onCleanup } from "solid-js"
22
import { createStore } from "solid-js/store"
33
import { TextShimmer } from "./text-shimmer"
44

@@ -15,10 +15,8 @@ function common(active: string, done: string) {
1515
}
1616

1717
function contentWidth(el: HTMLSpanElement | undefined) {
18-
if (!el) return 0
19-
const range = document.createRange()
20-
range.selectNodeContents(el)
21-
return Math.ceil(range.getBoundingClientRect().width)
18+
if (!el) return
19+
return `${Math.ceil(el.getBoundingClientRect().width)}px`
2220
}
2321

2422
export function ToolStatusTitle(props: {
@@ -37,99 +35,99 @@ export function ToolStatusTitle(props: {
3735
const doneTail = createMemo(() => (suffix() ? split().done : props.doneText))
3836

3937
const [state, setState] = createStore({
40-
width: "auto",
41-
ready: false,
38+
active: props.active,
39+
animating: false,
40+
width: undefined as string | undefined,
4241
})
4342
const width = () => state.width
44-
const ready = () => state.ready
43+
const active = () => state.active
44+
const animating = () => state.animating
4545
let activeRef: HTMLSpanElement | undefined
4646
let doneRef: HTMLSpanElement | undefined
47+
let widthRef: HTMLSpanElement | undefined
4748
let frame: number | undefined
48-
let readyFrame: number | undefined
49+
let finishTimer: ReturnType<typeof setTimeout> | undefined
4950

50-
const measure = () => {
51-
const target = props.active ? activeRef : doneRef
52-
const px = contentWidth(target)
53-
if (px > 0) setState("width", `${px}px`)
51+
const finish = () => {
52+
if (frame !== undefined) cancelAnimationFrame(frame)
53+
if (finishTimer !== undefined) clearTimeout(finishTimer)
54+
frame = undefined
55+
finishTimer = undefined
56+
setState("animating", false)
57+
setState("width", undefined)
5458
}
5559

56-
const schedule = () => {
57-
if (typeof requestAnimationFrame !== "function") {
58-
measure()
60+
const animate = () => {
61+
const first = contentWidth(widthRef)
62+
finish()
63+
setState("animating", true)
64+
setState("active", props.active)
65+
const last = contentWidth(props.active ? activeRef : doneRef)
66+
if (!first || !last) {
67+
finish()
5968
return
6069
}
61-
if (frame !== undefined) cancelAnimationFrame(frame)
62-
frame = requestAnimationFrame(() => {
63-
frame = undefined
64-
measure()
65-
})
66-
}
6770

68-
const finish = () => {
69-
if (typeof requestAnimationFrame !== "function") {
70-
setState("ready", true)
71+
setState("width", first)
72+
if (first === last) {
73+
finishTimer = setTimeout(finish, 600)
7174
return
7275
}
73-
if (readyFrame !== undefined) cancelAnimationFrame(readyFrame)
74-
readyFrame = requestAnimationFrame(() => {
75-
readyFrame = undefined
76-
setState("ready", true)
76+
77+
frame = requestAnimationFrame(() => {
78+
frame = undefined
79+
setState("width", last)
80+
finishTimer = setTimeout(finish, 600)
7781
})
7882
}
7983

80-
createEffect(on([() => props.active, activeTail, doneTail, suffix], () => schedule()))
81-
82-
onMount(() => {
83-
measure()
84-
const fonts = typeof document !== "undefined" ? document.fonts : undefined
85-
if (!fonts) {
86-
finish()
87-
return
88-
}
89-
void fonts.ready.finally(() => {
90-
measure()
91-
finish()
92-
})
93-
})
84+
createEffect(on([() => props.active, activeTail, doneTail], () => animate(), { defer: true }))
9485

9586
onCleanup(() => {
96-
if (frame !== undefined) cancelAnimationFrame(frame)
97-
if (readyFrame !== undefined) cancelAnimationFrame(readyFrame)
87+
finish()
9888
})
9989

10090
return (
10191
<span
10292
data-component="tool-status-title"
103-
data-active={props.active ? "true" : "false"}
104-
data-ready={ready() ? "true" : "false"}
93+
data-active={active() ? "true" : "false"}
94+
data-ready={animating() ? "true" : "false"}
10595
data-mode={suffix() ? "suffix" : "swap"}
10696
class={props.class}
107-
aria-label={props.active ? props.activeText : props.doneText}
97+
aria-label={active() ? props.activeText : props.doneText}
10898
>
10999
<Show
110100
when={suffix()}
111101
fallback={
112-
<span data-slot="tool-status-swap" style={{ width: width() }}>
113-
<span data-slot="tool-status-active" ref={activeRef}>
114-
<TextShimmer text={activeTail()} active={props.active} offset={0} />
115-
</span>
116-
<span data-slot="tool-status-done" ref={doneRef}>
117-
<TextShimmer text={doneTail()} active={false} offset={0} />
118-
</span>
102+
<span data-slot="tool-status-swap" ref={widthRef} style={{ width: width() }}>
103+
<Show when={animating() || active()}>
104+
<span data-slot="tool-status-active" ref={activeRef}>
105+
<TextShimmer text={activeTail()} active={active()} offset={0} />
106+
</span>
107+
</Show>
108+
<Show when={animating() || !active()}>
109+
<span data-slot="tool-status-done" ref={doneRef}>
110+
<TextShimmer text={doneTail()} active={false} offset={0} />
111+
</span>
112+
</Show>
119113
</span>
120114
}
121115
>
122116
<span data-slot="tool-status-suffix">
123117
<span data-slot="tool-status-prefix">
124-
<TextShimmer text={split().prefix} active={props.active} offset={0} />
118+
<TextShimmer text={split().prefix} active={active()} offset={0} />
125119
</span>
126-
<span data-slot="tool-status-tail" style={{ width: width() }}>
127-
<span data-slot="tool-status-active" ref={activeRef}>
128-
<TextShimmer text={activeTail()} active={props.active} offset={prefixLen()} />
129-
</span>
130-
<span data-slot="tool-status-done" ref={doneRef}>
131-
<TextShimmer text={doneTail()} active={false} offset={prefixLen()} />
132-
</span>
120+
<span data-slot="tool-status-tail" ref={widthRef} style={{ width: width() }}>
121+
<Show when={animating() || active()}>
122+
<span data-slot="tool-status-active" ref={activeRef}>
123+
<TextShimmer text={activeTail()} active={active()} offset={prefixLen()} />
124+
</span>
125+
</Show>
126+
<Show when={animating() || !active()}>
127+
<span data-slot="tool-status-done" ref={doneRef}>
128+
<TextShimmer text={doneTail()} active={false} offset={prefixLen()} />
129+
</span>
130+
</Show>
133131
</span>
134132
</span>
135133
</Show>

0 commit comments

Comments
 (0)