diff --git a/CHANGELOG.md b/CHANGELOG.md
index cbafadc80703..ce7086ecc2ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
* **breaking** Error on falsy values instead of stores passed to `derived` ([#7947](https://github.com/sveltejs/svelte/pull/7947))
* **breaking** Custom store implementers now need to pass an `update` function additionally to the `set` function ([#6750](https://github.com/sveltejs/svelte/pull/6750))
* **breaking** Change order in which preprocessors are applied ([#8618](https://github.com/sveltejs/svelte/pull/8618))
+* **breaking** apply `inert` to outroing elements ([#8627](https://github.com/sveltejs/svelte/pull/8627))
* Add a way to modify attributes for script/style preprocessors ([#8618](https://github.com/sveltejs/svelte/pull/8618))
* Improve hydration speed by adding `data-svelte-h` attribute to detect unchanged HTML elements ([#7426](https://github.com/sveltejs/svelte/pull/7426))
* Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391))
diff --git a/src/runtime/internal/transitions.js b/src/runtime/internal/transitions.js
index 1c0711fc502e..925316d75ee9 100644
--- a/src/runtime/internal/transitions.js
+++ b/src/runtime/internal/transitions.js
@@ -186,14 +186,15 @@ export function create_in_transition(node, fn, params) {
* @returns {{ end(reset: any): void; }}
*/
export function create_out_transition(node, fn, params) {
- /**
- * @type {TransitionOptions} */
+ /** @type {TransitionOptions} */
const options = { direction: 'out' };
let config = fn(node, params, options);
let running = true;
let animation_name;
const group = outros;
group.r += 1;
+ /** @type {boolean} */
+ let original_inert_value;
/**
* @returns {void} */
@@ -205,10 +206,18 @@ export function create_out_transition(node, fn, params) {
tick = noop,
css
} = config || null_transition;
+
if (css) animation_name = create_rule(node, 1, 0, duration, delay, easing, css);
+
const start_time = now() + delay;
const end_time = start_time + duration;
add_render_callback(() => dispatch(node, false, 'start'));
+
+ if ('inert' in node) {
+ original_inert_value = /** @type {HTMLElement} */ (node).inert;
+ node.inert = true;
+ }
+
loop((now) => {
if (running) {
if (now >= end_time) {
@@ -229,6 +238,7 @@ export function create_out_transition(node, fn, params) {
return running;
});
}
+
if (is_function(config)) {
wait().then(() => {
// @ts-ignore
@@ -238,8 +248,12 @@ export function create_out_transition(node, fn, params) {
} else {
go();
}
+
return {
end(reset) {
+ if (reset && 'inert' in node) {
+ node.inert = original_inert_value;
+ }
if (reset && config.tick) {
config.tick(1, 0);
}
@@ -274,6 +288,9 @@ export function create_bidirectional_transition(node, fn, params, intro) {
let pending_program = null;
let animation_name = null;
+ /** @type {boolean} */
+ let original_inert_value;
+
/**
* @returns {void} */
function clear_animation() {
@@ -318,11 +335,25 @@ export function create_bidirectional_transition(node, fn, params, intro) {
start: now() + delay,
b
};
+
if (!b) {
// @ts-ignore todo: improve typings
program.group = outros;
outros.r += 1;
}
+
+ if ('inert' in node) {
+ if (b) {
+ if (original_inert_value !== undefined) {
+ // aborted/reversed outro — restore previous inert value
+ node.inert = original_inert_value;
+ }
+ } else {
+ original_inert_value = /** @type {HTMLElement} */ (node).inert;
+ node.inert = true;
+ }
+ }
+
if (running_program || pending_program) {
pending_program = program;
} else {
diff --git a/test/runtime/samples/transition-inert/_config.js b/test/runtime/samples/transition-inert/_config.js
new file mode 100644
index 000000000000..7058269ab030
--- /dev/null
+++ b/test/runtime/samples/transition-inert/_config.js
@@ -0,0 +1,28 @@
+export default {
+ async test({ assert, component, target, raf }) {
+ // jsdom doesn't set the inert attribute, and the transition checks if it exists, so set it manually to trigger the inert logic
+ target.querySelector('button.a').inert = false;
+ target.querySelector('button.b').inert = false;
+
+ // check and abort halfway through the outro transition
+ component.visible = false;
+ raf.tick(50);
+ assert.strictEqual(target.querySelector('button.a').inert, true);
+ assert.strictEqual(target.querySelector('button.b').inert, true);
+
+ component.visible = true;
+ assert.strictEqual(target.querySelector('button.a').inert, false);
+ assert.strictEqual(target.querySelector('button.b').inert, false);
+
+ // let it transition out completely and then back in
+ component.visible = false;
+ raf.tick(101);
+ component.visible = true;
+ raf.tick(50);
+ assert.strictEqual(target.querySelector('button.a').inert, false);
+ assert.strictEqual(target.querySelector('button.b').inert, false);
+ raf.tick(51);
+ assert.strictEqual(target.querySelector('button.a').inert, false);
+ assert.strictEqual(target.querySelector('button.b').inert, false);
+ }
+};
diff --git a/test/runtime/samples/transition-inert/main.svelte b/test/runtime/samples/transition-inert/main.svelte
new file mode 100644
index 000000000000..39fcd1d1b620
--- /dev/null
+++ b/test/runtime/samples/transition-inert/main.svelte
@@ -0,0 +1,16 @@
+
+
+{#if visible}
+
+
+{/if}