move each logic into its own module; breathe sigh of relief

proxied-state-each-blocks
Rich Harris 9 months ago committed by Dominic Gannaway
parent 358c93853f
commit c063a71729

@ -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<number>) => 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<string> | 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<number>) => 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<number>) => 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<V>} 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<number>) => 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<import('./types.js').EachItemBlock>} */
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<Text | Comment | Element>} */ (
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<V>} 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<number>) => void} render_fn
* @param {number} flags
* @param {boolean} apply_transitions
* @param {Array<string> | 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<import('./types.js').EachItemBlock>} */
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<Text | Comment | Element>} */ (
get_hydration_fragment(hydrating_node)
);
set_current_hydration_fragment(fragment);
// Get the <!--ssr:..--> 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<import('./types.js').EachItemBlock>} 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;
}
}

@ -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<Text | Comment | Element>} */ (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<import('./types.js').EachItemBlock>} 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<V>} 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<number>) => 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<import('./types.js').EachItemBlock>} */
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<Text | Comment | Element>} */ (
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<V>} 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<number>) => void} render_fn
* @param {number} flags
* @param {boolean} apply_transitions
* @param {Array<string> | 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<import('./types.js').EachItemBlock>} */
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<Text | Comment | Element>} */ (
get_hydration_fragment(hydrating_node)
);
set_current_hydration_fragment(fragment);
// Get the <!--ssr:..--> 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];
}
}

@ -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<number>) => 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<string> | 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<number>) => 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<number>) => 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

@ -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';

Loading…
Cancel
Save