diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js new file mode 100644 index 0000000000..b77115648d --- /dev/null +++ b/packages/svelte/src/internal/client/each.js @@ -0,0 +1,646 @@ +import { + EACH_INDEX_REACTIVE, + EACH_IS_ANIMATED, + EACH_IS_CONTROLLED, + EACH_IS_PROXIED, + EACH_ITEM_REACTIVE +} from '../../constants.js'; +import { create_each_block } from './block.js'; +import { + current_hydration_fragment, + get_hydration_fragment, + hydrate_block_anchor, + set_current_hydration_fragment +} from './hydration.js'; +import { clear_text_content, map_get, map_set } from './operations.js'; +import { STATE_SYMBOL } from './proxy.js'; +import { insert, remove } from './reconciler.js'; +import { + destroy_each_item_block, + each_item_block, + empty, + update_each_item_block +} from './render.js'; +import { destroy_signal, execute_effect, push_destroy_fn, render_effect } from './runtime.js'; +import { trigger_transitions } from './transitions.js'; +import { is_array } from './utils.js'; + +const NEW_BLOCK = -1; +const MOVED_BLOCK = 99999999; +const LIS_BLOCK = -2; + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {null | ((item: V) => string)} key_fn + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn + * @returns {void} + */ +function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_fn) { + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + const block = create_each_block(flags, anchor_node); + + /** @type {null | import('./types.js').Render} */ + let current_fallback = null; + hydrate_block_anchor(anchor_node, is_controlled); + + /** @type {V[]} */ + let array; + + /** @type {Array | null} */ + let keys = null; + + /** @type {null | import('./types.js').EffectSignal} */ + let render = null; + block.r = + /** @param {import('./types.js').Transition} transition */ + (transition) => { + const fallback = /** @type {import('./types.js').Render} */ (current_fallback); + const transitions = fallback.s; + transitions.add(transition); + transition.f(() => { + transitions.delete(transition); + if (transitions.size === 0) { + if (fallback.e !== null) { + if (fallback.d !== null) { + remove(fallback.d); + fallback.d = null; + } + destroy_signal(fallback.e); + fallback.e = null; + } + } + }); + }; + const create_fallback_effect = () => { + /** @type {import('./types.js').Render} */ + const fallback = { + d: null, + e: null, + s: new Set(), + p: current_fallback + }; + // Managed effect + const effect = render_effect( + () => { + const dom = block.d; + if (dom !== null) { + remove(dom); + block.d = null; + } + let anchor = block.a; + const is_controlled = (block.f & EACH_IS_CONTROLLED) !== 0; + if (is_controlled) { + anchor = empty(); + block.a.appendChild(anchor); + } + /** @type {(anchor: Node) => void} */ (fallback_fn)(anchor); + fallback.d = block.d; + block.d = null; + }, + block, + true + ); + fallback.e = effect; + current_fallback = fallback; + }; + const each = render_effect( + () => { + /** @type {V[]} */ + const maybe_array = collection(); + array = is_array(maybe_array) + ? maybe_array + : maybe_array == null + ? [] + : Array.from(maybe_array); + if (key_fn !== null) { + keys = array.map(key_fn); + } + const length = array.length; + if (fallback_fn !== null) { + if (length === 0) { + if (block.v.length !== 0 || render === null) { + create_fallback_effect(); + } + } else if (block.v.length === 0 && current_fallback !== null) { + const fallback = current_fallback; + const transitions = fallback.s; + if (transitions.size === 0) { + if (fallback.d !== null) { + remove(fallback.d); + fallback.d = null; + } + } else { + trigger_transitions(transitions, 'out'); + } + } + } + if (render !== null) { + execute_effect(render); + } + }, + block, + false + ); + render = render_effect( + /** @param {import('./types.js').EachBlock} block */ + (block) => { + const flags = block.f; + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + const anchor_node = block.a; + reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys); + }, + block, + true + ); + push_destroy_fn(each, () => { + const flags = block.f; + const anchor_node = block.a; + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + let fallback = current_fallback; + while (fallback !== null) { + const dom = fallback.d; + if (dom !== null) { + remove(dom); + } + const effect = fallback.e; + if (effect !== null) { + destroy_signal(effect); + } + fallback = fallback.p; + } + // Clear the array + reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys); + destroy_signal(/** @type {import('./types.js').EffectSignal} */ (render)); + }); + block.e = each; +} + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {null | ((item: V) => string)} key_fn + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @returns {void} + */ +export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) { + each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_tracked_array); +} + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @returns {void} + */ +export function each_indexed(anchor_node, collection, flags, render_fn, fallback_fn) { + each(anchor_node, collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array); +} + +/** + * @template V + * @param {Array} array + * @param {import('./types.js').EachBlock} each_block + * @param {Element | Comment | Text} dom + * @param {boolean} is_controlled + * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn + * @param {number} flags + * @param {boolean} apply_transitions + * @returns {void} + */ +export function reconcile_indexed_array( + array, + each_block, + dom, + is_controlled, + render_fn, + flags, + apply_transitions +) { + var is_proxied_array = STATE_SYMBOL in array; + var a_blocks = each_block.v; + var active_transitions = each_block.s; + + if (is_proxied_array) { + flags &= ~EACH_ITEM_REACTIVE; + flags |= EACH_IS_PROXIED; + } + + /** @type {number | void} */ + var a = a_blocks.length; + + /** @type {number} */ + var b = array.length; + var length = Math.max(a, b); + var index = 0; + + /** @type {Array} */ + var b_blocks; + var block; + if (active_transitions.length !== 0) { + destroy_active_transition_blocks(active_transitions); + } + if (b === 0) { + b_blocks = []; + // Remove old blocks + if (is_controlled && a !== 0) { + clear_text_content(dom); + } + while (index < length) { + block = a_blocks[index++]; + destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); + } + } else { + var item; + b_blocks = Array(b); + if (current_hydration_fragment !== null) { + /** @type {Node} */ + var hydrating_node = current_hydration_fragment[0]; + for (; index < length; index++) { + // Hydrate block + item = array[index]; + var fragment = /** @type {Array} */ ( + get_hydration_fragment(hydrating_node) + ); + set_current_hydration_fragment(fragment); + hydrating_node = /** @type {Node} */ ( + /** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling + ); + block = each_item_block(array, item, null, index, render_fn, flags); + b_blocks[index] = block; + } + } else { + for (; index < length; index++) { + if (index >= a) { + // Add block + item = array[index]; + block = each_item_block(array, item, null, index, render_fn, flags); + b_blocks[index] = block; + insert_each_item_block(block, dom, is_controlled, null); + } else if (index >= b) { + // Remove block + block = a_blocks[index]; + destroy_each_item_block(block, active_transitions, apply_transitions); + } else { + // Update block + item = array[index]; + block = a_blocks[index]; + b_blocks[index] = block; + update_each_item_block(block, item, index, flags); + } + } + } + } + each_block.v = b_blocks; +} +// Reconcile arrays by the equality of the elements in the array. This algorithm +// is based on Ivi's reconcilation logic: +// +// https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968 +// + +/** + * @template V + * @param {Array} array + * @param {import('./types.js').EachBlock} each_block + * @param {Element | Comment | Text} dom + * @param {boolean} is_controlled + * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn + * @param {number} flags + * @param {boolean} apply_transitions + * @param {Array | null} keys + * @returns {void} + */ +export function reconcile_tracked_array( + array, + each_block, + dom, + is_controlled, + render_fn, + flags, + apply_transitions, + keys +) { + var a_blocks = each_block.v; + const is_computed_key = keys !== null; + var is_proxied_array = STATE_SYMBOL in array; + var active_transitions = each_block.s; + + if (is_proxied_array) { + flags &= ~EACH_ITEM_REACTIVE; + flags |= EACH_IS_PROXIED; + } + + /** @type {number | void} */ + var a = a_blocks.length; + + /** @type {number} */ + var b = array.length; + + /** @type {Array} */ + var b_blocks; + var block; + if (active_transitions.length !== 0) { + destroy_active_transition_blocks(active_transitions); + } + if (b === 0) { + b_blocks = []; + // Remove old blocks + if (is_controlled && a !== 0) { + clear_text_content(dom); + } + while (a > 0) { + block = a_blocks[--a]; + destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); + } + } else { + var a_end = a - 1; + var b_end = b - 1; + var key; + var item; + var idx; + b_blocks = Array(b); + if (current_hydration_fragment !== null) { + var fragment; + + /** @type {Node} */ + var hydrating_node = current_hydration_fragment[0]; + while (b > 0) { + // Hydrate block + idx = b_end - --b; + item = array[idx]; + key = is_computed_key ? keys[idx] : item; + fragment = /** @type {Array} */ ( + get_hydration_fragment(hydrating_node) + ); + set_current_hydration_fragment(fragment); + // Get the tag of the next item in the list + // The fragment array can be empty if each block has no content + hydrating_node = /** @type {Node} */ ( + /** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling + ); + block = each_item_block(array, item, key, idx, render_fn, flags); + b_blocks[idx] = block; + } + } else if (a === 0) { + // Create new blocks + while (b > 0) { + idx = b_end - --b; + item = array[idx]; + key = is_computed_key ? keys[idx] : item; + block = each_item_block(array, item, key, idx, render_fn, flags); + b_blocks[idx] = block; + insert_each_item_block(block, dom, is_controlled, null); + } + } else { + var should_update_block = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; + var start = 0; + + /** @type {null | Text | Element | Comment} */ + var sibling = null; + item = array[b_end]; + key = is_computed_key ? keys[b_end] : item; + // Step 1 + outer: while (true) { + // From the end + while (a_blocks[a_end].k === key) { + block = a_blocks[a_end--]; + item = array[b_end]; + if (should_update_block) { + update_each_item_block(block, item, b_end, flags); + } + sibling = get_first_child(block); + b_blocks[b_end] = block; + if (start > --b_end || start > a_end) { + break outer; + } + key = is_computed_key ? keys[b_end] : item; + } + item = array[start]; + key = is_computed_key ? keys[start] : item; + // At the start + while (start <= a_end && start <= b_end && a_blocks[start].k === key) { + item = array[start]; + block = a_blocks[start]; + if (should_update_block) { + update_each_item_block(block, item, start, flags); + } + b_blocks[start] = block; + ++start; + key = is_computed_key ? keys[start] : array[start]; + } + break; + } + // Step 2 + if (start > a_end) { + while (b_end >= start) { + item = array[b_end]; + key = is_computed_key ? keys[b_end] : item; + block = each_item_block(array, item, key, b_end, render_fn, flags); + b_blocks[b_end--] = block; + sibling = insert_each_item_block(block, dom, is_controlled, sibling); + } + } else if (start > b_end) { + b = start; + do { + if ((block = a_blocks[b++]) !== null) { + destroy_each_item_block(block, active_transitions, apply_transitions); + } + } while (b <= a_end); + } else { + // Step 3 + var pos = 0; + var b_length = b_end - start + 1; + var sources = new Int32Array(b_length); + var item_index = new Map(); + for (b = 0; b < b_length; ++b) { + a = b + start; + sources[b] = NEW_BLOCK; + item = array[a]; + key = is_computed_key ? keys[a] : item; + map_set(item_index, key, a); + } + for (b = start; b <= a_end; ++b) { + a = map_get(item_index, /** @type {V} */ (a_blocks[b].k)); + block = a_blocks[b]; + if (a !== undefined) { + pos = pos < a ? a : MOVED_BLOCK; + sources[a - start] = b; + b_blocks[a] = block; + } else if (block !== null) { + destroy_each_item_block(block, active_transitions, apply_transitions); + } + } + // Step 4 + if (pos === MOVED_BLOCK) { + mark_lis(sources); + } + // If keys are animated, we need to do updates before actual moves + var is_animated = (flags & EACH_IS_ANIMATED) !== 0; + var should_create; + if (is_animated) { + var i = b_length; + while (i-- > 0) { + b_end = i + start; + a = sources[i]; + if (pos === MOVED_BLOCK && a !== LIS_BLOCK) { + block = b_blocks[b_end]; + update_each_item_block(block, item, b_end, flags); + } + } + } + var last_block; + var last_sibling; + while (b_length-- > 0) { + b_end = b_length + start; + a = sources[b_length]; + should_create = a === -1; + item = array[b_end]; + if (should_create) { + key = is_computed_key ? keys[b_end] : item; + block = each_item_block(array, item, key, b_end, render_fn, flags); + } else { + block = b_blocks[b_end]; + if (!is_animated && should_update_block) { + update_each_item_block(block, item, b_end, flags); + } + } + if (should_create || (pos === MOVED_BLOCK && a !== LIS_BLOCK)) { + last_sibling = last_block === undefined ? sibling : get_first_child(last_block); + sibling = insert_each_item_block(block, dom, is_controlled, last_sibling); + } + b_blocks[b_end] = block; + last_block = block; + } + } + } + } + each_block.v = b_blocks; +} +// Longest Increased Subsequence algorithm. + +/** + * @param {Int32Array} a + * @returns {void} + */ +function mark_lis(a) { + var length = a.length; + var parent = new Int32Array(length); + var index = new Int32Array(length); + var index_length = 0; + var i = 0; + + /** @type {number} */ + var j; + + /** @type {number} */ + var k; + + /** @type {number} */ + var lo; + + /** @type {number} */ + var hi; + // Skip -1 values at the start of the input array `a`. + for (; a[i] === NEW_BLOCK; ++i) { + /**/ + } + index[0] = i++; + for (; i < length; ++i) { + k = a[i]; + if (k !== NEW_BLOCK) { + // Ignore -1 values. + j = index[index_length]; + if (a[j] < k) { + parent[i] = j; + index[++index_length] = i; + } else { + lo = 0; + hi = index_length; + while (lo < hi) { + j = (lo + hi) >> 1; + if (a[index[j]] < k) { + lo = j + 1; + } else { + hi = j; + } + } + if (k < a[index[lo]]) { + if (lo > 0) { + parent[i] = index[lo - 1]; + } + index[lo] = i; + } + } + } + } + // Mutate input array `a` and assign -2 value to all nodes that are part of LIS. + j = index[index_length]; + while (index_length-- >= 0) { + a[j] = LIS_BLOCK; + j = parent[j]; + } +} + +/** + * @param {import('./types.js').Block} block + * @param {Element | Comment | Text} dom + * @param {boolean} is_controlled + * @param {null | Text | Element | Comment} sibling + * @returns {Text | Element | Comment} + */ +function insert_each_item_block(block, dom, is_controlled, sibling) { + var current = /** @type {import('./types.js').TemplateNode} */ (block.d); + if (sibling === null) { + if (is_controlled) { + return insert(current, /** @type {Element} */ (dom), null); + } else { + return insert(current, /** @type {Element} */ (dom.parentNode), dom); + } + } + return insert(current, null, sibling); +} + +/** + * @param {import('./types.js').Block} block + * @returns {Text | Element | Comment} + */ +function get_first_child(block) { + var current = block.d; + if (is_array(current)) { + return /** @type {Text | Element | Comment} */ (current[0]); + } + return /** @type {Text | Element | Comment} */ (current); +} + +/** + * @param {Array} active_transitions + * @returns {void} + */ +function destroy_active_transition_blocks(active_transitions) { + var length = active_transitions.length; + if (length > 0) { + var i = 0; + var block; + var transition; + for (; i < length; i++) { + block = active_transitions[i]; + transition = block.r; + if (transition !== null) { + block.r = null; + destroy_each_item_block(block, null, false); + } + } + active_transitions.length = 0; + } +} diff --git a/packages/svelte/src/internal/client/reconciler.js b/packages/svelte/src/internal/client/reconciler.js index ae8d0dd8bd..9f736b1cb2 100644 --- a/packages/svelte/src/internal/client/reconciler.js +++ b/packages/svelte/src/internal/client/reconciler.js @@ -1,23 +1,7 @@ -import { append_child, map_get, map_set, clear_text_content } from './operations.js'; -import { - current_hydration_fragment, - get_hydration_fragment, - hydrate_block_anchor, - set_current_hydration_fragment -} from './hydration.js'; +import { append_child } from './operations.js'; +import { current_hydration_fragment, hydrate_block_anchor } from './hydration.js'; import { is_array } from './utils.js'; -import { each_item_block, destroy_each_item_block, update_each_item_block } from './render.js'; -import { - EACH_INDEX_REACTIVE, - EACH_IS_ANIMATED, - EACH_IS_PROXIED, - EACH_ITEM_REACTIVE -} from '../../constants.js'; -import { STATE_SYMBOL } from './proxy.js'; - -const NEW_BLOCK = -1; -const MOVED_BLOCK = 99999999; -const LIS_BLOCK = -2; +import { destroy_each_item_block } from './render.js'; /** @param {string} html */ export function create_fragment_from_html(html) { @@ -109,441 +93,3 @@ export function reconcile_html(dom, value, svg) { target.before(svg ? /** @type {Node} */ (clone.firstChild) : clone); return /** @type {Array} */ (frag_nodes); } - -/** - * @param {import('./types.js').Block} block - * @param {Element | Comment | Text} dom - * @param {boolean} is_controlled - * @param {null | Text | Element | Comment} sibling - * @returns {Text | Element | Comment} - */ -function insert_each_item_block(block, dom, is_controlled, sibling) { - var current = /** @type {import('./types.js').TemplateNode} */ (block.d); - if (sibling === null) { - if (is_controlled) { - return insert(current, /** @type {Element} */ (dom), null); - } else { - return insert(current, /** @type {Element} */ (dom.parentNode), dom); - } - } - return insert(current, null, sibling); -} - -/** - * @param {import('./types.js').Block} block - * @returns {Text | Element | Comment} - */ -function get_first_child(block) { - var current = block.d; - if (is_array(current)) { - return /** @type {Text | Element | Comment} */ (current[0]); - } - return /** @type {Text | Element | Comment} */ (current); -} - -/** - * @param {Array} active_transitions - * @returns {void} - */ -function destroy_active_transition_blocks(active_transitions) { - var length = active_transitions.length; - if (length > 0) { - var i = 0; - var block; - var transition; - for (; i < length; i++) { - block = active_transitions[i]; - transition = block.r; - if (transition !== null) { - block.r = null; - destroy_each_item_block(block, null, false); - } - } - active_transitions.length = 0; - } -} - -/** - * @template V - * @param {Array} array - * @param {import('./types.js').EachBlock} each_block - * @param {Element | Comment | Text} dom - * @param {boolean} is_controlled - * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn - * @param {number} flags - * @param {boolean} apply_transitions - * @returns {void} - */ -export function reconcile_indexed_array( - array, - each_block, - dom, - is_controlled, - render_fn, - flags, - apply_transitions -) { - var is_proxied_array = STATE_SYMBOL in array; - var a_blocks = each_block.v; - var active_transitions = each_block.s; - - if (is_proxied_array) { - flags &= ~EACH_ITEM_REACTIVE; - flags |= EACH_IS_PROXIED; - } - - /** @type {number | void} */ - var a = a_blocks.length; - - /** @type {number} */ - var b = array.length; - var length = Math.max(a, b); - var index = 0; - - /** @type {Array} */ - var b_blocks; - var block; - if (active_transitions.length !== 0) { - destroy_active_transition_blocks(active_transitions); - } - if (b === 0) { - b_blocks = []; - // Remove old blocks - if (is_controlled && a !== 0) { - clear_text_content(dom); - } - while (index < length) { - block = a_blocks[index++]; - destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); - } - } else { - var item; - b_blocks = Array(b); - if (current_hydration_fragment !== null) { - /** @type {Node} */ - var hydrating_node = current_hydration_fragment[0]; - for (; index < length; index++) { - // Hydrate block - item = array[index]; - var fragment = /** @type {Array} */ ( - get_hydration_fragment(hydrating_node) - ); - set_current_hydration_fragment(fragment); - hydrating_node = /** @type {Node} */ ( - /** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling - ); - block = each_item_block(array, item, null, index, render_fn, flags); - b_blocks[index] = block; - } - } else { - for (; index < length; index++) { - if (index >= a) { - // Add block - item = array[index]; - block = each_item_block(array, item, null, index, render_fn, flags); - b_blocks[index] = block; - insert_each_item_block(block, dom, is_controlled, null); - } else if (index >= b) { - // Remove block - block = a_blocks[index]; - destroy_each_item_block(block, active_transitions, apply_transitions); - } else { - // Update block - item = array[index]; - block = a_blocks[index]; - b_blocks[index] = block; - update_each_item_block(block, item, index, flags); - } - } - } - } - each_block.v = b_blocks; -} -// Reconcile arrays by the equality of the elements in the array. This algorithm -// is based on Ivi's reconcilation logic: -// -// https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968 -// - -/** - * @template V - * @param {Array} array - * @param {import('./types.js').EachBlock} each_block - * @param {Element | Comment | Text} dom - * @param {boolean} is_controlled - * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn - * @param {number} flags - * @param {boolean} apply_transitions - * @param {Array | null} keys - * @returns {void} - */ -export function reconcile_tracked_array( - array, - each_block, - dom, - is_controlled, - render_fn, - flags, - apply_transitions, - keys -) { - var a_blocks = each_block.v; - const is_computed_key = keys !== null; - var is_proxied_array = STATE_SYMBOL in array; - var active_transitions = each_block.s; - - if (is_proxied_array) { - flags &= ~EACH_ITEM_REACTIVE; - flags |= EACH_IS_PROXIED; - } - - /** @type {number | void} */ - var a = a_blocks.length; - - /** @type {number} */ - var b = array.length; - - /** @type {Array} */ - var b_blocks; - var block; - if (active_transitions.length !== 0) { - destroy_active_transition_blocks(active_transitions); - } - if (b === 0) { - b_blocks = []; - // Remove old blocks - if (is_controlled && a !== 0) { - clear_text_content(dom); - } - while (a > 0) { - block = a_blocks[--a]; - destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); - } - } else { - var a_end = a - 1; - var b_end = b - 1; - var key; - var item; - var idx; - b_blocks = Array(b); - if (current_hydration_fragment !== null) { - var fragment; - - /** @type {Node} */ - var hydrating_node = current_hydration_fragment[0]; - while (b > 0) { - // Hydrate block - idx = b_end - --b; - item = array[idx]; - key = is_computed_key ? keys[idx] : item; - fragment = /** @type {Array} */ ( - get_hydration_fragment(hydrating_node) - ); - set_current_hydration_fragment(fragment); - // Get the tag of the next item in the list - // The fragment array can be empty if each block has no content - hydrating_node = /** @type {Node} */ ( - /** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling - ); - block = each_item_block(array, item, key, idx, render_fn, flags); - b_blocks[idx] = block; - } - } else if (a === 0) { - // Create new blocks - while (b > 0) { - idx = b_end - --b; - item = array[idx]; - key = is_computed_key ? keys[idx] : item; - block = each_item_block(array, item, key, idx, render_fn, flags); - b_blocks[idx] = block; - insert_each_item_block(block, dom, is_controlled, null); - } - } else { - var should_update_block = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; - var start = 0; - - /** @type {null | Text | Element | Comment} */ - var sibling = null; - item = array[b_end]; - key = is_computed_key ? keys[b_end] : item; - // Step 1 - outer: while (true) { - // From the end - while (a_blocks[a_end].k === key) { - block = a_blocks[a_end--]; - item = array[b_end]; - if (should_update_block) { - update_each_item_block(block, item, b_end, flags); - } - sibling = get_first_child(block); - b_blocks[b_end] = block; - if (start > --b_end || start > a_end) { - break outer; - } - key = is_computed_key ? keys[b_end] : item; - } - item = array[start]; - key = is_computed_key ? keys[start] : item; - // At the start - while (start <= a_end && start <= b_end && a_blocks[start].k === key) { - item = array[start]; - block = a_blocks[start]; - if (should_update_block) { - update_each_item_block(block, item, start, flags); - } - b_blocks[start] = block; - ++start; - key = is_computed_key ? keys[start] : array[start]; - } - break; - } - // Step 2 - if (start > a_end) { - while (b_end >= start) { - item = array[b_end]; - key = is_computed_key ? keys[b_end] : item; - block = each_item_block(array, item, key, b_end, render_fn, flags); - b_blocks[b_end--] = block; - sibling = insert_each_item_block(block, dom, is_controlled, sibling); - } - } else if (start > b_end) { - b = start; - do { - if ((block = a_blocks[b++]) !== null) { - destroy_each_item_block(block, active_transitions, apply_transitions); - } - } while (b <= a_end); - } else { - // Step 3 - var pos = 0; - var b_length = b_end - start + 1; - var sources = new Int32Array(b_length); - var item_index = new Map(); - for (b = 0; b < b_length; ++b) { - a = b + start; - sources[b] = NEW_BLOCK; - item = array[a]; - key = is_computed_key ? keys[a] : item; - map_set(item_index, key, a); - } - for (b = start; b <= a_end; ++b) { - a = map_get(item_index, /** @type {V} */ (a_blocks[b].k)); - block = a_blocks[b]; - if (a !== undefined) { - pos = pos < a ? a : MOVED_BLOCK; - sources[a - start] = b; - b_blocks[a] = block; - } else if (block !== null) { - destroy_each_item_block(block, active_transitions, apply_transitions); - } - } - // Step 4 - if (pos === MOVED_BLOCK) { - mark_lis(sources); - } - // If keys are animated, we need to do updates before actual moves - var is_animated = (flags & EACH_IS_ANIMATED) !== 0; - var should_create; - if (is_animated) { - var i = b_length; - while (i-- > 0) { - b_end = i + start; - a = sources[i]; - if (pos === MOVED_BLOCK && a !== LIS_BLOCK) { - block = b_blocks[b_end]; - update_each_item_block(block, item, b_end, flags); - } - } - } - var last_block; - var last_sibling; - while (b_length-- > 0) { - b_end = b_length + start; - a = sources[b_length]; - should_create = a === -1; - item = array[b_end]; - if (should_create) { - key = is_computed_key ? keys[b_end] : item; - block = each_item_block(array, item, key, b_end, render_fn, flags); - } else { - block = b_blocks[b_end]; - if (!is_animated && should_update_block) { - update_each_item_block(block, item, b_end, flags); - } - } - if (should_create || (pos === MOVED_BLOCK && a !== LIS_BLOCK)) { - last_sibling = last_block === undefined ? sibling : get_first_child(last_block); - sibling = insert_each_item_block(block, dom, is_controlled, last_sibling); - } - b_blocks[b_end] = block; - last_block = block; - } - } - } - } - each_block.v = b_blocks; -} -// Longest Increased Subsequence algorithm. - -/** - * @param {Int32Array} a - * @returns {void} - */ -function mark_lis(a) { - var length = a.length; - var parent = new Int32Array(length); - var index = new Int32Array(length); - var index_length = 0; - var i = 0; - - /** @type {number} */ - var j; - - /** @type {number} */ - var k; - - /** @type {number} */ - var lo; - - /** @type {number} */ - var hi; - // Skip -1 values at the start of the input array `a`. - for (; a[i] === NEW_BLOCK; ++i) { - /**/ - } - index[0] = i++; - for (; i < length; ++i) { - k = a[i]; - if (k !== NEW_BLOCK) { - // Ignore -1 values. - j = index[index_length]; - if (a[j] < k) { - parent[i] = j; - index[++index_length] = i; - } else { - lo = 0; - hi = index_length; - while (lo < hi) { - j = (lo + hi) >> 1; - if (a[index[j]] < k) { - lo = j + 1; - } else { - hi = j; - } - } - if (k < a[index[lo]]) { - if (lo > 0) { - parent[i] = index[lo - 1]; - } - index[lo] = i; - } - } - } - } - // Mutate input array `a` and assign -2 value to all nodes that are part of LIS. - j = index[index_length]; - while (index_length-- >= 0) { - a[j] = LIS_BLOCK; - j = parent[j]; - } -} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 86f640ac01..71fcd14ce6 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -12,7 +12,6 @@ import { import { create_root_block, create_each_item_block, - create_each_block, create_if_block, create_key_block, create_await_block, @@ -23,7 +22,6 @@ import { } from './block.js'; import { EACH_KEYED, - EACH_IS_CONTROLLED, EACH_INDEX_REACTIVE, EACH_ITEM_REACTIVE, PassiveDelegatedEvents, @@ -31,14 +29,7 @@ import { AttributeAliases, EACH_IS_PROXIED } from '../../constants.js'; -import { - create_fragment_from_html, - insert, - reconcile_tracked_array, - reconcile_html, - remove, - reconcile_indexed_array -} from './reconciler.js'; +import { create_fragment_from_html, insert, reconcile_html, remove } from './reconciler.js'; import { render_effect, destroy_signal, @@ -2133,184 +2124,6 @@ export function each_item_block(array, item, key, index, render_fn, flags) { return block; } -/** - * @template V - * @param {Element | Comment} anchor_node - * @param {() => V[]} collection - * @param {number} flags - * @param {null | ((item: V) => string)} key_fn - * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn - * @param {null | ((anchor: Node) => void)} fallback_fn - * @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn - * @returns {void} - */ -function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_fn) { - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - const block = create_each_block(flags, anchor_node); - - /** @type {null | import('./types.js').Render} */ - let current_fallback = null; - hydrate_block_anchor(anchor_node, is_controlled); - - /** @type {V[]} */ - let array; - - /** @type {Array | null} */ - let keys = null; - - /** @type {null | import('./types.js').EffectSignal} */ - let render = null; - block.r = - /** @param {import('./types.js').Transition} transition */ - (transition) => { - const fallback = /** @type {import('./types.js').Render} */ (current_fallback); - const transitions = fallback.s; - transitions.add(transition); - transition.f(() => { - transitions.delete(transition); - if (transitions.size === 0) { - if (fallback.e !== null) { - if (fallback.d !== null) { - remove(fallback.d); - fallback.d = null; - } - destroy_signal(fallback.e); - fallback.e = null; - } - } - }); - }; - const create_fallback_effect = () => { - /** @type {import('./types.js').Render} */ - const fallback = { - d: null, - e: null, - s: new Set(), - p: current_fallback - }; - // Managed effect - const effect = render_effect( - () => { - const dom = block.d; - if (dom !== null) { - remove(dom); - block.d = null; - } - let anchor = block.a; - const is_controlled = (block.f & EACH_IS_CONTROLLED) !== 0; - if (is_controlled) { - anchor = empty(); - block.a.appendChild(anchor); - } - /** @type {(anchor: Node) => void} */ (fallback_fn)(anchor); - fallback.d = block.d; - block.d = null; - }, - block, - true - ); - fallback.e = effect; - current_fallback = fallback; - }; - const each = render_effect( - () => { - /** @type {V[]} */ - const maybe_array = collection(); - array = is_array(maybe_array) - ? maybe_array - : maybe_array == null - ? [] - : Array.from(maybe_array); - if (key_fn !== null) { - keys = array.map(key_fn); - } - const length = array.length; - if (fallback_fn !== null) { - if (length === 0) { - if (block.v.length !== 0 || render === null) { - create_fallback_effect(); - } - } else if (block.v.length === 0 && current_fallback !== null) { - const fallback = current_fallback; - const transitions = fallback.s; - if (transitions.size === 0) { - if (fallback.d !== null) { - remove(fallback.d); - fallback.d = null; - } - } else { - trigger_transitions(transitions, 'out'); - } - } - } - if (render !== null) { - execute_effect(render); - } - }, - block, - false - ); - render = render_effect( - /** @param {import('./types.js').EachBlock} block */ - (block) => { - const flags = block.f; - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - const anchor_node = block.a; - reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys); - }, - block, - true - ); - push_destroy_fn(each, () => { - const flags = block.f; - const anchor_node = block.a; - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - let fallback = current_fallback; - while (fallback !== null) { - const dom = fallback.d; - if (dom !== null) { - remove(dom); - } - const effect = fallback.e; - if (effect !== null) { - destroy_signal(effect); - } - fallback = fallback.p; - } - // Clear the array - reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys); - destroy_signal(/** @type {import('./types.js').EffectSignal} */ (render)); - }); - block.e = each; -} - -/** - * @template V - * @param {Element | Comment} anchor_node - * @param {() => V[]} collection - * @param {number} flags - * @param {null | ((item: V) => string)} key_fn - * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn - * @param {null | ((anchor: Node) => void)} fallback_fn - * @returns {void} - */ -export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) { - each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_tracked_array); -} - -/** - * @template V - * @param {Element | Comment} anchor_node - * @param {() => V[]} collection - * @param {number} flags - * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn - * @param {null | ((anchor: Node) => void)} fallback_fn - * @returns {void} - */ -export function each_indexed(anchor_node, collection, flags, render_fn, fallback_fn) { - each(anchor_node, collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array); -} - /** * @param {Element | Text | Comment} anchor * @param {boolean} is_html diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index d5345aab34..3cf98b119b 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -42,9 +42,11 @@ export { unwrap } from './client/runtime.js'; -export * from './client/validate.js'; - +export * from './client/each.js'; export * from './client/render.js'; +export * from './client/validate.js'; +export { raf } from './client/timing.js'; +export { proxy } from './client/proxy.js'; export { create_custom_element } from './client/custom-element.js'; @@ -55,7 +57,3 @@ export { $window as window, $document as document } from './client/operations.js'; - -export { raf } from './client/timing.js'; - -export { proxy } from './client/proxy.js';