revert bad changes
proxied-state-each-blocks
Dominic Gannaway 11 months ago
parent 575d0fcfe7
commit 80b2253cc2

@ -3,6 +3,7 @@ export const EACH_INDEX_REACTIVE = 1 << 1;
export const EACH_KEYED = 1 << 2; export const EACH_KEYED = 1 << 2;
export const EACH_IS_CONTROLLED = 1 << 3; export const EACH_IS_CONTROLLED = 1 << 3;
export const EACH_IS_ANIMATED = 1 << 4; export const EACH_IS_ANIMATED = 1 << 4;
export const EACH_IS_PROXIED = 1 << 5;
/** List of Element events that will be delegated */ /** List of Element events that will be delegated */
export const DelegatedEvents = [ export const DelegatedEvents = [

@ -1,6 +1,7 @@
import { effect_active, get, set, increment, source } from './runtime.js'; import { effect_active, get, set, increment, source } from './runtime.js';
const symbol = Symbol('magic'); export const MAGIC_SYMBOL = Symbol();
export const MAGIC_EACH_SYMBOL = Symbol();
/** /**
* @template T * @template T
@ -9,7 +10,21 @@ const symbol = Symbol('magic');
*/ */
export function magic(value) { export function magic(value) {
if (value && typeof value === 'object') { if (value && typeof value === 'object') {
return object(value); return wrap(value, null);
}
return value;
}
/**
* @template T
* @param {T} value
* @param {T | null} parent
* @returns {T}
*/
function wrap(value, parent) {
if (value && typeof value === 'object') {
return object(value, parent);
} }
return value; return value;
@ -17,11 +32,13 @@ export function magic(value) {
/** /**
* @template {Record<string | symbol, any>} T * @template {Record<string | symbol, any>} T
* @template P
* @param {T} value * @param {T} value
* @param {P | null} parent
* @returns {T} * @returns {T}
*/ */
function object(value) { function object(value, parent) {
if (symbol in value) return value; if (MAGIC_SYMBOL in value) return value;
/** @type {Map<string | symbol, any>} */ /** @type {Map<string | symbol, any>} */
const sources = new Map(); const sources = new Map();
@ -31,49 +48,53 @@ function object(value) {
return new Proxy(value, { return new Proxy(value, {
get(target, prop, receiver) { get(target, prop, receiver) {
if (prop === MAGIC_EACH_SYMBOL) {
return parent;
}
let s = sources.get(prop); let s = sources.get(prop);
if (effect_active() && !s) { if (s === undefined && effect_active()) {
s = source(magic(target[prop])); s = source(wrap(target[prop], receiver));
sources.set(prop, s); sources.set(prop, s);
} }
const value = s ? get(s) : target[prop]; const value = s !== undefined ? get(s) : target[prop];
if (typeof value === 'function') { if (typeof value === 'function') {
// @ts-ignore
return (...args) => { return (...args) => {
return value.apply(receiver, args); return value.apply(receiver, args);
}; };
} }
return value; return value;
}, },
set(target, prop, value) { set(target, prop, value) {
const s = sources.get(prop); const s = sources.get(prop);
if (s) set(s, magic(value)); if (s !== undefined) set(s, wrap(value, target));
if (is_array && prop === 'length') { if (is_array && prop === 'length') {
for (let i = value; i < target.length; i += 1) { for (let i = value; i < target.length; i += 1) {
const s = sources.get(i + ''); const s = sources.get(i + '');
if (s) set(s, undefined); if (s !== undefined) set(s, undefined);
} }
} }
if (!(prop in target)) increment(version); if (!(prop in target)) increment(version);
// @ts-ignore
target[prop] = value; target[prop] = value;
return true; return true;
}, },
deleteProperty(target, prop) { deleteProperty(target, prop) {
const s = sources.get(prop); const s = sources.get(prop);
if (s) set(s, undefined); if (s !== undefined) set(s, undefined);
if (prop in target) increment(version); if (prop in target) increment(version);
return delete target[prop]; return delete target[prop];
}, },
has(target, prop) { has(target, prop) {
if (prop === symbol) return true; if (prop === MAGIC_SYMBOL) return true;
get(version); get(version);
return Reflect.has(target, prop); return Reflect.has(target, prop);

@ -7,7 +7,13 @@ import {
} from './hydration.js'; } from './hydration.js';
import { is_array } from './utils.js'; import { is_array } from './utils.js';
import { each_item_block, destroy_each_item_block, update_each_item_block } from './render.js'; import { each_item_block, destroy_each_item_block, update_each_item_block } from './render.js';
import { EACH_INDEX_REACTIVE, EACH_IS_ANIMATED, EACH_ITEM_REACTIVE } from '../../constants.js'; import {
EACH_INDEX_REACTIVE,
EACH_IS_ANIMATED,
EACH_IS_PROXIED,
EACH_ITEM_REACTIVE
} from '../../constants.js';
import { MAGIC_SYMBOL } from './magic.js';
const NEW_BLOCK = -1; const NEW_BLOCK = -1;
const MOVED_BLOCK = 99999999; const MOVED_BLOCK = 99999999;
@ -177,9 +183,17 @@ export function reconcile_indexed_array(
flags, flags,
apply_transitions apply_transitions
) { ) {
var is_proxied_array = MAGIC_SYMBOL in array;
var a_blocks = each_block.v; var a_blocks = each_block.v;
var active_transitions = each_block.s; var active_transitions = each_block.s;
if (is_proxied_array) {
if ((flags & EACH_ITEM_REACTIVE) !== 0) {
flags ^= EACH_ITEM_REACTIVE;
}
flags |= EACH_IS_PROXIED;
}
/** @type {number | void} */ /** @type {number | void} */
var a = a_blocks.length; var a = a_blocks.length;
@ -220,7 +234,7 @@ export function reconcile_indexed_array(
hydrating_node = /** @type {Node} */ ( hydrating_node = /** @type {Node} */ (
/** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling /** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling
); );
block = each_item_block(item, null, index, render_fn, flags); block = each_item_block(array, item, null, index, render_fn, flags);
b_blocks[index] = block; b_blocks[index] = block;
} }
} else { } else {
@ -228,7 +242,7 @@ export function reconcile_indexed_array(
if (index >= a) { if (index >= a) {
// Add block // Add block
item = array[index]; item = array[index];
block = each_item_block(item, null, index, render_fn, flags); block = each_item_block(array, item, null, index, render_fn, flags);
b_blocks[index] = block; b_blocks[index] = block;
insert_each_item_block(block, dom, is_controlled, null); insert_each_item_block(block, dom, is_controlled, null);
} else if (index >= b) { } else if (index >= b) {
@ -277,8 +291,16 @@ export function reconcile_tracked_array(
) { ) {
var a_blocks = each_block.v; var a_blocks = each_block.v;
const is_computed_key = keys !== null; const is_computed_key = keys !== null;
var is_proxied_array = MAGIC_SYMBOL in array;
var active_transitions = each_block.s; var active_transitions = each_block.s;
if (is_proxied_array) {
if ((flags & EACH_ITEM_REACTIVE) !== 0) {
flags ^= EACH_ITEM_REACTIVE;
}
flags |= EACH_IS_PROXIED;
}
/** @type {number | void} */ /** @type {number | void} */
var a = a_blocks.length; var a = a_blocks.length;
@ -327,7 +349,7 @@ export function reconcile_tracked_array(
hydrating_node = /** @type {Node} */ ( hydrating_node = /** @type {Node} */ (
/** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling /** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling
); );
block = each_item_block(item, key, idx, render_fn, flags); block = each_item_block(array, item, key, idx, render_fn, flags);
b_blocks[idx] = block; b_blocks[idx] = block;
} }
} else if (a === 0) { } else if (a === 0) {
@ -336,7 +358,7 @@ export function reconcile_tracked_array(
idx = b_end - --b; idx = b_end - --b;
item = array[idx]; item = array[idx];
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(array, 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, dom, is_controlled, null);
} }
@ -384,7 +406,7 @@ export function reconcile_tracked_array(
while (b_end >= start) { while (b_end >= start) {
item = array[b_end]; item = array[b_end];
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(array, 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, dom, is_controlled, sibling);
} }
@ -446,7 +468,7 @@ export function reconcile_tracked_array(
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(array, item, key, b_end, render_fn, flags);
} else { } else {
block = b_blocks[b_end]; block = b_blocks[b_end];
if (!is_animated && should_update_block) { if (!is_animated && should_update_block) {

@ -28,7 +28,8 @@ import {
EACH_ITEM_REACTIVE, EACH_ITEM_REACTIVE,
PassiveDelegatedEvents, PassiveDelegatedEvents,
DelegatedEvents, DelegatedEvents,
AttributeAliases AttributeAliases,
EACH_IS_PROXIED
} from '../../constants.js'; } from '../../constants.js';
import { import {
create_fragment_from_html, create_fragment_from_html,
@ -61,7 +62,9 @@ import {
push, push,
current_component_context, current_component_context,
pop, pop,
schedule_task schedule_task,
unwrap,
lazy_property
} from './runtime.js'; } from './runtime.js';
import { import {
current_hydration_fragment, current_hydration_fragment,
@ -2101,6 +2104,7 @@ export function destroy_each_item_block(
/** /**
* @template V * @template V
* @param {V[]} array
* @param {V} item * @param {V} item
* @param {unknown} key * @param {unknown} key
* @param {number} index * @param {number} index
@ -2108,8 +2112,13 @@ export function destroy_each_item_block(
* @param {number} flags * @param {number} flags
* @returns {import('./types.js').EachItemBlock} * @returns {import('./types.js').EachItemBlock}
*/ */
export function each_item_block(item, key, index, render_fn, flags) { export function each_item_block(array, item, key, index, render_fn, flags) {
const item_value = (flags & EACH_ITEM_REACTIVE) === 0 ? item : source(item); const item_value =
(flags & EACH_IS_PROXIED) !== 0 && (flags & EACH_KEYED) === 0
? lazy_property(array, index)
: (flags & EACH_ITEM_REACTIVE) === 0
? item
: source(item);
const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index);
const block = create_each_item_block(item_value, index_value, key); const block = create_each_item_block(item_value, index_value, key);
const effect = render_effect( const effect = render_effect(
@ -2891,20 +2900,6 @@ export function spread_props(props) {
return merged_props; return merged_props;
} }
/**
* @template V
* @param {V} value
* @returns {import('./types.js').UnwrappedSignal<V>}
*/
export function unwrap(value) {
if (is_signal(value)) {
// @ts-ignore
return get(value);
}
// @ts-ignore
return value;
}
/** /**
* Mounts the given component to the given target and returns a handle to the component's public accessors * Mounts the given component to the given target and returns a handle to the component's public accessors
* as well as a `$set` and `$destroy` method to update the props of the component or destroy it. * as well as a `$set` and `$destroy` method to update the props of the component or destroy it.

@ -1,7 +1,6 @@
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { subscribe_to_store } from '../../store/utils.js'; import { subscribe_to_store } from '../../store/utils.js';
import { EMPTY_FUNC, run_all } from '../common.js'; import { EMPTY_FUNC, run_all } from '../common.js';
import { unwrap } from './render.js';
import { get_descriptors, is_array } from './utils.js'; import { get_descriptors, is_array } from './utils.js';
export const SOURCE = 1; export const SOURCE = 1;
@ -24,6 +23,7 @@ const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1; const FLUSH_SYNC = 1;
export const UNINITIALIZED = Symbol(); export const UNINITIALIZED = Symbol();
export const LAZY_PROPERTY = Symbol();
// Used for controlling the flush of effects. // Used for controlling the flush of effects.
let current_scheduler_mode = FLUSH_MICROTASK; let current_scheduler_mode = FLUSH_MICROTASK;
@ -1384,6 +1384,20 @@ export function is_signal(val) {
); );
} }
/**
* @template O
* @template P
* @param {any} val
* @returns {val is import('./types.js').LazyProperty<O, P>}
*/
export function is_lazy_property(val) {
return (
typeof val === 'object' &&
val !== null &&
/** @type {import('./types.js').LazyProperty<O, P>} */ (val).t === LAZY_PROPERTY
);
}
/** /**
* @template V * @template V
* @param {unknown} val * @param {unknown} val
@ -1860,3 +1874,35 @@ export function inspect(get_value, inspect = console.log) {
}; };
}); });
} }
/**
* @template O
* @template P
* @param {O} o
* @param {P} p
* @returns {import('./types.js').LazyProperty<O, P>}
*/
export function lazy_property(o, p) {
return {
o,
p,
t: LAZY_PROPERTY
};
}
/**
* @template V
* @param {V} value
* @returns {import('./types.js').UnwrappedSignal<V>}
*/
export function unwrap(value) {
if (is_signal(value)) {
// @ts-ignore
return get(value);
}
if (is_lazy_property(value)) {
return value.o[value.p];
}
// @ts-ignore
return value;
}

@ -10,7 +10,7 @@ import {
DYNAMIC_ELEMENT_BLOCK, DYNAMIC_ELEMENT_BLOCK,
SNIPPET_BLOCK SNIPPET_BLOCK
} from './block.js'; } from './block.js';
import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT } from './runtime.js'; import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT, LAZY_PROPERTY } from './runtime.js';
// Put all internal types in this file. Once we convert to JSDoc, we can make this a d.ts file // Put all internal types in this file. Once we convert to JSDoc, we can make this a d.ts file
@ -116,6 +116,12 @@ export type MaybeSignal<T = unknown> = T | Signal<T>;
export type UnwrappedSignal<T> = T extends Signal<infer U> ? U : T; export type UnwrappedSignal<T> = T extends Signal<infer U> ? U : T;
export type LazyProperty<O, P> = {
o: O;
p: P;
t: typeof LAZY_PROPERTY;
};
export type EqualsFunctions<T = any> = (a: T, v: T) => boolean; export type EqualsFunctions<T = any> = (a: T, v: T) => boolean;
export type BlockType = export type BlockType =

@ -38,7 +38,8 @@ export {
reactive_import, reactive_import,
effect_active, effect_active,
user_root_effect, user_root_effect,
inspect inspect,
unwrap
} from './client/runtime.js'; } from './client/runtime.js';
export * from './client/validate.js'; export * from './client/validate.js';

@ -1,8 +1,7 @@
import { flushSync } from 'svelte';
import { test } from '../../test'; import { test } from '../../test';
export default test({ export default test({
skip: true, // TODO `array.reverse()` doesn't trigger each block update
html: ` html: `
<button>1 + 2 + 3 = 6</button> <button>1 + 2 + 3 = 6</button>
<button>clear</button> <button>clear</button>
@ -31,7 +30,10 @@ export default test({
` `
); );
await reverse?.click(); flushSync(() => {
reverse?.click();
});
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -46,7 +48,10 @@ export default test({
` `
); );
await clear?.click(); flushSync(() => {
clear?.click();
});
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -54,7 +59,7 @@ export default test({
<button>clear</button> <button>clear</button>
<button>reverse</button> <button>reverse</button>
<span>4</span> <span>4</span>
<strong>array[1]: undefined</strong> <strong>array[1]:</strong>
` `
); );
} }

Loading…
Cancel
Save