1- import { h , cloneElement , render , hydrate } from 'preact' ;
1+ import {
2+ h ,
3+ cloneElement ,
4+ render ,
5+ hydrate ,
6+ options as preactOptions ,
7+ } from 'preact' ;
28
39/**
410 * @typedef {import('./internal.d.ts').PreactCustomElement } PreactCustomElement
@@ -12,6 +18,7 @@ export default function register(Component, tagName, propNames, options) {
1218 const inst = /** @type {PreactCustomElement } */ (
1319 Reflect . construct ( HTMLElement , [ ] , PreactElement )
1420 ) ;
21+ inst . _options = options ;
1522 inst . _vdomComponent = Component ;
1623
1724 if ( options && options . shadow ) {
@@ -31,9 +38,7 @@ export default function register(Component, tagName, propNames, options) {
3138 }
3239 PreactElement . prototype = Object . create ( HTMLElement . prototype ) ;
3340 PreactElement . prototype . constructor = PreactElement ;
34- PreactElement . prototype . connectedCallback = function ( ) {
35- connectedCallback . call ( this , options ) ;
36- } ;
41+ PreactElement . prototype . connectedCallback = connectedCallback ;
3742 PreactElement . prototype . attributeChangedCallback = attributeChangedCallback ;
3843 PreactElement . prototype . disconnectedCallback = disconnectedCallback ;
3944
@@ -54,10 +59,16 @@ export default function register(Component, tagName, propNames, options) {
5459 propNames . forEach ( ( name ) => {
5560 Object . defineProperty ( PreactElement . prototype , name , {
5661 get ( ) {
57- return this . _vdom ? this . _vdom . props [ name ] : this . _props [ name ] ;
62+ if ( this . _pendingProps && name in this . _pendingProps ) {
63+ return this . _pendingProps [ name ] ;
64+ }
65+
66+ return this . _vdom
67+ ? this . _vdom . props [ name ]
68+ : this . _props && this . _props [ name ] ;
5869 } ,
5970 set ( v ) {
60- if ( this . _vdom ) {
71+ if ( this . _vdom || this . isConnected ) {
6172 this . attributeChangedCallback ( name , null , v ) ;
6273 } else {
6374 if ( ! this . _props ) this . _props = { } ;
@@ -94,9 +105,9 @@ function ContextProvider(props) {
94105}
95106
96107/**
97- * @this {PreactCustomElement}
108+ * @param {PreactCustomElement } element
98109 */
99- function connectedCallback ( options ) {
110+ function createVdom ( element ) {
100111 // Obtain a reference to the previous context by pinging the nearest
101112 // higher up node that was rendered with Preact. If one Preact component
102113 // higher up receives our ping, it will set the `detail` property of
@@ -107,15 +118,22 @@ function connectedCallback(options) {
107118 bubbles : true ,
108119 cancelable : true ,
109120 } ) ;
110- this . dispatchEvent ( event ) ;
121+ element . dispatchEvent ( event ) ;
111122 const context = event . detail . context ;
112123
113- this . _vdom = h (
124+ return h (
114125 ContextProvider ,
115- { ...this . _props , context } ,
116- toVdom ( this , this . _vdomComponent , options )
126+ { ...element . _props , context } ,
127+ toVdom ( element , element . _vdomComponent , element . _options )
117128 ) ;
118- ( this . hasAttribute ( 'hydrate' ) ? hydrate : render ) ( this . _vdom , this . _root ) ;
129+ }
130+
131+ /**
132+ * @this {PreactCustomElement}
133+ */
134+ function connectedCallback ( ) {
135+ this . _vdom = null ;
136+ enqueueRender ( this ) ;
119137}
120138
121139/**
@@ -127,6 +145,49 @@ function toCamelCase(str) {
127145 return str . replace ( / - ( \w ) / g, ( _ , c ) => ( c ? c . toUpperCase ( ) : '' ) ) ;
128146}
129147
148+ /**
149+ * @param {PreactCustomElement } element
150+ */
151+ function enqueueRender ( element ) {
152+ if ( element . _renderQueued ) return ;
153+
154+ element . _renderQueued = true ;
155+ ( preactOptions . debounceRendering || queueMicrotask ) ( ( ) => {
156+ element . _renderQueued = false ;
157+ flushRender ( element ) ;
158+ } ) ;
159+ }
160+
161+ /**
162+ * @param {PreactCustomElement } element
163+ */
164+ function flushRender ( element ) {
165+ if ( ! element . isConnected ) {
166+ element . _pendingProps = null ;
167+
168+ if ( ! element . _vdom ) return ;
169+
170+ element . _mounted = false ;
171+ render ( ( element . _vdom = null ) , element . _root ) ;
172+ return ;
173+ }
174+
175+ if ( ! element . _vdom ) {
176+ element . _vdom = createVdom ( element ) ;
177+ element . _pendingProps = null ;
178+ } else if ( element . _pendingProps ) {
179+ const props = element . _pendingProps ;
180+ element . _pendingProps = null ;
181+ element . _vdom = cloneElement ( element . _vdom , props ) ;
182+ }
183+
184+ ( element . _mounted || ! element . hasAttribute ( 'hydrate' ) ? render : hydrate ) (
185+ element . _vdom ,
186+ element . _root
187+ ) ;
188+ element . _mounted = true ;
189+ }
190+
130191/**
131192 * Changed whenver an attribute of the HTML element changed
132193 * @this {PreactCustomElement}
@@ -135,24 +196,31 @@ function toCamelCase(str) {
135196 * @param {unknown } newValue The new value
136197 */
137198function attributeChangedCallback ( name , oldValue , newValue ) {
138- if ( ! this . _vdom ) return ;
139199 // Attributes use `null` as an empty value whereas `undefined` is more
140200 // common in pure JS components, especially with default parameters.
141201 // When calling `node.removeAttribute()` we'll receive `null` as the new
142202 // value. See issue #50.
143203 newValue = newValue == null ? undefined : newValue ;
144- const props = { } ;
145- props [ name ] = newValue ;
146- props [ toCamelCase ( name ) ] = newValue ;
147- this . _vdom = cloneElement ( this . _vdom , props ) ;
148- render ( this . _vdom , this . _root ) ;
204+ if ( ! this . _props ) this . _props = { } ;
205+ this . _props [ name ] = newValue ;
206+ this . _props [ toCamelCase ( name ) ] = newValue ;
207+
208+ if ( this . _vdom ) {
209+ if ( ! this . _pendingProps ) this . _pendingProps = { } ;
210+ this . _pendingProps [ name ] = newValue ;
211+ this . _pendingProps [ toCamelCase ( name ) ] = newValue ;
212+ }
213+
214+ if ( this . isConnected ) {
215+ enqueueRender ( this ) ;
216+ }
149217}
150218
151219/**
152220 * @this {PreactCustomElement}
153221 */
154222function disconnectedCallback ( ) {
155- render ( ( this . _vdom = null ) , this . _root ) ;
223+ enqueueRender ( this ) ;
156224}
157225
158226/**
0 commit comments