fix: fix merge bugs and adapt to latest changes

state-onchange
paoloricciuti 2 months ago
parent 9ddff21579
commit e1e3bbf051

@ -5,6 +5,7 @@ import * as b from '#compiler/builders';
import { get_rune } from '../../../scope.js';
import { transform_inspect_rune } from '../../utils.js';
import { should_proxy } from '../utils.js';
import { get_onchange } from './shared/state.js';
/**
* @param {CallExpression} node
@ -24,6 +25,7 @@ export function CallExpression(node, context) {
case '$state':
case '$state.raw': {
let arg = node.arguments[0];
let onchange = get_onchange(/** @type {Expression} */ (node.arguments[1]), context);
/** @type {Expression | undefined} */
let value = undefined;
@ -35,11 +37,11 @@ export function CallExpression(node, context) {
rune === '$state' &&
should_proxy(/** @type {Expression} */ (arg), context.state.scope)
) {
value = b.call('$.proxy', value);
return b.call('$.assignable_proxy', value, onchange);
}
}
return b.call('$.state', value);
return b.call('$.state', value, onchange);
}
case '$derived':

@ -140,7 +140,7 @@ export function VariableDeclaration(node, context) {
const is_proxy = should_proxy(value, context.state.scope);
if (rune === '$state' && is_proxy) {
value = b.call('$.proxy', value, onchange);
value = b.call(is_state ? '$.assignable_proxy' : '$.proxy', value, onchange);
if (dev && !is_state) {
value = b.call('$.tag_proxy', value, b.literal(id.name));
@ -148,7 +148,9 @@ export function VariableDeclaration(node, context) {
}
if (is_state) {
value = b.call('$.state', value, onchange);
if (!(rune === '$state' && is_proxy)) {
value = b.call('$.state', value, onchange);
}
if (dev) {
value = b.call('$.tag', value, b.literal(id.name));

@ -1,11 +1,11 @@
/** @import { Expression, Property } from 'estree' */
/** @import { ComponentContext } from '../../types' */
/** @import { ComponentContext, Context } from '../../types' */
import * as b from '../../../../../utils/builders.js';
/**
* Extract the `onchange` callback from the options passed to `$state`
* @param {Expression} options
* @param {ComponentContext} context
* @param {ComponentContext | Context} context
* @returns {Expression | undefined}
*/
export function get_onchange(options, context) {

@ -22,7 +22,8 @@ import {
flush_inspect_effects,
set_inspect_effects_deferred,
batch_onchange,
state
state,
onchange_batch
} from './reactivity/sources.js';
import { PROXY_PATH_SYMBOL, STATE_SYMBOL, PROXY_ONCHANGE_SYMBOL } from '#client/constants';
import { UNINITIALIZED } from '../../constants.js';
@ -325,7 +326,7 @@ export function proxy(value, onchange) {
// if we are changing the length of the array we batch all the changes
// to the sources and the original value by calling batch_onchange and immediately
// invoking it...otherwise we just invoke an identity function
(is_proxied_array && prop === 'length' ? batch_onchange : identity)(() => {
(is_proxied_array && prop === 'length' && !onchange_batch ? batch_onchange : identity)(() => {
// variable.length = value -> clear all signals with index >= value
if (is_proxied_array && prop === 'length') {
for (var i = value; i < /** @type {Source<number>} */ (s).v; i += 1) {
@ -361,9 +362,8 @@ export function proxy(value, onchange) {
if (s === undefined) {
if (!has || get_descriptor(target, prop)?.writable) {
s = with_parent(() => source(undefined, onchange, stack));
set(s, proxy(value, onchange));
sources.set(prop, s);
set(s, proxy(value, onchange));
if (DEV) {
tag(s, get_label(path, prop));
@ -373,13 +373,13 @@ export function proxy(value, onchange) {
has = s.v !== UNINITIALIZED;
var p = with_parent(() => proxy(value, onchange));
set(s, p);
// when we set a property if the source is a proxy we remove the current onchange from
// the proxy `onchanges` so that it doesn't trigger it anymore
if (onchange && typeof s.v === 'object' && s.v !== null && STATE_SYMBOL in s.v) {
s.v[PROXY_ONCHANGE_SYMBOL](onchange, true);
}
set(s, p);
}
})();

@ -57,7 +57,7 @@ export function set_inspect_effects_deferred() {
}
/** @type {null | Set<() => void>} */
let onchange_batch = null;
export let onchange_batch = null;
/**
* @param {Function} fn
@ -271,6 +271,15 @@ export function internal_set(source, value) {
if (DEV && inspect_effects.size > 0 && !inspect_effects_deferred) {
flush_inspect_effects();
}
var onchange = source.o;
if (onchange) {
if (onchange_batch) {
onchange_batch.add(onchange);
} else {
onchange();
}
}
}
return value;
@ -294,14 +303,6 @@ export function flush_inspect_effects() {
}
inspect_effects.clear();
var onchange = source.o;
if (onchange) {
if (onchange_batch) {
onchange_batch.add(onchange);
} else {
onchange();
}
}
}
/**

@ -3,9 +3,14 @@ import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
const [btn, btn2, btn3] = target.querySelectorAll('button');
const [btn, btn2, btn3, btn4, btn5, btn6, btn7] = target.querySelectorAll('button');
assert.deepEqual(logs, ['constructor count', 'constructor proxy']);
assert.deepEqual(logs, [
'constructor count',
'constructor proxy',
'assign in constructor',
'assign in constructor proxy'
]);
logs.length = 0;
@ -17,5 +22,43 @@ export default test({
flushSync(() => btn3.click());
assert.deepEqual(logs, ['class count', 'class proxy', 'class proxy']);
flushSync(() => btn4.click());
assert.deepEqual(logs, [
'class count',
'class proxy',
'class proxy',
'declared in constructor'
]);
flushSync(() => btn5.click());
assert.deepEqual(logs, [
'class count',
'class proxy',
'class proxy',
'declared in constructor',
'declared in constructor'
]);
flushSync(() => btn6.click());
assert.deepEqual(logs, [
'class count',
'class proxy',
'class proxy',
'declared in constructor',
'declared in constructor',
'declared in constructor proxy'
]);
flushSync(() => btn7.click());
assert.deepEqual(logs, [
'class count',
'class proxy',
'class proxy',
'declared in constructor',
'declared in constructor',
'declared in constructor proxy',
'declared in constructor proxy'
]);
}
});

@ -23,9 +23,36 @@
}
});
declared_in_constructor;
declared_in_constructor_proxy;
#assign_in_constructor;
#assign_in_constructor_proxy;
constructor(){
this.#in_constructor = 42;
this.#in_constructor_proxy.count++;
this.declared_in_constructor = $state(0, {
onchange(){
console.log("declared in constructor");
}
});
this.declared_in_constructor_proxy = $state({ count: 0 }, {
onchange(){
console.log("declared in constructor proxy");
}
});
this.#assign_in_constructor = $state(0, {
onchange(){
console.log("assign in constructor");
}
});
this.#assign_in_constructor++;
this.#assign_in_constructor_proxy = $state({ count: 0 }, {
onchange(){
console.log("assign in constructor proxy");
}
});
this.#assign_in_constructor_proxy.count++;
}
}
@ -34,4 +61,8 @@
<button onclick={()=> class_test.count++}>{class_test.count}</button>
<button onclick={()=> class_test.proxy.count++}>{class_test.proxy.count}</button>
<button onclick={()=> class_test.proxy = {count: class_test.proxy.count+1}}>{class_test.proxy.count}</button>
<button onclick={()=> class_test.proxy = {count: class_test.proxy.count+1}}>{class_test.proxy.count}</button>
<button onclick={()=> class_test.declared_in_constructor++}>{class_test.declared_in_constructor}</button>
<button onclick={()=> class_test.declared_in_constructor = class_test.declared_in_constructor + 1 }>{class_test.declared_in_constructor}</button>
<button onclick={()=> class_test.declared_in_constructor_proxy.count++}>{class_test.declared_in_constructor_proxy.count}</button>
<button onclick={()=> class_test.declared_in_constructor_proxy.count = class_test.declared_in_constructor_proxy.count + 1 }>{class_test.declared_in_constructor_proxy.count}</button>

@ -3,10 +3,15 @@ import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
const [btn, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn10] =
const [btn, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn10, btn11, btn12, btn13] =
target.querySelectorAll('button');
assert.deepEqual(logs, ['constructor count', 'constructor object']);
assert.deepEqual(logs, [
'constructor count',
'constructor object',
'assign in constructor',
'assign in constructor object'
]);
logs.length = 0;
@ -29,15 +34,73 @@ export default test({
assert.deepEqual(logs, ['count', 'object', 'class count', 'class object']);
flushSync(() => btn7.click());
assert.deepEqual(logs, ['count', 'object', 'class count', 'class object']);
assert.deepEqual(logs, [
'count',
'object',
'class count',
'class object',
'declared in constructor'
]);
flushSync(() => btn8.click());
assert.deepEqual(logs, ['count', 'object', 'class count', 'class object']);
assert.deepEqual(logs, [
'count',
'object',
'class count',
'class object',
'declared in constructor',
'declared in constructor object'
]);
flushSync(() => btn9.click());
assert.deepEqual(logs, ['count', 'object', 'class count', 'class object']);
assert.deepEqual(logs, [
'count',
'object',
'class count',
'class object',
'declared in constructor',
'declared in constructor object'
]);
flushSync(() => btn10.click());
assert.deepEqual(logs, ['count', 'object', 'class count', 'class object', 'arr']);
assert.deepEqual(logs, [
'count',
'object',
'class count',
'class object',
'declared in constructor',
'declared in constructor object'
]);
flushSync(() => btn11.click());
assert.deepEqual(logs, [
'count',
'object',
'class count',
'class object',
'declared in constructor',
'declared in constructor object'
]);
flushSync(() => btn12.click());
assert.deepEqual(logs, [
'count',
'object',
'class count',
'class object',
'declared in constructor',
'declared in constructor object'
]);
flushSync(() => btn13.click());
assert.deepEqual(logs, [
'count',
'object',
'class count',
'class object',
'declared in constructor',
'declared in constructor object',
'arr'
]);
}
});

@ -23,22 +23,48 @@
}
})
#in_constructor = $state(0, {
#in_constructor = $state.raw(0, {
onchange(){
console.log("constructor count");
}
});
#in_constructor_proxy = $state({ count: 0 }, {
#in_constructor_obj = $state.raw({ count: 0 }, {
onchange(){
console.log("constructor object");
}
});
declared_in_constructor;
declared_in_constructor_obj;
#assign_in_constructor;
#assign_in_constructor_obj;
constructor(){
this.#in_constructor++;
this.#in_constructor_proxy.count++;
this.#in_constructor_obj = { count: this.#in_constructor_obj.count + 1 };
this.declared_in_constructor = $state.raw(0, {
onchange(){
console.log("declared in constructor");
}
});
this.declared_in_constructor_obj = $state.raw({ count: 0 }, {
onchange(){
console.log("declared in constructor object");
}
});
this.#assign_in_constructor = $state.raw(0, {
onchange(){
console.log("assign in constructor");
}
});
this.#assign_in_constructor++;
this.#assign_in_constructor_obj = $state.raw({ count: 0 }, {
onchange(){
console.log("assign in constructor object");
}
});
this.#assign_in_constructor_obj = { count: this.#assign_in_constructor_obj.count + 1 };
}
}
@ -58,6 +84,9 @@
<button onclick={()=> class_test.count++}>{class_test.count}</button>
<button onclick={()=> class_test.object.count++}>{class_test.object.count}</button>
<button onclick={()=> class_test.object = {count: class_test.object.count+1}}>{class_test.object.count}</button>
<button onclick={()=> class_test.declared_in_constructor++}>{class_test.declared_in_constructor}</button>
<button onclick={()=> class_test.declared_in_constructor_obj = {count: class_test.declared_in_constructor_obj.count + 1}}>{class_test.declared_in_constructor_obj.count}</button>
<button onclick={()=> class_test.declared_in_constructor_obj.count++}>{class_test.declared_in_constructor_obj.count}</button>
<button onclick={()=> arr.push(arr.length)}>push</button>
<button onclick={()=>arr.splice(0, 2)}>splice</button>

Loading…
Cancel
Save