ProjectionSystem: its internal types

Interfaces

ProjectionSink

What the engine writes each node's projection through: a FlushGate is one, so the engine never imports a framework. write merges an element's projection into what it draws at the next frame; flushPass runs a measure now, and draws what it wrote before it returns, so a new layout never paints without its projection.

Properties

flushPass
readonly flushPass: (pass) => void;

Runs pass, a measure, now, and draws what it wrote before it returns.

Parameters
pass

() => void

Returns

void

write
readonly write: (id, partial) => void;

Merges partial into element id's values, drawn at the next frame.

Parameters
id

string

partial

Record<string, unknown>

Returns

void


ProjectionSystem

The Projection system: what a binding registers its layout elements with, one per provider.

Extends

  • Inspectable

Properties

animateLayout
readonly animateLayout: <A>(fn, options?) => A;

A Declared layout change: run fn, and animate the layout change it makes from where every node is at the call, even where no trigger sees the change or a node's last Measurement is out of date. Returns fn's result; its error throws to the caller.

  • The Measurements. The first call in a task measures every node before fn runs. A Measure pass already due in the task runs then instead, so what triggered it animates as before, and its reads are the Measurements. Later calls in the task share them, so a node one call's change pushes animates from before the first change (Motion's parallel-calls-intersecting).
  • The pass. The Measure pass at the end of the task animates every node that moved from those Measurements. A running animation fn moves restarts from where it paints; one it doesn't move carries on.
  • What stays as in any pass. A Resize block wins: fn runs and nothing animates. A dragged subtree takes its slot-only update. A call from inside a pass's own flush uses the reads that pass just made.
  • Synchronous only. A promise fn returns isn't awaited: a change after an await is in a later task. Such a call logs a warning.
  • onSettled is called once the change has settled: every layout animation the end-of-task pass started has ended, however each ended (landed, cut short by a resize or an unmount, or restarted by a later change), or at the end of that pass if it started none. Calls in one task share that pass, so they settle together.
Type Parameters
A

A

Parameters
fn

() => A

options?

AnimateLayoutOptions

Returns

A

awaitsTwin
readonly awaitsTwin: (el) => boolean;

Whether a twin that el is, or holds, left in this task with no live twin in its stack yet: a twin arriving later in the same update takes it over, so a presence decides on el's leave at the task's end, when handleLeave or handBackWithin finds that twin.

Parameters
el

HTMLElement

Returns

boolean

dispose
readonly dispose: () => void;

Stops the engine: its run, its observers and its listeners. The Layer's Scope calls it.

Returns

void

dropLeaver
readonly dropLeaver: (el) => Option<number>;

End a leaving Follower at once: a keyed presence child is back while its twin crossfades out, and takes over. Its onDone is called, as at its handover's end. Returns the crossfade factor it had faded to, for the child's new node to go on from (RegisterNodeOptions.fadeFrom); None for an element that isn't one.

Parameters
el

HTMLElement

Returns

Option<number>

handBackWithin
readonly handBackWithin: (el, onDone) => Option<() => void>;

Hand back the twins inside a leaving element that isn't a twin, for a presence child handleLeave declined: each crossfades onto its other twin, following the leaver's own exit. onDone once every handover has ended; the returned call drops them at once. None when it held none, so presence runs its exit alone.

Parameters
el

HTMLElement

onDone

() => void

Returns

Option<() => void>

handleLeave
readonly handleLeave: (el, onDone) => boolean;

A leaving layoutId element its exit keeps in the document: true when there's a live twin, the lead, to crossfade toward. The engine then draws el as a leaving follower, on the lead's box and fading out, through the renderer since its gate is gone, and calls onDone once the crossfade settles, so the exit removes it. false for one with no twin: its own exit runs as usual.

Parameters
el

HTMLElement

onDone

() => void

Returns

boolean

instant
readonly instant: (fn?) => void;

An Instant change (Motion's useInstantLayoutTransition): run fn, and the Measure pass at the end of the task gives every node its new box and starts nothing, as a Resize block's does. Every running layout animation but a dragged node's ends on its slot as that pass begins.

Parameters
fn?

() => void

Returns

void

keepLeaving
readonly keepLeaving: (el, taken) => () => void;

A presence child leaving with nobody to take its twins over: the twins it held as it went, inside it or it a twin itself, stay a later twin's to take over while it's on the page, until the returned call: the presence removing it, which ends their records at once (a held leaver otherwise lasts until a task ends with it off the page). A twin that registers meanwhile brings one back as its leaving Follower, as if it were live in its stack, and tells the presence through taken (LateTaken), which holds the child for that handover.

Its layout nodes with no layoutId, it included, come back as nodes it carries: corrected for an ancestor's layout animation while it exits, its exit's own values drawn on the correction, and gone with the returned call.

Parameters
el

HTMLElement

taken

LateTaken

Returns

() => void

keepSeen
readonly keepSeen: (el, move) => void;

A leaver its presence popped out of the flow: at the first frame after, if the page scrolled since (a router's scroll to the top at a navigation), move takes it by that scroll, so it stays where it was seen, and the leaving nodes inside it go with it. Once: scrolling after moves it with the page.

Parameters
el

HTMLElement

move

(by) => void

Returns

void

notify
readonly notify: () => void;

Asks for a measure of the whole tree at the end of the task, however many ask: for a change a group declares, as a layout group's layoutDependency does, so every node its change moves animates.

Returns

void

onPassEnd
readonly onPassEnd: (listener) => () => void;

Run listener at the end of every Measure pass; returns the unsubscribe. The engine's Layer hands it out as the internal PassEnds service, beside the engine, not on it (internal/pass-ends.ts).

Parameters
listener

() => void

Returns

() => void

register
readonly register: (opts) => NodeHandle;

Registers a layout element: its node, from then on measured at each layout change and animated from where it was. The handle tells the engine what changed, and its dispose removes the node.

Parameters
opts

RegisterNodeOptions

Returns

NodeHandle

registerValues
readonly registerValues: (el, read) => () => void;

Register an element's values: a binding hands every element's, read from what it last wrote, so a layoutId twin's flight follows the translate and scale Wardic animates on its ancestors each frame, an element that isn't a projection node included. A presence registers a leaver's drawn values in their place, its Transform template among them, so it's drawn through it as it leaves. Returns the unregistration, for the element's dispose.

Parameters
el

Element

read

() => Readonly<Record<string, unknown>>

Returns

() => void

reset
readonly reset: () => void;

A Projection reset (Motion's useResetProjection): every running layout animation and Release offset ends on its slot, every leaver and Switch seed goes (each layoutId stack keeps its live nodes), and the next Measure pass measures every box afresh, animating none.

Returns

void

rootBoxOf
readonly rootBoxOf: (el) => Option<Box>;

An element's box in the Root frame as it paints now, its transforms included: where a presence child taking over from it starts (RegisterNodeOptions.enteredFrom). None for one off the page.

Parameters
el

HTMLElement

Returns

Option<Box>

toJSON
readonly toJSON: () => Tree;

The tree as plain data, for a diagnostic read: every node now, in the order it was registered. console.log(system) shows it (Effect's Inspectable). Parent links are the last Measure pass's, as the engine reads them.

Returns

Tree

Overrides

Inspectable.Inspectable.toJSON


ProjectionSystemOptions

What a binding hands its engine.

Properties

reducedMotion?
readonly reducedMotion?: () => boolean;

Whether reduced motion applies now, read when a layout animation starts: the binding's ReducedMotion.applies of its app's setting and the user's preference. Under it, a layout animation lands on its new box at once, and a crossfade switches at once; a drag's release still returns to its slot, since it continues the gesture (as Motion's does). By default, never.

Returns

boolean

scaleCorrectors?
readonly scaleCorrectors?: Readonly<Record<string, ScaleCorrector>>;

The app's own scale correctors (Motion's addScaleCorrector entries), by the value key each corrects, for this system's nodes alone. Each is handed the element's own value for its key each frame (from its style or an animation), else its computed value read at its first Measurement, and writes nothing while the projection doesn't scale it, putting the value back once. One keyed borderRadius or boxShadow replaces the built-in one, and a replaced radius isn't blended across a handover.

Type Aliases

LateTaken

type LateTaken = (drop) => () => void;

A late take-over of a twin inside a leaving presence child, told to its presence, with drop to end the handover at once. Returns what ends it as one of the child's parts.

Parameters

drop

() => void

Returns

() => void


ProjectionServices

type ProjectionServices = 
  | FrameLoop.FrameLoop
  | Renderer.Renderer
  | Probe.Probe
  | AtomRegistry.AtomRegistry;

What the engine needs from its Environment: the Frame loop it runs on, the renderer, which draws a leaving follower whose gate is gone, and the Probe's viewport, read through the Environment's AtomRegistry, so it lands rather than animates through a resize.