diff --git a/.changeset/beige-llamas-grab.md b/.changeset/beige-llamas-grab.md new file mode 100644 index 000000000000..4461ce97ccde --- /dev/null +++ b/.changeset/beige-llamas-grab.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: allow usage of getContext() within $derived runes diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 8a57b01161d1..12108b00f8c2 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -314,7 +314,7 @@ export function apply( if (typeof handler === 'function') { handler.apply(element, args); - } else if (has_side_effects || handler != null) { + } else if (has_side_effects || handler != null || error) { const filename = component?.[FILENAME]; const location = loc ? ` at ${filename}:${loc[0]}:${loc[1]}` : ` in ${filename}`; diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index a5678f1d687b..98fbfb0f5242 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -17,7 +17,8 @@ import { skip_reaction, update_reaction, increment_version, - set_active_effect + set_active_effect, + component_context } from '../runtime.js'; import { equals, safe_equals } from './equality.js'; import * as e from '../errors.js'; @@ -44,6 +45,7 @@ export function derived(fn) { /** @type {Derived} */ const signal = { children: null, + ctx: component_context, deps: null, equals, f: flags, @@ -169,5 +171,5 @@ export function destroy_derived(signal) { set_signal_status(signal, DESTROYED); // TODO we need to ensure we remove the derived from any parent derives - signal.v = signal.children = signal.deps = signal.reactions = null; + signal.v = signal.children = signal.deps = signal.ctx = signal.reactions = null; } diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index c9626c394185..2cef49eb2d80 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -17,6 +17,8 @@ export interface Value extends Signal { } export interface Reaction extends Signal { + /** The associated component context */ + ctx: null | ComponentContext; /** The reaction function */ fn: null | Function; /** Signals that this signal reads from */ @@ -40,8 +42,6 @@ export interface Effect extends Reaction { */ nodes_start: null | TemplateNode; nodes_end: null | TemplateNode; - /** The associated component context */ - ctx: null | ComponentContext; /** Reactions created inside this signal */ deriveds: null | Derived[]; /** The effect function */ diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 08716840d672..074c81588a32 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -302,6 +302,7 @@ export function update_reaction(reaction) { var previous_reaction = active_reaction; var previous_skip_reaction = skip_reaction; var prev_derived_sources = derived_sources; + var previous_component_context = component_context; var flags = reaction.f; new_deps = /** @type {null | Value[]} */ (null); @@ -310,6 +311,7 @@ export function update_reaction(reaction) { active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null; skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0; derived_sources = null; + component_context = reaction.ctx; try { var result = /** @type {Function} */ (0, reaction.fn)(); @@ -347,6 +349,7 @@ export function update_reaction(reaction) { active_reaction = previous_reaction; skip_reaction = previous_skip_reaction; derived_sources = prev_derived_sources; + component_context = previous_component_context; } } @@ -422,7 +425,6 @@ export function update_effect(effect) { var previous_component_context = component_context; active_effect = effect; - component_context = effect.ctx; if (DEV) { var previous_component_fn = dev_current_component_function; @@ -449,7 +451,6 @@ export function update_effect(effect) { handle_error(/** @type {Error} */ (error), effect, previous_component_context); } finally { active_effect = previous_effect; - component_context = previous_component_context; if (DEV) { dev_current_component_function = previous_component_fn; diff --git a/packages/svelte/tests/runtime-runes/samples/derived-get-context/Child.svelte b/packages/svelte/tests/runtime-runes/samples/derived-get-context/Child.svelte new file mode 100644 index 000000000000..0d0e642c97b4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-get-context/Child.svelte @@ -0,0 +1,13 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/derived-get-context/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-get-context/_config.js new file mode 100644 index 000000000000..141fdb0e25db --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-get-context/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ``, + + test({ assert, target }) { + const btn = target.querySelector('button'); + + flushSync(() => { + btn?.click(); + }); + + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/derived-get-context/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-get-context/main.svelte new file mode 100644 index 000000000000..40b16f6f4cc0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-get-context/main.svelte @@ -0,0 +1,8 @@ + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-component-invalid-warning/_config.js b/packages/svelte/tests/runtime-runes/samples/event-handler-component-invalid-warning/_config.js index 7f8e745e015d..01ac3c9ad0d9 100644 --- a/packages/svelte/tests/runtime-runes/samples/event-handler-component-invalid-warning/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/event-handler-component-invalid-warning/_config.js @@ -8,8 +8,24 @@ export default test({ }, test({ assert, target, warnings }) { + /** @type {any} */ + let error; + + const handler = (/** @type {any}} */ e) => { + error = e.error; + e.stopImmediatePropagation(); + }; + + window.addEventListener('error', handler, true); + target.querySelector('button')?.click(); + assert.throws(() => { + throw error; + }, /state_unsafe_mutation/); + + window.removeEventListener('error', handler, true); + assert.deepEqual(warnings, [ '`click` handler at Button.svelte:5:9 should be a function. Did you mean to add a leading `() =>`?' ]); diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-warning/_config.js b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-warning/_config.js index c1461d4ada09..423351a4c7cf 100644 --- a/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-warning/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/event-handler-invalid-warning/_config.js @@ -8,8 +8,24 @@ export default test({ }, test({ assert, target, warnings }) { + /** @type {any} */ + let error; + + const handler = (/** @type {any} */ e) => { + error = e.error; + e.stopImmediatePropagation(); + }; + + window.addEventListener('error', handler, true); + target.querySelector('button')?.click(); + assert.throws(() => { + throw error; + }, /state_unsafe_mutation/); + + window.removeEventListener('error', handler, true); + assert.deepEqual(warnings, [ '`click` handler at main.svelte:9:17 should be a function. Did you mean to remove the trailing `()`?' ]);