fix: ensure last prop value is used on teardown more consistently

Adds logic to save the last retrieved prop value, and return it when we see that the component is getting destroyed or already is. This logic lives inside `prop` and `rest_props`. Logic in the compiler was added to determine whether we need to use `$.prop`, which is true if we cannot statically verify that the prop is never used in a position that might be read during/after teardown.

Related to #16019

Trimmed down version of #18760 without the other teardown changes
teardown-props-robustness
Simon Holthausen 22 hours ago
parent 4bf15ae6f3
commit a1b172c351
No known key found for this signature in database

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure last prop value is used on teardown more consistently

@ -5,14 +5,16 @@
/** @import { ExpressionMetadata } from '../../nodes.js' */
/** @import { Scope } from '../../scope.js' */
import * as b from '#compiler/builders';
import { is_simple_expression, save } from '../../../utils/ast.js';
import { is_event_attribute, is_simple_expression, save } from '../../../utils/ast.js';
import {
PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE,
PROPS_IS_RUNES,
PROPS_IS_UPDATED,
PROPS_IS_BINDABLE
PROPS_IS_BINDABLE,
PROPS_IS_RETAINED
} from '../../../../constants.js';
import { get_rune } from '../../scope.js';
/**
* @param {Binding} binding
@ -44,6 +46,291 @@ export function build_getter(node, state) {
return node;
}
/**
* @param {Binding} binding
* @param {ClientTransformState} state
*/
export function prop_binding_may_be_in_teardown(binding, state) {
return ((binding.metadata ??= {}).prop_read_may_be_in_teardown ??= binding.references.some(
(reference) => reference_may_be_in_teardown(reference, state, new Set())
));
}
/**
* @param {Binding['references'][number]} reference
* @param {ClientTransformState} state
* @param {Set<Binding>} checked
*/
function reference_may_be_in_teardown(reference, state, checked) {
const { path } = reference;
for (let i = path.length - 1; i >= 0; i -= 1) {
const fn = path[i];
if (
fn.type !== 'ArrowFunctionExpression' &&
fn.type !== 'FunctionExpression' &&
fn.type !== 'FunctionDeclaration'
) {
continue;
}
const parent = path[i - 1];
if (parent?.type === 'CallExpression') {
// An IIFE executes in the same context as the function containing it.
if (parent.callee === fn) continue;
if (parent.arguments.includes(/** @type {Expression} */ (fn))) {
const scope = get_scope(path, i - 1, state);
if (is_save_svelte_import_call(parent, scope)) return false;
const rune = get_rune(parent, scope);
if (is_effect_rune(rune)) return false;
if (rune === '$derived.by') {
return derived_may_be_read_in_teardown(parent, path, i - 1, state, checked);
}
}
}
if (is_safe_element_event_handler(path, i)) return false;
const function_binding = get_function_binding(fn, path, i, state);
return function_binding === null
? true
: function_may_be_called_in_teardown(function_binding, state, checked);
}
for (let i = path.length - 1; i >= 0; i -= 1) {
const node = path[i];
if (node.type !== 'CallExpression') continue;
const rune = get_rune(node, get_scope(path, i, state));
if (rune === '$derived' || rune === '$derived.by') {
return derived_may_be_read_in_teardown(node, path, i, state, checked);
}
}
return false;
}
/**
* @param {Binding} binding
* @param {ClientTransformState} state
* @param {Set<Binding>} checked
*/
function function_may_be_called_in_teardown(binding, state, checked) {
if (checked.has(binding)) return false;
checked.add(binding);
for (const reference of binding.references) {
if (reference.node === binding.node) continue;
const parent = reference.path.at(-1);
if (parent?.type !== 'CallExpression') {
if (is_safe_element_event_handler(reference.path, reference.path.length)) continue;
return true;
}
if (parent.callee === reference.node) {
if (reference_may_be_in_teardown(reference, state, checked)) return true;
continue;
}
if (!parent.arguments.includes(reference.node)) return true;
const scope = get_scope(reference.path, reference.path.length - 1, state);
if (is_save_svelte_import_call(parent, scope)) continue;
const rune = get_rune(parent, scope);
if (is_effect_rune(rune)) continue;
if (
rune === '$derived.by' &&
!derived_may_be_read_in_teardown(
parent,
reference.path,
reference.path.length - 1,
state,
checked
)
) {
continue;
}
return true;
}
return false;
}
/**
* @param {import('estree').CallExpression} call
* @param {import('#compiler').AST.SvelteNode[]} path
* @param {number} index
* @param {ClientTransformState} state
* @param {Set<Binding>} checked
*/
function derived_may_be_read_in_teardown(call, path, index, state, checked) {
const parent = path[index - 1];
if (parent?.type !== 'VariableDeclarator' || parent.init !== call) return true;
if (parent.id.type !== 'Identifier') return true;
const binding = get_scope(path, index - 1, state).get(parent.id.name);
if (binding === null || checked.has(binding)) return false;
checked.add(binding);
for (const reference of binding.references) {
if (reference.node === binding.node) continue;
if (reference_may_be_in_teardown(reference, state, checked)) return true;
}
return false;
}
/**
* @param {import('estree').FunctionDeclaration | import('estree').FunctionExpression | import('estree').ArrowFunctionExpression} fn
* @param {import('#compiler').AST.SvelteNode[]} path
* @param {number} index
* @param {ClientTransformState} state
* @returns {Binding | null}
*/
function get_function_binding(fn, path, index, state) {
if (fn.type === 'FunctionDeclaration' && fn.id !== null) {
return get_scope(path, index - 1, state).get(fn.id.name);
}
const parent = path[index - 1];
if (
parent?.type === 'VariableDeclarator' &&
parent.init === fn &&
parent.id.type === 'Identifier'
) {
return get_scope(path, index - 1, state).get(parent.id.name);
}
return null;
}
/**
* @param {import('#compiler').AST.SvelteNode[]} path
* @param {number} index
* @param {ClientTransformState} state
*/
function get_scope(path, index, state) {
for (let i = index; i >= 0; i -= 1) {
const scope = state.scopes.get(path[i]);
if (scope !== undefined) return scope;
}
return state.scope;
}
/** @param {string | null} rune */
function is_effect_rune(rune) {
return rune === '$effect' || rune === '$effect.pre' || rune === '$effect.root';
}
/**
* @param {import('estree').CallExpression} call
* @param {Scope} scope
*/
function is_save_svelte_import_call(call, scope) {
if (call.callee.type === 'Identifier') {
return (
is_svelte_import(scope.get(call.callee.name), call.callee.name, 'untrack') ||
is_svelte_import(scope.get(call.callee.name), call.callee.name, 'hydratable')
);
}
if (
call.callee.type === 'MemberExpression' &&
call.callee.object.type === 'Identifier' &&
((!call.callee.computed &&
call.callee.property.type === 'Identifier' &&
(call.callee.property.name === 'untrack' || call.callee.property.name === 'hydratable')) ||
(call.callee.computed &&
call.callee.property.type === 'Literal' &&
(call.callee.property.value === 'untrack' || call.callee.property.value === 'hydratable')))
) {
return is_svelte_import(scope.get(call.callee.object.name), call.callee.object.name, null);
}
return false;
}
/**
* @param {Binding | null} binding
* @param {string} local_name
* @param {string | null} imported_name
*/
function is_svelte_import(binding, local_name, imported_name) {
if (binding?.initial?.type !== 'ImportDeclaration' || binding.initial.source.value !== 'svelte') {
return false;
}
return binding.initial.specifiers.some((specifier) => {
if (specifier.local.name !== local_name) return false;
if (imported_name === null) return specifier.type === 'ImportNamespaceSpecifier';
return (
specifier.type === 'ImportSpecifier' &&
specifier.imported.type === 'Identifier' &&
specifier.imported.name === imported_name
);
});
}
/**
* @param {import('#compiler').AST.SvelteNode[]} path
* @param {number} index
*/
function is_safe_element_event_handler(path, index) {
for (let i = index - 1; i >= 0; i -= 1) {
const node = path[i];
if (
node.type === 'ArrowFunctionExpression' ||
node.type === 'FunctionExpression' ||
node.type === 'FunctionDeclaration' ||
node.type === 'CallExpression' ||
node.type === 'NewExpression' ||
node.type === 'SpreadAttribute'
) {
return false;
}
if (node.type === 'Attribute') {
if (!is_event_attribute(node)) return false;
const element = path[i - 1];
return (
(element?.type === 'RegularElement' || element?.type === 'SvelteElement') &&
is_safe_event_name(node.name.slice(2))
);
}
if (node.type === 'OnDirective') {
const element = path[i - 1];
return (
(element?.type === 'RegularElement' || element?.type === 'SvelteElement') &&
is_safe_event_name(node.name)
);
}
}
return false;
}
/** @param {string} name */
function is_safe_event_name(name) {
if (name.endsWith('capture')) name = name.slice(0, -7);
return name !== 'blur' && name !== 'focusout';
}
/**
* @param {Binding} binding
* @param {ComponentClientTransformState} state
@ -69,6 +356,10 @@ export function get_prop_source(binding, state, name, initial) {
flags |= PROPS_IS_RUNES;
}
if (state.analysis.runes && prop_binding_may_be_in_teardown(binding, state)) {
flags |= PROPS_IS_RETAINED;
}
if (
state.analysis.accessors ||
(state.analysis.immutable
@ -123,7 +414,8 @@ export function is_prop_source(binding, state) {
binding.initial ||
// Until legacy mode is gone, we also need to use the prop source when only mutated is true,
// because the parent could be a legacy component which needs coarse-grained reactivity
binding.updated)
binding.updated ||
prop_binding_may_be_in_teardown(binding, state))
);
}

@ -2,7 +2,7 @@
/** @import { Context } from '../types' */
import is_reference from 'is-reference';
import * as b from '#compiler/builders';
import { build_getter } from '../utils.js';
import { build_getter, prop_binding_may_be_in_teardown } from '../utils.js';
/**
* @param {Identifier} node
@ -22,7 +22,8 @@ export function Identifier(node, context) {
context.state.analysis.runes && // can't do this in legacy mode because the proxy does more than just read/write
binding !== null &&
node !== binding.node &&
binding.kind === 'rest_prop'
binding.kind === 'rest_prop' &&
!prop_binding_may_be_in_teardown(binding, context.state)
) {
const grand_parent = context.path.at(-2);

@ -11,6 +11,7 @@ import {
get_prop_source,
is_prop_source,
is_state_source,
prop_binding_may_be_in_teardown,
should_proxy
} from '../utils.js';
import { get_value } from './shared/declarations.js';
@ -55,6 +56,7 @@ export function VariableDeclaration(node, context) {
}
if (declarator.id.type === 'Identifier') {
const binding = /** @type {Binding} */ (context.state.scope.get(declarator.id.name));
const exclude_id = context.state.scope.root.unique('rest_excludes');
context.state.hoisted.push(
b.var(exclude_id, b.new('Set', b.array(seen.map((name) => b.literal(name)))))
@ -68,6 +70,11 @@ export function VariableDeclaration(node, context) {
args.push(b.literal(declarator.id.name));
}
if (prop_binding_may_be_in_teardown(binding, context.state)) {
if (!dev) args.push(b.void0);
args.push(b.true);
}
declarations.push(b.declarator(declarator.id, b.call('$.rest_props', ...args)));
} else {
assert.equal(declarator.id.type, 'ObjectPattern');
@ -106,6 +113,8 @@ export function VariableDeclaration(node, context) {
}
} else {
// RestElement
const rest_id = /** @type {Identifier} */ (property.argument);
const binding = /** @type {Binding} */ (context.state.scope.get(rest_id.name));
const exclude_id = context.state.scope.root.unique('rest_excludes');
context.state.hoisted.push(
b.var(exclude_id, b.new('Set', b.array(seen.map((name) => b.literal(name)))))
@ -116,7 +125,12 @@ export function VariableDeclaration(node, context) {
if (dev) {
// include rest name, so we can provide informative error messages
args.push(b.literal(/** @type {Identifier} */ (property.argument).name));
args.push(b.literal(rest_id.name));
}
if (prop_binding_may_be_in_teardown(binding, context.state)) {
if (!dev) args.push(b.void0);
args.push(b.true);
}
declarations.push(b.declarator(property.argument, b.call('$.rest_props', ...args)));

@ -134,7 +134,7 @@ export class Binding {
/**
* Additional metadata, varies per binding type
* @type {null | { inside_rest?: boolean; is_template_declaration?: boolean; exclude_props?: string[] }}
* @type {null | { inside_rest?: boolean; is_template_declaration?: boolean; exclude_props?: string[]; prop_read_may_be_in_teardown?: boolean }}
*/
metadata = null;

@ -10,6 +10,7 @@ export const PROPS_IS_RUNES = 1 << 1;
export const PROPS_IS_UPDATED = 1 << 2;
export const PROPS_IS_BINDABLE = 1 << 3;
export const PROPS_IS_LAZY_INITIAL = 1 << 4;
export const PROPS_IS_RETAINED = 1 << 5;
export const TRANSITION_IN = 1;
export const TRANSITION_OUT = 1 << 1;

@ -4,8 +4,10 @@ import {
PROPS_IS_BINDABLE,
PROPS_IS_IMMUTABLE,
PROPS_IS_LAZY_INITIAL,
PROPS_IS_RETAINED,
PROPS_IS_RUNES,
PROPS_IS_UPDATED
PROPS_IS_UPDATED,
UNINITIALIZED
} from '../../../constants.js';
import { get_descriptor, is_function } from '../../shared/utils.js';
import { set, source, update } from './sources.js';
@ -18,7 +20,7 @@ import {
untrack
} from '../runtime.js';
import * as e from '../errors.js';
import { DESTROYED, LEGACY_PROPS, STATE_SYMBOL } from '#client/constants';
import { DESTROYED, DESTROYING, INERT, LEGACY_PROPS, STATE_SYMBOL } from '#client/constants';
import { proxy } from '../proxy.js';
import { capture_store_binding } from './store.js';
import { legacy_mode_flag } from '../../flags/index.js';
@ -46,15 +48,34 @@ export function update_pre_prop(fn, d = 1) {
return value;
}
/** @typedef {{ props: Record<string | symbol, unknown>, exclude: Set<string | symbol>, name?: string, retained: null | { effect: Effect, values: Map<string | symbol, unknown> } }} RestPropsTarget */
/**
* @param {RestPropsTarget} target
* @param {string | symbol} key
*/
function get_rest_prop(target, key) {
var value = target.props[key];
if (target.retained === null) return value;
if ((target.retained.effect.f & (INERT | DESTROYING | DESTROYED)) !== 0) {
return target.retained.values.has(key) ? target.retained.values.get(key) : value;
}
target.retained.values.set(key, value);
return value;
}
/**
* The proxy handler for rest props (i.e. `const { x, ...rest } = $props()`).
* Is passed the full `$$props` object and excludes the named props.
* @type {ProxyHandler<{ props: Record<string | symbol, unknown>, exclude: Set<string | symbol>, name?: string }>}}
* @type {ProxyHandler<RestPropsTarget>}
*/
const rest_props_handler = {
get(target, key) {
if (target.exclude.has(key)) return;
return target.props[key];
return get_rest_prop(target, key);
},
set(target, key) {
if (DEV) {
@ -66,17 +87,29 @@ const rest_props_handler = {
},
getOwnPropertyDescriptor(target, key) {
if (target.exclude.has(key)) return;
if (key in target.props) {
var exists =
key in target.props ||
(target.retained !== null &&
(target.retained.effect.f & (INERT | DESTROYING | DESTROYED)) !== 0 &&
target.retained.values.has(key));
if (exists) {
return {
enumerable: true,
configurable: true,
value: target.props[key]
value: get_rest_prop(target, key)
};
}
},
has(target, key) {
if (target.exclude.has(key)) return false;
return key in target.props;
return (
key in target.props ||
(target.retained !== null &&
(target.retained.effect.f & (INERT | DESTROYING | DESTROYED)) !== 0 &&
target.retained.values.has(key))
);
},
ownKeys(target) {
return Reflect.ownKeys(target.props).filter((key) => !target.exclude.has(key));
@ -84,14 +117,28 @@ const rest_props_handler = {
};
/**
* @param {Record<string, unknown>} props
* @param {Set<string>} exclude
* @param {Record<string | symbol, unknown>} props
* @param {Set<string | symbol>} exclude
* @param {string} [name]
* @param {boolean} [retain]
* @returns {Record<string, unknown>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function rest_props(props, exclude, name) {
return new Proxy(DEV ? { props, exclude, name } : { props, exclude }, rest_props_handler);
export function rest_props(props, exclude, name, retain = false) {
/** @type {RestPropsTarget['retained']} */
var retained = null;
if (retain) {
retained = {
effect: /** @type {Effect} */ (active_effect),
values: new Map()
};
}
return new Proxy(
DEV ? { props, exclude, name, retained } : { props, exclude, retained },
rest_props_handler
);
}
/**
@ -277,6 +324,7 @@ export function prop(props, key, flags, fallback) {
var runes = !legacy_mode_flag || (flags & PROPS_IS_RUNES) !== 0;
var bindable = (flags & PROPS_IS_BINDABLE) !== 0;
var lazy = (flags & PROPS_IS_LAZY_INITIAL) !== 0;
var retained = (flags & PROPS_IS_RETAINED) !== 0;
var fallback_value = /** @type {V} */ (fallback);
var fallback_dirty = true;
@ -357,6 +405,24 @@ export function prop(props, key, flags, fallback) {
};
}
var parent_effect = /** @type {Effect} */ (active_effect);
if (retained) {
var get_value = getter;
var retained_value = /** @type {V | typeof UNINITIALIZED} */ (UNINITIALIZED);
getter = () => {
if (
(parent_effect.f & (INERT | DESTROYING | DESTROYED)) !== 0 &&
retained_value !== UNINITIALIZED
) {
return retained_value;
}
return (retained_value = get_value());
};
}
// prop is never written to — we only need a getter
if (runes && (flags & PROPS_IS_UPDATED) === 0) {
return getter;
@ -403,8 +469,6 @@ export function prop(props, key, flags, fallback) {
// Capture the initial value if it's bindable
if (bindable) get(d);
var parent_effect = /** @type {Effect} */ (active_effect);
return /** @type {() => V} */ (
function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
if (arguments.length > 0) {
@ -423,7 +487,7 @@ export function prop(props, key, flags, fallback) {
// special case — avoid recalculating the derived if we're in a
// teardown function and the prop was overridden locally, or the
// component was already destroyed (people could access props in a timeout)
if ((is_destroying_effect && overridden) || (parent_effect.f & DESTROYED) !== 0) {
if ((parent_effect.f & (INERT | DESTROYING | DESTROYED)) !== 0 && overridden) {
return d.v;
}

@ -0,0 +1,23 @@
<script>
import { onMount } from "svelte";
const { value } = $props();
onMount(() => {
console.log("CHILD: mount value is", value);
return () => console.log("CHILD: unmount value is", value);
})
function outro() {
console.log("CHILD: outro value is", value);
return { duration: 100 };
}
function onclick() {
setTimeout(() => console.log("CHILD: timeout value is", value), 1000);
}
</script>
<div out:outro>
<button {onclick}>Set timeout</button>
</div>

@ -0,0 +1,38 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
import { vi } from 'vitest';
export default test({
before_test() {
vi.useFakeTimers();
},
after_test() {
vi.useRealTimers();
},
test({ assert, logs, raf, target }) {
const toggle = target.querySelector('button');
flushSync(() => toggle?.click());
target.querySelectorAll('button')[1]?.click();
flushSync(() => toggle?.click());
raf.tick(100);
raf.tick(200);
vi.advanceTimersByTime(1000);
assert.deepEqual(logs, [
'PARENT: set value to',
true,
'CHILD: mount value is',
true,
'PARENT: set value to',
false,
'CHILD: outro value is',
true,
'CHILD: unmount value is',
true,
'CHILD: timeout value is',
true
]);
}
});

@ -0,0 +1,16 @@
<script>
import Child from "./Child.svelte"
let value = $state(false)
function toggleValue() {
value = !value
console.log("PARENT: set value to", value)
}
</script>
<button onclick={toggleValue}>Toggle value</button>
{#if value}
<Child {value} />
{/if}

@ -0,0 +1,5 @@
<script>
let props = $props();
</script>
<button {...props}>component</button>

@ -0,0 +1,13 @@
import 'svelte/internal/disclose-version';
import * as $ from 'svelte/internal/client';
var rest_excludes = new Set(['$$slots', '$$events', '$$legacy']);
var root = $.from_html(`<button>component</button>`);
export default function Component($$anchor, $$props) {
let props = $.rest_props($$props, rest_excludes);
var button = root();
$.attribute_effect(button, () => ({ ...props }));
$.append($$anchor, button);
}

@ -0,0 +1,60 @@
import 'svelte/internal/disclose-version';
import * as $ from 'svelte/internal/client';
import { untrack as read_untracked } from 'svelte';
import * as svelte from 'svelte';
import Component from './Component.svelte';
var root = $.from_html(`<button>inline</button> <button>named</button> <button>blur</button> <button>focusout</button> <button>spread</button> <!> <button>mixed</button> <!>`, 1);
export default function Props_retained_analysis($$anchor, $$props) {
$.push($$props, true);
let unsafe_blur = $.prop($$props, 'unsafe_blur', 35),
unsafe_focusout = $.prop($$props, 'unsafe_focusout', 35),
unsafe_component_event = $.prop($$props, 'unsafe_component_event', 35),
unsafe_spread_event = $.prop($$props, 'unsafe_spread_event', 35),
unsafe_local_untrack = $.prop($$props, 'unsafe_local_untrack', 35),
unsafe_mixed_event = $.prop($$props, 'unsafe_mixed_event', 35);
read_untracked(() => $$props.safe_untrack);
svelte.untrack(() => $$props.safe_namespace_untrack);
const handle_click = () => $$props.safe_named_event;
const handle_blur = () => unsafe_blur();
const handle_focusout = () => unsafe_focusout();
const handle_component_event = () => unsafe_component_event();
const handle_mixed_event = () => unsafe_mixed_event();
function untrack(fn) {
fn();
}
untrack(() => unsafe_local_untrack());
var fragment = root();
var button = $.first_child(fragment);
var button_1 = $.sibling(button, 2);
var button_2 = $.sibling(button_1, 2);
var button_3 = $.sibling(button_2, 2);
var button_4 = $.sibling(button_3, 2);
$.attribute_effect(button_4, () => ({ ...{ onclick: () => unsafe_spread_event() } }));
var node = $.sibling(button_4, 2);
Component(node, { onclick: handle_component_event });
var button_5 = $.sibling(node, 2);
var node_1 = $.sibling(button_5, 2);
Component(node_1, { onclick: handle_mixed_event });
$.delegated('click', button, () => $$props.safe_inline_event);
$.delegated('click', button_1, handle_click);
$.event('blur', button_2, handle_blur);
$.delegated('focusout', button_3, handle_focusout);
$.delegated('click', button_5, handle_mixed_event);
$.append($$anchor, fragment);
$.pop();
}
$.delegate(['click', 'focusout']);

@ -0,0 +1,7 @@
import * as $ from 'svelte/internal/server';
export default function Component($$renderer, $$props) {
let { $$slots, $$events, ...props } = $$props;
$$renderer.push(`<button${$.attributes({ ...props })}>component</button>`);
}

@ -0,0 +1,41 @@
import * as $ from 'svelte/internal/server';
import { untrack as read_untracked } from 'svelte';
import * as svelte from 'svelte';
import Component from './Component.svelte';
export default function Props_retained_analysis($$renderer, $$props) {
$$renderer.component(($$renderer) => {
let {
safe_untrack,
safe_namespace_untrack,
safe_inline_event,
safe_named_event,
unsafe_blur,
unsafe_focusout,
unsafe_component_event,
unsafe_spread_event,
unsafe_local_untrack,
unsafe_mixed_event
} = $$props;
read_untracked(() => safe_untrack);
svelte.untrack(() => safe_namespace_untrack);
const handle_click = () => safe_named_event;
const handle_blur = () => unsafe_blur;
const handle_focusout = () => unsafe_focusout;
const handle_component_event = () => unsafe_component_event;
const handle_mixed_event = () => unsafe_mixed_event;
function untrack(fn) {
fn();
}
untrack(() => unsafe_local_untrack);
$$renderer.push(`<button>inline</button> <button>named</button> <button>blur</button> <button>focusout</button> <button${$.attributes({ ...{ onclick: () => unsafe_spread_event } })}>spread</button> `);
Component($$renderer, { onclick: handle_component_event });
$$renderer.push(`<!----> <button>mixed</button> `);
Component($$renderer, { onclick: handle_mixed_event });
$$renderer.push(`<!---->`);
});
}

@ -0,0 +1,42 @@
<script>
import { untrack as read_untracked } from 'svelte';
import * as svelte from 'svelte';
import Component from './Component.svelte';
let {
safe_untrack,
safe_namespace_untrack,
safe_inline_event,
safe_named_event,
unsafe_blur,
unsafe_focusout,
unsafe_component_event,
unsafe_spread_event,
unsafe_local_untrack,
unsafe_mixed_event
} = $props();
read_untracked(() => safe_untrack);
svelte.untrack(() => safe_namespace_untrack);
const handle_click = () => safe_named_event;
const handle_blur = () => unsafe_blur;
const handle_focusout = () => unsafe_focusout;
const handle_component_event = () => unsafe_component_event;
const handle_mixed_event = () => unsafe_mixed_event;
function untrack(fn) {
fn();
}
untrack(() => unsafe_local_untrack);
</script>
<button onclick={() => safe_inline_event}>inline</button>
<button onclick={handle_click}>named</button>
<button onblur={handle_blur}>blur</button>
<button onfocusout={handle_focusout}>focusout</button>
<button {...{ onclick: () => unsafe_spread_event }}>spread</button>
<Component onclick={handle_component_event} />
<button onclick={handle_mixed_event}>mixed</button>
<Component onclick={handle_mixed_event} />
Loading…
Cancel
Save