1- import { createEffect , For , Match , on , onCleanup , Show , Switch , type JSX } from "solid-js"
1+ import { createEffect , For , Match , on , onCleanup , onMount , Show , Switch , type JSX } from "solid-js"
22import { animate , type AnimationPlaybackControls } from "motion"
33import { useI18n } from "../context/i18n"
44import { createStore } from "solid-js/store"
@@ -40,26 +40,76 @@ export interface BasicToolProps {
4040}
4141
4242const SPRING = { type : "spring" as const , visualDuration : 0.35 , bounce : 0 }
43+ const deferredMounts : Array < { active : boolean ; fn : ( ) => void } > = [ ]
44+ let deferredFrame : number | undefined
45+
46+ function flushDeferredMounts ( ) {
47+ while ( deferredMounts . length > 0 ) {
48+ // Timeline tools are mounted top-to-bottom, but the viewport starts at the latest turn.
49+ // Pop from the end so heavy default-open bodies near the bottom become interactive first.
50+ const item = deferredMounts . pop ( ) !
51+ if ( item . active ) {
52+ deferredFrame = deferredMounts . length > 0 ? requestAnimationFrame ( flushDeferredMounts ) : undefined
53+ item . fn ( )
54+ return
55+ }
56+ }
57+ deferredFrame = undefined
58+ }
59+
60+ function scheduleDeferredFlush ( ) {
61+ if ( deferredFrame !== undefined ) return
62+ deferredFrame = requestAnimationFrame ( ( ) => {
63+ deferredFrame = requestAnimationFrame ( flushDeferredMounts )
64+ } )
65+ }
66+
67+ function scheduleDeferredMount ( fn : ( ) => void ) {
68+ const item = { active : true , fn }
69+ deferredMounts . push ( item )
70+ scheduleDeferredFlush ( )
71+ return ( ) => {
72+ item . active = false
73+ }
74+ }
75+
76+ function scheduleFrameMount ( fn : ( ) => void ) {
77+ const frame = requestAnimationFrame ( fn )
78+ return ( ) => cancelAnimationFrame ( frame )
79+ }
4380
4481export function BasicTool ( props : BasicToolProps ) {
4582 const [ state , setState ] = createStore ( {
4683 open : props . defaultOpen ?? false ,
47- ready : props . defaultOpen ?? false ,
84+ ready : ! props . defer && ( props . defaultOpen ?? false ) ,
4885 } )
4986 const open = ( ) => state . open
5087 const ready = ( ) => state . ready
5188 const pending = ( ) => props . status === "pending" || props . status === "running"
89+ const hasChildren = ( ) => ( props . defer ? "children" in props : props . children )
5290
53- let frame : number | undefined
91+ let cancelReady : ( ( ) => void ) | undefined
5492
5593 const cancel = ( ) => {
56- if ( frame === undefined ) return
57- cancelAnimationFrame ( frame )
58- frame = undefined
94+ cancelReady ?.( )
95+ cancelReady = undefined
96+ }
97+
98+ const scheduleReady = ( initial = false ) => {
99+ cancel ( )
100+ cancelReady = ( initial ? scheduleDeferredMount : scheduleFrameMount ) ( ( ) => {
101+ cancelReady = undefined
102+ if ( ! open ( ) ) return
103+ setState ( "ready" , true )
104+ } )
59105 }
60106
61107 onCleanup ( cancel )
62108
109+ onMount ( ( ) => {
110+ if ( props . defer && open ( ) ) scheduleReady ( true )
111+ } )
112+
63113 createEffect ( ( ) => {
64114 if ( props . forceOpen ) setState ( "open" , true )
65115 } )
@@ -75,12 +125,7 @@ export function BasicTool(props: BasicToolProps) {
75125 return
76126 }
77127
78- cancel ( )
79- frame = requestAnimationFrame ( ( ) => {
80- frame = undefined
81- if ( ! open ( ) ) return
82- setState ( "ready" , true )
83- } )
128+ scheduleReady ( )
84129 } ,
85130 { defer : true } ,
86131 ) ,
@@ -189,7 +234,7 @@ export function BasicTool(props: BasicToolProps) {
189234 </ Switch >
190235 </ div >
191236 </ div >
192- < Show when = { props . children && ! props . hideDetails && ! props . locked && ! pending ( ) } >
237+ < Show when = { hasChildren ( ) && ! props . hideDetails && ! props . locked && ! pending ( ) } >
193238 < Collapsible . Arrow />
194239 </ Show >
195240 </ div >
@@ -219,7 +264,7 @@ export function BasicTool(props: BasicToolProps) {
219264 </ Collapsible . Trigger >
220265 ) }
221266 </ Show >
222- < Show when = { props . animated && props . children && ! props . hideDetails } >
267+ < Show when = { props . animated && hasChildren ( ) && ! props . hideDetails } >
223268 < div
224269 ref = { contentRef }
225270 data-slot = "collapsible-content"
@@ -229,10 +274,10 @@ export function BasicTool(props: BasicToolProps) {
229274 overflow : initialOpen ? "visible" : "hidden" ,
230275 } }
231276 >
232- { props . children }
277+ < Show when = { ! props . defer || ready ( ) } > { props . children } </ Show >
233278 </ div >
234279 </ Show >
235- < Show when = { ! props . animated && props . children && ! props . hideDetails } >
280+ < Show when = { ! props . animated && hasChildren ( ) && ! props . hideDetails } >
236281 < Collapsible . Content >
237282 < Show when = { ! props . defer || ready ( ) } > { props . children } </ Show >
238283 </ Collapsible . Content >
0 commit comments