11import { ClientOnly } from '@tanstack/react-router'
2+ import { Hydrate } from '@tanstack/react-start'
3+ import { visible } from '@tanstack/react-start/hydration'
24import * as React from 'react'
35import { ChartsCatalogPreview } from './ChartsCatalogPreview'
46import { Button } from '~/components/ds/ui'
@@ -10,6 +12,34 @@ const LazyChartsCatalogDocExample = React.lazy(() =>
1012)
1113
1214export function ChartsCatalogDocExample ( {
15+ ...props
16+ } : {
17+ caseId : string
18+ height : number
19+ source ?: 'hidden' | 'collapsed' | 'expanded'
20+ title ?: string
21+ } ) {
22+ return (
23+ < Hydrate
24+ key = { props . caseId }
25+ when = { visible ( { rootMargin : '600px 0px' } ) }
26+ fallback = {
27+ < section className = "not-prose my-5" >
28+ < ChartsCatalogDocExampleFallback
29+ caseId = { props . caseId }
30+ height = { props . height }
31+ source = { props . source ?? 'collapsed' }
32+ title = { props . title }
33+ />
34+ </ section >
35+ }
36+ >
37+ < ChartsCatalogDocExampleIsland { ...props } />
38+ </ Hydrate >
39+ )
40+ }
41+
42+ function ChartsCatalogDocExampleIsland ( {
1343 caseId,
1444 height,
1545 source = 'collapsed' ,
@@ -24,20 +54,138 @@ export function ChartsCatalogDocExample({
2454 caseId : string
2555 edit : boolean
2656 } > ( )
57+ const activatedCaseRef = React . useRef < string | undefined > ( undefined )
58+ const exampleRef = React . useRef < HTMLElement > ( null )
2759 const isActive = activation ?. caseId === caseId
60+ const activateRun = React . useCallback ( ( ) => {
61+ if ( activatedCaseRef . current === caseId ) return
62+ activatedCaseRef . current = caseId
63+ setActivation ( { caseId, edit : false } )
64+ } , [ caseId ] )
65+ const activateEdit = React . useCallback ( ( ) => {
66+ activatedCaseRef . current = caseId
67+ setActivation ( { caseId, edit : true } )
68+ } , [ caseId ] )
69+
70+ React . useEffect ( ( ) => {
71+ if ( isActive ) return
72+ if ( ! exampleRef . current ) return
73+ const element = exampleRef . current
74+
75+ let intersecting = false
76+ let dwellHandle : number | undefined
77+ let idleHandle : number | undefined
78+ let timeoutHandle : number | undefined
79+ let disposed = false
80+ let interactionBlocked = false
81+
82+ function cancelScheduledActivation ( ) {
83+ if ( dwellHandle !== undefined ) {
84+ window . clearTimeout ( dwellHandle )
85+ dwellHandle = undefined
86+ }
87+ if ( idleHandle !== undefined ) {
88+ window . cancelIdleCallback ( idleHandle )
89+ idleHandle = undefined
90+ }
91+ if ( timeoutHandle !== undefined ) {
92+ window . clearTimeout ( timeoutHandle )
93+ timeoutHandle = undefined
94+ }
95+ }
96+
97+ function canActivate ( ) {
98+ const rect = element . getBoundingClientRect ( )
99+ return (
100+ intersecting &&
101+ rect . bottom > 0 &&
102+ rect . right > 0 &&
103+ rect . top < window . innerHeight &&
104+ rect . left < window . innerWidth &&
105+ ! interactionBlocked &&
106+ document . visibilityState === 'visible' &&
107+ ! element . contains ( document . activeElement )
108+ )
109+ }
110+
111+ function scheduleActivation ( ) {
112+ cancelScheduledActivation ( )
113+ if ( disposed || ! canActivate ( ) ) return
114+
115+ dwellHandle = window . setTimeout ( ( ) => {
116+ dwellHandle = undefined
117+ if ( disposed || ! canActivate ( ) ) return
118+
119+ const run = ( ) => {
120+ idleHandle = undefined
121+ timeoutHandle = undefined
122+ if ( ! disposed && canActivate ( ) ) activateRun ( )
123+ }
124+
125+ if ( typeof window . requestIdleCallback === 'function' ) {
126+ idleHandle = window . requestIdleCallback ( run , { timeout : 1_700 } )
127+ } else {
128+ timeoutHandle = window . setTimeout ( run )
129+ }
130+ } , 300 )
131+ }
132+
133+ const observer = new IntersectionObserver ( ( [ entry ] ) => {
134+ intersecting = Boolean (
135+ entry ?. isIntersecting && entry . intersectionRatio > 0 ,
136+ )
137+ if ( intersecting ) scheduleActivation ( )
138+ else cancelScheduledActivation ( )
139+ } )
140+ observer . observe ( element )
141+
142+ function handleVisibilityChange ( ) {
143+ if ( document . visibilityState === 'visible' ) scheduleActivation ( )
144+ else cancelScheduledActivation ( )
145+ }
146+
147+ function handleFocusIn ( ) {
148+ cancelScheduledActivation ( )
149+ }
150+
151+ function handleFocusOut ( ) {
152+ queueMicrotask ( scheduleActivation )
153+ }
154+
155+ function handleInteraction ( ) {
156+ interactionBlocked = true
157+ cancelScheduledActivation ( )
158+ }
159+
160+ document . addEventListener ( 'visibilitychange' , handleVisibilityChange )
161+ element . addEventListener ( 'focusin' , handleFocusIn )
162+ element . addEventListener ( 'focusout' , handleFocusOut )
163+ element . addEventListener ( 'pointerdown' , handleInteraction )
164+
165+ return ( ) => {
166+ disposed = true
167+ cancelScheduledActivation ( )
168+ observer . disconnect ( )
169+ document . removeEventListener ( 'visibilitychange' , handleVisibilityChange )
170+ element . removeEventListener ( 'focusin' , handleFocusIn )
171+ element . removeEventListener ( 'focusout' , handleFocusOut )
172+ element . removeEventListener ( 'pointerdown' , handleInteraction )
173+ }
174+ } , [ activateRun , isActive ] )
175+
28176 const fallback = (
29177 < ChartsCatalogDocExampleFallback
30178 caseId = { caseId }
31179 height = { height }
32- onEdit = { ( ) => setActivation ( { caseId , edit : true } ) }
33- onRun = { ( ) => setActivation ( { caseId , edit : false } ) }
180+ onEdit = { activateEdit }
181+ onRun = { activateRun }
34182 source = { source }
35183 title = { title }
36184 />
37185 )
38186
39187 return (
40- < section className = "not-prose my-5" >
188+ < section ref = { exampleRef } className = "not-prose my-5" >
41189 { isActive ? (
42190 < ClientOnly fallback = { fallback } >
43191 < React . Suspense fallback = { fallback } >
@@ -68,8 +216,8 @@ function ChartsCatalogDocExampleFallback({
68216} : {
69217 caseId : string
70218 height : number
71- onEdit : ( ) => void
72- onRun : ( ) => void
219+ onEdit ? : ( ) => void
220+ onRun ? : ( ) => void
73221 source : 'hidden' | 'collapsed' | 'expanded'
74222 title ?: string
75223} ) {
@@ -79,6 +227,7 @@ function ChartsCatalogDocExampleFallback({
79227 < div
80228 className = "overflow-hidden rounded-lg border border-border-default bg-background-default"
81229 data-chart-example = { caseId }
230+ data-chart-example-state = "static"
82231 >
83232 < div
84233 className = { `flex min-h-10 items-center gap-3 border-b border-border-default px-2 ${ source === 'hidden' ? 'justify-end' : 'justify-between pl-3' } ` }
@@ -90,11 +239,23 @@ function ChartsCatalogDocExampleFallback({
90239 ) : null }
91240 < div className = "flex shrink-0 items-center gap-1" >
92241 { source !== 'hidden' ? (
93- < Button type = "button" variant = "ghost" size = "xs" onClick = { onEdit } >
242+ < Button
243+ type = "button"
244+ variant = "ghost"
245+ size = "xs"
246+ disabled = { ! onEdit }
247+ onClick = { onEdit }
248+ >
94249 Edit
95250 </ Button >
96251 ) : null }
97- < Button type = "button" variant = "primary" size = "xs" onClick = { onRun } >
252+ < Button
253+ type = "button"
254+ variant = "primary"
255+ size = "xs"
256+ disabled = { ! onRun }
257+ onClick = { onRun }
258+ >
98259 Run
99260 </ Button >
100261 </ div >
0 commit comments