From 09510c8c5c0888578446781e54f2229428e21bfc Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Tue, 21 Jan 2025 20:01:14 +0000
Subject: [PATCH 01/67] fix: address regression with untrack (#15079)
* fix: address regression with untrack
* add test
---
.changeset/ninety-chefs-know.md | 5 ++
.../src/internal/client/reactivity/sources.js | 2 +-
.../untracked-derived-local/_config.js | 5 ++
.../untracked-derived-local/main.svelte | 47 +++++++++++++++++++
4 files changed, 58 insertions(+), 1 deletion(-)
create mode 100644 .changeset/ninety-chefs-know.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte
diff --git a/.changeset/ninety-chefs-know.md b/.changeset/ninety-chefs-know.md
new file mode 100644
index 0000000000..b5eac6a07a
--- /dev/null
+++ b/.changeset/ninety-chefs-know.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: address regression with untrack
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index d10008dae2..c2448c9ee5 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -116,7 +116,7 @@ export function mutable_state(v, immutable = false) {
*/
/*#__NO_SIDE_EFFECTS__*/
function push_derived_source(source) {
- if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0) {
+ if (active_reaction !== null && !untracking && (active_reaction.f & DERIVED) !== 0) {
if (derived_sources === null) {
set_derived_sources([source]);
} else {
diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js
new file mode 100644
index 0000000000..1fe92ed2d7
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/_config.js
@@ -0,0 +1,5 @@
+import { test } from '../../test';
+
+export default test({
+ html: `3`
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte
new file mode 100644
index 0000000000..0873eb741d
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/untracked-derived-local/main.svelte
@@ -0,0 +1,47 @@
+
+
+
+
+{test}
From ee024ffd0e57dd5eca90d0c7aca5dc0e83d55449 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 22 Jan 2025 13:30:18 +0000
Subject: [PATCH 02/67] Version Packages (#15080)
Co-authored-by: github-actions[bot]
---
.changeset/ninety-chefs-know.md | 5 -----
packages/svelte/CHANGELOG.md | 6 ++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
4 files changed, 8 insertions(+), 7 deletions(-)
delete mode 100644 .changeset/ninety-chefs-know.md
diff --git a/.changeset/ninety-chefs-know.md b/.changeset/ninety-chefs-know.md
deleted file mode 100644
index b5eac6a07a..0000000000
--- a/.changeset/ninety-chefs-know.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: address regression with untrack
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index d16bdbc327..e15c0e0a09 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
# svelte
+## 5.19.2
+
+### Patch Changes
+
+- fix: address regression with untrack ([#15079](https://github.com/sveltejs/svelte/pull/15079))
+
## 5.19.1
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index b725e52fed..48287cf901 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.19.1",
+ "version": "5.19.2",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index b0ea99b6f4..7a3f016aaa 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.19.1';
+export const VERSION = '5.19.2';
export const PUBLIC_VERSION = '5';
From 7da86ef80cd3503a14e26393cbbb2e6a2a933ed9 Mon Sep 17 00:00:00 2001
From: Paolo Ricciuti
Date: Wed, 22 Jan 2025 14:34:57 +0100
Subject: [PATCH 03/67] fix: don't throw for `undefined` non delegated event
handlers (#15087)
* fix: don't throw for `undefined` non delegated event handlers
* chore: update typings
---
.changeset/clever-cherries-hear.md | 5 +++++
.../src/internal/client/dom/elements/events.js | 12 ++++++------
.../samples/undefined-event-handler/_config.js | 9 +++++++++
.../samples/undefined-event-handler/main.svelte | 1 +
4 files changed, 21 insertions(+), 6 deletions(-)
create mode 100644 .changeset/clever-cherries-hear.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/undefined-event-handler/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/undefined-event-handler/main.svelte
diff --git a/.changeset/clever-cherries-hear.md b/.changeset/clever-cherries-hear.md
new file mode 100644
index 0000000000..df37943b46
--- /dev/null
+++ b/.changeset/clever-cherries-hear.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't throw for `undefined` non delegated event handlers
diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js
index f2038f96ad..363b8e1ed5 100644
--- a/packages/svelte/src/internal/client/dom/elements/events.js
+++ b/packages/svelte/src/internal/client/dom/elements/events.js
@@ -49,10 +49,10 @@ export function replay_events(dom) {
/**
* @param {string} event_name
* @param {EventTarget} dom
- * @param {EventListener} handler
- * @param {AddEventListenerOptions} options
+ * @param {EventListener} [handler]
+ * @param {AddEventListenerOptions} [options]
*/
-export function create_event(event_name, dom, handler, options) {
+export function create_event(event_name, dom, handler, options = {}) {
/**
* @this {EventTarget}
*/
@@ -63,7 +63,7 @@ export function create_event(event_name, dom, handler, options) {
}
if (!event.cancelBubble) {
return without_reactive_context(() => {
- return handler.call(this, event);
+ return handler?.call(this, event);
});
}
}
@@ -108,8 +108,8 @@ export function on(element, type, handler, options = {}) {
/**
* @param {string} event_name
* @param {Element} dom
- * @param {EventListener} handler
- * @param {boolean} capture
+ * @param {EventListener} [handler]
+ * @param {boolean} [capture]
* @param {boolean} [passive]
* @returns {void}
*/
diff --git a/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/_config.js b/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/_config.js
new file mode 100644
index 0000000000..012fedb160
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/_config.js
@@ -0,0 +1,9 @@
+import { ok, test } from '../../test';
+
+export default test({
+ async test({ target }) {
+ const button = target.querySelector('button');
+ ok(button);
+ button.dispatchEvent(new window.MouseEvent('mouseenter'));
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/main.svelte b/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/main.svelte
new file mode 100644
index 0000000000..ea4b4443e9
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/undefined-event-handler/main.svelte
@@ -0,0 +1 @@
+
From eee808fb79cfb182df7285b7107d5f7d2bee44d1 Mon Sep 17 00:00:00 2001
From: "Trevor N. Suarez"
Date: Wed, 22 Jan 2025 06:35:26 -0700
Subject: [PATCH 04/67] fix: correctly handle `novalidate` attribute casing
(#15083)
This PR fixes #15082 by correctly handling the HTML element novalidate attribute casing through noValidate DOM property mapping.
---
.changeset/pink-wolves-search.md | 5 +++++
packages/svelte/src/utils.js | 3 ++-
.../samples/form-novalidate-casing/_config.js | 8 ++++++++
.../samples/form-novalidate-casing/main.svelte | 6 ++++++
4 files changed, 21 insertions(+), 1 deletion(-)
create mode 100644 .changeset/pink-wolves-search.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte
diff --git a/.changeset/pink-wolves-search.md b/.changeset/pink-wolves-search.md
new file mode 100644
index 0000000000..cd734062c6
--- /dev/null
+++ b/.changeset/pink-wolves-search.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: correctly handle `novalidate` attribute casing
diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js
index 76486d32ac..b16c0551f1 100644
--- a/packages/svelte/src/utils.js
+++ b/packages/svelte/src/utils.js
@@ -196,7 +196,8 @@ const ATTRIBUTE_ALIASES = {
readonly: 'readOnly',
defaultvalue: 'defaultValue',
defaultchecked: 'defaultChecked',
- srcobject: 'srcObject'
+ srcobject: 'srcObject',
+ novalidate: 'noValidate'
};
/**
diff --git a/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js
new file mode 100644
index 0000000000..4fdf3632d6
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/_config.js
@@ -0,0 +1,8 @@
+import { test } from '../../test';
+
+export default test({
+ html: `
+
+
+`
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte
new file mode 100644
index 0000000000..1e8115ff62
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/form-novalidate-casing/main.svelte
@@ -0,0 +1,6 @@
+
+
+
+
From 8bba70b8e30a824218f909a36f9dd255fae9710f Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 22 Jan 2025 10:37:43 -0500
Subject: [PATCH 05/67] fix: avoid double deriveds in component props (#15089)
* fix: prevent double deriveds in component props
* unused
* reuse mechanism
* move code to where it is used
* changeset
---
.changeset/tasty-pigs-greet.md | 5 ++
.../phases/3-transform/client/utils.js | 35 --------------
.../3-transform/client/visitors/AwaitBlock.js | 41 +++++++++++++++-
.../client/visitors/RegularElement.js | 8 ++--
.../client/visitors/SlotElement.js | 6 ++-
.../client/visitors/shared/component.js | 47 +++++++++----------
.../client/visitors/shared/element.js | 23 ++++-----
.../client/visitors/shared/utils.js | 15 +++---
8 files changed, 93 insertions(+), 87 deletions(-)
create mode 100644 .changeset/tasty-pigs-greet.md
diff --git a/.changeset/tasty-pigs-greet.md b/.changeset/tasty-pigs-greet.md
new file mode 100644
index 0000000000..63decceed8
--- /dev/null
+++ b/.changeset/tasty-pigs-greet.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: avoid double deriveds in component props
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
index c59a5544df..421118cf68 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
@@ -269,41 +269,6 @@ export function should_proxy(node, scope) {
return true;
}
-/**
- * @param {Pattern} node
- * @param {import('zimmerframe').Context} context
- * @returns {{ id: Pattern, declarations: null | Statement[] }}
- */
-export function create_derived_block_argument(node, context) {
- if (node.type === 'Identifier') {
- context.state.transform[node.name] = { read: get_value };
- return { id: node, declarations: null };
- }
-
- const pattern = /** @type {Pattern} */ (context.visit(node));
- const identifiers = extract_identifiers(node);
-
- const id = b.id('$$source');
- const value = b.id('$$value');
-
- const block = b.block([
- b.var(pattern, b.call('$.get', id)),
- b.return(b.object(identifiers.map((identifier) => b.prop('init', identifier, identifier))))
- ]);
-
- const declarations = [b.var(value, create_derived(context.state, b.thunk(block)))];
-
- for (const id of identifiers) {
- context.state.transform[id.name] = { read: get_value };
-
- declarations.push(
- b.var(id, create_derived(context.state, b.thunk(b.member(b.call('$.get', value), id))))
- );
- }
-
- return { id, declarations };
-}
-
/**
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
* @param {ComponentClientTransformState} state
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
index 146f75d405..e0aef2d316 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitBlock.js
@@ -1,8 +1,10 @@
/** @import { BlockStatement, Expression, Pattern, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
-/** @import { ComponentContext } from '../types' */
+/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
+import { extract_identifiers } from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js';
-import { create_derived_block_argument } from '../utils.js';
+import { create_derived } from '../utils.js';
+import { get_value } from './shared/declarations.js';
/**
* @param {AST.AwaitBlock} node
@@ -65,3 +67,38 @@ export function AwaitBlock(node, context) {
)
);
}
+
+/**
+ * @param {Pattern} node
+ * @param {import('zimmerframe').Context} context
+ * @returns {{ id: Pattern, declarations: null | Statement[] }}
+ */
+function create_derived_block_argument(node, context) {
+ if (node.type === 'Identifier') {
+ context.state.transform[node.name] = { read: get_value };
+ return { id: node, declarations: null };
+ }
+
+ const pattern = /** @type {Pattern} */ (context.visit(node));
+ const identifiers = extract_identifiers(node);
+
+ const id = b.id('$$source');
+ const value = b.id('$$value');
+
+ const block = b.block([
+ b.var(pattern, b.call('$.get', id)),
+ b.return(b.object(identifiers.map((identifier) => b.prop('init', identifier, identifier))))
+ ]);
+
+ const declarations = [b.var(value, create_derived(context.state, b.thunk(block)))];
+
+ for (const id of identifiers) {
+ context.state.transform[id.name] = { read: get_value };
+
+ declarations.push(
+ b.var(id, create_derived(context.state, b.thunk(b.member(b.call('$.get', value), id))))
+ );
+ }
+
+ return { id, declarations };
+}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
index 21a78de032..d0a148f216 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
@@ -537,8 +537,8 @@ function build_element_attribute_update_assignment(
const is_svg = context.state.metadata.namespace === 'svg' || element.name === 'svg';
const is_mathml = context.state.metadata.namespace === 'mathml';
- let { value, has_state } = build_attribute_value(attribute.value, context, (value) =>
- get_expression_id(state, value)
+ let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
+ metadata.has_call ? get_expression_id(state, value) : value
);
if (name === 'autofocus') {
@@ -665,8 +665,8 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co
*/
function build_element_special_value_attribute(element, node_id, attribute, context) {
const state = context.state;
- const { value, has_state } = build_attribute_value(attribute.value, context, (value) =>
- get_expression_id(state, value)
+ const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
+ metadata.has_call ? get_expression_id(state, value) : value
);
const inner_assignment = b.assignment(
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
index f1b08acbc6..fdd705e32e 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
@@ -30,8 +30,10 @@ export function SlotElement(node, context) {
if (attribute.type === 'SpreadAttribute') {
spreads.push(b.thunk(/** @type {Expression} */ (context.visit(attribute))));
} else if (attribute.type === 'Attribute') {
- const { value, has_state } = build_attribute_value(attribute.value, context, (value) =>
- memoize_expression(context.state, value)
+ const { value, has_state } = build_attribute_value(
+ attribute.value,
+ context,
+ (value, metadata) => (metadata.has_call ? memoize_expression(context.state, value) : value)
);
if (attribute.name === 'name') {
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js
index 9ac0bac120..15e4f68e9e 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js
@@ -4,7 +4,6 @@
import { dev, is_ignored } from '../../../../../state.js';
import { get_attribute_chunks, object } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
-import { create_derived } from '../../utils.js';
import { build_bind_this, memoize_expression, validate_binding } from '../shared/utils.js';
import { build_attribute_value } from '../shared/element.js';
import { build_event_handler } from './events.js';
@@ -134,9 +133,9 @@ export function build_component(node, component_name, context, anchor = context.
custom_css_props.push(
b.init(
attribute.name,
- build_attribute_value(attribute.value, context, (value) =>
+ build_attribute_value(attribute.value, context, (value, metadata) =>
// TODO put the derived in the local block
- memoize_expression(context.state, value)
+ metadata.has_call ? memoize_expression(context.state, value) : value
).value
)
);
@@ -151,31 +150,29 @@ export function build_component(node, component_name, context, anchor = context.
has_children_prop = true;
}
- const { value, has_state } = build_attribute_value(attribute.value, context, (value) =>
- memoize_expression(context.state, value)
- );
-
- if (has_state) {
- let arg = value;
-
- // When we have a non-simple computation, anything other than an Identifier or Member expression,
- // then there's a good chance it needs to be memoized to avoid over-firing when read within the
- // child component.
- const should_wrap_in_derived = get_attribute_chunks(attribute.value).some((n) => {
- return (
- n.type === 'ExpressionTag' &&
- n.expression.type !== 'Identifier' &&
- n.expression.type !== 'MemberExpression'
- );
- });
+ const { value, has_state } = build_attribute_value(
+ attribute.value,
+ context,
+ (value, metadata) => {
+ if (!metadata.has_state) return value;
+
+ // When we have a non-simple computation, anything other than an Identifier or Member expression,
+ // then there's a good chance it needs to be memoized to avoid over-firing when read within the
+ // child component (e.g. `active={i === index}`)
+ const should_wrap_in_derived = get_attribute_chunks(attribute.value).some((n) => {
+ return (
+ n.type === 'ExpressionTag' &&
+ n.expression.type !== 'Identifier' &&
+ n.expression.type !== 'MemberExpression'
+ );
+ });
- if (should_wrap_in_derived) {
- const id = b.id(context.state.scope.generate(attribute.name));
- context.state.init.push(b.var(id, create_derived(context.state, b.thunk(value))));
- arg = b.call('$.get', id);
+ return should_wrap_in_derived ? memoize_expression(context.state, value) : value;
}
+ );
- push_prop(b.get(attribute.name, [b.return(arg)]));
+ if (has_state) {
+ push_prop(b.get(attribute.name, [b.return(value)]));
} else {
push_prop(b.init(attribute.name, value));
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
index 8fb6b8bdde..abffad0ff7 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
@@ -1,11 +1,11 @@
/** @import { Expression, Identifier, ObjectExpression } from 'estree' */
-/** @import { AST } from '#compiler' */
+/** @import { AST, ExpressionMetadata } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../../types' */
import { normalize_attribute } from '../../../../../../utils.js';
import { is_ignored } from '../../../../../state.js';
import { is_event_attribute } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
-import { build_getter, create_derived } from '../../utils.js';
+import { build_getter } from '../../utils.js';
import { build_template_chunk, get_expression_id } from './utils.js';
/**
@@ -35,8 +35,10 @@ export function build_set_attributes(
for (const attribute of attributes) {
if (attribute.type === 'Attribute') {
- const { value, has_state } = build_attribute_value(attribute.value, context, (value) =>
- get_expression_id(context.state, value)
+ const { value, has_state } = build_attribute_value(
+ attribute.value,
+ context,
+ (value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value)
);
if (
@@ -59,10 +61,9 @@ export function build_set_attributes(
let value = /** @type {Expression} */ (context.visit(attribute));
if (attribute.metadata.expression.has_call) {
- const id = b.id(state.scope.generate('spread_with_call'));
- state.init.push(b.const(id, create_derived(state, b.thunk(value))));
- value = b.call('$.get', id);
+ value = get_expression_id(context.state, value);
}
+
values.push(b.spread(value));
}
}
@@ -111,8 +112,8 @@ export function build_style_directives(
let value =
directive.value === true
? build_getter({ name: directive.name, type: 'Identifier' }, context.state)
- : build_attribute_value(directive.value, context, (value) =>
- get_expression_id(context.state, value)
+ : build_attribute_value(directive.value, context, (value, metadata) =>
+ metadata.has_call ? get_expression_id(context.state, value) : value
).value;
const update = b.stmt(
@@ -169,7 +170,7 @@ export function build_class_directives(
/**
* @param {AST.Attribute['value']} value
* @param {ComponentContext} context
- * @param {(value: Expression) => Expression} memoize
+ * @param {(value: Expression, metadata: ExpressionMetadata) => Expression} memoize
* @returns {{ value: Expression, has_state: boolean }}
*/
export function build_attribute_value(value, context, memoize = (value) => value) {
@@ -187,7 +188,7 @@ export function build_attribute_value(value, context, memoize = (value) => value
let expression = /** @type {Expression} */ (context.visit(chunk.expression));
return {
- value: chunk.metadata.expression.has_call ? memoize(expression) : expression,
+ value: memoize(expression, chunk.metadata.expression),
has_state: chunk.metadata.expression.has_state
};
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
index c4f81274d9..4c28616665 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
@@ -1,5 +1,5 @@
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super } from 'estree' */
-/** @import { AST } from '#compiler' */
+/** @import { AST, ExpressionMetadata } from '#compiler' */
/** @import { ComponentClientTransformState } from '../../types' */
import { walk } from 'zimmerframe';
import { object } from '../../../../../utils/ast.js';
@@ -79,14 +79,14 @@ function compare_expressions(a, b) {
* @param {Array} values
* @param {(node: AST.SvelteNode, state: any) => any} visit
* @param {ComponentClientTransformState} state
- * @param {(value: Expression) => Expression} memoize
+ * @param {(value: Expression, metadata: ExpressionMetadata) => Expression} memoize
* @returns {{ value: Expression, has_state: boolean }}
*/
export function build_template_chunk(
values,
visit,
state,
- memoize = (value) => get_expression_id(state, value)
+ memoize = (value, metadata) => (metadata.has_call ? get_expression_id(state, value) : value)
) {
/** @type {Expression[]} */
const expressions = [];
@@ -106,14 +106,13 @@ export function build_template_chunk(
quasi.value.cooked += node.expression.value + '';
}
} else {
- let value = /** @type {Expression} */ (visit(node.expression, state));
+ let value = memoize(
+ /** @type {Expression} */ (visit(node.expression, state)),
+ node.metadata.expression
+ );
has_state ||= node.metadata.expression.has_state;
- if (node.metadata.expression.has_call) {
- value = memoize(value);
- }
-
if (values.length === 1) {
// If we have a single expression, then pass that in directly to possibly avoid doing
// extra work in the template_effect (instead we do the work in set_text).
From c2e805f05cb652ed3e7fa29f4a7c3f2912a79678 Mon Sep 17 00:00:00 2001
From: Theodore Brown
Date: Wed, 22 Jan 2025 13:54:12 -0600
Subject: [PATCH 06/67] fix: consistently set value to blank string when value
attribute is undefined (#15057)
Fixes #4467
---
.changeset/dirty-sloths-move.md | 5 ++
.../client/dom/elements/attributes.js | 4 +-
.../value-attribute-null-undefined/_config.js | 49 +++++++++++++++++++
.../main.svelte | 9 ++++
4 files changed, 65 insertions(+), 2 deletions(-)
create mode 100644 .changeset/dirty-sloths-move.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte
diff --git a/.changeset/dirty-sloths-move.md b/.changeset/dirty-sloths-move.md
new file mode 100644
index 0000000000..07436a0b2e
--- /dev/null
+++ b/.changeset/dirty-sloths-move.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: consistently set value to blank string when value attribute is undefined
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index a2fffe8696..eab27e6c02 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -68,14 +68,14 @@ export function set_value(element, value) {
// treat null and undefined the same for the initial value
value ?? undefined) ||
// @ts-expect-error
- // `progress` elements always need their value set when its `0`
+ // `progress` elements always need their value set when it's `0`
(element.value === value && (value !== 0 || element.nodeName !== 'PROGRESS'))
) {
return;
}
// @ts-expect-error
- element.value = value;
+ element.value = value ?? '';
}
/**
diff --git a/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js
new file mode 100644
index 0000000000..08f0c5aec7
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/_config.js
@@ -0,0 +1,49 @@
+import { test, ok } from '../../test';
+import { flushSync } from 'svelte';
+
+export default test({
+ mode: ['client'],
+
+ async test({ assert, target }) {
+ /**
+ * @type {HTMLInputElement | null}
+ */
+ const input = target.querySelector('input[type=text]');
+ /**
+ * @type {HTMLButtonElement | null}
+ */
+ const setString = target.querySelector('#setString');
+ /**
+ * @type {HTMLButtonElement | null}
+ */
+ const setNull = target.querySelector('#setNull');
+ /**
+ * @type {HTMLButtonElement | null}
+ */
+ const setUndefined = target.querySelector('#setUndefined');
+
+ ok(input);
+ ok(setString);
+ ok(setNull);
+ ok(setUndefined);
+
+ // value should always be blank string when value attribute is set to null or undefined
+
+ assert.equal(input.value, '');
+ setString.click();
+ flushSync();
+ assert.equal(input.value, 'foo');
+
+ setNull.click();
+ flushSync();
+ assert.equal(input.value, '');
+
+ setString.click();
+ flushSync();
+ assert.equal(input.value, 'foo');
+
+ setUndefined.click();
+ flushSync();
+ assert.equal(input.value, '');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte
new file mode 100644
index 0000000000..9f944923c2
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/value-attribute-null-undefined/main.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
From 7740d4576b95f2e4e4e95a9df2d5504cb12a9461 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 22 Jan 2025 15:48:41 -0500
Subject: [PATCH 07/67] fix: optimise || expressions in template (#15092)
---
.changeset/large-islands-cover.md | 5 ++++
.../client/visitors/shared/utils.js | 26 +++++++++----------
2 files changed, 18 insertions(+), 13 deletions(-)
create mode 100644 .changeset/large-islands-cover.md
diff --git a/.changeset/large-islands-cover.md b/.changeset/large-islands-cover.md
new file mode 100644
index 0000000000..969d2fc254
--- /dev/null
+++ b/.changeset/large-islands-cover.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: optimise || expressions in template
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
index 4c28616665..00634f229e 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
@@ -118,22 +118,22 @@ export function build_template_chunk(
// extra work in the template_effect (instead we do the work in set_text).
return { value, has_state };
} else {
- let expression = value;
- // only add nullish coallescence if it hasn't been added already
- if (value.type === 'LogicalExpression' && value.operator === '??') {
- const { right } = value;
- // `undefined` isn't a Literal (due to pre-ES5 shenanigans), so the only nullish literal is `null`
- // however, you _can_ make a variable called `undefined` in a Svelte component, so we can't just treat it the same way
- if (right.type !== 'Literal') {
- expression = b.logical('??', value, b.literal(''));
- } else if (right.value === null) {
- // if they do something weird like `stuff ?? null`, replace `null` with empty string
- value.right = b.literal('');
+ // add `?? ''` where necessary (TODO optimise more cases)
+ if (
+ value.type === 'LogicalExpression' &&
+ value.right.type === 'Literal' &&
+ (value.operator === '??' || value.operator === '||')
+ ) {
+ // `foo ?? null` -=> `foo ?? ''`
+ // otherwise leave the expression untouched
+ if (value.right.value === null) {
+ value = { ...value, right: b.literal('') };
}
} else {
- expression = b.logical('??', value, b.literal(''));
+ value = b.logical('??', value, b.literal(''));
}
- expressions.push(expression);
+
+ expressions.push(value);
}
quasi = b.quasi('', i + 1 === values.length);
From d17f5c748d4b842e63d0017461ccde49b00b78e0 Mon Sep 17 00:00:00 2001
From: Edoardo Cavazza
Date: Wed, 22 Jan 2025 21:51:26 +0100
Subject: [PATCH 08/67] fix: add check for is attribute (#15086)
Fixes #15085
Add a check for is attribute in is_custom_element_node function.
---
.changeset/two-doors-exercise.md | 5 +++++
.../client/visitors/RegularElement.js | 2 +-
.../client/visitors/shared/fragment.js | 3 ++-
packages/svelte/src/compiler/phases/nodes.js | 8 ++++++--
.../custom-element-attributes/_config.js | 19 +++++++++++++++++++
.../custom-element-attributes/main.svelte | 2 ++
6 files changed, 35 insertions(+), 4 deletions(-)
create mode 100644 .changeset/two-doors-exercise.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte
diff --git a/.changeset/two-doors-exercise.md b/.changeset/two-doors-exercise.md
new file mode 100644
index 0000000000..b41e582ae7
--- /dev/null
+++ b/.changeset/two-doors-exercise.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: add check for `is` attribute to correctly detect custom elements
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
index d0a148f216..c7e218d521 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
@@ -227,7 +227,7 @@ export function RegularElement(node, context) {
node_id,
attributes_id,
(node.metadata.svg || node.metadata.mathml || is_custom_element_node(node)) && b.true,
- node.name.includes('-') && b.true,
+ is_custom_element_node(node) && b.true,
context.state
);
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js
index 0b4ac87342..5bc3041ca4 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js
@@ -4,6 +4,7 @@
import { cannot_be_set_statically } from '../../../../../../utils.js';
import { is_event_attribute, is_text_attribute } from '../../../../../utils/ast.js';
import * as b from '../../../../../utils/builders.js';
+import { is_custom_element_node } from '../../../../nodes.js';
import { build_template_chunk } from './utils.js';
/**
@@ -128,7 +129,7 @@ export function process_children(nodes, initial, is_element, { visit, state }) {
function is_static_element(node, state) {
if (node.type !== 'RegularElement') return false;
if (node.fragment.metadata.dynamic) return false;
- if (node.name.includes('-')) return false; // we're setting all attributes on custom elements through properties
+ if (is_custom_element_node(node)) return false; // we're setting all attributes on custom elements through properties
for (const attribute of node.attributes) {
if (attribute.type !== 'Attribute') {
diff --git a/packages/svelte/src/compiler/phases/nodes.js b/packages/svelte/src/compiler/phases/nodes.js
index 5ca4ce3380..003c3a2c49 100644
--- a/packages/svelte/src/compiler/phases/nodes.js
+++ b/packages/svelte/src/compiler/phases/nodes.js
@@ -23,10 +23,14 @@ export function is_element_node(node) {
/**
* @param {AST.RegularElement | AST.SvelteElement} node
- * @returns {node is AST.RegularElement}
+ * @returns {boolean}
*/
export function is_custom_element_node(node) {
- return node.type === 'RegularElement' && node.name.includes('-');
+ return (
+ node.type === 'RegularElement' &&
+ (node.name.includes('-') ||
+ node.attributes.some((attr) => attr.type === 'Attribute' && attr.name === 'is'))
+ );
}
/**
diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js
new file mode 100644
index 0000000000..118a51157e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js
@@ -0,0 +1,19 @@
+import { test } from '../../test';
+
+export default test({
+ mode: ['client', 'server'],
+ async test({ assert, target }) {
+ const my_element = /** @type HTMLElement & { object: { test: true }; } */ (
+ target.querySelector('my-element')
+ );
+ const my_link = /** @type HTMLAnchorElement & { object: { test: true }; } */ (
+ target.querySelector('a')
+ );
+ assert.equal(my_element.getAttribute('string'), 'test');
+ assert.equal(my_element.hasAttribute('object'), false);
+ assert.deepEqual(my_element.object, { test: true });
+ assert.equal(my_link.getAttribute('string'), 'test');
+ assert.equal(my_link.hasAttribute('object'), false);
+ assert.deepEqual(my_link.object, { test: true });
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte
new file mode 100644
index 0000000000..ff94a9484c
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte
@@ -0,0 +1,2 @@
+
+
From d47f4f59084a26d8b47d864e961ddf76336a0ed8 Mon Sep 17 00:00:00 2001
From: "Trevor N. Suarez"
Date: Thu, 23 Jan 2025 00:55:12 -0700
Subject: [PATCH 09/67] fix: expand boolean attribute support (#15095)
* Adding test for expected boolean attribute support
* Improving support for more boolean attributes:
Added:
- `defer`
- `disablepictureinpicture`
- `disableremoteplayback`
Improved:
- `allowfullscreen`
- `novalidate`
* Skipping JSDOM version of boolean attribute test
JSDOM lacks support for some attributes, so we'll skip it for now.
See:
- `async`: https://github.com/jsdom/jsdom/issues/1564
- `nomodule`: https://github.com/jsdom/jsdom/issues/2475
- `autofocus`: https://github.com/jsdom/jsdom/issues/3041
- `inert`: https://github.com/jsdom/jsdom/issues/3605
- etc...: https://github.com/jestjs/jest/issues/139#issuecomment-592673550
* Adding changeset
---
.changeset/strange-hairs-trade.md | 5 ++
packages/svelte/src/utils.js | 16 ++++-
.../_config.js | 60 +++++++++++++++++++
.../main.svelte | 22 +++++++
4 files changed, 100 insertions(+), 3 deletions(-)
create mode 100644 .changeset/strange-hairs-trade.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/main.svelte
diff --git a/.changeset/strange-hairs-trade.md b/.changeset/strange-hairs-trade.md
new file mode 100644
index 0000000000..192e5ef3d1
--- /dev/null
+++ b/.changeset/strange-hairs-trade.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: expand boolean attribute support
diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js
index b16c0551f1..e8e1bc224c 100644
--- a/packages/svelte/src/utils.js
+++ b/packages/svelte/src/utils.js
@@ -170,7 +170,10 @@ const DOM_BOOLEAN_ATTRIBUTES = [
'reversed',
'seamless',
'selected',
- 'webkitdirectory'
+ 'webkitdirectory',
+ 'defer',
+ 'disablepictureinpicture',
+ 'disableremoteplayback'
];
/**
@@ -197,7 +200,10 @@ const ATTRIBUTE_ALIASES = {
defaultvalue: 'defaultValue',
defaultchecked: 'defaultChecked',
srcobject: 'srcObject',
- novalidate: 'noValidate'
+ novalidate: 'noValidate',
+ allowfullscreen: 'allowFullscreen',
+ disablepictureinpicture: 'disablePictureInPicture',
+ disableremoteplayback: 'disableRemotePlayback'
};
/**
@@ -219,7 +225,11 @@ const DOM_PROPERTIES = [
'volume',
'defaultValue',
'defaultChecked',
- 'srcObject'
+ 'srcObject',
+ 'noValidate',
+ 'allowFullscreen',
+ 'disablePictureInPicture',
+ 'disableRemotePlayback'
];
/**
diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js
new file mode 100644
index 0000000000..d0b8a421b3
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/attribute-boolean-case-insensitivity/_config.js
@@ -0,0 +1,60 @@
+import { test } from '../../test';
+
+export default test({
+ // JSDOM lacks support for some of these attributes, so we'll skip it for now.
+ //
+ // See:
+ // - `async`: https://github.com/jsdom/jsdom/issues/1564
+ // - `nomodule`: https://github.com/jsdom/jsdom/issues/2475
+ // - `autofocus`: https://github.com/jsdom/jsdom/issues/3041
+ // - `inert`: https://github.com/jsdom/jsdom/issues/3605
+ // - etc...: https://github.com/jestjs/jest/issues/139#issuecomment-592673550
+ skip_mode: ['client'],
+
+ html: `
+
+
+
+
+
+
+
+
+
bar
+{#each [1]}
+
+{/each}
From e12fe8795ca2b86a9509c8924fb0fe07a8858d03 Mon Sep 17 00:00:00 2001
From: pgliang001 <65393356+pgliang001@users.noreply.github.com>
Date: Mon, 3 Feb 2025 17:13:30 +0800
Subject: [PATCH 39/67] chore: simplify assignment expression (#15177)
---
packages/svelte/src/internal/client/render.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js
index bc74047f64..3256fe8274 100644
--- a/packages/svelte/src/internal/client/render.js
+++ b/packages/svelte/src/internal/client/render.js
@@ -55,7 +55,7 @@ export function set_text(text, value) {
if (str !== (text.__t ??= text.nodeValue)) {
// @ts-expect-error
text.__t = str;
- text.nodeValue = str == null ? '' : str + '';
+ text.nodeValue = str + '';
}
}
From e0b3dc020cb0ee693635d3ad9711a39689c32da6 Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Mon, 3 Feb 2025 17:20:29 +0000
Subject: [PATCH 40/67] chore: remove unused code from signal logic (#15195)
---
.changeset/blue-sheep-joke.md | 5 +++++
.../src/internal/client/reactivity/deriveds.js | 12 ------------
.../src/internal/client/reactivity/effects.js | 2 +-
packages/svelte/src/internal/client/runtime.js | 16 +---------------
4 files changed, 7 insertions(+), 28 deletions(-)
create mode 100644 .changeset/blue-sheep-joke.md
diff --git a/.changeset/blue-sheep-joke.md b/.changeset/blue-sheep-joke.md
new file mode 100644
index 0000000000..1d9ff973c5
--- /dev/null
+++ b/.changeset/blue-sheep-joke.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+chore: remove unused code from signal logic
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index 1f65ff38c3..60b55970e6 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -176,15 +176,3 @@ export function update_derived(derived) {
derived.wv = increment_write_version();
}
}
-
-/**
- * @param {Derived} derived
- * @returns {void}
- */
-export function destroy_derived(derived) {
- destroy_derived_effects(derived);
- remove_reactions(derived, 0);
- set_signal_status(derived, DESTROYED);
-
- derived.v = derived.deps = derived.ctx = derived.reactions = null;
-}
diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js
index d014ff793d..eab6c767f8 100644
--- a/packages/svelte/src/internal/client/reactivity/effects.js
+++ b/packages/svelte/src/internal/client/reactivity/effects.js
@@ -42,7 +42,7 @@ import * as e from '../errors.js';
import { DEV } from 'esm-env';
import { define_property } from '../../shared/utils.js';
import { get_next_sibling } from '../dom/operations.js';
-import { derived, destroy_derived } from './deriveds.js';
+import { derived } from './deriveds.js';
import { component_context, dev_current_component_function } from '../context.js';
/**
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index a572e27bf4..57cefccc01 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -27,12 +27,7 @@ import {
} from './constants.js';
import { flush_tasks } from './dom/task.js';
import { internal_set } from './reactivity/sources.js';
-import {
- destroy_derived,
- destroy_derived_effects,
- execute_derived,
- update_derived
-} from './reactivity/deriveds.js';
+import { destroy_derived_effects, update_derived } from './reactivity/deriveds.js';
import * as e from './errors.js';
import { FILENAME } from '../../constants.js';
import { tracing_mode_flag } from '../flags/index.js';
@@ -919,15 +914,6 @@ export function get(signal) {
var flags = signal.f;
var is_derived = (flags & DERIVED) !== 0;
- // If the derived is destroyed, just execute it again without retaining
- // its memoisation properties as the derived is stale
- if (is_derived && (flags & DESTROYED) !== 0) {
- var value = execute_derived(/** @type {Derived} */ (signal));
- // Ensure the derived remains destroyed
- destroy_derived(/** @type {Derived} */ (signal));
- return value;
- }
-
if (captured_signals !== null) {
captured_signals.add(signal);
}
From e1014e30160dfbca414bfa907d77f30f8e4f0204 Mon Sep 17 00:00:00 2001
From: Scott Wu
Date: Tue, 4 Feb 2025 01:21:28 +0800
Subject: [PATCH 41/67] Update 11-bind.md (#15192)
---
documentation/docs/03-template-syntax/11-bind.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/documentation/docs/03-template-syntax/11-bind.md b/documentation/docs/03-template-syntax/11-bind.md
index e56c2b4f77..119f87ed8e 100644
--- a/documentation/docs/03-template-syntax/11-bind.md
+++ b/documentation/docs/03-template-syntax/11-bind.md
@@ -219,11 +219,10 @@ You can give the `