@@ -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 ,
@@ -427,6 +476,7 @@ export const createPropertyElement = (
427476 if ( isExpandable ) {
428477 const isExpanded = EXPANDED_PATHS . has ( currentPath ) ;
429478
479+ // Check for circular references first
430480 if ( typeof value === 'object' && value !== null ) {
431481 let paths = objectPathMap . get ( value ) ;
432482 if ( ! paths ) {
@@ -439,6 +489,28 @@ export const createPropertyElement = (
439489 paths . add ( currentPath ) ;
440490 }
441491
492+ const unwrapped = getProxyValue ( value ) ;
493+ const isNonExpandable = ( unwrapped === value &&
494+ value &&
495+ Object . getPrototypeOf ( value ) ?. constructor ?. name === 'Proxy' ) ||
496+ value instanceof Promise ;
497+
498+ // For non-expandable items, render like a simple property
499+ if ( isNonExpandable ) {
500+ const preview = document . createElement ( 'div' ) ;
501+ preview . className = 'react-scan-preview-line' ;
502+ preview . dataset . key = key ;
503+ preview . dataset . section = section ;
504+ preview . innerHTML = `
505+ <span style="width: 8px; display: inline-block"></span>
506+ <span class="react-scan-key">${ key } : </span>
507+ <span class="${ getValueClassName ( value ) } ">${ getValuePreview ( value ) } </span>
508+ ` ;
509+ container . appendChild ( preview ) ;
510+ return container ;
511+ }
512+
513+ // Normal expandable logic for other objects
442514 container . classList . add ( 'react-scan-expandable' ) ;
443515 if ( isExpanded ) {
444516 container . classList . add ( 'react-scan-expanded' ) ;
@@ -519,77 +591,79 @@ export const createPropertyElement = (
519591 }
520592 }
521593
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- } ) ;
594+ if ( ! isNonExpandable ) {
595+ arrow . addEventListener ( 'click' , ( e ) => {
596+ e . stopPropagation ( ) ;
597+ const isExpanding = ! container . classList . contains (
598+ 'react-scan-expanded' ,
599+ ) ;
600+
601+ if ( isExpanding ) {
602+ EXPANDED_PATHS . add ( currentPath ) ;
603+ container . classList . add ( 'react-scan-expanded' ) ;
604+ content . classList . remove ( 'react-scan-hidden' ) ;
605+
606+ if ( ! content . hasChildNodes ( ) ) {
607+ if ( Array . isArray ( value ) ) {
608+ const arrayContainer = document . createElement ( 'div' ) ;
609+ arrayContainer . className = 'react-scan-array-container' ;
610+ value . forEach ( ( item , index ) => {
611+ const el = createPropertyElement (
612+ componentName ,
613+ didRender ,
614+ propsContainer ,
615+ fiber ,
616+ index . toString ( ) ,
617+ item ,
618+ section ,
619+ level + 1 ,
620+ changedKeys ,
621+ currentPath ,
622+ new WeakMap ( ) ,
623+ ) ;
624+ if ( ! el ) {
625+ return ;
626+ }
627+ arrayContainer . appendChild ( el ) ;
628+ } ) ;
629+ content . appendChild ( arrayContainer ) ;
630+ } else {
631+ Object . entries ( value ) . forEach ( ( [ k , v ] ) => {
632+ const el = createPropertyElement (
633+ componentName ,
634+ didRender ,
635+ propsContainer ,
636+ fiber ,
637+ k ,
638+ v ,
639+ section ,
640+ level + 1 ,
641+ changedKeys ,
642+ currentPath ,
643+ new WeakMap ( ) ,
644+ ) ;
645+ if ( ! el ) {
646+ return ;
647+ }
648+ content . appendChild ( el ) ;
649+ } ) ;
650+ }
577651 }
652+ } else {
653+ EXPANDED_PATHS . delete ( currentPath ) ;
654+ container . classList . remove ( 'react-scan-expanded' ) ;
655+ content . classList . add ( 'react-scan-hidden' ) ;
578656 }
579- } else {
580- EXPANDED_PATHS . delete ( currentPath ) ;
581- container . classList . remove ( 'react-scan-expanded' ) ;
582- content . classList . add ( 'react-scan-hidden' ) ;
583- }
584657
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- }
658+ requestAnimationFrame ( ( ) => {
659+ const inspector = propsContainer . firstElementChild as HTMLElement ;
660+ if ( inspector ) {
661+ const contentHeight = inspector . getBoundingClientRect ( ) . height ;
662+ propsContainer . style . maxHeight = `${ contentHeight } px` ;
663+ }
664+ } ) ;
591665 } ) ;
592- } ) ;
666+ }
593667 } else {
594668 const preview = document . createElement ( 'div' ) ;
595669 preview . className = 'react-scan-preview-line' ;
@@ -737,14 +811,35 @@ export const getValuePreview = (value: any) => {
737811 case 'boolean' :
738812 return value . toString ( ) ;
739813 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 ( ', ' ) } }` ;
814+ try {
815+ if ( value === null ) return 'null' ;
816+ if ( value === undefined ) return 'undefined' ;
817+
818+ // Handle special built-in types first
819+ if ( value instanceof Promise ) return '[Promise]' ;
820+ if ( value instanceof Set ) return `Set(${ value . size } )` ;
821+ if ( value instanceof Map ) return `Map(${ value . size } )` ;
822+
823+ // Try to unwrap proxy values
824+ const proto = Object . getPrototypeOf ( value ) ;
825+ if ( proto ?. constructor ?. name === 'Proxy' ) {
826+ const unwrapped = getProxyValue ( value ) ;
827+ if ( unwrapped !== value ) {
828+ const keys = Object . keys ( unwrapped ) ;
829+ return `Proxy{${ keys . join ( ', ' ) } }` ;
830+ }
831+ return '[Next.js Params]' ;
832+ }
833+
834+ // Handle regular objects
835+ const keys = Object . keys ( value ) ;
836+ if ( keys . length <= 3 ) {
837+ return `{${ keys . join ( ', ' ) } }` ;
838+ }
839+ return `{${ keys . slice ( 0 , 3 ) . join ( ', ' ) } , ...}` ;
840+ } catch ( error ) {
841+ return '{...}' ;
746842 }
747- return `{${ keys . slice ( 0 , 3 ) . join ( ', ' ) } , ...}` ;
748843 }
749844 default :
750845 return typeof value ;
0 commit comments