@@ -15,9 +15,11 @@ import { untrack } from '../../runtime.js';
1515import {
1616 block ,
1717 branch ,
18+ destroy_effect ,
1819 effect ,
20+ run_out_transitions ,
21+ pause_children ,
1922 pause_effect ,
20- pause_effects ,
2123 resume_effect
2224} from '../../reactivity/effects.js' ;
2325import { source , mutable_source , set } from '../../reactivity/sources.js' ;
@@ -39,6 +41,39 @@ export function set_current_each_item(item) {
3941 current_each_item = item ;
4042}
4143
44+ /**
45+ * Pause multiple effects simultaneously, and coordinate their
46+ * subsequent destruction. Used in each blocks
47+ * @param {import('#client').Effect[] } effects
48+ * @param {null | Node } controlled_anchor
49+ * @param {() => void } [callback]
50+ */
51+ function pause_effects ( effects , controlled_anchor , callback ) {
52+ /** @type {import('#client').TransitionManager[] } */
53+ var transitions = [ ] ;
54+ var length = effects . length ;
55+
56+ for ( var i = 0 ; i < length ; i ++ ) {
57+ pause_children ( effects [ i ] , transitions , true ) ;
58+ }
59+
60+ // If we have a controlled anchor, it means that the each block is inside a single
61+ // DOM element, so we can apply a fast-path for clearing the contents of the element.
62+ if ( effects . length > 0 && transitions . length === 0 && controlled_anchor !== null ) {
63+ var parent_node = /** @type {Element } */ ( controlled_anchor . parentNode ) ;
64+ parent_node . textContent = '' ;
65+ parent_node . append ( controlled_anchor ) ;
66+ }
67+
68+ run_out_transitions ( transitions , ( ) => {
69+ for ( var i = 0 ; i < length ; i ++ ) {
70+ destroy_effect ( effects [ i ] ) ;
71+ }
72+
73+ if ( callback !== undefined ) callback ( ) ;
74+ } ) ;
75+ }
76+
4277/**
4378 * @template V
4479 * @param {Element | Comment } anchor The next sibling node, or the parent node if this is a 'controlled' block
@@ -145,7 +180,6 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re
145180 }
146181
147182 if ( ! hydrating ) {
148- // TODO add 'empty controlled block' optimisation here
149183 reconcile_fn ( array , state , anchor , render_fn , flags , keys ) ;
150184 }
151185
@@ -244,7 +278,9 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
244278 effects . push ( a_items [ i ] . e ) ;
245279 }
246280
247- pause_effects ( effects , ( ) => {
281+ var controlled_anchor = ( flags & EACH_IS_CONTROLLED ) !== 0 && b === 0 ? anchor : null ;
282+
283+ pause_effects ( effects , controlled_anchor , ( ) => {
248284 state . items . length = b ;
249285 } ) ;
250286 }
@@ -274,6 +310,7 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
274310
275311 var is_animated = ( flags & EACH_IS_ANIMATED ) !== 0 ;
276312 var should_update = ( flags & ( EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE ) ) !== 0 ;
313+ var is_controlled = ( flags & EACH_IS_CONTROLLED ) !== 0 ;
277314 var start = 0 ;
278315 var item ;
279316
@@ -381,6 +418,11 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
381418 // I fully understand this part)
382419 if ( moved ) {
383420 mark_lis ( sources ) ;
421+ } else if ( is_controlled && to_destroy . length === a_items . length ) {
422+ // We can optimize the case in which all items are replaced —
423+ // destroy everything first, then append new items
424+ pause_effects ( to_destroy , anchor ) ;
425+ to_destroy = [ ] ;
384426 }
385427
386428 // working from the back, insert new or moved items
@@ -421,9 +463,9 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
421463 } ) ;
422464 }
423465
424- // TODO: would be good to avoid this closure in the case where we have no
425- // transitions at all. It would make it far more JIT friendly in the hot cases.
426- pause_effects ( to_destroy , ( ) => {
466+ var controlled_anchor = is_controlled && b_items . length === 0 ? anchor : null ;
467+
468+ pause_effects ( to_destroy , controlled_anchor , ( ) => {
427469 state . items = b_items ;
428470 } ) ;
429471}
0 commit comments