import type { Accessor, Element as JSXElement } from "solid-js"; import { useClock } from "./clock.ts"; /** * A per-frame color field over a region: given a glyph cell's local position * `(x, y)` within a `width`×`height` box, return its packed 24-bit fg, or * `undefined` to leave the painted color as-is. */ export type AnimationField = ( x: number, y: number, width: number, height: number, ) => number | undefined; /** What an {@link Effect} is handed each frame to build its field. */ export type EffectEnv = { /** Animation clock (ms since mount). */ t: number; /** True while the group is paused. */ paused: boolean; }; /** * A reusable animation. Called every frame with the current {@link EffectEnv}, * it returns the {@link AnimationField} sampled for each glyph cell. See * `shimmer`, `wave`, and `pulse` in `./effects.ts`. */ export type Effect = (env: EffectEnv) => AnimationField; export type AnimatedProps = { children: JSXElement; /** The animation to run across the region (e.g. `shimmer(...)`). */ effect: Effect; /** Clock tick in ms — the animation's temporal resolution. Default 50. */ tick?: number; /** Freeze the animation (effects fall back to their resting color). */ paused?: boolean; /** Share an external clock instead of creating one. */ time?: Accessor; }; /** * Runs an {@link Effect} as a paint-time color filter over its children, so the * animation sweeps across whatever they paint — text, a spinner glyph, nested * boxes — purely by screen position. Children stay plain; nothing needs to be * animation-aware: * * ```tsx * * * {` ${verb}…`} * * ``` * * The named `Shimmer` / `Wave` / `Pulse` wrappers cover the common effects. */ export function Animated(props: AnimatedProps) { const time = props.time ?? useClock(props.tick ?? 50); // Rebuilds the field each tick; the changed prop re-paints the region. const colorField = () => props.effect({ t: time(), paused: props.paused ?? false }); return ( {props.children} ); }