From a61cc7a425730c96f308dfc8887be8690c1d88be Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 24 May 2023 17:38:35 +0200 Subject: [PATCH] feat: apply inert to outroing elements that way they are invisible to assistive technology and can't be interacted with, which makes sense since the element is already "dead" and only transitioning out at this point closes #8445 --- CHANGELOG.md | 1 + src/runtime/internal/transitions.js | 35 +++++++++++++++++-- .../samples/transition-inert/_config.js | 28 +++++++++++++++ .../samples/transition-inert/main.svelte | 16 +++++++++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 test/runtime/samples/transition-inert/_config.js create mode 100644 test/runtime/samples/transition-inert/main.svelte 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}