major
#29792
TL Views: a closed dialog (and a left tile-stack frame) stays visible for 1 s unless the application ships its own exit animation - move the demo's animations into the engine
Affects 8.0.0-alpha10, the transition hook of #29596 (useKeyedTransition / whenFinished in com.top_logic.layout.react/react-src/bridge/transition.ts) and its consumers TLDialogManager and TLTileStack.
Observation
In an application without its own stylesheet for the transition classes, every dialog close feels laggy: after "Cancel" (or "Create") the server answers within ~60 ms, the dialog is already gone from the accessibility tree, but it stays visible on screen for about a second (a user reported up to 3 s). The same happens when leaving a tile-stack frame. tl-demo-react does not show the effect, because style/tl-demo-react.css animates the classes (150 ms dialog fade, 200 ms frame slide).
Measured in an application (E-Home, React UI only), time from the click on "Cancel" until no .tlDialog__backdrop / .tlDialog__backdrop--exiting element is left in the DOM:
| role=dialog gone | visually gone | |
| no application animation | 62 ms | 1,068 ms |
| with a 120 ms fade added by the application | ~60 ms | ~190 ms |
A second defect showed up while verifying: drilling into a tile stack never showed its slide-in. The hook puts tlTileStack__frame--entering on the frame, but the frame re-renders in the same task (the fill class arrives with a second render) and React rewrites its whole className, dropping the class. The animation is cancelled, no animationend comes, and the class waits for the 1 s fallback.
Cause
When a key leaves, the hook replaces the element by an inert copy carrying the exit class (tlDialog__backdrop--exiting, tlTileStack__frame--exiting) and removes the copy on animationend / transitionend of that element - or after TRANSITION_FALLBACK_MS (1000 ms) if nothing ends it. The engine ships no animation for the dialog and tile-stack classes (tlReactControls.css: "The engine ships no animation for either class"; only the wizard keeps a default animation). So without application CSS no event ever comes and the visible copy of the closed dialog always waits for the full fallback. The fallback's own JavaDoc names this case ("a stylesheet that animates nothing") - but treating it with a 1 s timeout makes it the default experience of every application.
Proposal
- Default animations in the engine. Move the demo's rules for tlDialog__backdrop--entering|--exiting and tlTileStack__frame--entering|--exiting (incl. the forward/backward variants) into the engine's default styles (tlReactControls.css or the design-system component stylesheet), with durations taken from motion tokens of the default theme, so that a theme or application can change or switch them off. Viewers with prefers-reduced-motion: reduce already get no classes.
- Remove the custom animation from tl-demo-react. tl-demo-react.css then no longer needs its own transition rules; the demo shows the engine default like every other application (and would have revealed this defect).
- No waiting when nothing animates. In whenFinished, check the computed style of the element right after setting the class (getComputedStyle(element): animation-name is none and all transition-duration`s are `0s) and end immediately instead of arming the 1 s fallback. The fallback stays for animations that never report their end.
With 1 and 3 an application that switches the animation off (theme token or own rule animation: none) gets an immediate close instead of a one-second ghost.
Workaround
An application stylesheet registered as ClientResources that animates the four classes, e.g.
.tlDialog__backdrop--entering { animation: appFadeIn 120ms ease-out; }
.tlDialog__backdrop--exiting { animation: appFadeOut 120ms ease-in forwards; }
.tlTileStack__frame--entering { animation: appFadeIn 150ms ease-out; }
.tlTileStack__frame--exiting { animation: appFadeOut 150ms ease-in forwards; }
Solution
- tlReactControls.css ships default animations for the dialog backdrop (fade, --tl-duration-base, easing --tl-easing-entrance / --tl-easing-exit) and the tile-stack frames (fade and slide towards the direction of the stack change, --tl-duration-slow). The wizard step animation uses the same motion tokens instead of literal durations. A theme changes or switches off all of them through the motion tokens; an application restyles a single one by redefining its rule.
- whenFinished reads the computed style of the marked element: if neither a named animation nor a transition with a duration (plus delay) above zero applies, the mark is removed immediately, before the browser paints. The fallback timeout remains only for animations that never report their end.
- useKeyedTransition keeps the enter class on an arriving element while its animation runs, even if the element's own renderer rewrites its class attribute in the meantime (a MutationObserver on the class attribute puts it back before the next style recalculation).
- tl-demo-react.css no longer carries its own dialog and tile-stack animations; the demo shows the engine default.
Measured in tl-demo-react afterwards: dialog close ~240 ms after the click (150 ms fade), tile-stack drill-in and back ~300 ms (250 ms slide); with animation: none on the classes the copy and the classes go in the same task.