each blocks work!

aa-coordination
Rich Harris 7 months ago
parent 010108a38c
commit 028dba829f

@ -20,7 +20,8 @@ import {
clear_text_content,
create_text,
get_first_child,
get_next_sibling
get_next_sibling,
should_defer_append
} from '../operations.js';
import {
block,
@ -35,10 +36,10 @@ import { source, mutable_source, internal_set } from '../../reactivity/sources.j
import { array_from, is_array } from '../../../shared/utils.js';
import { INERT } from '../../constants.js';
import { queue_micro_task } from '../task.js';
import { active_effect, active_reaction, get } from '../../runtime.js';
import { active_effect, get } from '../../runtime.js';
import { DEV } from 'esm-env';
import { derived_safe_equal } from '../../reactivity/deriveds.js';
import { find_boundary } from './boundary.js';
import { add_boundary_callback, find_boundary } from './boundary.js';
/**
* The row of a keyed each block that is currently updating. We track this
@ -64,17 +65,18 @@ export function index(_, i) {
* Pause multiple effects simultaneously, and coordinate their
* subsequent destruction. Used in each blocks
* @param {EachState} state
* @param {EachItem[]} items
* @param {EachItem[]} to_destroy
* @param {null | Node} controlled_anchor
* @param {Map<any, EachItem>} items_map
*/
function pause_effects(state, items, controlled_anchor, items_map) {
function pause_effects(state, to_destroy, controlled_anchor) {
var items_map = state.items;
/** @type {TransitionManager[]} */
var transitions = [];
var length = items.length;
var length = to_destroy.length;
for (var i = 0; i < length; i++) {
pause_children(items[i].e, transitions, true);
pause_children(to_destroy[i].e, transitions, true);
}
var is_controlled = length > 0 && transitions.length === 0 && controlled_anchor !== null;
@ -87,12 +89,12 @@ function pause_effects(state, items, controlled_anchor, items_map) {
clear_text_content(parent_node);
parent_node.append(/** @type {Element} */ (controlled_anchor));
items_map.clear();
link(state, items[0].prev, items[length - 1].next);
link(state, to_destroy[0].prev, to_destroy[length - 1].next);
}
run_out_transitions(transitions, () => {
for (var i = 0; i < length; i++) {
var item = items[i];
var item = to_destroy[i];
if (!is_controlled) {
items_map.delete(item.k);
link(state, item.prev, item.next);
@ -139,6 +141,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
var boundary = find_boundary(active_effect);
/** @type {Map<any, EachItem>} */
var pending_items = new Map();
// TODO: ideally we could use derived for runes mode but because of the ability
// to use a store which can be mutated, we can't do that here as mutating a store
// 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
/** @type {V[]} */
var array;
/** @type {Effect} */
var each_effect;
function commit() {
reconcile(array, state, anchor, render_fn, flags, get_key, get_collection);
reconcile(
each_effect,
array,
state,
pending_items,
anchor,
render_fn,
flags,
get_key,
get_collection
);
if (fallback_fn !== null) {
if (array.length === 0) {
@ -170,6 +188,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
}
block(() => {
// store a reference to the effect so that we can update the start/end nodes in reconciliation
each_effect ??= /** @type {Effect} */ (active_effect);
array = get(each_array);
var length = array.length;
@ -247,7 +268,42 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
fallback = branch(() => fallback_fn(anchor));
}
} else {
commit();
var defer = boundary !== null && should_defer_append();
if (defer) {
for (i = 0; i < length; i += 1) {
value = array[i];
key = get_key(value, i);
var existing = state.items.get(key) ?? pending_items.get(key);
if (existing) {
// update before reconciliation, to trigger any async updates
if ((flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0) {
update_item(existing, value, i, flags);
}
} else {
var item = create_item(
null,
state,
null,
null,
value,
key,
i,
render_fn,
flags,
get_collection
);
pending_items.set(key, item);
}
}
add_boundary_callback(boundary, commit);
} else {
commit();
}
}
if (mismatch) {
@ -272,8 +328,10 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
/**
* Add, remove, or reorder items output by an each block as its input changes
* @template V
* @param {Effect} each_effect
* @param {Array<V>} array
* @param {EachState} state
* @param {Map<any, EachItem>} pending_items
* @param {Element | Comment | Text} anchor
* @param {(anchor: Node, item: MaybeSource<V>, index: number | Source<number>, collection: () => V[]) => void} render_fn
* @param {number} flags
@ -281,7 +339,17 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
* @param {() => V[]} get_collection
* @returns {void}
*/
function reconcile(array, state, anchor, render_fn, flags, get_key, get_collection) {
function reconcile(
each_effect,
array,
state,
pending_items,
anchor,
render_fn,
flags,
get_key,
get_collection
) {
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
@ -333,7 +401,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key, get_collecti
for (i = 0; i < length; i += 1) {
value = array[i];
key = get_key(value, i);
item = items.get(key);
item = items.get(key) ?? pending_items.get(key);
if (item === undefined) {
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
}
}
pause_effects(state, to_destroy, controlled_anchor, items);
pause_effects(state, to_destroy, controlled_anchor);
}
}
@ -481,8 +549,13 @@ function reconcile(array, state, anchor, render_fn, flags, get_key, get_collecti
});
}
/** @type {Effect} */ (active_effect).first = state.first && state.first.e;
/** @type {Effect} */ (active_effect).last = prev && prev.e;
// TODO this seems super weird... should be `each_effect`, but that doesn't seem to work?
if (active_effect !== null) {
active_effect.first = state.first && state.first.e;
active_effect.last = prev && prev.e;
}
pending_items.clear();
}
/**
@ -506,7 +579,7 @@ function update_item(item, value, index, type) {
/**
* @template V
* @param {Node} anchor
* @param {Node | null} anchor
* @param {EachState} state
* @param {EachItem | null} prev
* @param {EachItem | null} next
@ -562,7 +635,12 @@ function create_item(
current_each_item = item;
try {
item.e = branch(() => render_fn(anchor, v, i, get_collection), hydrating);
if (anchor === null) {
var fragment = document.createDocumentFragment();
fragment.append((anchor = document.createComment('')));
}
item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection), hydrating);
item.e.prev = prev && prev.e;
item.e.next = next && next.e;
@ -596,7 +674,7 @@ function move(item, next, anchor) {
var dest = next ? /** @type {TemplateNode} */ (next.e.nodes_start) : anchor;
var node = /** @type {TemplateNode} */ (item.e.nodes_start);
while (node !== end) {
while (node !== null && node !== end) {
var next_node = /** @type {TemplateNode} */ (get_next_sibling(node));
dest.before(node);
node = next_node;

@ -35,6 +35,7 @@ export default test({
items[1].resolve('c');
items[2].resolve('d');
items[3].resolve('e');
await Promise.resolve();
await tick();
assert.htmlEqual(target.innerHTML, '<p>b</p><p>c</p><p>d</p><p>e</p>');
}

Loading…
Cancel
Save