1- import { useRef , useEffect , useCallback } from "preact/hooks" ;
1+ import { useRef , useEffect , useCallback , useState } from "preact/hooks" ;
22import { getDisplayName } from 'bippy' ;
3+ import { cn } from "@web-utils/helpers" ;
34import { Store } from "../../../.." ;
45import { getCompositeComponentFromElement , getOverrideMethods } from "../../inspect-element/utils" ;
56import { replayComponent } from "../../inspect-element/view-state" ;
67import { Icon } from "../icon" ;
78
8- const BtnReplay = ( ) => {
9- const refBtnReplay = useRef < HTMLButtonElement > ( null ) ;
10- const refIsReplaying = useRef ( false ) ;
9+ export const Header = ( ) => {
10+ const inspectState = Store . inspectState . value ;
11+ const refComponentName = useRef < HTMLSpanElement > ( null ) ;
12+ const refMetrics = useRef < HTMLSpanElement > ( null ) ;
13+ const [ isReplaying , setIsReplaying ] = useState ( false ) ;
1114
12- const { overrideProps, overrideHookState } = getOverrideMethods ( ) ;
13- const canEdit = ! overrideProps ;
15+ useEffect ( ( ) => {
16+ const updateMetrics = ( ) => {
17+ if ( ! refComponentName . current || ! refMetrics . current ) return ;
18+ if ( inspectState . kind !== 'focused' ) return ;
19+
20+ if ( ! document . contains ( inspectState . focusedDomElement ) ) {
21+ if ( Store . inspectState . value . propContainer ) {
22+ Store . inspectState . value = {
23+ kind : 'inspect-off' ,
24+ propContainer : Store . inspectState . value . propContainer ,
25+ } ;
26+ }
27+ return ;
28+ }
1429
15- const handleReplay = useCallback ( ( e : MouseEvent ) => {
16- e . stopPropagation ( ) ;
1730
18- const inspectState = Store . inspectState . value ;
19- if ( refIsReplaying . current || inspectState . kind !== 'focused' ) return ;
31+ const { parentCompositeFiber } = getCompositeComponentFromElement ( inspectState . focusedDomElement ) ;
2032
21- const { parentCompositeFiber } = getCompositeComponentFromElement ( inspectState . focusedDomElement ) ;
22- if ( ! parentCompositeFiber || ! overrideProps || ! overrideHookState ) return ;
33+ if ( ! parentCompositeFiber ) return ;
2334
24- refIsReplaying . current = true ;
25- refBtnReplay . current ?. classList . toggle ( 'disabled' ) ;
35+ const reportDataFiber =
36+ Store . reportData . get ( parentCompositeFiber ) ??
37+ ( parentCompositeFiber . alternate
38+ ? Store . reportData . get ( parentCompositeFiber . alternate )
39+ : null ) ;
2640
27- void replayComponent ( parentCompositeFiber ) . finally ( ( ) => {
28- setTimeout ( ( ) => {
29- refIsReplaying . current = false ;
30- refBtnReplay . current ?. classList . toggle ( 'disabled' ) ;
31- } , 300 ) ;
32- } ) ;
33- } , [ ] ) ;
41+ const componentName = getDisplayName ( parentCompositeFiber . type ) ?? 'Unknown' ;
3442
35- if ( canEdit ) {
36- return (
37- < button
38- ref = { refBtnReplay }
39- title = "Replay component"
40- className = "react-scan-replay-button"
41- onClick = { handleReplay }
42- >
43- < Icon name = "icon-replay" />
44- </ button >
45- )
46- }
47-
48- return null ;
49- }
43+ const renderCount = reportDataFiber ?. count ?? 0 ;
44+ const renderTime = reportDataFiber ?. time ?? 0 ;
5045
51- export const Header = ( ) => {
52- const refComponentName = useRef < HTMLSpanElement > ( null ) ;
53- const refMetrics = useRef < HTMLSpanElement > ( null ) ;
46+ refComponentName . current . textContent = componentName ;
47+ refMetrics . current . textContent = renderCount > 0
48+ ? `${ renderCount } renders${ renderTime > 0 ? ` • ${ renderTime . toFixed ( 2 ) } ms` : '' } `
49+ : '' ;
50+ } ;
51+
52+ const unsubscribe = Store . lastReportTime . subscribe ( updateMetrics ) ;
53+
54+ return ( ) => {
55+ unsubscribe ( ) ;
56+ } ;
57+ } , [ inspectState ] ) ;
5458
5559 const handleClose = useCallback ( ( ) => {
5660 if ( Store . inspectState . value . propContainer ) {
@@ -61,73 +65,64 @@ export const Header = () => {
6165 }
6266 } , [ ] ) ;
6367
64- const updateHeaderContent = useCallback ( ( ) => {
65- const inspectState = Store . inspectState . value ;
66- if ( inspectState . kind !== 'focused' ) return ;
67-
68- const focusedDomElement = inspectState . focusedDomElement ;
69-
70- if ( ! refComponentName . current || ! refMetrics . current ) return ;
71-
72- const { parentCompositeFiber } = getCompositeComponentFromElement ( focusedDomElement ) ;
73- if ( ! parentCompositeFiber ) return ;
74-
75- const reportDataFiber =
76- Store . reportData . get ( parentCompositeFiber ) ??
77- ( parentCompositeFiber . alternate
78- ? Store . reportData . get ( parentCompositeFiber . alternate )
79- : null ) ;
80-
81- const componentName = getDisplayName ( parentCompositeFiber . type ) ?? 'Unknown' ;
82- const renderCount = reportDataFiber ?. count ?? 0 ;
83- const renderTime = reportDataFiber ?. time ?? 0 ;
68+ const handleReplay = useCallback ( ( e : MouseEvent ) => {
69+ void ( async ( ) => {
70+ e . stopPropagation ( ) ;
71+ if ( isReplaying || inspectState . kind !== 'focused' ) return ;
8472
85- // Update both text content and dataset in a single batch
86- requestAnimationFrame ( ( ) => {
87- if ( ! refComponentName . current || ! refMetrics . current ) return ;
73+ const { parentCompositeFiber } = getCompositeComponentFromElement ( inspectState . focusedDomElement ) ;
74+ if ( ! parentCompositeFiber ) return ;
8875
89- refComponentName . current . dataset . text = componentName ;
90- refMetrics . current . dataset . text = renderCount > 0
91- ? `${ renderCount } renders${ renderTime > 0 ? ` • ${ renderTime . toFixed ( 2 ) } ms` : '' } `
92- : '' ;
93- } ) ;
94- } , [ ] ) ;
76+ const { overrideProps, overrideHookState } = getOverrideMethods ( ) ;
77+ if ( ! overrideProps || ! overrideHookState ) return ;
9578
96- useEffect ( ( ) => {
97- const unsubscribeLastReportTime = Store . lastReportTime . subscribe ( updateHeaderContent ) ;
79+ setIsReplaying ( true ) ;
9880
99- const unsubscribeStoreInspectState = Store . inspectState . subscribe ( state => {
100- if ( state . kind === 'focused' ) {
101- updateHeaderContent ( ) ;
81+ try {
82+ await replayComponent ( parentCompositeFiber ) ;
83+ } finally {
84+ setTimeout ( ( ) => {
85+ setIsReplaying ( false ) ;
86+ } , 300 ) ;
10287 }
103- } ) ;
88+ } ) ( ) ;
89+ } , [ inspectState , isReplaying ] ) ;
10490
105- return ( ) => {
106- unsubscribeLastReportTime ( ) ;
107- unsubscribeStoreInspectState ( ) ;
108- } ;
109- } , [ ] ) ;
91+ const { overrideProps } = getOverrideMethods ( ) ;
92+ const canEdit = ! ! overrideProps ;
11093
11194 return (
112- < div className = "react-scan-header" >
113- < span
114- ref = { refComponentName }
115- className = "with-data-text"
116- />
117- < span
118- ref = { refMetrics }
119- className = "with-data-text mr-auto !overflow-visible text-xs text-[#888]"
120- />
121-
122- < BtnReplay />
123-
124- < button
125- title = "Close"
126- class = "react-scan-close-button"
127- onClick = { handleClose }
128- >
129- < Icon name = "icon-close" />
130- </ button >
95+ < div
96+ className = { cn (
97+ "react-scan-header" ,
98+ "flex" ,
99+ "min-h-9" ,
100+ 'whitespace-nowrap' ,
101+ "overflow-hidden" ,
102+ ) }
103+ >
104+ < div className = "react-scan-header-left overflow-hidden" >
105+ < span ref = { refComponentName } className = "react-scan-component-name" />
106+ < span ref = { refMetrics } className = "react-scan-metrics" />
107+ </ div >
108+ < div class = "react-scan-header-right" >
109+ { canEdit && (
110+ < button
111+ title = "Replay component"
112+ class = { `react-scan-replay-button${ isReplaying ? ' disabled' : '' } ` }
113+ onClick = { handleReplay }
114+ >
115+ < Icon name = "icon-replay" />
116+ </ button >
117+ ) }
118+ < button
119+ title = "Close"
120+ class = "react-scan-close-button"
121+ onClick = { handleClose }
122+ >
123+ < Icon name = "icon-close" />
124+ </ button >
125+ </ div >
131126 </ div >
132127 ) ;
133128} ;
0 commit comments