diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml
index 4292ec900aba..99f8153517f9 100644
--- a/.github/workflows/pkg.pr.new.yml
+++ b/.github/workflows/pkg.pr.new.yml
@@ -9,6 +9,9 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
+ - name: install corepack
+ run: npm i -g corepack@0.31.0
+
- run: corepack enable
- uses: actions/setup-node@v4
with:
diff --git a/documentation/docs/07-misc/07-v5-migration-guide.md b/documentation/docs/07-misc/07-v5-migration-guide.md
index ce95bf6ac7f9..94ade6e88796 100644
--- a/documentation/docs/07-misc/07-v5-migration-guide.md
+++ b/documentation/docs/07-misc/07-v5-migration-guide.md
@@ -722,7 +722,39 @@ If a bindable property has a default value (e.g. `let { foo = $bindable('bar') }
### `accessors` option is ignored
-Setting the `accessors` option to `true` makes properties of a component directly accessible on the component instance. In runes mode, properties are never accessible on the component instance. You can use component exports instead if you need to expose them.
+Setting the `accessors` option to `true` makes properties of a component directly accessible on the component instance.
+
+```svelte
+
+
+
+```
+
+In runes mode, properties are never accessible on the component instance. You can use component exports instead if you need to expose them.
+
+```svelte
+
+```
+
+Alternatively, if the place where they are instantiated is under your control, you can also make use of runes inside `.js/.ts` files by adjusting their ending to include `.svelte`, i.e. `.svelte.js` or `.svelte.ts`, and then use `$state`:
+
+```js
++++import { mount } from 'svelte';+++
+import App from './App.svelte'
+
+---const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
+app.foo = 'baz'---
++++const props = $state({ foo: 'bar' });
+const app = mount(App, { target: document.getElementById("app"), props });
+props.foo = 'baz';+++
+```
### `immutable` option is ignored
diff --git a/package.json b/package.json
index 57dc4cdebedc..efbee35c08d7 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,6 @@
"typescript": "^5.5.4",
"typescript-eslint": "^8.2.0",
"v8-natives": "^1.2.5",
- "vitest": "^2.0.5"
+ "vitest": "^2.1.9"
}
}
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 9ebd37aadf28..837697e70a96 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,15 @@
# svelte
+## 5.19.8
+
+### Patch Changes
+
+- fix: properly set `value` property of custom elements ([#15206](https://github.com/sveltejs/svelte/pull/15206))
+
+- fix: ensure custom element updates don't run in hydration mode ([#15217](https://github.com/sveltejs/svelte/pull/15217))
+
+- fix: ensure tracking returns true, even if in unowned ([#15214](https://github.com/sveltejs/svelte/pull/15214))
+
## 5.19.7
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 8ea495af99d8..ddbae00601cb 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.7",
+ "version": "5.19.8",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
@@ -143,7 +143,7 @@
"source-map": "^0.7.4",
"tiny-glob": "^0.2.9",
"typescript": "^5.5.4",
- "vitest": "^2.0.5"
+ "vitest": "^2.1.9"
},
"dependencies": {
"@ampproject/remapping": "^2.3.0",
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index eab27e6c02d5..308f23d34000 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -1,5 +1,5 @@
import { DEV } from 'esm-env';
-import { hydrating } from '../hydration.js';
+import { hydrating, set_hydrating } from '../hydration.js';
import { get_descriptors, get_prototype_of } from '../../../shared/utils.js';
import { create_event, delegate } from './events.js';
import { add_form_reset_listener, autofocus } from './misc.js';
@@ -213,6 +213,12 @@ export function set_custom_element_data(node, prop, value) {
// or effect
var previous_reaction = active_reaction;
var previous_effect = active_effect;
+ // If we're hydrating but the custom element is from Svelte, and it already scaffolded,
+ // then it might run block logic in hydration mode, which we have to prevent.
+ let was_hydrating = hydrating;
+ if (hydrating) {
+ set_hydrating(false);
+ }
set_active_reaction(null);
set_active_effect(null);
@@ -239,6 +245,9 @@ export function set_custom_element_data(node, prop, value) {
} finally {
set_active_reaction(previous_reaction);
set_active_effect(previous_effect);
+ if (was_hydrating) {
+ set_hydrating(true);
+ }
}
}
@@ -262,6 +271,13 @@ export function set_attributes(
is_custom_element = false,
skip_warning = false
) {
+ // If we're hydrating but the custom element is from Svelte, and it already scaffolded,
+ // then it might run block logic in hydration mode, which we have to prevent.
+ let is_hydrating_custom_element = hydrating && is_custom_element;
+ if (is_hydrating_custom_element) {
+ set_hydrating(false);
+ }
+
var current = prev || {};
var is_option_element = element.tagName === 'OPTION';
@@ -363,9 +379,10 @@ export function set_attributes(
element.style.cssText = value + '';
} else if (key === 'autofocus') {
autofocus(/** @type {HTMLElement} */ (element), Boolean(value));
- } else if (key === '__value' || (key === 'value' && value != null)) {
- // @ts-ignore
- element.value = element[key] = element.__value = value;
+ } else if (!is_custom_element && (key === '__value' || (key === 'value' && value != null))) {
+ // @ts-ignore We're not running this for custom elements because __value is actually
+ // how Lit stores the current value on the element, and messing with that would break things.
+ element.value = element.__value = value;
} else if (key === 'selected' && is_option_element) {
set_selected(/** @type {HTMLOptionElement} */ (element), value);
} else {
@@ -415,6 +432,10 @@ export function set_attributes(
}
}
+ if (is_hydrating_custom_element) {
+ set_hydrating(true);
+ }
+
return current;
}
diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js
index eab6c767f868..9d7b5e9de624 100644
--- a/packages/svelte/src/internal/client/reactivity/effects.js
+++ b/packages/svelte/src/internal/client/reactivity/effects.js
@@ -164,13 +164,7 @@ function create_effect(type, fn, sync, push = true) {
* @returns {boolean}
*/
export function effect_tracking() {
- if (active_reaction === null || untracking) {
- return false;
- }
-
- // If it's skipped, that's because we're inside an unowned
- // that is not being tracked by another reaction
- return !skip_reaction;
+ return active_reaction !== null && !untracking;
}
/**
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index d3c6e6b32199..70be2b216f72 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.7';
+export const VERSION = '5.19.8';
export const PUBLIC_VERSION = '5';
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 118a51157eb9..7f406d8f0d28 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
@@ -1,19 +1,24 @@
import { test } from '../../test';
export default test({
- mode: ['client', 'server'],
+ mode: ['client'],
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 });
+
+ const my_link = /** @type HTMLAnchorElement & { object: { test: true }; } */ (
+ target.querySelector('a')
+ );
assert.equal(my_link.getAttribute('string'), 'test');
assert.equal(my_link.hasAttribute('object'), false);
assert.deepEqual(my_link.object, { test: true });
+
+ const [value1, value2] = target.querySelectorAll('value-element');
+ assert.equal(value1.shadowRoot?.innerHTML, 'test');
+ assert.equal(value2.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 ff94a9484c9a..4c98245e5b6b 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
@@ -1,2 +1,22 @@
+
+
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js
new file mode 100644
index 000000000000..749b9997c2c5
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/_config.js
@@ -0,0 +1,16 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target, logs }) {
+ const b1 = target.querySelector('button');
+
+ b1?.click();
+ flushSync();
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `Store: new
Text: new message
`
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte
new file mode 100644
index 000000000000..3c16e3c0366c
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-tracking-unowned/main.svelte
@@ -0,0 +1,12 @@
+
+
+Store: {$store}
+Text: {text}
+
diff --git a/playgrounds/sandbox/index.html b/playgrounds/sandbox/index.html
index 512b5426a932..845538abf073 100644
--- a/playgrounds/sandbox/index.html
+++ b/playgrounds/sandbox/index.html
@@ -12,7 +12,7 @@