@@ -38,6 +38,7 @@ export class BaseNode<T> {
3838 private _previousSibling : ElementNode < T > | null = null ;
3939 private _nextSibling : ElementNode < T > | null = null ;
4040 private _parentNode : BaseNode < T > | null = null ;
41+ private _minInvalidChildIndex : ElementNode < T > | null = null ;
4142 ownerDocument : Document < T , any > ;
4243
4344 constructor ( ownerDocument : Document < T , any > ) {
@@ -101,6 +102,21 @@ export class BaseNode<T> {
101102 return this . parentNode ?. isConnected || false ;
102103 }
103104
105+ private invalidateChildIndices ( child : ElementNode < T > ) {
106+ if ( this . _minInvalidChildIndex == null || child . index < this . _minInvalidChildIndex . index ) {
107+ this . _minInvalidChildIndex = child ;
108+ }
109+ }
110+
111+ updateChildIndices ( ) {
112+ let node = this . _minInvalidChildIndex ;
113+ while ( node ) {
114+ node . index = node . previousSibling ? node . previousSibling . index + 1 : 0 ;
115+ node = node . nextSibling ;
116+ }
117+ this . _minInvalidChildIndex = null ;
118+ }
119+
104120 appendChild ( child : ElementNode < T > ) {
105121 this . ownerDocument . startTransaction ( ) ;
106122 if ( child . parentNode ) {
@@ -158,11 +174,7 @@ export class BaseNode<T> {
158174 referenceNode . previousSibling = newNode ;
159175 newNode . parentNode = referenceNode . parentNode ;
160176
161- let node : ElementNode < T > | null = referenceNode ;
162- while ( node ) {
163- node . index ++ ;
164- node = node . nextSibling ;
165- }
177+ this . invalidateChildIndices ( referenceNode ) ;
166178
167179 if ( newNode . hasSetProps ) {
168180 this . ownerDocument . addNode ( newNode ) ;
@@ -178,13 +190,9 @@ export class BaseNode<T> {
178190 }
179191
180192 this . ownerDocument . startTransaction ( ) ;
181- let node = child . nextSibling ;
182- while ( node ) {
183- node . index -- ;
184- node = node . nextSibling ;
185- }
186-
193+
187194 if ( child . nextSibling ) {
195+ this . invalidateChildIndices ( child . nextSibling ) ;
188196 child . nextSibling . previousSibling = child . previousSibling ;
189197 }
190198
@@ -330,6 +338,8 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
330338 private mutatedNodes : Set < ElementNode < T > > = new Set ( ) ;
331339 private subscriptions : Set < ( ) => void > = new Set ( ) ;
332340 private transactionCount = 0 ;
341+ private queuedRender = false ;
342+ private inSubscription = false ;
333343
334344 constructor ( collection : C ) {
335345 // @ts -ignore
@@ -412,10 +422,22 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
412422 }
413423
414424 this . updateCollection ( ) ;
425+
426+ // Reset queuedRender to false when getCollection is called during render.
427+ if ( ! this . inSubscription ) {
428+ this . queuedRender = false ;
429+ }
430+
415431 return this . collection ;
416432 }
417433
418434 updateCollection ( ) {
435+ // First, update the indices of dirty element children.
436+ for ( let element of this . dirtyNodes ) {
437+ element . updateChildIndices ( ) ;
438+ }
439+
440+ // Next, update dirty collection nodes.
419441 for ( let element of this . dirtyNodes ) {
420442 if ( element instanceof ElementNode && element . isConnected ) {
421443 element . updateNode ( ) ;
@@ -424,6 +446,7 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
424446
425447 this . dirtyNodes . clear ( ) ;
426448
449+ // Finally, update the collection.
427450 if ( this . mutatedNodes . size || this . collectionMutated ) {
428451 let collection = this . getMutableCollection ( ) ;
429452 for ( let element of this . mutatedNodes ) {
@@ -442,13 +465,21 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
442465 queueUpdate ( ) {
443466 // Don't emit any updates if there is a transaction in progress.
444467 // queueUpdate should be called again after the transaction.
445- if ( this . dirtyNodes . size === 0 || this . transactionCount > 0 ) {
468+ if ( this . dirtyNodes . size === 0 || this . transactionCount > 0 || this . queuedRender ) {
446469 return ;
447470 }
448471
472+ // Only trigger subscriptions once during an update, when the first item changes.
473+ // React's useSyncExternalStore will call getCollection immediately, to check whether the snapshot changed.
474+ // If so, React will queue a render to happen after the current commit to our fake DOM finishes.
475+ // We track whether getCollection is called in a subscription, and once it is called during render,
476+ // we reset queuedRender back to false.
477+ this . queuedRender = true ;
478+ this . inSubscription = true ;
449479 for ( let fn of this . subscriptions ) {
450480 fn ( ) ;
451481 }
482+ this . inSubscription = false ;
452483 }
453484
454485 subscribe ( fn : ( ) => void ) {
0 commit comments