From 9f76267774883f732c955491f7c705b3a447b871 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 28 Jun 2024 09:40:38 -0400 Subject: [PATCH] cleaner separation between EachState and EachItem - precursor to efficient relinking --- .../src/internal/client/dom/blocks/each.js | 82 ++++++++++++------- .../svelte/src/internal/client/types.d.ts | 4 +- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index e0e0f78511..e3d00de95c 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -56,11 +56,12 @@ export function index(_, i) { /** * Pause multiple effects simultaneously, and coordinate their * subsequent destruction. Used in each blocks + * @param {import('#client').EachState} state * @param {import('#client').EachItem[]} items * @param {null | Node} controlled_anchor * @param {Map} items_map */ -function pause_effects(items, controlled_anchor, items_map) { +function pause_effects(state, items, controlled_anchor, items_map) { /** @type {import('#client').TransitionManager[]} */ var transitions = []; var length = items.length; @@ -79,7 +80,7 @@ function pause_effects(items, controlled_anchor, items_map) { clear_text_content(parent_node); parent_node.append(/** @type {Element} */ (controlled_anchor)); items_map.clear(); - link(items[0].prev, items[length - 1].next); + link(state, items[0].prev, items[length - 1].next); } run_out_transitions(transitions, () => { @@ -87,7 +88,7 @@ function pause_effects(items, controlled_anchor, items_map) { var item = items[i]; if (!is_controlled) { items_map.delete(item.k); - link(item.prev, item.next); + link(state, item.prev, item.next); } destroy_effect(item.e, !is_controlled); } @@ -106,7 +107,7 @@ function pause_effects(items, controlled_anchor, items_map) { */ export function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn = null) { /** @type {import('#client').EachState} */ - var state = { flags, items: new Map(), next: null }; + var state = { flags, items: new Map(), first: null }; var is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; @@ -165,8 +166,8 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback /** @type {Node} */ var child_anchor = hydrate_start; - /** @type {import('#client').EachItem | import('#client').EachState} */ - var prev = state; + /** @type {import('#client').EachItem | null} */ + var prev = null; /** @type {import('#client').EachItem} */ var item; @@ -186,7 +187,7 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback child_anchor = hydrate_anchor(child_anchor); var value = array[i]; var key = get_key(value, i); - item = create_item(child_anchor, prev, null, value, key, i, render_fn, flags); + item = create_item(child_anchor, state, prev, null, value, key, i, render_fn, flags); state.items.set(key, item); child_anchor = /** @type {Comment} */ (child_anchor.nextSibling); @@ -244,14 +245,14 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { var length = array.length; var items = state.items; - var first = state.next; + var first = state.first; var current = first; /** @type {Set} */ var seen = new Set(); - /** @type {import('#client').EachState | import('#client').EachItem} */ - var prev = state; + /** @type {import('#client').EachItem | null} */ + var prev = null; /** @type {Set} */ var to_animate = new Set(); @@ -295,7 +296,17 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { if (item === undefined) { var child_anchor = current ? get_first_node(current.e) : anchor; - prev = create_item(child_anchor, prev, prev.next, value, key, i, render_fn, flags); + prev = create_item( + child_anchor, + state, + prev, + prev === null ? state.first : prev.next, + value, + key, + i, + render_fn, + flags + ); items.set(key, prev); @@ -338,9 +349,9 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { seen.delete(stashed[j]); } - link(a.prev, b.next); - link(prev, a); - link(b, start); + link(state, a.prev, b.next); + link(state, prev, a); + link(state, b, start); current = start; prev = b; @@ -353,9 +364,9 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { seen.delete(item); move(item, current, anchor); - link(item.prev, item.next); - link(item, prev.next); - link(prev, item); + link(state, item.prev, item.next); + link(state, item, prev === null ? state.first : prev.next); + link(state, prev, item); prev = item; } @@ -405,7 +416,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { } } - pause_effects(to_destroy, controlled_anchor, items); + pause_effects(state, to_destroy, controlled_anchor, items); } if (is_animated) { @@ -419,7 +430,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { // TODO this is inefficient, this should happen during linking, but that's currently tricky /** @type {import('#client').Effect | null} */ var effect = null; - var next = state.next; + var next = state.first; /** @type {import('#client').Effect} */ (current_effect).first = next && next.e; @@ -463,7 +474,8 @@ function update_item(item, value, index, type) { /** * @template V * @param {Node} anchor - * @param {import('#client').EachItem | import('#client').EachState} prev + * @param {import('#client').EachState} state + * @param {import('#client').EachItem | null} prev * @param {import('#client').EachItem | null} next * @param {V} value * @param {unknown} key @@ -472,7 +484,7 @@ function update_item(item, value, index, type) { * @param {number} flags * @returns {import('#client').EachItem} */ -function create_item(anchor, prev, next, value, key, index, render_fn, flags) { +function create_item(anchor, state, prev, next, value, key, index, render_fn, flags) { var previous_each_item = current_each_item; try { @@ -494,8 +506,15 @@ function create_item(anchor, prev, next, value, key, index, render_fn, flags) { next }; - prev.next = item; - if (next !== null) next.prev = item; + if (prev === null) { + state.first = item; + } else { + prev.next = item; + } + + if (next !== null) { + next.prev = item; + } current_each_item = item; item.e = branch(() => render_fn(anchor, v, i)); @@ -525,11 +544,18 @@ function move(item, next, anchor) { } /** - * - * @param {import('#client').EachItem | import('#client').EachState} prev + * @param {import('#client').EachState} state + * @param {import('#client').EachItem | null} prev * @param {import('#client').EachItem | null} next */ -function link(prev, next) { - prev.next = next; - if (next !== null) next.prev = prev; +function link(state, prev, next) { + if (prev === null) { + state.first = next; + } else { + prev.next = next; + } + + if (next !== null) { + next.prev = prev; + } } diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 292c233fa6..53fd222d18 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -71,7 +71,7 @@ export type EachState = { /** a key -> item lookup */ items: Map; /** head of the linked list of items */ - next: EachItem | null; + first: EachItem | null; }; export type EachItem = { @@ -85,7 +85,7 @@ export type EachItem = { i: number | Source; /** key */ k: unknown; - prev: EachItem | EachState; + prev: EachItem | null; next: EachItem | null; };