- floating-gap canon: --floating-gap-menu 0 -> var(--space-1); FloatingContent reads --floating-gap via a reactive $derived (was a one-shot rAF that never reached portaled menus -> they fell back to sideOffset, flush); nav-menu wired to the token via CSS. - focus-ring: exclude item/option/content from the universal archetype ring; menu rows use the canonical highlight on :focus-visible too (no thick keyboard border, no ring framing the whole float). - overscroll-behavior: contain on the ScrollArea viewport + 7 overlay scroll regions (no scroll chaining to the page). - archetypes.css: default rules wrapped in :where() so component recipes always win. - radius decoupled from size (Radix model) in toggle/textarea/menubar/nav-menu/ virtual-list/grid; toggle `rounded` prop. Canon-read + token reach portaled menus pending a fresh-load check (the dev tab cached the floating module) -- see CONTINUE-floating-gap-canon.md.active-uix
parent
5974be2675
commit
61ea97b5bb
@ -0,0 +1,80 @@
|
|||||||
|
# Continuation — Floating-gap canon + menu focus-ring (2026-06-28)
|
||||||
|
|
||||||
|
Where we left off. Spun out of the eidos inherit/radius audit; converged on
|
||||||
|
canonizing the trigger→panel gap of menus and killing the keyboard focus-ring
|
||||||
|
that framed the whole menu panel.
|
||||||
|
|
||||||
|
## ✅ DONE + VERIFIED LIVE (CSS — HMR is reliable)
|
||||||
|
|
||||||
|
1. **Archetype `:where()` systemic fix** (`src/uix/eidos/archetypes.css`) — every
|
||||||
|
DEFAULT archetype rule wrapped in `:where()` (specificity 0,0,0) so any component
|
||||||
|
recipe (0,1,0) wins regardless of load order. Kept at full specificity ON PURPOSE:
|
||||||
|
the `:has([data-archetype='field-trigger'])` padding override + the canonical item
|
||||||
|
highlight.
|
||||||
|
2. **Overscroll containment** — `overscroll-behavior: contain` added to the shared
|
||||||
|
ScrollArea viewport (`scroll-area.css`) + 7 overlay scroll regions (context-menu,
|
||||||
|
dialog, drawer, dropdown-menu, float-panel, popover, tooltip). Fixes scroll chaining
|
||||||
|
to the `<body>`. Verified on select + dropdown.
|
||||||
|
3. **Focus-ring fix** (`archetypes.css`) — the universal `[data-archetype]:focus-visible`
|
||||||
|
box-shadow ring now EXCLUDES `item`, `option`, AND `content`:
|
||||||
|
- Menu/listbox ROWS keyboard-focus via roving tabindex → drew the thick ring on
|
||||||
|
keyboard but only a subtle bg on hover (inconsistent). Now rows use the canonical
|
||||||
|
highlight for BOTH `:hover` AND `:focus-visible`.
|
||||||
|
- Menu/popover/dialog PANELS (`data-archetype='content'`) take focus for key capture
|
||||||
|
→ drew the ring around the WHOLE float (the thick purple frame the user hated). Now
|
||||||
|
excluded; the panel's own elevation (border + shadow) is the boundary.
|
||||||
|
- **Verified live**: loaded rule tail = `…:not([item]):not([option]):not([content])`;
|
||||||
|
highlight selector includes `:focus-visible`.
|
||||||
|
|
||||||
|
## ⏳ DONE but NEEDS A FRESH-LOAD CHECK (JS — the tab cached the old module)
|
||||||
|
|
||||||
|
4. **`--floating-gap-menu` token**: `0px → var(--space-1)` (4px) in `render-css.ts` +
|
||||||
|
regenerated `generated/base.css`. Menus get a small gap now, not flush.
|
||||||
|
5. **nav-menu wired to the canon** (`navigation-menu.css`) — both orientations now
|
||||||
|
`calc(100% + var(--floating-gap, 0px))` (was hardcoded `--space-1`). nav-menu is
|
||||||
|
CSS-positioned (not portaled) so it consumes the canon through CSS. **Verified 4px
|
||||||
|
early, before the cache trouble — reliable.**
|
||||||
|
6. **Canon-read FIX** (`src/uix/soma/layers/floating/floating.svelte.ts`) — THE fix:
|
||||||
|
- `FloatingContent.canonicalGapPx` was a `$state` filled by a one-shot rAF
|
||||||
|
(`requestFrame`) inside a `watch(contentRef)`. For PORTALED menus that rAF callback
|
||||||
|
**never fired** (instrumented: watch passes the guard, frame scheduled, never run,
|
||||||
|
not cancelled — `requestFrame`/`resolveWindow` at `arts/adom/active-dom.svelte.ts:481`
|
||||||
|
suspected). So `canonicalGapPx` stayed `null` → menus fell back to `sideOffset`
|
||||||
|
(flush). The token change alone never reached them.
|
||||||
|
- **Fix**: `canonicalGapPx` is now a `$derived` reading `--floating-gap` off
|
||||||
|
`contentRef.current` directly (reactive, no rAF). The rAF keeps only the zIndex read.
|
||||||
|
`--floating-gap` is a style value (no layout) so reading on mount is safe.
|
||||||
|
- **Type-clean** (svelte-check passed) but **NOT visually verified** — see CACHE below.
|
||||||
|
|
||||||
|
## ⚠️ CACHE GOTCHA — READ FIRST (it ate most of the session)
|
||||||
|
|
||||||
|
- Editing a deeply-imported `.svelte.ts` (e.g. `floating.svelte.ts`) does NOT reliably
|
||||||
|
HMR, AND the connected tab serves that module from a STRONG cache. `navigate` and a
|
||||||
|
`?nocache=` query do NOT revalidate the module (it's fetched by path).
|
||||||
|
- **Tell-tale**: a `gap: 12` from a hardcode I'd reverted long ago persisted through
|
||||||
|
edits + a full dev-server restart → the tab was running a frozen old module.
|
||||||
|
- **Detect stale**: temporarily set `mainAxis: 12` in the offset middleware → if the gap
|
||||||
|
isn't 12, the code is fresh.
|
||||||
|
- **Only reliable bust**: browser HARD reload (Ctrl+Shift+R). Not `navigate`.
|
||||||
|
- CSS (`archetypes.css`, recipes) DOES hot-update fine — only the `.svelte.ts` module is
|
||||||
|
the problem.
|
||||||
|
|
||||||
|
## ▶️ FIRST ACTIONS TOMORROW
|
||||||
|
|
||||||
|
1. **Hard-reload** (Ctrl+Shift+R) the menubar/dropdown page.
|
||||||
|
2. Confirm the canon-read fix: **menubar** (passes no `sideOffset`) should now show a
|
||||||
|
~4px gap (no longer pegado). If still flush → re-instrument; you're guaranteed fresh.
|
||||||
|
3. **Clean the menu demos** so they follow the canon (now that the read works). These
|
||||||
|
pass explicit `sideOffset` that BYPASSES the canon:
|
||||||
|
- `web/routes/uix/components/dropdown-menu/+page.svelte` → `sideOffset = $state(4)`
|
||||||
|
- `web/routes/uix/components/context-menu/+page.svelte` → `sideOffset = $state(0)`
|
||||||
|
- `web/routes/uix/components/select/+page.svelte` → hardcoded `<Select.Content sideOffset={6}>`
|
||||||
|
Default each to `undefined` so the token retunes all. Slider pattern to avoid the
|
||||||
|
`number|undefined` bind error: `value={sideOffset ?? 0} oninput={(e) => (sideOffset = e.currentTarget.valueAsNumber)}` + label `{sideOffset === undefined ? 'auto' : sideOffset + 'px'}`.
|
||||||
|
4. If the canon-read fix DIDN'T take on fresh load, isolate why `requestFrame`'s rAF
|
||||||
|
never fires for portaled menu content — but the `$derived` should sidestep it entirely.
|
||||||
|
|
||||||
|
## Dev server
|
||||||
|
|
||||||
|
Restarted fresh on 5199: `npm run dev -- --port 5199 --strictPort` (the bare `dev`
|
||||||
|
script uses the default port, so pass the flags).
|
||||||
Loading…
Reference in new issue