1- import { Show , createEffect , createMemo , on , onCleanup , onMount } from "solid-js"
1+ import { Show , createEffect , createMemo , on , onCleanup } from "solid-js"
22import { createStore } from "solid-js/store"
33import { TextShimmer } from "./text-shimmer"
44
@@ -15,10 +15,8 @@ function common(active: string, done: string) {
1515}
1616
1717function 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
2422export 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