@@ -662,10 +662,49 @@ export default class XElement extends HTMLElement {
662662 observeMap . set ( property , { value : undefined } ) ;
663663 }
664664 }
665- XElement . #hosts . set ( host , {
665+ const hostInfo = {
666666 initialized : false , reflecting : false , invalidProperties, listenerMap,
667667 renderRoot, render, template, properties, internal, computeMap,
668668 observeMap, defaultMap, valueMap,
669+ } ;
670+ XElement . #hosts. set ( host , hostInfo ) ;
671+ XElement . #upgradeOwnProperties( host ) ;
672+ // Define properties during construction for React 19 introspection (#331).
673+ for ( const property of propertyMap . values ( ) ) {
674+ if ( ! property . internal ) {
675+ if ( Reflect . has ( host , property . key ) ) {
676+ valueMap . set ( property , host [ property . key ] ) ;
677+ }
678+ XElement . #defineProperty( host , property ) ;
679+ }
680+ }
681+ }
682+
683+ // Called during host construction.
684+ static #defineProperty( host , property ) {
685+ const hostInfo = XElement . #hosts. get ( host ) ;
686+ const { valueMap } = hostInfo ;
687+ const { key } = property ;
688+ Reflect . defineProperty ( host , key , {
689+ configurable : false ,
690+ enumerable : true ,
691+ get ( ) {
692+ const { initialized } = hostInfo ;
693+ if ( initialized ) {
694+ return XElement . #getPropertyValue( host , property ) ;
695+ } else {
696+ return valueMap . get ( property ) ;
697+ }
698+ } ,
699+ set ( value ) {
700+ const { initialized } = hostInfo ;
701+ if ( initialized ) {
702+ XElement . #validatePropertyMutable( host , property ) ;
703+ XElement . #setPropertyValue( host , property , value ) ;
704+ } else {
705+ valueMap . set ( property , value ) ;
706+ }
707+ } ,
669708 } ) ;
670709 }
671710
@@ -765,19 +804,21 @@ export default class XElement extends HTMLElement {
765804
766805 static #initializeHost( host ) {
767806 const hostInfo = XElement . #hosts. get ( host ) ;
768- const { computeMap, initialized, invalidProperties } = hostInfo ;
807+ const { computeMap, initialized, invalidProperties, valueMap } = hostInfo ;
769808 if ( initialized === false ) {
770- XElement . #upgradeOwnProperties( host ) ;
771809 // Only reflect attributes when the element is connected.
772810 const { propertyMap } = XElement . #constructors. get ( host . constructor ) ;
773811 for ( const property of propertyMap . values ( ) ) {
774812 const { value, found } = XElement . #getPreUpgradePropertyValue( host , property ) ;
775- XElement . #initializeProperty( host , property ) ;
776813 if ( found ) {
777- host [ property . key ] = property . default ( host , property . initial ( value ) ) ;
814+ XElement . #validatePropertyMutable( host , property ) ;
815+ const initialValue = property . default ( host , property . initial ( value ) ) ;
816+ XElement . #validatePropertyValue( host , property , initialValue ) ;
817+ valueMap . set ( property , initialValue ) ;
778818 } else if ( ! property . compute ) {
779- // Set to a nullish value so that it coalesces to the default.
780- XElement . #setPropertyValue( host , property , property . default ( host , property . initial ( ) ) ) ;
819+ const initialValue = property . default ( host , property . initial ( value ) ) ;
820+ XElement . #validatePropertyValue( host , property , initialValue ) ;
821+ valueMap . set ( property , initialValue ) ;
781822 }
782823 invalidProperties . add ( property ) ;
783824 if ( property . compute ) {
@@ -804,13 +845,15 @@ export default class XElement extends HTMLElement {
804845 // Process possible sources of initial state, with this priority:
805846 // 1. imperative, e.g. `element.prop = 'value';`
806847 // 2. declarative, e.g. `<element prop="value"></element>`
807- const { key , attribute, internal } = property ;
848+ const { attribute, internal } = property ;
808849 let value ;
809850 let found = false ;
810851 if ( ! internal ) {
811852 // Only look for public (i.e., non-internal) properties.
812- if ( Reflect . has ( host , key ) ) {
813- value = host [ key ] ;
853+ const { valueMap } = XElement . #hosts. get ( host ) ;
854+ if ( valueMap . has ( property ) ) {
855+ value = valueMap . get ( property ) ;
856+ valueMap . delete ( property ) ;
814857 found = true ;
815858 } else if ( attribute && host . hasAttribute ( attribute ) ) {
816859 const attributeValue = host . getAttribute ( attribute ) ;
@@ -821,25 +864,6 @@ export default class XElement extends HTMLElement {
821864 return { value, found } ;
822865 }
823866
824- static #initializeProperty( host , property ) {
825- if ( ! property . internal ) {
826- const { key, compute, readOnly } = property ;
827- const path = `${ host . constructor . name } .properties.${ key } ` ;
828- const get = ( ) => XElement . #getPropertyValue( host , property ) ;
829- const set = compute || readOnly
830- ? ( ) => {
831- if ( compute ) {
832- throw new Error ( `Property "${ path } " is computed (computed properties are read-only).` ) ;
833- } else {
834- throw new Error ( `Property "${ path } " is read-only.` ) ;
835- }
836- }
837- : value => XElement . #setPropertyValue( host , property , value ) ;
838- Reflect . deleteProperty ( host , key ) ;
839- Reflect . defineProperty ( host , key , { get, set, enumerable : true } ) ;
840- }
841- }
842-
843867 static #addListener( host , element , type , callback , options ) {
844868 callback = XElement . #getListener( host , callback ) ;
845869 element . addEventListener ( type , callback , options ) ;
@@ -907,20 +931,18 @@ export default class XElement extends HTMLElement {
907931 }
908932
909933 static #invalidateProperty( host , property ) {
910- const { initialized, invalidProperties, computeMap } = XElement . #hosts. get ( host ) ;
911- if ( initialized ) {
912- for ( const output of property . output ) {
913- XElement . #invalidateProperty( host , output ) ;
914- }
915- const queueUpdate = invalidProperties . size === 0 ;
916- invalidProperties . add ( property ) ;
917- if ( property . compute ) {
918- computeMap . get ( property ) . valid = false ;
919- }
920- if ( queueUpdate ) {
921- // Batch on microtask to allow multiple, synchronous changes.
922- queueMicrotask ( ( ) => XElement . #updateHost( host ) ) ;
923- }
934+ const { invalidProperties, computeMap } = XElement . #hosts. get ( host ) ;
935+ for ( const output of property . output ) {
936+ XElement . #invalidateProperty( host , output ) ;
937+ }
938+ const queueUpdate = invalidProperties . size === 0 ;
939+ invalidProperties . add ( property ) ;
940+ if ( property . compute ) {
941+ computeMap . get ( property ) . valid = false ;
942+ }
943+ if ( queueUpdate ) {
944+ // Batch on microtask to allow multiple, synchronous changes.
945+ queueMicrotask ( ( ) => XElement . #updateHost( host ) ) ;
924946 }
925947 }
926948
@@ -929,6 +951,18 @@ export default class XElement extends HTMLElement {
929951 return property . compute ?. ( host ) ?? valueMap . get ( property ) ;
930952 }
931953
954+ static #validatePropertyMutable( host , property ) {
955+ const { compute, readOnly, key } = property ;
956+ if ( compute ) {
957+ const path = `${ host . constructor . name } .properties.${ key } ` ;
958+ throw new Error ( `Property "${ path } " is computed (computed properties are read-only).` ) ;
959+ }
960+ if ( readOnly ) {
961+ const path = `${ host . constructor . name } .properties.${ key } ` ;
962+ throw new Error ( `Property "${ path } " is read-only.` ) ;
963+ }
964+ }
965+
932966 static #validatePropertyValue( host , property , value ) {
933967 if ( property . type && XElement . #notNullish( value ) ) {
934968 if ( XElement . #typeIsWrong( property . type , value ) ) {
0 commit comments