Description
lab/Drawer defers dialog.close() so its slide-out can play, and holds the drawer rendered meanwhile by transitioning display with allow-discrete. The hold is a theme value; the delay is a literal. They are the same number today by coincidence, and drift apart as soon as a theme changes.
Drawer.tsx:
// the hold — themeable
transitionProperty: 'transform, max-width, display',
transitionDuration: durationVars['--duration-medium'],
transitionBehavior: 'allow-discrete',
'@media (prefers-reduced-motion: reduce)': {
transitionDuration: '0.01s',
},
// the delay — literal
const duration = window.matchMedia('(prefers-reduced-motion: reduce)').matches
? 10
: 250;
closeTimeoutRef.current = setTimeout(() => {
dialog.close();
...
}, duration);
Four cases:
| theme |
hold |
delay |
result |
default (--duration-medium: 410ms) |
410ms |
250ms |
inside the hold |
prefers-reduced-motion |
10ms |
10ms |
exactly on the boundary |
shipped y2k (motion.medium: 250) |
250ms |
250ms |
exactly on the boundary |
| any theme below 250ms |
< 250ms |
250ms |
outside the hold |
In the last row close() runs after display has already flipped to none. A <dialog> opened with showModal() blocks the whole document for as long as it is in the top layer whether or not it is rendered, so that window is a page that looks normal and is not interactive. A browser that then fails to un-block on close leaves it that way, with no JavaScript error — which is #4290, the same failure MobileNav had.
Nothing validates motion values, so defineTheme({motion: {medium: 200}}) is enough to reach the last row.
Suggested fix
Derive the delay from the hold actually in effect rather than assuming it, as MobileNav now does in #4388 (resolveCloseDelay, reading getComputedStyle(dialog).transitionDuration and closing at a fraction of it). Note browsers serialise computed <time> in seconds, so 410ms reads back as "0.41s".
Reduced motion should shorten the delay, not the hold. Shortening both leaves no slack at all — one slow frame between the commit and the close macrotask and the drawer has already stopped being rendered.
Wider point
Both components would be safe without per-component guards if theme motion values were floored where themes are defined (expandMotionScale / defineTheme). That is one change covering every component rather than one patch per component, and it also closes the medium: 0 case that no per-component fix can reach. Raised here rather than in #4388 because it is an API call.
Astryx Version
main (packages/lab/src/Drawer/Drawer.tsx)
Description
lab/Drawerdefersdialog.close()so its slide-out can play, and holds the drawer rendered meanwhile by transitioningdisplaywithallow-discrete. The hold is a theme value; the delay is a literal. They are the same number today by coincidence, and drift apart as soon as a theme changes.Drawer.tsx:Four cases:
--duration-medium: 410ms)prefers-reduced-motionmotion.medium: 250)In the last row
close()runs afterdisplayhas already flipped tonone. A<dialog>opened withshowModal()blocks the whole document for as long as it is in the top layer whether or not it is rendered, so that window is a page that looks normal and is not interactive. A browser that then fails to un-block on close leaves it that way, with no JavaScript error — which is #4290, the same failureMobileNavhad.Nothing validates motion values, so
defineTheme({motion: {medium: 200}})is enough to reach the last row.Suggested fix
Derive the delay from the hold actually in effect rather than assuming it, as
MobileNavnow does in #4388 (resolveCloseDelay, readinggetComputedStyle(dialog).transitionDurationand closing at a fraction of it). Note browsers serialise computed<time>in seconds, so410msreads back as"0.41s".Reduced motion should shorten the delay, not the hold. Shortening both leaves no slack at all — one slow frame between the commit and the close macrotask and the drawer has already stopped being rendered.
Wider point
Both components would be safe without per-component guards if theme motion values were floored where themes are defined (
expandMotionScale/defineTheme). That is one change covering every component rather than one patch per component, and it also closes themedium: 0case that no per-component fix can reach. Raised here rather than in #4388 because it is an API call.Astryx Version
main(packages/lab/src/Drawer/Drawer.tsx)