WIP each blocks

blockless
Rich Harris 2 years ago
parent 75d7ca850c
commit b73c077950

@ -18,16 +18,18 @@ import { clear_text_content, empty, map_get, map_set } from '../../operations.js
import { insert, remove } from '../../reconciler.js'; import { insert, remove } from '../../reconciler.js';
import { import {
current_block, current_block,
current_effect,
destroy_signal, destroy_signal,
execute_effect, execute_effect,
push_destroy_fn, push_destroy_fn,
set_signal_value set_signal_value
} from '../../runtime.js'; } from '../../runtime.js';
import { render_effect } from '../../reactivity/computations.js'; import { 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';
import { EACH_BLOCK, EACH_ITEM_BLOCK } from '../../constants.js'; import { EACH_BLOCK, EACH_ITEM_BLOCK } from '../../constants.js';
import { unstate } from '../../proxy.js';
const NEW_BLOCK = -1; const NEW_BLOCK = -1;
const MOVED_BLOCK = 99999999; const MOVED_BLOCK = 99999999;
@ -99,25 +101,32 @@ export function create_each_item_block(item, index, key) {
* @param {null | ((item: V) => string)} key_fn * @param {null | ((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
* @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn
* @returns {void} * @returns {void}
*/ */
function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_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;
const block = create_each_block(flags, anchor_node);
/** @type {null | import('../../types.js').Render} */ if (is_controlled) {
let current_fallback = null; if (hydrating) {
hydrate_block_anchor(anchor_node, is_controlled); hydrate_block_anchor(anchor_node, is_controlled);
anchor_node = /** @type {Comment} */ (anchor_node.firstChild);
/** @type {V[]} */ } else {
let array; anchor_node.appendChild((anchor_node = empty()));
}
/** @type {Array<string> | null} */ }
let keys = null;
/** @type {null | import('../../types.js').EffectSignal} */
let render = null;
/** /**
* Whether or not there was a "rendered fallback but want to render items" (or vice versa) hydration mismatch. * Whether or not there was a "rendered fallback but want to render items" (or vice versa) hydration mismatch.
@ -125,197 +134,72 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
*/ */
let mismatch = false; let mismatch = false;
block.r = let length = 0;
/** @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) {
// If the each block is controlled, then the anchor node will be the surrounding
// element in which the each block is rendered, which requires certain handling
// depending on whether we're in hydration mode or not
if (!hydrating) {
// Create a new anchor on the fly because there's none due to the optimization
anchor = empty();
block.a.appendChild(anchor);
} else {
// In case of hydration the anchor will be the first child of the surrounding element
anchor = /** @type {Comment} */ (anchor.firstChild);
}
}
/** @type {(anchor: Node) => void} */ (fallback_fn)(anchor);
fallback.d = block.d;
block.d = null;
},
block,
true
);
fallback.e = effect;
current_fallback = fallback;
};
/** @param {import('../../types.js').EachBlock} block */ /** @type {Array<import('../../types.js').ComputationSignal | null>} */
const render_each = (block) => { let effects = [];
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);
};
const each = render_effect( /** @type {import('../../types.js').ComputationSignal | null} */
() => { let alternate;
/** @type {V[]} */
const maybe_array = collection();
array = is_array(maybe_array)
? maybe_array
: maybe_array == null
? []
: Array.from(maybe_array);
if (key_fn !== null) { function truncate() {
keys = array.map(key_fn); let i = effects.length;
} else if ((flags & EACH_KEYED) === 0) { while (i--) {
array.map(noop); if (effects[i]) {
effects.length = i + 1;
break;
} }
const length = array.length;
if (hydrating) {
const is_each_else_comment =
/** @type {Comment} */ (current_hydration_fragment?.[0])?.data === 'ssr:each_else';
// Check for hydration mismatch which can happen if the server renders the each fallback
// but the client has items, or vice versa. If so, remove everything inside the anchor and start fresh.
if ((is_each_else_comment && length) || (!is_each_else_comment && !length)) {
remove(current_hydration_fragment);
set_current_hydration_fragment(null);
mismatch = true;
} else if (is_each_else_comment) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment
).shift();
} }
} }
if (fallback_fn !== null) { render_effect(() => {
if (length === 0) { const new_items = collection();
if (block.v.length !== 0 || render === null) {
render_each(block);
create_fallback_effect();
return;
}
} 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) { let nl = new_items ? new_items.length : 0;
execute_effect(render);
} for (let i = length; i < nl; i += 1) {
if (effects[i]) {
resume_effect(effects[i]);
} else {
effects[i] = render_effect(
() =>
render_fn(
anchor_node,
() => {
return collection()[i];
}, },
block, i
false ),
{},
true
); );
render = render_effect(render_each, block, true);
if (mismatch) {
// Set a fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
} }
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) { for (let i = nl; i < length; i += 1) {
destroy_signal(effect); const item = effects[i];
if (item) {
pause_effect(item, () => {
effects[i] = null;
truncate();
});
} }
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; 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, () => {
* @template V alternate = null;
* @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);
} }
/** length = nl;
* @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);
} }
/** /**

@ -1359,6 +1359,11 @@ export function inspect(get_value, inspect = console.log) {
* @returns {import('./types.js').UnwrappedSignal<V>} * @returns {import('./types.js').UnwrappedSignal<V>}
*/ */
export function unwrap(value) { export function unwrap(value) {
// temporary!
if (typeof value === 'function') {
return value();
}
if (is_signal(value)) { if (is_signal(value)) {
// @ts-ignore // @ts-ignore
return get(value); return get(value);

Loading…
Cancel
Save