# 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 {#each items as item (item.id)} {item.label} {/each} ``` `--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 | `` 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 ### `` | 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`. Set the layout + the rhythm (`--motion-stagger-each`) here. ### `` 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`. ## 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** — `` (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 `` 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: ``** — 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.