mirror of https://github.com/sveltejs/svelte
Proxied state (#9739)
* magic objects * read length eagerly — triggers reconciliation * nested magic * tests * more tests * fix array memory leak * skipped, partially passing array test * Fix each revert bad changes * more 1337 * eliminate closures * maybe this is unnecessary? * only create sources for own properties * more * rename stuff * shuffle things around * emit $.proxy * remove proxy helper from public API * only create sources for writable properties * update test * get tests passing * fix * remove state-not-mutated warning, which is no longer valid * track reassignments separately from mutations * update test - effects no longer fire on mutations unnecessarily * move util into utils file * move each logic into its own module; breathe sigh of relief * tweaks * more tweaks * improve runtime * fix mistake * ensure we proxy when assigning to state * fix test * handle frozen * create sources when reading proxy properties inside deriveds * only mutate in legacy mode * add immutable to transform state * remove unused second argument to derived * remove unused second argument to source, and runtime immutable option to createRoot (not sure what that was doing there?) * oops, backwards * dedicated binding.kind for legacy reactive imports * avoid using prop_source in more cases. bit hacky, could be tidier, but it works * distinguish between source and mutable_source * remove immutable option from mount * remove unused apparatus around immutable option * deprecate immutable * fix * tweak * better default value handling * remove unnecessary exports * whitespace is free * remove obsolete symbol from proxy * cleanup ts * improve runtime perf by removing version reads in has() * add missing proxy call * tune perf of has() more * ensure we only create sources in effect_active() * fix proxy of computed fields * simplify and fix issue with indexed each blocks * fix compiler errors around exported state * only create source for state that is reassigned * temporary fix, we should come up with something better than this * readonly props * fix test * add test for bind: * make readonly dev-only * docs * forbid setPrototypeOf * lol whoops --------- Co-authored-by: Rich Harris <rich.harris@vercel.com> Co-authored-by: Dominic Gannaway <dg@domgan.com>pull/9753/head
parent
f0c47c31bc
commit
75fc09a79e
@ -0,0 +1,803 @@
|
|||||||
|
import {
|
||||||
|
EACH_INDEX_REACTIVE,
|
||||||
|
EACH_IS_ANIMATED,
|
||||||
|
EACH_IS_CONTROLLED,
|
||||||
|
EACH_IS_IMMUTABLE,
|
||||||
|
EACH_ITEM_REACTIVE,
|
||||||
|
EACH_KEYED
|
||||||
|
} from '../../constants.js';
|
||||||
|
import { create_each_block, create_each_item_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/proxy.js';
|
||||||
|
import { insert, remove } from './reconciler.js';
|
||||||
|
import { empty } from './render.js';
|
||||||
|
import {
|
||||||
|
destroy_signal,
|
||||||
|
execute_effect,
|
||||||
|
is_lazy_property,
|
||||||
|
lazy_property,
|
||||||
|
mutable_source,
|
||||||
|
push_destroy_fn,
|
||||||
|
render_effect,
|
||||||
|
schedule_task,
|
||||||
|
set_signal_value,
|
||||||
|
source
|
||||||
|
} 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;
|
||||||
|
|
||||||
|
function no_op() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
} else if ((flags & EACH_KEYED) === 0) {
|
||||||
|
array.map(no_op);
|
||||||
|
}
|
||||||
|
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}
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @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 = is_proxied_array ? lazy_property(array, index) : 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(item, null, index, render_fn, flags);
|
||||||
|
b_blocks[index] = block;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (; index < length; index++) {
|
||||||
|
if (index >= a) {
|
||||||
|
// Add block
|
||||||
|
item = is_proxied_array ? lazy_property(array, index) : array[index];
|
||||||
|
block = each_item_block(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}
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @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(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(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(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(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./types.js').Block} block
|
||||||
|
* @returns {Text | Element | Comment}
|
||||||
|
*/
|
||||||
|
function get_first_element(block) {
|
||||||
|
const current = block.d;
|
||||||
|
|
||||||
|
if (is_array(current)) {
|
||||||
|
for (let i = 0; i < current.length; i++) {
|
||||||
|
const node = current[i];
|
||||||
|
if (node.nodeType !== 8) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return /** @type {Text | Element | Comment} */ (current);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./types.js').EachItemBlock} block
|
||||||
|
* @param {any} item
|
||||||
|
* @param {number} index
|
||||||
|
* @param {number} type
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function update_each_item_block(block, item, index, type) {
|
||||||
|
if ((type & EACH_ITEM_REACTIVE) !== 0) {
|
||||||
|
set_signal_value(block.v, item);
|
||||||
|
} else if (is_lazy_property(block.v)) {
|
||||||
|
block.v.o[block.v.p] = item;
|
||||||
|
}
|
||||||
|
const transitions = block.s;
|
||||||
|
const index_is_reactive = (type & EACH_INDEX_REACTIVE) !== 0;
|
||||||
|
// Handle each item animations
|
||||||
|
if (transitions !== null && (type & EACH_KEYED) !== 0) {
|
||||||
|
let prev_index = block.i;
|
||||||
|
if (index_is_reactive) {
|
||||||
|
prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v;
|
||||||
|
}
|
||||||
|
const items = block.p.v;
|
||||||
|
if (prev_index !== index && /** @type {number} */ (index) < items.length) {
|
||||||
|
const from_dom = /** @type {Element} */ (get_first_element(block));
|
||||||
|
const from = from_dom.getBoundingClientRect();
|
||||||
|
schedule_task(() => {
|
||||||
|
trigger_transitions(transitions, 'key', from);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index_is_reactive) {
|
||||||
|
set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index);
|
||||||
|
} else {
|
||||||
|
block.i = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./types.js').EachItemBlock} block
|
||||||
|
* @param {null | Array<import('./types.js').Block>} transition_block
|
||||||
|
* @param {boolean} apply_transitions
|
||||||
|
* @param {any} controlled
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function destroy_each_item_block(
|
||||||
|
block,
|
||||||
|
transition_block,
|
||||||
|
apply_transitions,
|
||||||
|
controlled = false
|
||||||
|
) {
|
||||||
|
const transitions = block.s;
|
||||||
|
|
||||||
|
if (apply_transitions && transitions !== null) {
|
||||||
|
trigger_transitions(transitions, 'out');
|
||||||
|
if (transition_block !== null) {
|
||||||
|
transition_block.push(block);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const dom = block.d;
|
||||||
|
if (!controlled && dom !== null) {
|
||||||
|
remove(dom);
|
||||||
|
}
|
||||||
|
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template V
|
||||||
|
* @template O
|
||||||
|
* @template P
|
||||||
|
* @param {V | import('./types.js').LazyProperty<O, P>} item
|
||||||
|
* @param {unknown} key
|
||||||
|
* @param {number} index
|
||||||
|
* @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn
|
||||||
|
* @param {number} flags
|
||||||
|
* @returns {import('./types.js').EachItemBlock}
|
||||||
|
*/
|
||||||
|
function each_item_block(item, key, index, render_fn, flags) {
|
||||||
|
const each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0;
|
||||||
|
|
||||||
|
const item_value = each_item_not_reactive
|
||||||
|
? item
|
||||||
|
: (flags & EACH_IS_IMMUTABLE) === 0
|
||||||
|
? mutable_source(item)
|
||||||
|
: source(item);
|
||||||
|
|
||||||
|
const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index);
|
||||||
|
const block = create_each_item_block(item_value, index_value, key);
|
||||||
|
|
||||||
|
const effect = render_effect(
|
||||||
|
/** @param {import('./types.js').EachItemBlock} block */
|
||||||
|
(block) => {
|
||||||
|
render_fn(null, block.v, block.i);
|
||||||
|
},
|
||||||
|
block,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
block.e = effect;
|
||||||
|
return block;
|
||||||
|
}
|
@ -0,0 +1,176 @@
|
|||||||
|
import { DEV } from 'esm-env';
|
||||||
|
import {
|
||||||
|
effect_active,
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
increment,
|
||||||
|
source,
|
||||||
|
updating_derived,
|
||||||
|
UNINITIALIZED
|
||||||
|
} from '../runtime.js';
|
||||||
|
import { define_property, get_descriptor, is_array } from '../utils.js';
|
||||||
|
import { READONLY_SYMBOL } from './readonly.js';
|
||||||
|
|
||||||
|
/** @typedef {{ s: Map<string | symbol, import('../types.js').SourceSignal<any>>; v: import('../types.js').SourceSignal<number>; a: boolean }} Metadata */
|
||||||
|
/** @typedef {Record<string | symbol, any> & { [STATE_SYMBOL]: Metadata }} StateObject */
|
||||||
|
|
||||||
|
export const STATE_SYMBOL = Symbol('$state');
|
||||||
|
|
||||||
|
const object_prototype = Object.prototype;
|
||||||
|
const array_prototype = Array.prototype;
|
||||||
|
const get_prototype_of = Object.getPrototypeOf;
|
||||||
|
const is_frozen = Object.isFrozen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {StateObject} T
|
||||||
|
* @param {T} value
|
||||||
|
* @returns {T}
|
||||||
|
*/
|
||||||
|
export function proxy(value) {
|
||||||
|
if (typeof value === 'object' && value != null && !is_frozen(value) && !(STATE_SYMBOL in value)) {
|
||||||
|
const prototype = get_prototype_of(value);
|
||||||
|
|
||||||
|
// TODO handle Map and Set as well
|
||||||
|
if (prototype === object_prototype || prototype === array_prototype) {
|
||||||
|
define_property(value, STATE_SYMBOL, { value: init(value), writable: false });
|
||||||
|
|
||||||
|
// @ts-expect-error not sure how to fix this
|
||||||
|
return new Proxy(value, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {StateObject} value
|
||||||
|
* @returns {Metadata}
|
||||||
|
*/
|
||||||
|
function init(value) {
|
||||||
|
return {
|
||||||
|
s: new Map(),
|
||||||
|
v: source(0),
|
||||||
|
a: is_array(value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {ProxyHandler<StateObject>} */
|
||||||
|
const handler = {
|
||||||
|
defineProperty(target, prop, descriptor) {
|
||||||
|
if (descriptor.value) {
|
||||||
|
const metadata = target[STATE_SYMBOL];
|
||||||
|
|
||||||
|
const s = metadata.s.get(prop);
|
||||||
|
if (s !== undefined) set(s, proxy(descriptor.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Reflect.defineProperty(target, prop, descriptor);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteProperty(target, prop) {
|
||||||
|
const metadata = target[STATE_SYMBOL];
|
||||||
|
|
||||||
|
const s = metadata.s.get(prop);
|
||||||
|
if (s !== undefined) set(s, UNINITIALIZED);
|
||||||
|
|
||||||
|
if (prop in target) increment(metadata.v);
|
||||||
|
|
||||||
|
return delete target[prop];
|
||||||
|
},
|
||||||
|
|
||||||
|
get(target, prop, receiver) {
|
||||||
|
if (prop === READONLY_SYMBOL) return target[READONLY_SYMBOL];
|
||||||
|
|
||||||
|
const metadata = target[STATE_SYMBOL];
|
||||||
|
let s = metadata.s.get(prop);
|
||||||
|
|
||||||
|
// if we're reading a property in a reactive context, create a source,
|
||||||
|
// but only if it's an own property and not a prototype property
|
||||||
|
if (
|
||||||
|
s === undefined &&
|
||||||
|
(effect_active() || updating_derived) &&
|
||||||
|
(!(prop in target) || get_descriptor(target, prop)?.writable)
|
||||||
|
) {
|
||||||
|
s = source(proxy(target[prop]));
|
||||||
|
metadata.s.set(prop, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = s !== undefined ? get(s) : Reflect.get(target, prop, receiver);
|
||||||
|
return value === UNINITIALIZED ? undefined : value;
|
||||||
|
},
|
||||||
|
|
||||||
|
getOwnPropertyDescriptor(target, prop) {
|
||||||
|
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
||||||
|
if (descriptor && 'value' in descriptor) {
|
||||||
|
const metadata = target[STATE_SYMBOL];
|
||||||
|
const s = metadata.s.get(prop);
|
||||||
|
|
||||||
|
if (s) {
|
||||||
|
descriptor.value = get(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
},
|
||||||
|
|
||||||
|
has(target, prop) {
|
||||||
|
if (prop === STATE_SYMBOL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const metadata = target[STATE_SYMBOL];
|
||||||
|
const has = Reflect.has(target, prop);
|
||||||
|
|
||||||
|
let s = metadata.s.get(prop);
|
||||||
|
if (s !== undefined || (effect_active() && (!has || get_descriptor(target, prop)?.writable))) {
|
||||||
|
if (s === undefined) {
|
||||||
|
s = source(has ? proxy(target[prop]) : UNINITIALIZED);
|
||||||
|
metadata.s.set(prop, s);
|
||||||
|
}
|
||||||
|
const value = get(s);
|
||||||
|
if (value === UNINITIALIZED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return has;
|
||||||
|
},
|
||||||
|
|
||||||
|
set(target, prop, value) {
|
||||||
|
if (prop === READONLY_SYMBOL) {
|
||||||
|
target[READONLY_SYMBOL] = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = target[STATE_SYMBOL];
|
||||||
|
|
||||||
|
const s = metadata.s.get(prop);
|
||||||
|
if (s !== undefined) set(s, proxy(value));
|
||||||
|
|
||||||
|
if (metadata.a && prop === 'length') {
|
||||||
|
for (let i = value; i < target.length; i += 1) {
|
||||||
|
const s = metadata.s.get(i + '');
|
||||||
|
if (s !== undefined) set(s, UNINITIALIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(prop in target)) increment(metadata.v);
|
||||||
|
// @ts-ignore
|
||||||
|
target[prop] = value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
ownKeys(target) {
|
||||||
|
const metadata = target[STATE_SYMBOL];
|
||||||
|
|
||||||
|
get(metadata.v);
|
||||||
|
return Reflect.ownKeys(target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (DEV) {
|
||||||
|
handler.setPrototypeOf = () => {
|
||||||
|
throw new Error('Cannot set prototype of $state object');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { readonly } from './readonly.js';
|
@ -0,0 +1,65 @@
|
|||||||
|
import { define_property, get_descriptor } from '../utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {Record<string | symbol, any>} T
|
||||||
|
* @typedef {T & { [READONLY_SYMBOL]: Proxy<T> }} StateObject
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const READONLY_SYMBOL = Symbol('readonly');
|
||||||
|
|
||||||
|
const object_prototype = Object.prototype;
|
||||||
|
const array_prototype = Array.prototype;
|
||||||
|
const get_prototype_of = Object.getPrototypeOf;
|
||||||
|
const is_frozen = Object.isFrozen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {Record<string | symbol, any>} T
|
||||||
|
* @template {StateObject<T>} U
|
||||||
|
* @param {U} value
|
||||||
|
* @returns {Proxy<U> | U}
|
||||||
|
*/
|
||||||
|
export function readonly(value) {
|
||||||
|
const proxy = value && value[READONLY_SYMBOL];
|
||||||
|
if (proxy) return proxy;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value != null &&
|
||||||
|
!is_frozen(value) &&
|
||||||
|
!(READONLY_SYMBOL in value)
|
||||||
|
) {
|
||||||
|
const prototype = get_prototype_of(value);
|
||||||
|
|
||||||
|
// TODO handle Map and Set as well
|
||||||
|
if (prototype === object_prototype || prototype === array_prototype) {
|
||||||
|
const proxy = new Proxy(value, handler);
|
||||||
|
define_property(value, READONLY_SYMBOL, { value: proxy, writable: false });
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {never} */
|
||||||
|
const readonly_error = () => {
|
||||||
|
throw new Error(`Props are read-only, unless used with \`bind:\``);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {ProxyHandler<StateObject<any>>} */
|
||||||
|
const handler = {
|
||||||
|
defineProperty: readonly_error,
|
||||||
|
deleteProperty: readonly_error,
|
||||||
|
set: readonly_error,
|
||||||
|
|
||||||
|
get(target, prop, receiver) {
|
||||||
|
const value = Reflect.get(target, prop, receiver);
|
||||||
|
|
||||||
|
if (!(prop in target)) {
|
||||||
|
return readonly(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,9 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
error: {
|
||||||
|
code: 'invalid-derived-export',
|
||||||
|
message: 'Cannot export derived state',
|
||||||
|
position: process.platform === 'win32' ? [26, 68] : [24, 66]
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,3 @@
|
|||||||
|
let count = $state(0);
|
||||||
|
|
||||||
|
export const double = $derived(count * 2);
|
@ -0,0 +1,9 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
error: {
|
||||||
|
code: 'invalid-state-export',
|
||||||
|
message: 'Cannot export state if it is reassigned',
|
||||||
|
position: process.platform === 'win32' ? [50, 90] : [46, 86]
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,13 @@
|
|||||||
|
export const object = $state({
|
||||||
|
ok: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export const primitive = $state('nope');
|
||||||
|
|
||||||
|
export function update_object() {
|
||||||
|
object.ok = !object.ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update_primitive() {
|
||||||
|
primitive = 'yep';
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue