@@ -12,6 +12,55 @@ const EXPANDED_PATHS = new Set<string>();
1212const fadeOutTimers = new WeakMap < HTMLElement , ReturnType < typeof setTimeout > > ( ) ;
1313const disabledButtons = new Set < HTMLButtonElement > ( ) ;
1414
15+ // Utility to check and unwrap proxies
16+ const isProxy = ( obj : any ) => Object . getPrototypeOf ( obj ) ?. constructor ?. name === 'Proxy' ;
17+
18+ const unwrapProxy = ( proxy : any ) => {
19+ try {
20+ const descriptors = Object . getOwnPropertyDescriptors ( proxy ) ;
21+ const unwrapped = Object . fromEntries (
22+ Object . entries ( descriptors ) . reduce < Array < [ string , any ] > > ( ( acc , [ key , descriptor ] ) => {
23+ if ( key !== 'Symbol(Symbol.iterator)' ) {
24+ acc . push ( [ key , descriptor . value ] ) ;
25+ }
26+ return acc ;
27+ } , [ ] ) ,
28+ ) ;
29+
30+ return unwrapped ;
31+ } catch ( error ) {
32+ return proxy ;
33+ }
34+ } ;
35+
36+
37+
38+ const getProxyValue = ( proxy : any ) => {
39+ try {
40+ if ( ! proxy || typeof proxy !== 'object' ) {
41+ return proxy
42+ } ;
43+
44+ // Handle URLSearchParams-like objects
45+ if ( proxy [ Symbol . iterator ] ) {
46+ try {
47+ return Object . fromEntries ( proxy ) ;
48+ } catch ( err ) {
49+ // Silent fail
50+ }
51+ }
52+
53+ // Handle standard proxies
54+ if ( isProxy ( proxy ) ) {
55+ return unwrapProxy ( proxy ) ;
56+ }
57+
58+ return proxy ;
59+ } catch ( err ) {
60+ return proxy ;
61+ }
62+ } ;
63+
1564export const renderPropsAndState = (
1665 didRender : boolean ,
1766 fiber : any ,
@@ -59,14 +108,12 @@ export const renderPropsAndState = (
59108 canEdit
60109 ? `
61110 <button class="react-scan-replay-button" title="Replay component">
62- <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="rgb(203, 182, 242)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scan-eye"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="1"/><path d="M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0"/></svg>
111+ Replay
63112 </button>
64113 `
65114 : ''
66115 }
67- <button class="react-scan-close-button" title="Close">
68- <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
69- </button>
116+ <button class="react-scan-close-button" title="Close">Close</button>
70117 </div>
71118 ` ;
72119 inspector . appendChild ( header ) ;
@@ -427,6 +474,7 @@ export const createPropertyElement = (
427474 if ( isExpandable ) {
428475 const isExpanded = EXPANDED_PATHS . has ( currentPath ) ;
429476
477+ // Check for circular references first
430478 if ( typeof value === 'object' && value !== null ) {
431479 let paths = objectPathMap . get ( value ) ;
432480 if ( ! paths ) {
@@ -439,6 +487,28 @@ export const createPropertyElement = (
439487 paths . add ( currentPath ) ;
440488 }
441489
490+ const unwrapped = getProxyValue ( value ) ;
491+ const isNonExpandable = ( unwrapped === value &&
492+ value &&
493+ Object . getPrototypeOf ( value ) ?. constructor ?. name === 'Proxy' ) ||
494+ value instanceof Promise ;
495+
496+ // For non-expandable items, render like a simple property
497+ if ( isNonExpandable ) {
498+ const preview = document . createElement ( 'div' ) ;
499+ preview . className = 'react-scan-preview-line' ;
500+ preview . dataset . key = key ;
501+ preview . dataset . section = section ;
502+ preview . innerHTML = `
503+ <span style="width: 8px; display: inline-block"></span>
504+ <span class="react-scan-key">${ key } : </span>
505+ <span class="${ getValueClassName ( value ) } ">${ getValuePreview ( value ) } </span>
506+ ` ;
507+ container . appendChild ( preview ) ;
508+ return container ;
509+ }
510+
511+ // Normal expandable logic for other objects
442512 container . classList . add ( 'react-scan-expandable' ) ;
443513 if ( isExpanded ) {
444514 container . classList . add ( 'react-scan-expanded' ) ;
@@ -519,77 +589,79 @@ export const createPropertyElement = (
519589 }
520590 }
521591
522- arrow . addEventListener ( 'click' , ( e ) => {
523- e . stopPropagation ( ) ;
524- const isExpanding = ! container . classList . contains (
525- 'react-scan-expanded' ,
526- ) ;
527-
528- if ( isExpanding ) {
529- EXPANDED_PATHS . add ( currentPath ) ;
530- container . classList . add ( 'react-scan-expanded' ) ;
531- content . classList . remove ( 'react-scan-hidden' ) ;
532-
533- if ( ! content . hasChildNodes ( ) ) {
534- if ( Array . isArray ( value ) ) {
535- const arrayContainer = document . createElement ( 'div' ) ;
536- arrayContainer . className = 'react-scan-array-container' ;
537- value . forEach ( ( item , index ) => {
538- const el = createPropertyElement (
539- componentName ,
540- didRender ,
541- propsContainer ,
542- fiber ,
543- index . toString ( ) ,
544- item ,
545- section ,
546- level + 1 ,
547- changedKeys ,
548- currentPath ,
549- new WeakMap ( ) ,
550- ) ;
551- if ( ! el ) {
552- return ;
553- }
554- arrayContainer . appendChild ( el ) ;
555- } ) ;
556- content . appendChild ( arrayContainer ) ;
557- } else {
558- Object . entries ( value ) . forEach ( ( [ k , v ] ) => {
559- const el = createPropertyElement (
560- componentName ,
561- didRender ,
562- propsContainer ,
563- fiber ,
564- k ,
565- v ,
566- section ,
567- level + 1 ,
568- changedKeys ,
569- currentPath ,
570- new WeakMap ( ) ,
571- ) ;
572- if ( ! el ) {
573- return ;
574- }
575- content . appendChild ( el ) ;
576- } ) ;
592+ if ( ! isNonExpandable ) {
593+ arrow . addEventListener ( 'click' , ( e ) => {
594+ e . stopPropagation ( ) ;
595+ const isExpanding = ! container . classList . contains (
596+ 'react-scan-expanded' ,
597+ ) ;
598+
599+ if ( isExpanding ) {
600+ EXPANDED_PATHS . add ( currentPath ) ;
601+ container . classList . add ( 'react-scan-expanded' ) ;
602+ content . classList . remove ( 'react-scan-hidden' ) ;
603+
604+ if ( ! content . hasChildNodes ( ) ) {
605+ if ( Array . isArray ( value ) ) {
606+ const arrayContainer = document . createElement ( 'div' ) ;
607+ arrayContainer . className = 'react-scan-array-container' ;
608+ value . forEach ( ( item , index ) => {
609+ const el = createPropertyElement (
610+ componentName ,
611+ didRender ,
612+ propsContainer ,
613+ fiber ,
614+ index . toString ( ) ,
615+ item ,
616+ section ,
617+ level + 1 ,
618+ changedKeys ,
619+ currentPath ,
620+ new WeakMap ( ) ,
621+ ) ;
622+ if ( ! el ) {
623+ return ;
624+ }
625+ arrayContainer . appendChild ( el ) ;
626+ } ) ;
627+ content . appendChild ( arrayContainer ) ;
628+ } else {
629+ Object . entries ( value ) . forEach ( ( [ k , v ] ) => {
630+ const el = createPropertyElement (
631+ componentName ,
632+ didRender ,
633+ propsContainer ,
634+ fiber ,
635+ k ,
636+ v ,
637+ section ,
638+ level + 1 ,
639+ changedKeys ,
640+ currentPath ,
641+ new WeakMap ( ) ,
642+ ) ;
643+ if ( ! el ) {
644+ return ;
645+ }
646+ content . appendChild ( el ) ;
647+ } ) ;
648+ }
577649 }
650+ } else {
651+ EXPANDED_PATHS . delete ( currentPath ) ;
652+ container . classList . remove ( 'react-scan-expanded' ) ;
653+ content . classList . add ( 'react-scan-hidden' ) ;
578654 }
579- } else {
580- EXPANDED_PATHS . delete ( currentPath ) ;
581- container . classList . remove ( 'react-scan-expanded' ) ;
582- content . classList . add ( 'react-scan-hidden' ) ;
583- }
584655
585- requestAnimationFrame ( ( ) => {
586- const inspector = propsContainer . firstElementChild as HTMLElement ;
587- if ( inspector ) {
588- const contentHeight = inspector . getBoundingClientRect ( ) . height ;
589- propsContainer . style . maxHeight = `${ contentHeight } px` ;
590- }
656+ requestAnimationFrame ( ( ) => {
657+ const inspector = propsContainer . firstElementChild as HTMLElement ;
658+ if ( inspector ) {
659+ const contentHeight = inspector . getBoundingClientRect ( ) . height ;
660+ propsContainer . style . maxHeight = `${ contentHeight } px` ;
661+ }
662+ } ) ;
591663 } ) ;
592- } ) ;
664+ }
593665 } else {
594666 const preview = document . createElement ( 'div' ) ;
595667 preview . className = 'react-scan-preview-line' ;
@@ -737,14 +809,35 @@ export const getValuePreview = (value: any) => {
737809 case 'boolean' :
738810 return value . toString ( ) ;
739811 case 'object' : {
740- if ( value instanceof Promise ) {
741- return 'Promise' ;
742- }
743- const keys = Object . keys ( value ) ;
744- if ( keys . length <= 3 ) {
745- return `{${ keys . join ( ', ' ) } }` ;
812+ try {
813+ if ( value === null ) return 'null' ;
814+ if ( value === undefined ) return 'undefined' ;
815+
816+ // Handle special built-in types first
817+ if ( value instanceof Promise ) return '[Promise]' ;
818+ if ( value instanceof Set ) return `Set(${ value . size } )` ;
819+ if ( value instanceof Map ) return `Map(${ value . size } )` ;
820+
821+ // Try to unwrap proxy values
822+ const proto = Object . getPrototypeOf ( value ) ;
823+ if ( proto ?. constructor ?. name === 'Proxy' ) {
824+ const unwrapped = getProxyValue ( value ) ;
825+ if ( unwrapped !== value ) {
826+ const keys = Object . keys ( unwrapped ) ;
827+ return `Proxy{${ keys . join ( ', ' ) } }` ;
828+ }
829+ return '[Next.js Params]' ;
830+ }
831+
832+ // Handle regular objects
833+ const keys = Object . keys ( value ) ;
834+ if ( keys . length <= 3 ) {
835+ return `{${ keys . join ( ', ' ) } }` ;
836+ }
837+ return `{${ keys . slice ( 0 , 3 ) . join ( ', ' ) } , ...}` ;
838+ } catch ( error ) {
839+ return '{...}' ;
746840 }
747- return `{${ keys . slice ( 0 , 3 ) . join ( ', ' ) } , ...}` ;
748841 }
749842 default :
750843 return typeof value ;
0 commit comments