Transitions
import { enterTransition, leaveTransition, transition } from '@studiometa/js-toolkit/utils';Promoted into core from the migration set, beside the easings, spring() and smoothTo().
transition
transition(
el: HTMLElement,
nameOrStyles: string | TransitionStyles,
mode?: 'keep' | 'remove',
): Promise<void>interface TransitionStyles {
from?: string | string[] | Partial<CSSStyleDeclaration>;
active?: string | string[] | Partial<CSSStyleDeclaration>;
to?: string | string[] | Partial<CSSStyleDeclaration>;
}The three-state dance every CSS transition needs, with the frame boundaries in the right places:
import { transition } from '@studiometa/js-toolkit/utils';
const el = document.body;
async function run() {
// The class form: `fade-from`, `fade-active`, `fade-to`.
await transition(el, 'fade');
// The inline-style form.
await transition(el, {
from: { opacity: '0' },
active: { transition: 'opacity 300ms' },
to: { opacity: '1' },
});
// Keep the `to` state when it ends.
await transition(el, 'fade', 'keep');
}mode decides what happens at the end: 'remove' (the default) clears the states, 'keep' leaves the to state applied.
Each of the three states takes a class name, an array of class names, or an inline-style object — setClassesOrStyles() is what makes that work.
The two directions
type TransitionOptions = {
enterFrom: string;
enterActive: string;
enterTo: string;
enterKeep: boolean;
leaveFrom: string;
leaveActive: string;
leaveTo: string;
leaveKeep: boolean;
};One option object, so a component declares eight options once and both calls read the same thing:
await enterTransition(this.$el, this.$options);
await leaveTransition(this.$el, this.$options);enterTransition
enterTransition(el: HTMLElement, options: TransitionOptions): Promise<void>leaveTransition
leaveTransition(el: HTMLElement, options: TransitionOptions): Promise<void>TRANSITION_OPTIONS
const TRANSITION_OPTIONS: Record<string, OptionDefinition>;That option set as a config.options fragment, ready to spread:
static config = {
name: 'Panel',
options: {
...TRANSITION_OPTIONS,
open: Boolean,
},
};Which means the eight names, their types and their defaults are declared in one place, and a component that wants them writes one line.
Testing them
A transition's end state is asserted by polling, never by settle()
A method that starts a transition does not hand it back, and a kept end state lands only after nextFrame(), the from and active states, and either a transitionend or one more frame. settle() is generous rather than deterministic, which is a flake that passes alone and fails under load.
Polling for an absence is wrong for the mirror-image reason: leaveTransition() clears the other direction's to state synchronously, so the poll passes before anything has happened.
See waitFor().
What is not here
Time-based playback, stagger, sequencing, morphing and text splitting are the separate ui-animation package. tween and animate are not shipped.
exit, layout and layoutId are not an engine's job — viewTransition() solves them, and it is in core.