@@ -15,14 +15,17 @@ import { untrack } from '../../runtime.js';
15
15
import {
16
16
block ,
17
17
branch ,
18
+ destroy_effect ,
18
19
effect ,
20
+ out ,
21
+ pause_children ,
19
22
pause_effect ,
20
- pause_effects ,
21
23
resume_effect
22
24
} from '../../reactivity/effects.js' ;
23
25
import { source , mutable_source , set } from '../../reactivity/sources.js' ;
24
26
import { is_array , is_frozen , map_get , map_set } from '../../utils.js' ;
25
27
import { STATE_SYMBOL } from '../../constants.js' ;
28
+ import { noop } from '../../../shared/utils.js' ;
26
29
27
30
var NEW_ITEM = - 1 ;
28
31
var LIS_ITEM = - 2 ;
@@ -39,6 +42,38 @@ export function set_current_each_item(item) {
39
42
current_each_item = item ;
40
43
}
41
44
45
+ /**
46
+ * Pause multiple effects simultaneously, and coordinate their
47
+ * subsequent destruction. Used in each blocks
48
+ * @param {import('#client').Effect[] } effects
49
+ * @param {null | Node } controlled_anchor
50
+ * @param {() => void } callback
51
+ */
52
+ function pause_effects ( effects , controlled_anchor , callback = noop ) {
53
+ /** @type {import('#client').TransitionManager[] } */
54
+ var transitions = [ ] ;
55
+ var length = effects . length ;
56
+
57
+ for ( var i = 0 ; i < length ; i ++ ) {
58
+ pause_children ( effects [ i ] , transitions , true ) ;
59
+ }
60
+
61
+ // If we have a controlled anchor, it means that the each block is inside a single
62
+ // DOM element, so we can apply a fast-path for clearing the contents of the element.
63
+ if ( effects . length > 0 && transitions . length === 0 && controlled_anchor !== null ) {
64
+ const each_element = /** @type {Element } */ ( controlled_anchor . parentNode ) ;
65
+ each_element . textContent = '' ;
66
+ each_element . append ( controlled_anchor ) ;
67
+ }
68
+
69
+ out ( transitions , ( ) => {
70
+ for ( var i = 0 ; i < length ; i ++ ) {
71
+ destroy_effect ( effects [ i ] ) ;
72
+ }
73
+ callback ( ) ;
74
+ } ) ;
75
+ }
76
+
42
77
/**
43
78
* @template V
44
79
* @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
145
180
}
146
181
147
182
if ( ! hydrating ) {
148
- // TODO add 'empty controlled block' optimisation here
149
183
reconcile_fn ( array , state , anchor , render_fn , flags , keys ) ;
150
184
}
151
185
@@ -244,7 +278,9 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
244
278
effects . push ( a_items [ i ] . e ) ;
245
279
}
246
280
247
- pause_effects ( effects , ( ) => {
281
+ var controlled_anchor = ( flags & EACH_IS_CONTROLLED ) !== 0 && b === 0 ? anchor : null ;
282
+
283
+ pause_effects ( effects , controlled_anchor , ( ) => {
248
284
state . items . length = b ;
249
285
} ) ;
250
286
}
@@ -274,6 +310,7 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
274
310
275
311
var is_animated = ( flags & EACH_IS_ANIMATED ) !== 0 ;
276
312
var should_update = ( flags & ( EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE ) ) !== 0 ;
313
+ var is_controlled = ( flags & EACH_IS_CONTROLLED ) !== 0 ;
277
314
var start = 0 ;
278
315
var item ;
279
316
@@ -381,6 +418,12 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
381
418
// I fully understand this part)
382
419
if ( moved ) {
383
420
mark_lis ( sources ) ;
421
+ } else if ( is_controlled && to_destroy . length === a_items . length ) {
422
+ // We can optimize the case where we change all items of the each block to an entirely new set of items.
423
+ // In this case we can first clear the DOM fast, along with all their effect, then we can continue
424
+ // with the normal logic that appends them.
425
+ pause_effects ( to_destroy , anchor ) ;
426
+ to_destroy = [ ] ;
384
427
}
385
428
386
429
// working from the back, insert new or moved items
@@ -421,9 +464,9 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
421
464
} ) ;
422
465
}
423
466
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 , ( ) => {
467
+ var controlled_anchor = is_controlled && b === 0 ? anchor : null ;
468
+
469
+ pause_effects ( to_destroy , controlled_anchor , ( ) => {
427
470
state . items = b_items ;
428
471
} ) ;
429
472
}
0 commit comments