|
|
@ -24,7 +24,12 @@ import {
|
|
|
|
push_destroy_fn,
|
|
|
|
push_destroy_fn,
|
|
|
|
set_signal_value
|
|
|
|
set_signal_value
|
|
|
|
} from '../../runtime.js';
|
|
|
|
} from '../../runtime.js';
|
|
|
|
import { pause_effect, render_effect, resume_effect } from '../../reactivity/computations.js';
|
|
|
|
import {
|
|
|
|
|
|
|
|
destroy_effect,
|
|
|
|
|
|
|
|
pause_effect,
|
|
|
|
|
|
|
|
render_effect,
|
|
|
|
|
|
|
|
resume_effect
|
|
|
|
|
|
|
|
} from '../../reactivity/computations.js';
|
|
|
|
import { source, mutable_source } from '../../reactivity/sources.js';
|
|
|
|
import { source, mutable_source } from '../../reactivity/sources.js';
|
|
|
|
import { trigger_transitions } from '../../transitions.js';
|
|
|
|
import { trigger_transitions } from '../../transitions.js';
|
|
|
|
import { is_array } from '../../utils.js';
|
|
|
|
import { is_array } from '../../utils.js';
|
|
|
@ -94,29 +99,19 @@ export function create_each_item_block(item, index, key) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 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
|
|
|
|
* @template V
|
|
|
|
* @param {Element | Comment} anchor_node
|
|
|
|
* @param {Element | Comment} anchor_node
|
|
|
|
* @param {() => V[]} collection
|
|
|
|
* @param {() => V[]} collection
|
|
|
|
* @param {number} flags
|
|
|
|
* @param {number} flags
|
|
|
|
* @param {null | ((item: V) => string)} key_fn
|
|
|
|
* @param { ((item: V) => string)} key_fn
|
|
|
|
* @param {(anchor: null, item: V, index: import('../../types.js').MaybeSignal<number>) => void} render_fn
|
|
|
|
* @param {(anchor: null, item: V, index: import('../../types.js').MaybeSignal<number>) => void} render_fn
|
|
|
|
* @param {null | ((anchor: Node) => void)} fallback_fn
|
|
|
|
* @param {null | ((anchor: Node) => void)} fallback_fn
|
|
|
|
* @returns {void}
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) {
|
|
|
|
export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) {
|
|
|
|
throw new Error('TODO each_keyed');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @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) {
|
|
|
|
|
|
|
|
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
|
|
|
|
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
|
|
|
|
|
|
|
|
|
|
|
|
hydrate_block_anchor(anchor_node, is_controlled);
|
|
|
|
hydrate_block_anchor(anchor_node, is_controlled);
|
|
|
@ -129,158 +124,43 @@ export function each_indexed(anchor_node, collection, flags, render_fn, fallback
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/** @type {import('../../types.js').EachItemBlock[]} */
|
|
|
|
* Whether or not there was a "rendered fallback but want to render items" (or vice versa) hydration mismatch.
|
|
|
|
var a_blocks = [];
|
|
|
|
* Needs to be a `let` or else it isn't treeshaken out
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
let mismatch = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let length = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Array<import('../../types.js').ComputationSignal | null>} */
|
|
|
|
|
|
|
|
let effects = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {import('../../types.js').ComputationSignal | null} */
|
|
|
|
|
|
|
|
let alternate;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function truncate() {
|
|
|
|
|
|
|
|
let i = effects.length;
|
|
|
|
|
|
|
|
while (i--) {
|
|
|
|
|
|
|
|
if (effects[i]) {
|
|
|
|
|
|
|
|
effects.length = i + 1;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
render_effect(() => {
|
|
|
|
render_effect(() => {
|
|
|
|
const new_items = collection();
|
|
|
|
/** @type {V[]} */
|
|
|
|
|
|
|
|
const maybe_array = collection();
|
|
|
|
|
|
|
|
var array = is_array(maybe_array)
|
|
|
|
|
|
|
|
? maybe_array
|
|
|
|
|
|
|
|
: maybe_array == null
|
|
|
|
|
|
|
|
? []
|
|
|
|
|
|
|
|
: Array.from(maybe_array);
|
|
|
|
|
|
|
|
|
|
|
|
let nl = new_items ? new_items.length : 0;
|
|
|
|
const is_computed_key = true;
|
|
|
|
|
|
|
|
|
|
|
|
let hydrating_node = hydrating ? current_hydration_fragment[0] : null;
|
|
|
|
/** @type {number | void} */
|
|
|
|
|
|
|
|
var a = a_blocks.length;
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = length; i < nl; i += 1) {
|
|
|
|
/** @type {number} */
|
|
|
|
if (effects[i]) {
|
|
|
|
var b = array.length;
|
|
|
|
resume_effect(effects[i]);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (hydrating && !mismatch) {
|
|
|
|
|
|
|
|
let fragment = get_hydration_fragment(hydrating_node);
|
|
|
|
|
|
|
|
set_current_hydration_fragment(fragment);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!fragment) {
|
|
|
|
/** @type {Array<import('../../types.js').EachItemBlock>} */
|
|
|
|
// If fragment is null, then that means that the server rendered less items than what
|
|
|
|
var b_blocks;
|
|
|
|
// the client code specifies -> break out and continue with client-side node creation
|
|
|
|
|
|
|
|
mismatch = true;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hydrating_node = fragment.at(-1).nextSibling?.nextSibling;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
effects[i] = render_effect(
|
|
|
|
|
|
|
|
() =>
|
|
|
|
|
|
|
|
render_fn(
|
|
|
|
|
|
|
|
hydrating ? null : anchor_node,
|
|
|
|
|
|
|
|
() => {
|
|
|
|
|
|
|
|
return collection()[i];
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
i
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
{},
|
|
|
|
|
|
|
|
true
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = nl; i < length; i += 1) {
|
|
|
|
|
|
|
|
const item = effects[i];
|
|
|
|
|
|
|
|
if (item) {
|
|
|
|
|
|
|
|
pause_effect(item, () => {
|
|
|
|
|
|
|
|
effects[i] = null;
|
|
|
|
|
|
|
|
truncate();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (nl === 0) {
|
|
|
|
|
|
|
|
if (alternate) {
|
|
|
|
|
|
|
|
resume_effect(alternate);
|
|
|
|
|
|
|
|
} else if (fallback_fn) {
|
|
|
|
|
|
|
|
alternate = render_effect(() => fallback_fn(anchor_node), {}, true);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (alternate) {
|
|
|
|
|
|
|
|
pause_effect(alternate, () => {
|
|
|
|
|
|
|
|
alternate = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
length = nl;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 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}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
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 active_transitions = each_block.s;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @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 a_end = a - 1;
|
|
|
|
var b_end = b - 1;
|
|
|
|
var b_end = b - 1;
|
|
|
|
var key;
|
|
|
|
var key;
|
|
|
|
var item;
|
|
|
|
var item;
|
|
|
|
var idx;
|
|
|
|
var idx;
|
|
|
|
|
|
|
|
|
|
|
|
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
|
|
|
|
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
|
|
|
|
let mismatch = false;
|
|
|
|
let mismatch = false;
|
|
|
|
|
|
|
|
|
|
|
|
b_blocks = Array(b);
|
|
|
|
b_blocks = Array(b);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var keys = array.map(key_fn);
|
|
|
|
|
|
|
|
var block;
|
|
|
|
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
if (hydrating) {
|
|
|
|
// Hydrate block
|
|
|
|
// Hydrate block
|
|
|
|
var fragment;
|
|
|
|
var fragment;
|
|
|
@ -322,7 +202,7 @@ function reconcile_tracked_array(
|
|
|
|
key = is_computed_key ? keys[idx] : item;
|
|
|
|
key = is_computed_key ? keys[idx] : item;
|
|
|
|
block = each_item_block(item, key, idx, render_fn, flags);
|
|
|
|
block = each_item_block(item, key, idx, render_fn, flags);
|
|
|
|
b_blocks[idx] = block;
|
|
|
|
b_blocks[idx] = block;
|
|
|
|
insert_each_item_block(block, dom, is_controlled, null);
|
|
|
|
insert_each_item_block(block, anchor_node, is_controlled, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
|
|
|
|
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
|
|
|
@ -334,6 +214,7 @@ function reconcile_tracked_array(
|
|
|
|
var sibling = null;
|
|
|
|
var sibling = null;
|
|
|
|
item = array[b_end];
|
|
|
|
item = array[b_end];
|
|
|
|
key = is_computed_key ? keys[b_end] : item;
|
|
|
|
key = is_computed_key ? keys[b_end] : item;
|
|
|
|
|
|
|
|
|
|
|
|
// Step 1
|
|
|
|
// Step 1
|
|
|
|
outer: while (true) {
|
|
|
|
outer: while (true) {
|
|
|
|
// From the end
|
|
|
|
// From the end
|
|
|
@ -365,6 +246,7 @@ function reconcile_tracked_array(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Step 2
|
|
|
|
// Step 2
|
|
|
|
if (start > a_end) {
|
|
|
|
if (start > a_end) {
|
|
|
|
while (b_end >= start) {
|
|
|
|
while (b_end >= start) {
|
|
|
@ -372,13 +254,14 @@ function reconcile_tracked_array(
|
|
|
|
key = is_computed_key ? keys[b_end] : item;
|
|
|
|
key = is_computed_key ? keys[b_end] : item;
|
|
|
|
block = each_item_block(item, key, b_end, render_fn, flags);
|
|
|
|
block = each_item_block(item, key, b_end, render_fn, flags);
|
|
|
|
b_blocks[b_end--] = block;
|
|
|
|
b_blocks[b_end--] = block;
|
|
|
|
sibling = insert_each_item_block(block, dom, is_controlled, sibling);
|
|
|
|
sibling = insert_each_item_block(block, anchor_node, is_controlled, sibling);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (start > b_end) {
|
|
|
|
} else if (start > b_end) {
|
|
|
|
b = start;
|
|
|
|
b = start;
|
|
|
|
do {
|
|
|
|
do {
|
|
|
|
if ((block = a_blocks[b++]) !== null) {
|
|
|
|
if ((block = a_blocks[b++]) !== null) {
|
|
|
|
destroy_each_item_block(block, active_transitions, apply_transitions);
|
|
|
|
// destroy_each_item_block(block, [], false);
|
|
|
|
|
|
|
|
destroy_effect(block.e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (b <= a_end);
|
|
|
|
} while (b <= a_end);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
@ -394,6 +277,7 @@ function reconcile_tracked_array(
|
|
|
|
key = is_computed_key ? keys[a] : item;
|
|
|
|
key = is_computed_key ? keys[a] : item;
|
|
|
|
map_set(item_index, key, a);
|
|
|
|
map_set(item_index, key, a);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If keys are animated, we need to do updates before actual moves
|
|
|
|
// If keys are animated, we need to do updates before actual moves
|
|
|
|
if (is_animated) {
|
|
|
|
if (is_animated) {
|
|
|
|
for (b = start; b <= a_end; ++b) {
|
|
|
|
for (b = start; b <= a_end; ++b) {
|
|
|
@ -405,6 +289,7 @@ function reconcile_tracked_array(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (b = start; b <= a_end; ++b) {
|
|
|
|
for (b = start; b <= a_end; ++b) {
|
|
|
|
a = map_get(item_index, /** @type {V} */ (a_blocks[b].k));
|
|
|
|
a = map_get(item_index, /** @type {V} */ (a_blocks[b].k));
|
|
|
|
block = a_blocks[b];
|
|
|
|
block = a_blocks[b];
|
|
|
@ -413,21 +298,26 @@ function reconcile_tracked_array(
|
|
|
|
sources[a - start] = b;
|
|
|
|
sources[a - start] = b;
|
|
|
|
b_blocks[a] = block;
|
|
|
|
b_blocks[a] = block;
|
|
|
|
} else if (block !== null) {
|
|
|
|
} else if (block !== null) {
|
|
|
|
destroy_each_item_block(block, active_transitions, apply_transitions);
|
|
|
|
// destroy_each_item_block(block, [], false);
|
|
|
|
|
|
|
|
destroy_effect(block.e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Step 4
|
|
|
|
// Step 4
|
|
|
|
if (pos === MOVED_BLOCK) {
|
|
|
|
if (pos === MOVED_BLOCK) {
|
|
|
|
mark_lis(sources);
|
|
|
|
mark_lis(sources);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var last_block;
|
|
|
|
var last_block;
|
|
|
|
var last_sibling;
|
|
|
|
var last_sibling;
|
|
|
|
var should_create;
|
|
|
|
var should_create;
|
|
|
|
|
|
|
|
|
|
|
|
while (b_length-- > 0) {
|
|
|
|
while (b_length-- > 0) {
|
|
|
|
b_end = b_length + start;
|
|
|
|
b_end = b_length + start;
|
|
|
|
a = sources[b_length];
|
|
|
|
a = sources[b_length];
|
|
|
|
should_create = a === -1;
|
|
|
|
should_create = a === -1;
|
|
|
|
item = array[b_end];
|
|
|
|
item = array[b_end];
|
|
|
|
|
|
|
|
|
|
|
|
if (should_create) {
|
|
|
|
if (should_create) {
|
|
|
|
key = is_computed_key ? keys[b_end] : item;
|
|
|
|
key = is_computed_key ? keys[b_end] : item;
|
|
|
|
block = each_item_block(item, key, b_end, render_fn, flags);
|
|
|
|
block = each_item_block(item, key, b_end, render_fn, flags);
|
|
|
@ -437,23 +327,137 @@ function reconcile_tracked_array(
|
|
|
|
update_each_item_block(block, item, b_end, flags);
|
|
|
|
update_each_item_block(block, item, b_end, flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (should_create || (pos === MOVED_BLOCK && a !== LIS_BLOCK)) {
|
|
|
|
if (should_create || (pos === MOVED_BLOCK && a !== LIS_BLOCK)) {
|
|
|
|
last_sibling = last_block === undefined ? sibling : get_first_child(last_block);
|
|
|
|
last_sibling = last_block === undefined ? sibling : get_first_child(last_block);
|
|
|
|
sibling = insert_each_item_block(block, dom, is_controlled, last_sibling);
|
|
|
|
sibling = insert_each_item_block(block, anchor_node, is_controlled, last_sibling);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
b_blocks[b_end] = block;
|
|
|
|
b_blocks[b_end] = block;
|
|
|
|
last_block = block;
|
|
|
|
last_block = block;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (mismatch) {
|
|
|
|
|
|
|
|
// Server rendered less nodes than the client -> set empty array so that Svelte continues to operate in hydration mode
|
|
|
|
|
|
|
|
set_current_hydration_fragment([]);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (mismatch) {
|
|
|
|
a_blocks = b_blocks;
|
|
|
|
// Server rendered less nodes than the client -> set empty array so that Svelte continues to operate in hydration mode
|
|
|
|
});
|
|
|
|
set_current_hydration_fragment([]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @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) {
|
|
|
|
|
|
|
|
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hydrate_block_anchor(anchor_node, is_controlled);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (is_controlled) {
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
|
|
|
|
anchor_node = /** @type {Comment} */ (anchor_node.firstChild);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
anchor_node.appendChild((anchor_node = empty()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
each_block.v = b_blocks;
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Whether or not there was a "rendered fallback but want to render items" (or vice versa) hydration mismatch.
|
|
|
|
|
|
|
|
* Needs to be a `let` or else it isn't treeshaken out
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
let mismatch = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let length = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Array<import('../../types.js').ComputationSignal | null>} */
|
|
|
|
|
|
|
|
let effects = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {import('../../types.js').ComputationSignal | null} */
|
|
|
|
|
|
|
|
let alternate;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function truncate() {
|
|
|
|
|
|
|
|
let i = effects.length;
|
|
|
|
|
|
|
|
while (i--) {
|
|
|
|
|
|
|
|
if (effects[i]) {
|
|
|
|
|
|
|
|
effects.length = i + 1;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
render_effect(() => {
|
|
|
|
|
|
|
|
const new_items = collection();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let nl = new_items ? new_items.length : 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let hydrating_node = hydrating ? current_hydration_fragment[0] : null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = length; i < nl; i += 1) {
|
|
|
|
|
|
|
|
if (effects[i]) {
|
|
|
|
|
|
|
|
resume_effect(effects[i]);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (hydrating && !mismatch) {
|
|
|
|
|
|
|
|
let fragment = get_hydration_fragment(hydrating_node);
|
|
|
|
|
|
|
|
set_current_hydration_fragment(fragment);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!fragment) {
|
|
|
|
|
|
|
|
// If fragment is null, then that means that the server rendered less items than what
|
|
|
|
|
|
|
|
// the client code specifies -> break out and continue with client-side node creation
|
|
|
|
|
|
|
|
mismatch = true;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hydrating_node = fragment.at(-1).nextSibling?.nextSibling;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
effects[i] = render_effect(
|
|
|
|
|
|
|
|
() =>
|
|
|
|
|
|
|
|
render_fn(
|
|
|
|
|
|
|
|
hydrating ? null : anchor_node,
|
|
|
|
|
|
|
|
() => {
|
|
|
|
|
|
|
|
return collection()[i];
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
i
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
{},
|
|
|
|
|
|
|
|
true
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = nl; i < length; i += 1) {
|
|
|
|
|
|
|
|
const item = effects[i];
|
|
|
|
|
|
|
|
if (item) {
|
|
|
|
|
|
|
|
pause_effect(item, () => {
|
|
|
|
|
|
|
|
effects[i] = null;
|
|
|
|
|
|
|
|
truncate();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (nl === 0) {
|
|
|
|
|
|
|
|
if (alternate) {
|
|
|
|
|
|
|
|
resume_effect(alternate);
|
|
|
|
|
|
|
|
} else if (fallback_fn) {
|
|
|
|
|
|
|
|
alternate = render_effect(() => fallback_fn(anchor_node), {}, true);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (alternate) {
|
|
|
|
|
|
|
|
pause_effect(alternate, () => {
|
|
|
|
|
|
|
|
alternate = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
length = nl;
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|