+ {/snippet}
+
From 6534aa08e32b3c2d09c2ffb06e4ae7a18fdffa30 Mon Sep 17 00:00:00 2001
From: Edoardo Cavazza
Date: Mon, 25 Aug 2025 21:06:53 +0200
Subject: [PATCH 03/15] fix: Add check for builtin custom elements in
`set_custom_element_data` (#16592)
Fixes #16591
This PR introduces a check for builtin custom elements (is attribute) inside the set_custom_element_data helper in order to correctly set properties that have a setter.
---
.changeset/fuzzy-shrimps-dream.md | 5 +++++
.../client/dom/elements/attributes.js | 9 +++++----
.../custom-element-attributes/_config.js | 3 +++
.../custom-element-attributes/main.svelte | 19 +++++++++++++++++++
4 files changed, 32 insertions(+), 4 deletions(-)
create mode 100644 .changeset/fuzzy-shrimps-dream.md
diff --git a/.changeset/fuzzy-shrimps-dream.md b/.changeset/fuzzy-shrimps-dream.md
new file mode 100644
index 0000000000..0ddab531ac
--- /dev/null
+++ b/.changeset/fuzzy-shrimps-dream.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: Add check for builtin custom elements in `set_custom_element_data`
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index 2fa5d4541c..a5b7140f25 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -238,10 +238,10 @@ export function set_custom_element_data(node, prop, value) {
// Don't compute setters for custom elements while they aren't registered yet,
// because during their upgrade/instantiation they might add more setters.
// Instead, fall back to a simple "an object, then set as property" heuristic.
- (setters_cache.has(node.nodeName) ||
+ (setters_cache.has(node.getAttribute('is') || node.nodeName) ||
// customElements may not be available in browser extension contexts
!customElements ||
- customElements.get(node.tagName.toLowerCase())
+ customElements.get(node.getAttribute('is') || node.tagName.toLowerCase())
? get_setters(node).includes(prop)
: value && typeof value === 'object')
) {
@@ -546,9 +546,10 @@ var setters_cache = new Map();
/** @param {Element} element */
function get_setters(element) {
- var setters = setters_cache.get(element.nodeName);
+ var cache_key = element.getAttribute('is') || element.nodeName;
+ var setters = setters_cache.get(cache_key);
if (setters) return setters;
- setters_cache.set(element.nodeName, (setters = []));
+ setters_cache.set(cache_key, (setters = []));
var descriptors;
var proto = element; // In the case of custom elements there might be setters on the instance
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
index 7f406d8f0d..3d8917c147 100644
--- a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js
@@ -20,5 +20,8 @@ export default test({
const [value1, value2] = target.querySelectorAll('value-element');
assert.equal(value1.shadowRoot?.innerHTML, 'test');
assert.equal(value2.shadowRoot?.innerHTML, 'test');
+
+ const value_builtin = target.querySelector('div');
+ assert.equal(value_builtin?.shadowRoot?.innerHTML, 'test');
}
});
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
index 82774f160d..badb8f96c7 100644
--- a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte
@@ -15,6 +15,24 @@
}
});
}
+ if(!customElements.get('value-builtin')) {
+ customElements.define('value-builtin', class extends HTMLDivElement {
+
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' });
+ }
+
+ set value(v) {
+ if (this.__value !== v) {
+ this.__value = v;
+ this.shadowRoot.innerHTML = `${v}`;
+ }
+ }
+ }, {
+ extends: 'div'
+ });
+ }
@@ -22,3 +40,4 @@
+
From 0d48916e020cb99f5573925a47b4231d0fb6ac5a Mon Sep 17 00:00:00 2001
From: hariharan <36292275+fabhari@users.noreply.github.com>
Date: Mon, 25 Aug 2025 22:14:17 +0100
Subject: [PATCH 04/15] fix: cursor jumps in input two way binding (#16649)
* fix : remove cursor manipulation for input bindings
Old Fix: Restore input binding selection position (#14649)
Current Fix: Remove unnecessary cursor manipulation as the presence of runes no longer requires special handling.
* fix : add change set to my previous commit
* Revert "fix : add change set to my previous commit"
This reverts commit 6ca8ef3f97941bb8d8e0675b36d0c14af452364d.
* fix: revert previous changeset added new to fix lint errors
* chore : resolve lint error to fix pipeline issue
* Revert "fix: revert previous changeset added new to fix lint errors"
This reverts commit 91094949a616898729b85a95b5e1a8880b450170.
* fix: input binding to handle code in a synchronous manner
Introduced Promise.resolve to ensure that the 'set' operation completes before the 'get' operation Minimizing update delays.
* Fix: resolve cursor jumps and change sets
* better fix
* test
* changeset
* simplify
* failing test
* gah we can't fix the input in an effect, need to do it here, but after a tick so that changes have been flushed through each blocks
* add explanatory comment
* fix test
* this seems to work?
---------
Co-authored-by: Hariharan Srinivasan
Co-authored-by: Rich Harris
---
.changeset/rare-cups-fold.md | 5 ++++
.changeset/tasty-chicken-care.md | 5 ++++
.../client/dom/elements/bindings/input.js | 16 +++++-----
.../samples/binding-update-in-each/_config.js | 28 +++++++++++++++++
.../binding-update-in-each/main.svelte | 8 +++++
.../binding-update-while-focused-3/_config.js | 30 +++++++++++++++++++
.../main.svelte | 6 ++++
7 files changed, 91 insertions(+), 7 deletions(-)
create mode 100644 .changeset/rare-cups-fold.md
create mode 100644 .changeset/tasty-chicken-care.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-in-each/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-in-each/main.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-3/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-3/main.svelte
diff --git a/.changeset/rare-cups-fold.md b/.changeset/rare-cups-fold.md
new file mode 100644
index 0000000000..8cd5995651
--- /dev/null
+++ b/.changeset/rare-cups-fold.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: Introduced Promise.resolve to ensure that the 'set' operation completes before the 'get' operation Minimizing update delays.
diff --git a/.changeset/tasty-chicken-care.md b/.changeset/tasty-chicken-care.md
new file mode 100644
index 0000000000..ea579efe4c
--- /dev/null
+++ b/.changeset/tasty-chicken-care.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: wait until changes propagate before updating input selection state
diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js
index 67e6ff1dd2..815acde7c5 100644
--- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js
+++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js
@@ -6,7 +6,7 @@ import * as e from '../../../errors.js';
import { is } from '../../../proxy.js';
import { queue_micro_task } from '../../task.js';
import { hydrating } from '../../hydration.js';
-import { untrack } from '../../../runtime.js';
+import { tick, untrack } from '../../../runtime.js';
import { is_runes } from '../../../context.js';
import { current_batch, previous_batch } from '../../../reactivity/batch.js';
@@ -17,11 +17,9 @@ import { current_batch, previous_batch } from '../../../reactivity/batch.js';
* @returns {void}
*/
export function bind_value(input, get, set = get) {
- var runes = is_runes();
-
var batches = new WeakSet();
- listen_to_event_and_reset_event(input, 'input', (is_reset) => {
+ listen_to_event_and_reset_event(input, 'input', async (is_reset) => {
if (DEV && input.type === 'checkbox') {
// TODO should this happen in prod too?
e.bind_invalid_checkbox_value();
@@ -36,9 +34,13 @@ export function bind_value(input, get, set = get) {
batches.add(current_batch);
}
- // In runes mode, respect any validation in accessors (doesn't apply in legacy mode,
- // because we use mutable state which ensures the render effect always runs)
- if (runes && value !== (value = get())) {
+ // Because `{#each ...}` blocks work by updating sources inside the flush,
+ // we need to wait a tick before checking to see if we should forcibly
+ // update the input and reset the selection state
+ await tick();
+
+ // Respect any validation in accessors
+ if (value !== (value = get())) {
var start = input.selectionStart;
var end = input.selectionEnd;
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-in-each/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-update-in-each/_config.js
new file mode 100644
index 0000000000..b6371ce11c
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/binding-update-in-each/_config.js
@@ -0,0 +1,28 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ mode: ['client', 'hydrate'],
+
+ html: `