|
|
# Cascade
|
|
|
|
|
|
A thin **explicit** orchestrator that staggers its children's enter/exit. It owns **no recipe
|
|
|
and no timing of its own** — it rides the foundation stagger + the existing state-presets. It
|
|
|
is the *explicit* / content-animation way to sequence a set of elements (you designed them to
|
|
|
stagger), as opposed to the *event-driven* path where a component's own `emerge` firma + the
|
|
|
same foundation stagger do it (see "Two ways" below).
|
|
|
|
|
|
```svelte
|
|
|
<script>
|
|
|
import { Cascade } from '$uix/eidos/components/cascade'
|
|
|
let open = $state(true)
|
|
|
</script>
|
|
|
|
|
|
<Cascade {open} motion="scale-fade" style="display: grid; gap: 0.75rem; --motion-stagger-each: 60ms">
|
|
|
{#each items as item (item.id)}
|
|
|
<Cascade.Item>{item.label}</Cascade.Item>
|
|
|
{/each}
|
|
|
</Cascade>
|
|
|
```
|
|
|
|
|
|
`--motion-stagger-each: 0` (the default) → **parallel**; `N` → **cascade**. The keyframe,
|
|
|
duration, easing and reduced-motion all come from the `motion` preset.
|
|
|
|
|
|
## How it works — it reuses everything
|
|
|
|
|
|
Nothing here is a new system. The cascade is the *existing declared motion* plus one
|
|
|
foundation rule:
|
|
|
|
|
|
| Piece | Where it's declared / derived |
|
|
|
| --- | --- |
|
|
|
| the per-item **animation** (keyframe, duration, ease, reduced-motion) | a **state-preset** declared in `EidosConfig.motion` (`fade` / `scale-fade` / …), generated by eidos to `generated/base.css` and registered with `uix.motion` |
|
|
|
| the **stagger delay** (`index × --motion-stagger-each`, default 0 = parallel) | already in **every preset's** enter/exit rule (`lib/render-css.ts`) |
|
|
|
| the **structural index** (`--motion-stagger-index` / `-rev`) | the one new foundation rule: `[data-stagger] > *:nth-child / :nth-last-child` (generated by `lib/render-css.ts`) — **nobody writes it**, soma never writes a visual var |
|
|
|
|
|
|
`<Cascade>` just **wires** those: it marks the container `[data-stagger]`, reflects `open` as
|
|
|
`data-state`, and passes `open` + the chosen preset to its items (context), so each item
|
|
|
carries `data-animation-style` + `data-state`. Enter counts up (`:nth-child`); exit counts
|
|
|
down (`:nth-last-child`) so the last item leaves first.
|
|
|
|
|
|
## Props
|
|
|
|
|
|
### `<Cascade>`
|
|
|
|
|
|
| Prop | Type | Default | Notes |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `open` | `boolean` | `true` | Drives the staggered enter (forward) / exit (reverse) via `data-state`. |
|
|
|
| `motion` | `MotionPresetName` | `'fade'` | The state-preset every item plays (`fade` / `scale-fade` / `slide-fade` / …). Unified with the overlays' selector (RFC §D.12). |
|
|
|
|
|
|
Plus any `HTMLAttributes<HTMLDivElement>`. Set the layout + the rhythm (`--motion-stagger-each`)
|
|
|
here.
|
|
|
|
|
|
### `<Cascade.Item>`
|
|
|
|
|
|
A participant. On removal it flips to `data-state='closed'` (the existing **exit** preset
|
|
|
plays, with the reverse structural index), is retained (`out:`) for the eidos-declared exit
|
|
|
duration, then unmounts. Accepts any `HTMLAttributes<HTMLDivElement>`.
|
|
|
|
|
|
## Ownership
|
|
|
|
|
|
| | Owns | Never touches |
|
|
|
| --- | --- | --- |
|
|
|
| **soma** (the wrappers) | **state** (`open` → `data-state`, propagated to items) + **lifecycle** (`out:` retention) | any `--motion-*` / visual variable — it reads the eidos duration via the ActiveDom `getComputedStyle`, writes nothing |
|
|
|
| **eidos** | **all the visual** — the preset (keyframe/duration/ease/reduced-motion) + the foundation stagger index | the semantics / state |
|
|
|
|
|
|
## Two ways to stagger children
|
|
|
|
|
|
1. **Explicit** — `<Cascade>` (this component). You want these elements to stagger; there is no
|
|
|
semantic event. The content/intrinsic animation domain.
|
|
|
2. **Event-driven** — a real component (a menu, a list) declares its own `emerge` firma (the
|
|
|
container flourish + sound) **and** marks its item container `[data-stagger]`; its items
|
|
|
carry a preset (`data-animation-style`). Same foundation stagger, no `<Cascade>` wrapper —
|
|
|
the appearance flows from the declared event. The container-driven preset rule
|
|
|
`[data-stagger][data-state='open'] > [data-animation-style]` fires the children's enter off
|
|
|
the *container's* `data-state` (which soma already writes via the morfo `commits`), so the
|
|
|
items carry **no per-item `data-state`** — a `menuitem` isn't open/closed; the menu is.
|
|
|
(The firma is *generic by event*: "children of an emerging container present together, by
|
|
|
structural order"; the concrete timing — parallel/cascade, ms, keyframe — is the
|
|
|
per-component realization.)
|
|
|
|
|
|
**First concrete consumer: `<DropdownMenu>`** — items fade in staggered on open
|
|
|
(`src/uix/eidos/components/dropdown-menu/`). See its README → *Motion*. The container-driven
|
|
|
rule is **enter-only**; the coordinated *exit* cascade (retain the container until its
|
|
|
children finish) is the deferred soma `PresenceGroup` work.
|
|
|
|
|
|
## Known limitation — bulk removal
|
|
|
|
|
|
When **several** items are removed at once (e.g. a slider jumping 10 → 3), the leaving nodes
|
|
|
unmount one-by-one as each finishes, so the grid reflows during the cascade and the remaining
|
|
|
items' `:nth-last-child` index re-evaluates. In practice it reads fine, but the glitch-free
|
|
|
version wants the **container to retain the leaving set as a unit** (one reflow at the end) —
|
|
|
a soma **lifecycle** concern (what the retired `pending` did, without touching the visual).
|
|
|
Dragging the slider down step-by-step (a sequence of lone removals) is always clean.
|
|
|
|
|
|
## Decisions
|
|
|
|
|
|
- **No recipe, no `--cascade-*` namespace, no bespoke keyframe.** An earlier prototype forked
|
|
|
all of that — it reinvented the existing `--motion-stagger-*` + state-preset system. This
|
|
|
replaces it by reusing them; the only new code is the foundation structural-index writer.
|
|
|
- **`Cascade` is the EXPLICIT domain.** Event-driven children appearance rides the firma +
|
|
|
the same foundation stagger, declared in the component's own morfo — not this wrapper.
|
|
|
- **Index from `:nth-child`, capped 1–24** (the foundation writer). Beyond the cap items share
|
|
|
the last index until `sibling-index()` is broadly supported.
|