keyed each partly working

blockless
Rich Harris 2 years ago
parent e474ffe79c
commit f9eae70158

@ -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,124 +124,19 @@ 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();
let nl = new_items ? new_items.length : 0; var array = is_array(maybe_array)
? maybe_array
let hydrating_node = hydrating ? current_hydration_fragment[0] : null; : maybe_array == null
? []
for (let i = length; i < nl; i += 1) { : Array.from(maybe_array);
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 is_computed_key = true;
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} */ /** @type {number | void} */
var a = a_blocks.length; var a = a_blocks.length;
@ -256,31 +146,21 @@ function reconcile_tracked_array(
/** @type {Array<import('../../types.js').EachItemBlock>} */ /** @type {Array<import('../../types.js').EachItemBlock>} */
var b_blocks; 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,15 +327,16 @@ 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) { if (mismatch) {
// Server rendered less nodes than the client -> set empty array so that Svelte continues to operate in hydration mode // Server rendered less nodes than the client -> set empty array so that Svelte continues to operate in hydration mode
@ -453,7 +344,120 @@ function reconcile_tracked_array(
} }
} }
each_block.v = b_blocks; a_blocks = b_blocks;
});
}
/**
* @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()));
}
}
/**
* 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;
});
} }
/** /**

Loading…
Cancel
Save