From 19ecbf44e4c9ea77bb8d0ae210475639a6aa7fe5 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 20 Jun 2026 14:47:54 +0200 Subject: [PATCH] feat(motion): event-driven cascade rollout to the menu family (ContextMenu + Menubar) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the just-landed DropdownMenu event-driven item cascade to its siblings so the framework is coherent — every menu in the family fades its items in staggered on open, same mechanism, zero new generator code (rides the container-driven preset branch shipped with DropdownMenu). - ContextMenu (own recipe): Content marks the panel `[data-stagger]` + `--motion-stagger-each: var(--motion-stagger)`; item / checkbox-item / radio-item carry `data-animation-style="fade"`. - Menubar (ONE line): soma `Menubar.Content` delegates to `DropdownMenuContent`, so its panel already carries `data-dropdown-menu-content` (→ the dropdown recipe rhythm applies) and re-uses `DropdownMenu.Item` (→ already `data-animation-style="fade"`). It only adds the `[data-stagger]` marker. Verified at runtime: ContextMenu (scrubbed opacity gradient 0.96/0.84/0.00), Menubar (panel carries `data-stagger` + `data-dropdown-menu-content`, items `name:fade-in`, structural index from `:nth-child` 0/1/2/6/7). `check` clean for the touched components. (Preview tab runs backgrounded → rAF paused; verified by scrubbing `animation.currentTime` via WAAPI rather than waiting for playback.) Deliberately NOT rolled out: select / combobox / command — live-filtered or long-scroll lists where an open cascade is questionable UX. Docs: ContextMenu README (Motion parity note), Menubar index doc, DropdownMenu README (family cross-reference), RFC §D.11.4 (rollout). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/uix/eidos/MOTION_SERVICE_RFC.md | 8 ++++++++ src/uix/eidos/components/context-menu/README.md | 7 +++++++ .../context-menu/context-menu-checkbox-item.svelte | 2 +- .../components/context-menu/context-menu-content.svelte | 8 +++++++- .../components/context-menu/context-menu-item.svelte | 7 ++++++- .../context-menu/context-menu-radio-item.svelte | 2 +- src/uix/eidos/components/context-menu/context-menu.css | 9 +++++++++ src/uix/eidos/components/dropdown-menu/README.md | 5 +++++ src/uix/eidos/components/menubar/index.ts | 6 +++++- src/uix/eidos/components/menubar/menubar-content.svelte | 9 ++++++++- 10 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/uix/eidos/MOTION_SERVICE_RFC.md b/src/uix/eidos/MOTION_SERVICE_RFC.md index 8eff0fc1b..ca88d48e6 100644 --- a/src/uix/eidos/MOTION_SERVICE_RFC.md +++ b/src/uix/eidos/MOTION_SERVICE_RFC.md @@ -1404,6 +1404,14 @@ each`, gradiente de opacidad en vuelo (Ruler 0.98 → Grid 0.89 → Guides 0.69, solo cascadean los items que son **hijos directos** del panel — los envueltos en ``/`` (un elemento DOM real) no son `:nth-child` del contenedor y aparecen instantáneos (el «intermedio transparente»). +**Rollout a la familia de menús (2026-06-20):** el mismo cableado en `` (recipe propio: +`data-context-menu-content` `[data-stagger]` + `--motion-stagger-each` + items `data-animation-style="fade"`) y +`` (**1 línea** — su panel ya carga `data-dropdown-menu-content` y reusa `DropdownMenu.Item`, así que +solo añade el marker `[data-stagger]`; el ritmo + el preset salen del recipe dropdown compartido). Verificados en +runtime: context-menu (gradiente scrubbed 0.96/0.84/0.00), menubar (panel con `data-stagger`+`data-dropdown-menu-content`, +items `name:fade-in`, índice `:nth-child` 0/1/2/6/7). **Fuera del rollout a propósito:** select/combobox/command — +listas con filtrado-en-vivo o scroll largo donde una cascada al abrir es UX cuestionable. + #### D.11.5 — El servicio `motion` es el motor (par/superior a Framer) `EngineMotion` (`arts/motion` · `uix.motion`): drivers `spring` (integrador de Euler semi-implícito: diff --git a/src/uix/eidos/components/context-menu/README.md b/src/uix/eidos/components/context-menu/README.md index 69a270962..6cb1d3ea6 100644 --- a/src/uix/eidos/components/context-menu/README.md +++ b/src/uix/eidos/components/context-menu/README.md @@ -11,6 +11,13 @@ DropdownMenu: | Anchored to | Trigger's bounding box | Cursor position | | `CheckboxGroup` | ✓ | — (Bits parity gap; can be added later) | +**Motion parity too.** Items fade in staggered on open — the *event-driven* +cascade. The Content marks the panel `[data-stagger]` + sets the rhythm +`--motion-stagger-each: var(--motion-stagger)`; each item carries +`data-animation-style="fade"`; the per-item index comes from `:nth-child`. Same +mechanism + caveats (direct-child items only; enter-only) as DropdownMenu — see +its README → **Motion** for the full explanation. + ## Usage ```svelte diff --git a/src/uix/eidos/components/context-menu/context-menu-checkbox-item.svelte b/src/uix/eidos/components/context-menu/context-menu-checkbox-item.svelte index 52099b1e1..6184475aa 100644 --- a/src/uix/eidos/components/context-menu/context-menu-checkbox-item.svelte +++ b/src/uix/eidos/components/context-menu/context-menu-checkbox-item.svelte @@ -15,7 +15,7 @@ }: ContextMenuCheckboxItemProps = $props(); - + {#snippet children(snippetProps)} {@render bodyContent?.(snippetProps)} {/snippet} diff --git a/src/uix/eidos/components/context-menu/context-menu-content.svelte b/src/uix/eidos/components/context-menu/context-menu-content.svelte index 2ecac79c1..d5741dc83 100644 --- a/src/uix/eidos/components/context-menu/context-menu-content.svelte +++ b/src/uix/eidos/components/context-menu/context-menu-content.svelte @@ -1,11 +1,17 @@ - + {#snippet children(snippetProps)} {@render bodyContent?.(snippetProps)} {/snippet} diff --git a/src/uix/eidos/components/context-menu/context-menu-item.svelte b/src/uix/eidos/components/context-menu/context-menu-item.svelte index 2da94aa62..51d76d38c 100644 --- a/src/uix/eidos/components/context-menu/context-menu-item.svelte +++ b/src/uix/eidos/components/context-menu/context-menu-item.svelte @@ -1,7 +1,12 @@ -{@render children?.()} +{@render children?.()} diff --git a/src/uix/eidos/components/context-menu/context-menu-radio-item.svelte b/src/uix/eidos/components/context-menu/context-menu-radio-item.svelte index 5b55d7637..18bab9879 100644 --- a/src/uix/eidos/components/context-menu/context-menu-radio-item.svelte +++ b/src/uix/eidos/components/context-menu/context-menu-radio-item.svelte @@ -14,7 +14,7 @@ }: ContextMenuRadioItemProps = $props(); - + {#snippet children(snippetProps)} {@render bodyContent?.(snippetProps)} {/snippet} diff --git a/src/uix/eidos/components/context-menu/context-menu.css b/src/uix/eidos/components/context-menu/context-menu.css index 3b4392c74..2e7e69b3c 100644 --- a/src/uix/eidos/components/context-menu/context-menu.css +++ b/src/uix/eidos/components/context-menu/context-menu.css @@ -40,6 +40,15 @@ outline: none; } +/* Event-driven item cascade rhythm — the Content wrapper marks itself + `[data-stagger]`; each direct-child item carries `data-animation-style` and + gets its structural index from `:nth-child`. `var(--motion-stagger)` is the + canonical 20ms (0 = parallel). Same mechanism as DropdownMenu; the appearance + flows from the `open` (emerge) event — no JS writes the index. */ +[data-context-menu-content] { + --motion-stagger-each: var(--motion-stagger); +} + /* ── Item rows ─────────────────────────────────────────────────────── */ [data-context-menu-item], diff --git a/src/uix/eidos/components/dropdown-menu/README.md b/src/uix/eidos/components/dropdown-menu/README.md index 87abfd504..77232643c 100644 --- a/src/uix/eidos/components/dropdown-menu/README.md +++ b/src/uix/eidos/components/dropdown-menu/README.md @@ -113,6 +113,11 @@ Two honest limits (both documented framework-wide): lifecycle), still deferred — so a closing container does **not** drive a child exit that would be cut off on unmount. +`` and `` share this verbatim (same soma menu machinery): +ContextMenu has its own recipe; Menubar's per-menu panel already carries +`data-dropdown-menu-content` and re-uses `DropdownMenu.Item`, so it inherits the +rhythm + the item preset and only adds the `[data-stagger]` marker. + ## Comparativa | Lib | Sub-menus | CheckboxItem / RadioItem | CheckboxGroup | Arrow | Modal toggle | diff --git a/src/uix/eidos/components/menubar/index.ts b/src/uix/eidos/components/menubar/index.ts index a500021c6..5a6d799e2 100644 --- a/src/uix/eidos/components/menubar/index.ts +++ b/src/uix/eidos/components/menubar/index.ts @@ -26,7 +26,11 @@ // switch when active, focus return, typeahead, RTL. Eidos paints the // bar surface + trigger states; per-menu Content reuses the // dropdown-menu recipe (Item / Sub / Checkbox / Radio / Separator -// chrome) since soma re-exports those DropdownMenu parts. +// chrome) since soma re-exports those DropdownMenu parts — so per-menu +// items also inherit the event-driven open cascade (items fade in +// staggered; see DropdownMenu README -> Motion). The eidos Content just +// adds the `[data-stagger]` marker; the rhythm + the item preset come +// from the shared dropdown recipe + the re-used `DropdownMenu.Item`. // // Drops `Arrow` part — the menubar idiom anchors panels to the bar // edge; an arrow chrome doesn't fit. If you need one, render diff --git a/src/uix/eidos/components/menubar/menubar-content.svelte b/src/uix/eidos/components/menubar/menubar-content.svelte index e70fdf0d6..120c240cd 100644 --- a/src/uix/eidos/components/menubar/menubar-content.svelte +++ b/src/uix/eidos/components/menubar/menubar-content.svelte @@ -23,7 +23,14 @@ const passthrough = $derived(rest as Record); - + + {#snippet children(snippetProps)} {@render bodyContent?.(snippetProps)} {/snippet}