diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/UseDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/UseDirective.js index 311ba3649763..46284436c3f2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/UseDirective.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/UseDirective.js @@ -9,26 +9,22 @@ import { parse_directive_name } from './shared/utils.js'; * @param {ComponentContext} context */ export function UseDirective(node, context) { - const params = [b.id('$$node')]; - - if (node.expression) { - params.push(b.id('$$action_arg')); + let action = /** @type {Expression} */ (context.visit(parse_directive_name(node.name))); + if (action.type === 'MemberExpression') { + action = b.maybe_call( + b.member(action, 'bind', false, true), + /** @type {Expression} */ (action.object) + ); } - /** @type {Expression[]} */ - const args = [ - context.state.node, - b.arrow( - params, - b.call(/** @type {Expression} */ (context.visit(parse_directive_name(node.name))), ...params) - ) - ]; - - if (node.expression) { - args.push(b.thunk(/** @type {Expression} */ (context.visit(node.expression)))); - } + const get_action = b.arrow([], action); + const get_value = node.expression + ? b.thunk(/** @type {Expression} */ (context.visit(node.expression))) + : undefined; // actions need to run after attribute updates in order with bindings/events - context.state.after_update.push(b.stmt(b.call('$.action', ...args))); + context.state.after_update.push( + b.stmt(b.call('$.action', context.state.node, get_action, get_value)) + ); context.next(); } diff --git a/packages/svelte/src/internal/client/dom/elements/actions.js b/packages/svelte/src/internal/client/dom/elements/actions.js index dad3ce6fef5d..4a8ff45d77c3 100644 --- a/packages/svelte/src/internal/client/dom/elements/actions.js +++ b/packages/svelte/src/internal/client/dom/elements/actions.js @@ -6,12 +6,16 @@ import { deep_read_state, untrack } from '../../runtime.js'; /** * @template P * @param {Element} dom - * @param {(dom: Element, value?: P) => ActionPayload
} action + * @param {() => (((dom: Element, value?: P) => ActionPayload
) | null | undefined)} get_action * @param {() => P} [get_value] * @returns {void} */ -export function action(dom, action, get_value) { +export function action(dom, get_action, get_value) { effect(() => { + const action = get_action(); + if (action == null) { + return; + } var payload = untrack(() => action(dom, get_value?.()) || {}); if (get_value && payload?.update) { diff --git a/packages/svelte/tests/runtime-runes/samples/action-reactive/Child.svelte b/packages/svelte/tests/runtime-runes/samples/action-reactive/Child.svelte new file mode 100644 index 000000000000..cc79512368bb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/action-reactive/Child.svelte @@ -0,0 +1,4 @@ + +
\ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/action-reactive/_config.js b/packages/svelte/tests/runtime-runes/samples/action-reactive/_config.js new file mode 100644 index 000000000000..bda5e019be93 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/action-reactive/_config.js @@ -0,0 +1,49 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const [div] = target.querySelectorAll('div'); + const [set_action1, set_action2, set_null, increment] = target.querySelectorAll('button'); + + assert.equal(div.innerText, undefined); + + flushSync(() => set_action1.click()); + assert.equal(div.innerText, 'action1 value=0'); + + flushSync(() => increment.click()); + assert.equal(div.innerText, 'action1 updated=1'); + + flushSync(() => set_null.click()); + assert.equal(div.innerText, 'action1 destroyed'); + + flushSync(() => set_action2.click()); + assert.equal(div.innerText, '1'); + + flushSync(() => increment.click()); + assert.equal(div.innerText, '2'); + + flushSync(() => set_null.click()); + assert.equal(div.innerText, ''); + + flushSync(() => increment.click()); + assert.equal(div.innerText, ''); + + flushSync(() => set_action1.click()); + assert.equal(div.innerText, 'action1 value=3'); + + flushSync(() => increment.click()); + assert.equal(div.innerText, 'action1 updated=4'); + + flushSync(() => set_action1.click()); + assert.equal(div.innerText, 'action1 updated=4'); + + flushSync(() => increment.click()); + assert.equal(div.innerText, 'action1 updated=5'); + + flushSync(() => set_action2.click()); + assert.equal(div.innerText, '5'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/action-reactive/main.svelte b/packages/svelte/tests/runtime-runes/samples/action-reactive/main.svelte new file mode 100644 index 000000000000..090f157133b9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/action-reactive/main.svelte @@ -0,0 +1,36 @@ + + +