Skip to content

Commit 028dba8

Browse files
committed
each blocks work!
1 parent 010108a commit 028dba8

File tree

2 files changed

+99
-20
lines changed
  • packages/svelte
    • src/internal/client/dom/blocks
    • tests/runtime-runes/samples/async-each-await-item

2 files changed

+99
-20
lines changed

packages/svelte/src/internal/client/dom/blocks/each.js

Lines changed: 98 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {
2020
clear_text_content,
2121
create_text,
2222
get_first_child,
23-
get_next_sibling
23+
get_next_sibling,
24+
should_defer_append
2425
} from '../operations.js';
2526
import {
2627
block,
@@ -35,10 +36,10 @@ import { source, mutable_source, internal_set } from '../../reactivity/sources.j
3536
import { array_from, is_array } from '../../../shared/utils.js';
3637
import { INERT } from '../../constants.js';
3738
import { queue_micro_task } from '../task.js';
38-
import { active_effect, active_reaction, get } from '../../runtime.js';
39+
import { active_effect, get } from '../../runtime.js';
3940
import { DEV } from 'esm-env';
4041
import { derived_safe_equal } from '../../reactivity/deriveds.js';
41-
import { find_boundary } from './boundary.js';
42+
import { add_boundary_callback, find_boundary } from './boundary.js';
4243

4344
/**
4445
* The row of a keyed each block that is currently updating. We track this
@@ -64,17 +65,18 @@ export function index(_, i) {
6465
* Pause multiple effects simultaneously, and coordinate their
6566
* subsequent destruction. Used in each blocks
6667
* @param {EachState} state
67-
* @param {EachItem[]} items
68+
* @param {EachItem[]} to_destroy
6869
* @param {null | Node} controlled_anchor
69-
* @param {Map<any, EachItem>} items_map
7070
*/
71-
function pause_effects(state, items, controlled_anchor, items_map) {
71+
function pause_effects(state, to_destroy, controlled_anchor) {
72+
var items_map = state.items;
73+
7274
/** @type {TransitionManager[]} */
7375
var transitions = [];
74-
var length = items.length;
76+
var length = to_destroy.length;
7577

7678
for (var i = 0; i < length; i++) {
77-
pause_children(items[i].e, transitions, true);
79+
pause_children(to_destroy[i].e, transitions, true);
7880
}
7981

8082
var is_controlled = length > 0 && transitions.length === 0 && controlled_anchor !== null;
@@ -87,12 +89,12 @@ function pause_effects(state, items, controlled_anchor, items_map) {
8789
clear_text_content(parent_node);
8890
parent_node.append(/** @type {Element} */ (controlled_anchor));
8991
items_map.clear();
90-
link(state, items[0].prev, items[length - 1].next);
92+
link(state, to_destroy[0].prev, to_destroy[length - 1].next);
9193
}
9294

9395
run_out_transitions(transitions, () => {
9496
for (var i = 0; i < length; i++) {
95-
var item = items[i];
97+
var item = to_destroy[i];
9698
if (!is_controlled) {
9799
items_map.delete(item.k);
98100
link(state, item.prev, item.next);
@@ -139,6 +141,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
139141

140142
var boundary = find_boundary(active_effect);
141143

144+
/** @type {Map<any, EachItem>} */
145+
var pending_items = new Map();
146+
142147
// TODO: ideally we could use derived for runes mode but because of the ability
143148
// to use a store which can be mutated, we can't do that here as mutating a store
144149
// will still result in the collection array being the same from the store
@@ -151,8 +156,21 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
151156
/** @type {V[]} */
152157
var array;
153158

159+
/** @type {Effect} */
160+
var each_effect;
161+
154162
function commit() {
155-
reconcile(array, state, anchor, render_fn, flags, get_key, get_collection);
163+
reconcile(
164+
each_effect,
165+
array,
166+
state,
167+
pending_items,
168+
anchor,
169+
render_fn,
170+
flags,
171+
get_key,
172+
get_collection
173+
);
156174

157175
if (fallback_fn !== null) {
158176
if (array.length === 0) {
@@ -170,6 +188,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
170188
}
171189

172190
block(() => {
191+
// store a reference to the effect so that we can update the start/end nodes in reconciliation
192+
each_effect ??= /** @type {Effect} */ (active_effect);
193+
173194
array = get(each_array);
174195
var length = array.length;
175196

@@ -247,7 +268,42 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
247268
fallback = branch(() => fallback_fn(anchor));
248269
}
249270
} else {
250-
commit();
271+
var defer = boundary !== null && should_defer_append();
272+
273+
if (defer) {
274+
for (i = 0; i < length; i += 1) {
275+
value = array[i];
276+
key = get_key(value, i);
277+
278+
var existing = state.items.get(key) ?? pending_items.get(key);
279+
280+
if (existing) {
281+
// update before reconciliation, to trigger any async updates
282+
if ((flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0) {
283+
update_item(existing, value, i, flags);
284+
}
285+
} else {
286+
var item = create_item(
287+
null,
288+
state,
289+
null,
290+
null,
291+
value,
292+
key,
293+
i,
294+
render_fn,
295+
flags,
296+
get_collection
297+
);
298+
299+
pending_items.set(key, item);
300+
}
301+
}
302+
303+
add_boundary_callback(boundary, commit);
304+
} else {
305+
commit();
306+
}
251307
}
252308

253309
if (mismatch) {
@@ -272,16 +328,28 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
272328
/**
273329
* Add, remove, or reorder items output by an each block as its input changes
274330
* @template V
331+
* @param {Effect} each_effect
275332
* @param {Array<V>} array
276333
* @param {EachState} state
334+
* @param {Map<any, EachItem>} pending_items
277335
* @param {Element | Comment | Text} anchor
278336
* @param {(anchor: Node, item: MaybeSource<V>, index: number | Source<number>, collection: () => V[]) => void} render_fn
279337
* @param {number} flags
280338
* @param {(value: V, index: number) => any} get_key
281339
* @param {() => V[]} get_collection
282340
* @returns {void}
283341
*/
284-
function reconcile(array, state, anchor, render_fn, flags, get_key, get_collection) {
342+
function reconcile(
343+
each_effect,
344+
array,
345+
state,
346+
pending_items,
347+
anchor,
348+
render_fn,
349+
flags,
350+
get_key,
351+
get_collection
352+
) {
285353
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
286354
var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
287355

@@ -333,7 +401,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key, get_collecti
333401
for (i = 0; i < length; i += 1) {
334402
value = array[i];
335403
key = get_key(value, i);
336-
item = items.get(key);
404+
item = items.get(key) ?? pending_items.get(key);
337405

338406
if (item === undefined) {
339407
var child_anchor = current ? /** @type {TemplateNode} */ (current.e.nodes_start) : anchor;
@@ -468,7 +536,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key, get_collecti
468536
}
469537
}
470538

471-
pause_effects(state, to_destroy, controlled_anchor, items);
539+
pause_effects(state, to_destroy, controlled_anchor);
472540
}
473541
}
474542

@@ -481,8 +549,13 @@ function reconcile(array, state, anchor, render_fn, flags, get_key, get_collecti
481549
});
482550
}
483551

484-
/** @type {Effect} */ (active_effect).first = state.first && state.first.e;
485-
/** @type {Effect} */ (active_effect).last = prev && prev.e;
552+
// TODO this seems super weird... should be `each_effect`, but that doesn't seem to work?
553+
if (active_effect !== null) {
554+
active_effect.first = state.first && state.first.e;
555+
active_effect.last = prev && prev.e;
556+
}
557+
558+
pending_items.clear();
486559
}
487560

488561
/**
@@ -506,7 +579,7 @@ function update_item(item, value, index, type) {
506579

507580
/**
508581
* @template V
509-
* @param {Node} anchor
582+
* @param {Node | null} anchor
510583
* @param {EachState} state
511584
* @param {EachItem | null} prev
512585
* @param {EachItem | null} next
@@ -562,7 +635,12 @@ function create_item(
562635
current_each_item = item;
563636

564637
try {
565-
item.e = branch(() => render_fn(anchor, v, i, get_collection), hydrating);
638+
if (anchor === null) {
639+
var fragment = document.createDocumentFragment();
640+
fragment.append((anchor = document.createComment('')));
641+
}
642+
643+
item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection), hydrating);
566644

567645
item.e.prev = prev && prev.e;
568646
item.e.next = next && next.e;
@@ -596,7 +674,7 @@ function move(item, next, anchor) {
596674
var dest = next ? /** @type {TemplateNode} */ (next.e.nodes_start) : anchor;
597675
var node = /** @type {TemplateNode} */ (item.e.nodes_start);
598676

599-
while (node !== end) {
677+
while (node !== null && node !== end) {
600678
var next_node = /** @type {TemplateNode} */ (get_next_sibling(node));
601679
dest.before(node);
602680
node = next_node;

packages/svelte/tests/runtime-runes/samples/async-each-await-item/_config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export default test({
3535
items[1].resolve('c');
3636
items[2].resolve('d');
3737
items[3].resolve('e');
38+
await Promise.resolve();
3839
await tick();
3940
assert.htmlEqual(target.innerHTML, '<p>b</p><p>c</p><p>d</p><p>e</p>');
4041
}

0 commit comments

Comments
 (0)