feat: apply inert to outroing elements (#8628)

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
pull/8654/head
Simon H 1 year ago committed by GitHub
parent df361a2d6d
commit 734cc19846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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))

@ -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 {

@ -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);
}
};

@ -0,0 +1,16 @@
<script>
export let visible = true;
function slide(_, params) {
return params;
}
</script>
{#if visible}
<button class="a" transition:slide={{ duration: 100 }}>
foo
</button>
<button class="b" out:slide={{ duration: 100 }}>
bar
</button>
{/if}
Loading…
Cancel
Save