Nic Polumeyv 3 days ago committed by GitHub
commit 320f424274
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: avoid `NaN` keyframe values in `slide` transition for elements without a layout box

@ -28,6 +28,18 @@ function split_css_unit(value) {
return split ? [parseFloat(split[1]), split[2] || 'px'] : [/** @type {number} */ (value), 'px'];
}
/**
* Elements without a layout box (e.g. inside a `display: none` parent) have computed
* dimensions like `auto` that parse to `NaN`, producing keyframe values the browser
* rejects (https://github.com/sveltejs/svelte/issues/14205). Treat them as `0` instead
* @param {string} value
* @returns {number}
*/
function parse_dimension(value) {
const result = parseFloat(value);
return Number.isNaN(result) ? 0 : result;
}
/**
* Animates a `blur` filter alongside an element's opacity.
*
@ -116,19 +128,21 @@ export function slide(node, { delay = 0, duration = 400, easing = cubic_out, axi
const opacity = +style.opacity;
const primary_property = axis === 'y' ? 'height' : 'width';
const primary_property_value = parseFloat(style[primary_property]);
const primary_property_value = parse_dimension(style[primary_property]);
const secondary_properties = axis === 'y' ? ['top', 'bottom'] : ['left', 'right'];
const capitalized_secondary_properties = secondary_properties.map(
(e) => /** @type {'Left' | 'Right' | 'Top' | 'Bottom'} */ (`${e[0].toUpperCase()}${e.slice(1)}`)
);
const padding_start_value = parseFloat(style[`padding${capitalized_secondary_properties[0]}`]);
const padding_end_value = parseFloat(style[`padding${capitalized_secondary_properties[1]}`]);
const margin_start_value = parseFloat(style[`margin${capitalized_secondary_properties[0]}`]);
const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`]);
const border_width_start_value = parseFloat(
const padding_start_value = parse_dimension(
style[`padding${capitalized_secondary_properties[0]}`]
);
const padding_end_value = parse_dimension(style[`padding${capitalized_secondary_properties[1]}`]);
const margin_start_value = parse_dimension(style[`margin${capitalized_secondary_properties[0]}`]);
const margin_end_value = parse_dimension(style[`margin${capitalized_secondary_properties[1]}`]);
const border_width_start_value = parse_dimension(
style[`border${capitalized_secondary_properties[0]}Width`]
);
const border_width_end_value = parseFloat(
const border_width_end_value = parse_dimension(
style[`border${capitalized_secondary_properties[1]}Width`]
);
return {

@ -0,0 +1,25 @@
import { test } from '../../assert';
export default test({
async test({ assert, window }) {
window.document.querySelector('button')?.click();
await new Promise((r) => setTimeout(r, 100));
const p = window.document.querySelector('p');
const animations = /** @type {HTMLElement} */ (p).getAnimations();
assert.equal(animations.length, 1);
// when the element has no layout box, computed dimensions resolve to 'auto',
// which must not end up as NaN values that the browser rejects (#14205)
const keyframes = /** @type {KeyframeEffect} */ (animations[0].effect).getKeyframes();
assert.ok(keyframes.length > 0);
for (const keyframe of keyframes) {
assert.ok('height' in keyframe, 'height was rejected as an invalid keyframe value');
for (const value of Object.values(keyframe)) {
assert.ok(!String(value).includes('NaN'), `unexpected NaN in keyframe: ${value}`);
}
}
}
});

@ -0,0 +1,13 @@
<script>
import { slide } from 'svelte/transition';
let visible = $state(false);
</script>
<button onclick={() => (visible = !visible)}>toggle</button>
<div style="display: none">
{#if visible}
<p transition:slide>hello</p>
{/if}
</div>

@ -18,7 +18,7 @@ export default test({
raf.tick(150);
assert.htmlEqual(
target.innerHTML,
'<p>foo</p><p class="red svelte-1yszte8 border" style="overflow: hidden; opacity: 0; border-top-width: 0.5px; border-bottom-width: 0.5px; min-height: 0;">bar</p>'
'<p>foo</p><p class="red svelte-1yszte8 border" style="overflow: hidden; opacity: 0; height: 0px; padding-top: 0px; padding-bottom: 0px; margin-top: 0px; margin-bottom: 0px; border-top-width: 0.5px; border-bottom-width: 0.5px; min-height: 0;">bar</p>'
);
component.open = true;
raf.tick(250);

Loading…
Cancel
Save