pull/18763/merge
Simon H 21 hours ago committed by GitHub
commit 16483d2aa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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 { ExpressionMetadata } from '../../nodes.js' */
/** @import { Scope } from '../../scope.js' */ /** @import { Scope } from '../../scope.js' */
import * as b from '#compiler/builders'; 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 { import {
PROPS_IS_LAZY_INITIAL, PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE, PROPS_IS_IMMUTABLE,
PROPS_IS_RUNES, PROPS_IS_RUNES,
PROPS_IS_UPDATED, PROPS_IS_UPDATED,
PROPS_IS_BINDABLE PROPS_IS_BINDABLE,
PROPS_IS_RETAINED
} from '../../../../constants.js'; } from '../../../../constants.js';
import { get_rune } from '../../scope.js';
/** /**
* @param {Binding} binding * @param {Binding} binding
@ -44,6 +46,291 @@ export function build_getter(node, state) {
return node; 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 {Binding} binding
* @param {ComponentClientTransformState} state * @param {ComponentClientTransformState} state
@ -69,6 +356,10 @@ export function get_prop_source(binding, state, name, initial) {
flags |= PROPS_IS_RUNES; flags |= PROPS_IS_RUNES;
} }
if (state.analysis.runes && prop_binding_may_be_in_teardown(binding, state)) {
flags |= PROPS_IS_RETAINED;
}
if ( if (
state.analysis.accessors || state.analysis.accessors ||
(state.analysis.immutable (state.analysis.immutable
@ -123,7 +414,8 @@ export function is_prop_source(binding, state) {
binding.initial || binding.initial ||
// Until legacy mode is gone, we also need to use the prop source when only mutated is true, // 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 // 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 { Context } from '../types' */
import is_reference from 'is-reference'; import is_reference from 'is-reference';
import * as b from '#compiler/builders'; 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 * @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 context.state.analysis.runes && // can't do this in legacy mode because the proxy does more than just read/write
binding !== null && binding !== null &&
node !== binding.node && 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); const grand_parent = context.path.at(-2);

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

@ -134,7 +134,7 @@ export class Binding {
/** /**
* Additional metadata, varies per binding type * 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; metadata = null;

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

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