astra
alpha-0.1-background
alpha-0.1-dir-prefs
alpha-0.1-sec-dom
menubar-v4-safe
active-uix
morfo-runtime
morfo-driven-soma
semantuix
glm-5
main
sium-v1.0
${ noResults }
231 Commits (7d31cd3fd7e574e836c788690112f4ed1d850f7f)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
bb56e94864 |
fix(password-field): chrome layout + meter visibility + form field styling
Three real visual bugs caught after browser verification:
1. **No visible border around input + meter row.** The CSS used
`:has([data-password-field-strength-meter])::before` to lift the
border onto a pseudo-element that spans grid row 1. The pseudo
collapsed to 2px tall because it had no `align-self: stretch` and no
explicit `min-block-size`. Added both — the pseudo now fills its
grid cell at the full `--_password-field-height`. Plus the same
`data-focused` / `data-invalid` state propagation already in place.
2. **Strength meter segments invisible.** Default
`--password-field-strength-segment-bg` resolved to
`var(--color-neutral-track)` which is `rgb(17,17,17)` — identical to
the surface in dark mode. Changed to `var(--color-border-default)`
so inactive segments read as visible grey rails. Also bumped segment
height from 4 → 6 px so the bar is clearly readable.
3. **Eye icon too small inside a thin trigger.** `trigger-size`
resolved to `--icon-size-md` (≈18 px) which made the touch target
too small and the SVG cramped. Promoted trigger-size to use
`--control-height-{xs..md}` so it matches the input row height per
size. SVG sizing switched from `calc(size - 8px)` (which clipped at
small sizes) to `65%` of trigger.
4. **Stage wrapper collapsed to content width.** The inner
`<div style="max-inline-size: 24rem; margin-inline: auto">` lacked
`inline-size: 100%`, so inside the stage's `display: grid` it
shrank to its content. Added the explicit `inline-size: 100%`.
5. **Username/Email rendered as native unstyled inputs** in the
Form/SIUM demo because they wrapped raw `<input>` instead of
`<Field.Input>` (Field.Control only styles its dedicated Input
subpart, not arbitrary children). Swapped to `<Field.Input>`.
6. **SIUM error refs (`#?sium.errors.min_length|...`) rendered
literal.** `Field.ErrorText` just renders the string it receives.
Added a local `issueText(form, field)` helper that calls
`uix.langs.ts(raw)` to resolve the langref and then interpolates
`{{param}}` from `form.issues[field][0].params`. Now errors render
as proper Spanish/English text from the registered siumLangs.
Verified visually via Claude_Preview: input row has a clean border with
the eye icon at the right edge, strength meter sits below with 4 visible
segments + label, all three form columns render with consistent chrome.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
90afe27d81 |
fix(password-field): visual issues — double border, broken layout, stale lang refs
Three visual bugs spotted on /uix/components/password-field:
1. **Double-bordered inputs.** The demo wrapped `<PasswordField>` inside
`<Field.Control>`, which adds its own border + padding. PasswordField
IS the field's input shell — it ships its own chrome. Removed the
five offending Field.Control wrappers from login/registration/change
forms; PasswordField now sits directly inside `<Field>`.
2. **Icon wrapping to a new row + StrengthMeter sharing the bordered
box.** Replaced `display: inline-flex; flex-wrap: wrap` on the root
with `display: grid; grid-template-columns: minmax(0, 1fr) auto auto`
so Input + CapsLockIndicator + VisibilityTrigger stay on the first
row. When StrengthMeter is composed, `:has()` switches the root to a
2-row grid where the bordered chrome lifts onto a `::before`
pseudo-element that only spans the first row, and the meter sits
below as a sibling. State chrome (`data-focused`, `data-invalid`)
propagates to the pseudo via the same `:has()` selectors.
3. **CapsLock pill claiming space even when inactive.** Was
`color: transparent; background: transparent` which still consumed
width in the grid. Now `display: none` by default and
`display: inline-flex` only when `[data-active]` is set.
Also fixed: `#?sium.errors.min_length|…` and friends rendered literal
in the demo because the docs shell didn't register `$sium/langs/langs`.
Registered `siumLangs` under the `sium` namespace in the layout's
`createActiveUix({ langs: { schema: { sium: siumLangs } } })`.
svelte-check: 0 errors. Page now loads at 200.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
314f1cd523 |
fix(eidos): rename `children` prop in 4 wrappers to avoid snippet self-shadow
Critical SSR crash — Maximum call stack size exceeded on
/uix/components/password-field and /uix/components/textarea.
Root cause: DEMO_AUTHORING_GUIDE §12.4 pitfall.
When a wrapper does:
let { children, ...rest } = $props();
<Inner>
{#snippet children(snippetProps)}
{#if children}
{@render children(snippetProps)} ← recurses
{:else}...{/if}
{/snippet}
</Inner>
The inner `{#snippet children}` SHADOWS the `children` prop. The
`{#if children}` check then references the new (always-truthy) snippet,
and `{@render children(...)}` recursively renders itself → infinite
loop, RangeError at SSR.
Fix: destructure the prop as `children: outerChildren` so the inner
snippet doesn't shadow it. Same pattern used in drawer wrappers per
the guide.
Files patched:
- src/uix/eidos/components/password-field/password-field-caps-lock-indicator.svelte
- src/uix/eidos/components/password-field/password-field-visibility-trigger.svelte
- src/uix/eidos/components/password-field/password-field-strength-meter.svelte
- src/uix/eidos/components/textarea/textarea-count.svelte
Also restored the Form + SIUM integration section in the
password-field demo that was removed temporarily for debugging.
Verified:
- /uix/components/password-field → 200
- /uix/components/textarea → 200
- npx svelte-check → 0 errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
8d277fe929 |
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens (`--color-neutral-border`, `--ring-width`) instead of consuming the recipe layer like every other text input. Now matches search-field and number-field exactly: - Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts` with the same surface as `search-field` (height, px, gap, font-size per size; border, focus, invalid, bg, color, focus-shadow tokens). - Regenerated `src/uix/eidos/generated/base.css`. - Rewrote `password-field.css` to consume the new tokens — same focus ring (`focus-shadow`), same border colors, same disabled opacity, same transition tokens. VisibilityTrigger gets its own border/bg/ color-on-hover scheme aligned with search-field's clear button. - Added per-size icon + caps font-size tokens so the trigger and caps pill scale with the input. Form + SIUM demo — answers "how does PasswordField validate?": Added a new subsection in the Live tab with three working forms wired through `<Form>` + `<Field>` and SIUM schemas: 1. Login (purpose='current', non-empty password) 2. Registration (purpose='new', complexity rules via regex pipes — uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter as a companion to the schema) 3. Password change (3 fields with a cross-field `refine()` so new === confirm; demonstrates SIUM's refine pattern) The section explicitly documents that PasswordField has no built-in complexity rules — validation is schema-first. The schema decides what's "correct"; PasswordField's flags propagate via Field's OR-merge of `formFieldState.isInvalid`. svelte-check: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
da3e5026bb |
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
fead097ce1 |
fix(demos): rewrite Skeleton + Spinner demos to canonical 6-tab depth
The previous demos violated DEMO_AUTHORING_GUIDE in several places. Both have been rewritten end-to-end against the aspect-ratio canary template (canonical reference for 0-events eidos primitives). Changes per demo: - Stage always rendered between header and tablist (was inside Live tab). - MutationObserver wired with stageRef + 6-entry trace strip (was absent — required even for 0-event components per §6). - Trace strip layout: 'no semantic events' badge + shape/variant + size + color, matches aspect-ratio shape. - Live tab adds the soma + eidos code snippets with full data-uix-code wrappers and head badges (§7 + §8). - somaSnippet now shows the equivalent semantic markup with a clear "not real soma" note (since these are eidos-native). - API tab uses data-uix-table with explicit "Skeleton-specific props" + "Inherited from Box" + "Reference comparison" subsections (§4). - Morfo tab matches §10: header field/value table + Parts table with marker column + per-part data/aria subsection tables. - Sema tab keeps the explicit 0-events explanation (was absent for one of the demos). - Recipe tab uses the Selectors table with morfo/eidos owner tags (§11) instead of a single code snippet. - A11y tab uses data-uix-table (concern/contract) instead of a bullet list, matching aspect-ratio. Reference template: web/routes/uix/components/aspect-ratio/+page.svelte. Per §1, when in doubt, copy from the canary template and adapt. svelte-check: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
616d8535f7 |
feat(eidos): add Skeleton and Spinner loader primitives
Two pure-eidos loader components with morfos at scope `['eidos']` (no soma backing, no semantic events). Skeleton — content placeholder: - 3 shapes: rect (default), circle, text (multi-line with staggered widths) - 5 sizes (xs..xl), 8 color accents - Optional shimmer animation via CSS `background-position` keyframes - Single `<div role="status" aria-busy="true">` with translated aria-label - Reduced-motion freezes animation, keeps visual Spinner — indeterminate progress: - 3 variants: ring (rotating arc), dots (bouncing), bars (stretching) - 5 sizes, 8 colors - Optional inline label via children snippet - `role="status"` + `aria-live="polite"` for SR announcements - Reduced-motion freezes animation, keeps visual Files: - morfo: skeleton.ts, spinner.ts (scope eidos, no events) - eidos: full component + types + css + langs + index per component - langs: skeleton/spinner under componentLangs barrel - demos: canonical 6-tab depth + composition examples (avatar+text card) - layout: sidebar additions under Status group Note: also includes responsive shell additions to web/routes/uix/uix.css that surfaced from an external session — same file, leaving them in. Reference libraries reviewed (Mantine, Chakra v3, Radix Themes, MUI): all expose the same shape/variant/size/color surface. shadcn Skeleton uses the same pulse animation. No surprises in our API. svelte-check: 0 errors in skeleton/spinner files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f8897d92d6 |
fix: clear remaining `npm run check` errors (12 → 0)
Three groups of errors blocking the global typecheck:
A. Announce morfo used invalid family/intent vocabulary
- `absent-clear` event used `family: 'absent'` and `intent: 'absent'`,
neither valid in current MorfoEventSemantic schema
- Renamed to `commit-reset` (family: commit, verb: reset, intent: neutral)
matching SearchField's clear-reset pattern
- Updated demo copy to reference the new event name
- Cascade-resolved 4 errors (morfo + soma provider + demo + active-uix test)
B. Tree wrappers stripped bindable props
- eidos `<TreeGrid>` didn't expose `expanded`/`value` as `$bindable`;
they leaked through `...rest` and Svelte rejected `bind:expanded`
- Same issue for `<TreeView>` with `expandedValue`/`selectedValue`
- Now declared explicitly + forwarded via `bind:` to soma's Provider
C. Virtual containers passed wide `style` to native markup
- `style` was typed `string | Record<string, unknown> | null | undefined`
but the markup `<div style={...}>` only accepts string
- Filtered with `typeof style === 'string'` derived guard before render
svelte-check result: 5076 files, 0 errors, 23 warnings (no-op warnings
in navigation-menu `href="#"` and table `state_referenced_locally`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
940e94bb64 |
Build TextArea component + fix locale propagation bug across soma providers
Major work in this session:
1. NumberField demo refactored to expose Intl.NumberFormatOptions live
- 9 canonical presets (currency/unit/percent/compact/plain)
- Locale/currency/unit lists derived from $libs/langs, $libs/currency,
$format/units (no more hardcoded arrays)
- Default locale from uix.format.numbers.getLocale()
2. Bug fix: locale prop ignored when soma.format service is active
- soma/NumberField: 5 bypass points via new `hasOwnLocale` getter
(decimalSeparator, groupSeparator, formatValue, formatEditValue, parseValue)
- soma/Calendar: dateOrder respects locale prop (matches DateField precedence)
- soma/RangeCalendar: same dateOrder fix
- When the consumer passes `locale` prop, soma.nums/dates is bypassed
and Intl is used directly with the prop locale
3. TextArea — new component, full 4-layer stack
- morfo: 3 parts (provider/input/count), 3 events
(commit-input/commit-submit/shift-count-overflow), APG textbox
- soma: TextAreaProvider with autosize (scrollHeight measurement),
submitOn modes (false|enter|shift+enter|mod+enter), maxLength soft clamp,
Field integration (OR-merge), counter overflow tracking
- eidos: 5 sizes × 3 variants × 8 colors, autosize suppresses CSS resize
- sema: commit-submit + shift-count-overflow perceptual cues
- demo: canonical 6-tab depth, all props exposed as live controls
- PrimitiveTextareaAttributes added to soma/types/html.ts
4. Also includes accumulated unreleased work from prior sessions:
- Eidos ports: announce, clipboard, drag-drop, feed, table,
tree-grid, tree-view, virtual-grid, virtual-list, words
- Their canonical demos under web/routes/uix/components/
- Semantic events defined for utility morfos
(announce/clipboard/virtual-list/virtual-grid)
- Tree indent guides fixed (TreeView ::before, TreeGrid stacked gradients)
- Sidebar reorganized with Data + Utilities groups
- Eidos words content layer and primitives
Reference library survey performed before TextArea API design (Mantine,
Radix Themes, Chakra v3, React Aria, Ark UI, Bits UI, shadcn, MUI, Prime).
Decision documented: no separate CurrencyField/UnitField — single
NumberField with formatOptions is the canon (matches all surveyed
headless libs except PrimeReact's mode enum, which still uses 1 component).
svelte-check: 0 errors in modified/created files.
See CONTINUE.md for full session notes and next steps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
3fc4ced37b |
feat(carousel): showIndicators + showControls visibility props
Two new eidos-level booleans on `<Carousel>` so consumers can toggle the indicator dots and the prev/next triggers without touching the markup composition. Defaults stay `true` (existing behaviour). The consumer still renders the parts in markup; the recipe just hides them via `display: none` when the eidos root stamps `data-hide-indicators` / `data-hide-controls`. Toggling on/off doesn't unmount the DOM, so ARIA focus is preserved across the change and the layout doesn't reflow into a different size. Naming follows the canonical eidos cascade — `showIndicators` mirrors calendar's `showWeekNumbers`. Mantine uses `withIndicators` (default false); we default `true` because the Eidos recipe assumes indicators are part of the default surface. Demo: two switches added to the eidos visual-props section + snippet preview emits the prop when set to false. Verified: clicking the demo switch flips `data-hide-indicators` on the root and the indicator-group's computed `display` goes from default to `none` without remounting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
92c5e421c9 |
fix(carousel): :active press feedback was replacing centering transform
Bug behind the user's "el botón se desplaza en el eje contrario al
orientation" report. The Button recipe sets
[data-button]:active { transform: translateY(1px); }
on press to simulate a physical button push. Carousel prev/next triggers
absolute-position via:
[data-carousel-prev-trigger] { transform: translateY(-50%); } (horiz)
[data-carousel][data-orientation='vertical']
[data-carousel-prev-trigger] { transform: translateX(-50%); } (vert)
CSS `transform` is a single property — Button's :active value
REPLACES the carousel's centering transform, so on press the trigger
jumped off its anchor: in horizontal it lost vertical centering, in
vertical it lost horizontal centering AND moved 1px down (wrong axis
relative to slide travel).
Override `:active` on the carousel triggers to compose both transforms
on the appropriate axis:
horizontal: translateY(calc(-50% + 1px))
vertical: translateX(calc(-50% + 1px))
Now press feedback shifts the trigger 1px along the centering axis
without breaking the absolute anchor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
09bbf05ec0 |
fix(carousel demo): default loop=true so triggers never go dead at boundaries
The "los clicks siguen fallando" report was tested against loop=false default behavior — at idx=3 (last slide) the next button correctly went disabled (canGoNext=false), opacity 0.4, no advance — but testers read that as a broken click handler rather than a boundary stop. Default the demo to loop=true so each click predictably advances (0→1→2→3→0...). Verified 6 successive clicks wrap correctly. Component default stays loop=false (matches Embla / Mantine / ark-ui); the demo just picks the more illustrative interactive baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f197463968 |
refactor(carousel demo): move Prev/Next triggers outside Viewport
Canonical placement (matches Mantine, shadcn-svelte): triggers sit as siblings of Viewport (children of the Carousel root), not as siblings of ItemGroup inside Viewport. Keeps the gesture-bearing ItemGroup region clean of unrelated event targets; the recipe's absolute positioning still anchors the triggers correctly because the carousel root is `position: relative`. The "clicks fail" report was the expected disabled state at the last slide under loop=false — verified the next button correctly reports disabled=true + opacity 0.4 once canGoNext becomes false. Enable loop from the controls for infinite navigation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
a6baae0cf1 |
fix(carousel/range-calendar/time-range-field): apply canonical i18n + vertical layout patterns
## Vertical carousel layout
Recipe was making the indicator group stretch full width (380px) because
the root stayed in `flex-direction: column` and the indicator-group
inherited `align-items: stretch`. Reshaped:
- `[data-carousel][data-orientation='vertical']` now flips the root to
`flex-direction: row` so viewport and indicator-group sit side-by-side.
- Viewport takes `flex: 1 1 auto` of the inline axis with explicit
100% block-size.
- Indicator-group becomes `flex: 0 0 auto; inline-size: max-content;
align-self: center` — narrow column of dots on the right edge.
Active vertical-stretched indicator already had `block-size: 2.25×`
treatment from the prior commit, so it now appears as a vertical pill
inside the dot column.
## i18n controls: follow canonical pattern
Mirrored the date-range-picker / calendar demos verbatim — those are
the canon and the new demos drifted from them. Three corrections:
1. **Locale**: `<select>` with `en-US / es-ES / fr-FR` (the docs
canon — chip rows and made-up locale lists like ar/he/ja were both
inventions). Carousel exposes locale too; it's forwarded to soma
for downstream lang-aware behavior (announcer text, future label
formatting hooks).
2. **Direction**: chip row with `ltr / rtl` (no `auto`). Label is
"direction" not "dir".
3. **State shape**: plain `let dir = $state<Direction>('ltr')`, not
the synthetic `dirCtl + derived` pattern I had invented.
Applied across all three demos so they read identically to the canon.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
23ad9351ea |
feat(carousel): wire dir + locale (was hardcoded 'ltr'); expose i18n in demos
Audit caught Carousel ignored direction entirely: `getDirectionalKeys` received a hardcoded `'ltr'` so ArrowLeft / ArrowRight had the same semantics under RTL as LTR, and the horizontal translate had no RTL flip so slides advanced the wrong way visually. Drag direction had the same bug. ## Soma - New `dir` + `locale` props on `Carousel.Provider` (fall back to `soma.prefs.getDir()` then `'ltr'`). - `onkeydown` now reads `this.opts.dir.current` for `getDirectionalKeys`. - `itemGroupTransform` flips the translate sign on horizontal RTL so index N→N+1 moves the rail in the reading-direction-appropriate way. - `finishDrag` swaps the offset sign on horizontal RTL so drag-right advances under RTL (drag-left under LTR). - Root stamps `dir="..."`, `lang="..."`, and `data-dir="..."` for consumers and the recipe. - Test mocks updated. ## Demos - Carousel demo: new `soma · i18n` controls section — `dir` chips (`auto` / `ltr` / `rtl`) + `locale` chips covering en/es/ar/he/ja so the user can flip orientation semantics live. - Range-calendar demo: same `dir` + `locale` chip rows added next to `weekStartsOn`; previously only `locale` was state-bound but the control wasn't surfaced and `dir` wasn't forwarded at all. - The eidos code snippet preview emits `dir="..."` and `locale="..."` attrs when they differ from defaults. ## Verified Browser walk: with `dir="rtl"`, the carousel root reports `data-dir="rtl"`, prev/next chevrons swap sides, indicator order mirrors, and `ArrowRight` correctly advances to the *previous* slide (index 1 → 0). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f744583c12 |
fix(range-calendar demo): add explicit none/preset chips to minDays + maxDays
A bare `<input type="number" placeholder="—">` made it non-obvious how to clear the constraint — users would type and not realise empty=undefined. Mirroring the date-range-picker demo's bounds row, both minDays and maxDays now expose a chip row with `none` as the first option plus common presets (`2/3/7/14` for min, `7/14/30/90` for max). The custom number input stays alongside for ad-hoc values. The label header shows the current effective value (`none` when undefined). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f291c08865 |
fix(carousel/range-calendar/time-range-field): visual bugs across all three
Real visual audit caught three classes of bugs that the prior commits
missed because they were never opened in the browser.
## Carousel
### Vertical orientation overflow
Recipe gave the root no block-size in vertical, so items spilled. Plus
the soma `viewportSize` derivation only ran on ref / orientation-prop
changes, not on layout reshape — so flipping horizontal → vertical
kept reading the old clientWidth and the translate maths used the wrong
slide size (observed -214 instead of -156 after a flip).
Fix:
- Recipe: `[data-carousel][data-orientation='vertical']` gets explicit
`block-size`, and the viewport / item-group / item each get `100%`
along the block axis. Default vertical size lives on
`--_carousel-vertical-block-size` (16rem), overridable via the new
eidos `verticalBlockSize` prop or the custom property directly.
- Soma: replaced the synchronous `clientWidth/Height` reads with
`viewportClientWidth/Height` `$state` mirrors populated by a
ResizeObserver on the viewport. Layout-only changes (orientation
flip, container resize, font scaling) now propagate to translate
calc on the next reactive tick.
## RangeCalendar
### Multi-month stacked vertically
`numberOfMonths > 1` rendered grids stacked in a column because the
eidos `[data-range-calendar]` recipe is `inline-flex; column`. Copied
the side-by-side grid layout from the existing date-range-picker demo:
`.range-months { display: grid; grid-auto-flow: column; }` with
`grid-auto-flow: row` collapse for `data-month-count='1'`.
### Min/max controls were nonsense
The prior demo had a custom "today | custom" mode that conflated
fixtures with real today, and required two separate enum states.
Rewrote the controls to mirror the date-range-picker demo exactly:
two `<input type="date">` bindings on string state, parsed by
`parseDateInput`, plus a preset chip row (none / min only / max only /
window / invalid).
### Weekday key duplicate
The narrow-format weekday strings repeat letters ('T' for Tuesday and
Thursday, 'S' for Saturday and Sunday), so keying by `wd` raised
`each_key_duplicate`. Changed to key by index.
## TimeRangeField
### Start endpoint rendered no segments
Keying the segments `#each` by `seg.part` collapsed when segment
parts repeat. Copied the canonical key shape from the time-range-picker
demo: `(${part}-${i})`. Verified both endpoints now render their
four segments (`09:30` — `17:45` for the default fixture).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
44514c3cc1 |
fix(carousel): vertical overflow + close gaps vs embla/ark-ui/mantine
Real reference audit walked the published embla `.d.ts`, Mantine docs, ark-ui, shadcn-svelte and Swiper. Found three classes of gaps in the initial port (a) the vertical bug breaking the only non-default orientation, (b) layout primitives every other lib ships (slidesPerView, slidesToScroll, align, gap), (c) autoplay granularity (playOnInit, stopOnInteraction, stopOnFocusIn, stopOnMouseEnter, stopOnLastSnap), plus an imperative API every consumer needs. ## Vertical bug fix Recipe now gives the root a default `block-size` in vertical orientation (`16rem`, overridable via `--_carousel-vertical-block-size` or the new eidos `verticalBlockSize` prop) and stretches the viewport to fill it. Without this the viewport collapses to content height and items spill. ## Soma extensions - `defaultValue` (uncontrolled init) - `slidesPerView` (1+) — each Item basis = (100% − gap·(n−1)) / n - `slidesToScroll` (1+) — paged group navigation - `align: 'start' | 'center' | 'end'` — viewport alignment - `gap: string` (CSS length) — between-slide spacing on the ItemGroup - `playOnInit` — start autoplay on mount (default true) - `stopOnInteraction` — latch-stop after user clicks prev/next/indicator or drags - `stopOnFocusIn` — pause while focus is inside (default true) - `stopOnMouseEnter` — pause while cursor over root (Embla parity) - `stopOnLastSnap` — latch-stop at last snap when loop=false - Snippet exposes `isPlaying` + an `api` handle with `scrollNext / scrollPrev / scrollTo / play / pause / reset` `commit()` now respects `slidesPerView` (last snap = count − spv). `next()` / `prev()` step by `slidesToScroll`. Translate calc derives from `slideSize = viewportSize / slidesPerView` so multi-view works. Item flex-basis adapts to slidesPerView + gap. ## Eidos additions - `verticalBlockSize` prop — forwarded as CSS custom property for the vertical recipe override - Recipe selectors for vertical orientation enforcing the block-size + viewport stretch - Style composition so consumers can still pass inline `style` ## Demo Live tab now has 4 organized sections (layout / drag / autoplay / imperative API) covering every new prop. The imperative API row drives the carousel from external buttons via the snippet handle so consumers see the pattern in action. `verticalBlockSize` slider exercises the vertical fix. Snippet preview emits every set prop. ## Reference comparison Following the canon's mandate to compare with references BEFORE declaring done, the actual feature matrix from embla published `.d.ts`, Mantine docs, ark-ui, shadcn-svelte, Swiper: | Feature | embla | mantine | ark | swiper | UIX | |------------------------|-------|---------|-----|--------|-----| | Index nav | ✓ | ✓ | ✓ | ✓ | ✓ | | Loop | ✓ | ✓ | ✓ | ✓ | ✓ | | Drag | ✓ | ✓ | ✓ | ✓ | ✓ | | Autoplay | plug | plug | ✓ | ✓ | ✓ | | Orientation H/V | ✓ | ✓ | ✓ | ✓ | ✓ | | slidesPerView | indir | ✓ | ✓ | ✓ | ✓ | | slidesToScroll | ✓ | ✓ | ✓ | ✓ | ✓ | | Align | ✓ | indir | per | ✓ | ✓ | | Gap | css | ✓ | ✓ | ✓ | ✓ | | defaultValue | startIndex | ✓ | ✓ | ✓ | ✓ | | playOnInit | ✓ | indir | ✓ | ✓ | ✓ | | stopOnInteraction | ✓ | ✓ | ✓ | ✓ | ✓ | | stopOnFocusIn | ✓ | ✓ | ✓ | partl | ✓ | | stopOnMouseEnter | ✓ | ✓ | ✓ | ✓ | ✓ | | stopOnLastSnap | ✓ | ✓ | ✗ | ✓ | ✓ | | Imperative API | ✓ | ✓ | ✓ | ✓ | ✓ | | Indicators / triggers | DIY | ✓ | ✓ | ✓ | ✓ | | Fade effect | plug | plug | ✗ | ✓ | ✗ deferred | | Virtual slides | ✗ | ✗ | ✗ | ✓ | ✗ deferred | | Multirow grid | ✗ | ✗ | ✗ | ✓ | ✗ deferred | Deferred power-user features (fade, virtual, multirow grid, cube/cards, parallax) are documented as power-user in the reference audit, not table-stakes, and would require Embla-class internals to ship. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
2cecc0b3b7 |
feat(eidos): port range-calendar from soma + extract shared partial
Standalone eidos `<RangeCalendar>` wrapper over the soma range-calendar primitive. Adds size / variant / color cascades; otherwise pass-through 14 compound parts (Provider, Header, Heading, PrevButton, NextButton, MonthSelect, YearSelect, Grid, GridHead, GridBody, GridRow, HeadCell, Cell, Day). ## Feature comparison | Feature | bits-ui | ark-ui | react-aria | Eidos | |----------------------|---------|--------|------------|-------| | Range start/end | ✓ | ✓ | ✓ | ✓ | | Hover preview state | ✓ | ✓ | ✓ | ✓ | | minDays / maxDays | — | — | ✓ | ✓ | | allowSingleDay | ✓ | ✓ | ✓ | ✓ | | preventDeselect | ✓ | ✓ | — | ✓ | | Multi-month display | ✓ | ✓ | ✓ | ✓ | | Week-number column | — | — | ✓ | ✓ | | Month/Year selects | — | ✓ | — | ✓ | | Disabled / readonly | ✓ | ✓ | ✓ | ✓ | | isDateUnavailable | ✓ | ✓ | ✓ | ✓ | | Holiday markers | — | — | — | ✓ | | Locale / dir | ✓ | ✓ | ✓ | ✓ | | Size cascade | — | — | — | ✓ 4 | | Variant cascade | — | — | — | ✓ 3 | | Color cascade | — | — | — | ✓ 8 | ## Range-cells partial extracted The recipe at `src/uix/eidos/components/range-calendar/range-calendar.css` now owns ALL `[data-range-calendar-*]` selectors — the shared rules that previously lived inside `date-range-picker.css` (lines 537-834) moved here. Both consumers (the standalone `<RangeCalendar>` and `<DateRangePicker>`'s popover content) stamp the same data-attrs, so the same selectors paint without duplication. `date-range-picker.css` drops 298 lines of duplication; it keeps only the picker-specific scope overrides (`[data-date-range-picker-calendar]` with no chrome, max-content sizing, transparent bg). ## Morfo Extended `rangeCalendarMorfo.scope` to include `'eidos'` so the new wrapper appears in the eidos-layer inventory and the demo Morfo tab declares it correctly. ## Demo Canonical 6-tab depth (~600L) at `/uix/components/range-calendar`. Live tab exposes every range-specific prop: minDays / maxDays sliders, allowSingleDay, preventDeselect, numberOfMonths chips (1 / 2 / 3), pagedNavigation, fixedWeeks, showWeekNumbers, disableDaysOutsideMonth, weekStartsOn, disabled, readonly, locale, plus size / variant / color cascades. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
68670b6331 |
feat(eidos): port carousel from soma
Eidos wrapper for the soma Carousel primitive — horizontally or vertically scrollable rail of slides with keyboard, button, indicator, drag and autoplay nav. Soma owns the index + autoplay timer + gesture; eidos adds size/variant/color cascades and renders prev/next triggers as `<Button iconOnly variant="ghost" rounded="full">` per the canonical Button consumer pattern. ## Feature comparison | Feature | embla | Mantine | ark-ui | Eidos | |----------------------|-------|---------|--------|-------| | Index nav | ✓ | ✓ | ✓ | ✓ | | Loop | ✓ | ✓ | ✓ | ✓ | | Drag / scrub | ✓ | — | ✓ | ✓ | | Autoplay | plugin| ✓ | ✓ | ✓ | | Pause on hover | plugin| ✓ | ✓ | ✓ | | Orientation V/H | ✓ | ✓ | ✓ | ✓ | | Keyboard nav | ✓ | ✓ | ✓ | ✓ | | Indicators | — | ✓ | ✓ | ✓ | | Prev/Next triggers | — | ✓ | ✓ | ✓ | | Size cascade | — | — | — | ✓ 5 | | Variant cascade | — | — | — | ✓ 3 | | Color cascade | — | — | — | ✓ 8 | | Multi-slides view | ✓ | ✓ | — | deferred | | Lazy-mount slides | plugin| — | — | deferred | ## Parts (8) Provider · Viewport · ItemGroup · Item · PrevTrigger · NextTrigger · IndicatorGroup · Indicator. PrevTrigger and NextTrigger render Button via the soma `child` snippet (canonical Button consumer pattern). Default chrome: `iconOnly variant='ghost' rounded='full'` + chevron glyph. Size cascades from Carousel root; consumers can override every Button visual prop inline. Passing `child` bypasses Button entirely. ## Recipe Card chrome around the rail (`variant='surface'`), absolute-positioned triggers over the viewport edges (vertical flips to top/bottom). Active indicator stretches inline-size 2.25× for clearer current-position hint. 8 ColorRole accents. ## Demo Canonical 6-tab depth (~470L): Live / API / Morfo / Sema / Recipe / A11y. Live controls cover all soma props (orientation, loop, dragEnabled, autoplay + delay slider, pauseOnHover, disabled) and all eidos props (variant, size, color). 4 colored placeholder slides for visual contrast. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
db21767db1 |
feat(eidos): port time-range-field from soma
Eidos wrapper for time range selection — start/end TimeField inputs sharing a single ZonedDateTime range value. Mirrors DateRangeField pattern with `data-time-field` overlay trick so item segments inherit the time-field recipe. ## Feature comparison | Feature | react-aria | Mantine | Eidos | |----------------------|------------|---------|-------| | Range value | ✓ | ✓ | ✓ | | Per-endpoint Input | ✓ | ✓ | ✓ | | Segment editing | ✓ | — | ✓ | | Granularity prop | ✓ | ✓ | ✓ | | Hour cycle (12/24) | ✓ | ✓ | ✓ | | Min/max value | ✓ | ✓ | ✓ | | Placeholder per-end | ✓ | — | ✓ | | Invalid signalling | ✓ | ✓ | ✓ | | Disabled / readOnly | ✓ | ✓ | ✓ | | Form integration | ✓ | ✓ | ✓ | | Size cascade | — | ✓ | ✓ xs–xl | | Variant cascade | — | ✓ | ✓ 3 | | Color cascade | — | — | ✓ 6 | ## Parts Root · Label · Input (type='start' | 'end') · Segment (re-exported from TimeField). Each Input scopes its own TimeField context so segment editing routes to the correct endpoint. ## Recipe Grid `1fr · auto · 1fr` with em-dash separator. Specificity bump on `[data-time-range-field][data-time-field]` to win over the overlay data-time-field rules. ## Demo Canonical 6-tab depth (~530L): Live / API / Morfo / Sema / Recipe / A11y. Variant / size / color / granularity / hour-cycle / min-max / disabled / readOnly / required controls. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
a010f8f255 |
fix(eidos navigation-menu): style sub-items inside Content
Bug: anchors inside Content panels had no hover/padding/border treatment — consumers writing typical mega-menu markup (<a> elements) got a wall of unstyled text. Adds a default sub-item recipe that targets \`[data-navigation-menu-content] :is(a, [data-navigation-menu-link])\` — covers both plain anchors AND the official \`NavigationMenu.Link\` part when used inside Content. Each item gets: - display: block - padding 8px / 12px - border-radius (matches md radius) - color = primary content - text-decoration: none, cursor: pointer - hover/focus background = --color-primary-element - aria-current="page" + data-active = same tinted active state Demo cleanup: dropped the redundant inline \`style="color: inherit; text-decoration: none;"\` overrides on every <a>, tightened the grid gap from space-3 to space-1 (rows now have their own background hover so gap can shrink). Content panel padding reduced to space-2 (was space-4) because sub-items already provide their own padding. Verified in browser: items render with padding 8/12px, border-radius 6px, text-decoration: none, cursor: pointer. Hover changes the background to primary-element. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
065cc6a369 |
feat(eidos): port GridList (selectable list w/ interactive row children)
Closes #133. Eidos wrapper + types + recipe + canonical-depth demo. 4 parts: Provider, Row, Cell, SelectionCheckbox. Soma owns selection (none/single/multiple), 2D keyboard nav (Up/Down rows + Left/Right cells), Shift/Ctrl range/toggle, Ctrl+A select-all, Escape-clear, typeahead, hidden form input, Field merge. Eidos adds variant + size + color + defaultValue (uncontrolled init). Reference parity (no air baseline): | Feature | soma | react-aria GridList | this port | |---|---|---|---| | selectionMode none/single/multiple | ✓ | ✓ | ✓ | | bind:value (controlled) | ✓ | ✓ | ✓ | | defaultValue (uncontrolled init) | ✗ | ✓ | ✓ added (parity with Combobox/Select) | | Item with interactive descendants | ✓ | ✓ | ✓ | | 2D keyboard nav (Up/Down rows, L/R cells) | ✓ | ✓ | ✓ | | Shift/Ctrl range/toggle | ✓ | ✓ | ✓ | | Ctrl+A select-all (multiple) | ✓ | ✓ | ✓ | | Escape-clear | ✓ | ✓ | ✓ | | Typeahead | ✓ | ✓ | ✓ | | ARIA grid/row/gridcell | ✓ | ✓ | ✓ | | Field merge (disabled/invalid/required/readonly) | ✓ | ✗ | ✓ | | Form participation (hidden input) | ✓ | ✗ | ✓ | | Drag-to-reorder | ✗ | ✓ | ⚠ deferred (compose w/ future DragDrop) | | onAction (row click → navigate) | ✗ | ✓ | ⚠ deferred (use button/link inside Cell) | | selectionBehavior toggle/replace | ✗ implicit | ✓ | ⚠ deferred | | disabledBehavior 'selection'/'all' | ✗ skip-only | ✓ | ⚠ deferred | | Async / infinite loading | ✗ | ✓ | ⚠ deferred | | Empty state slot | ✗ | ✓ | ⚠ document `data-empty` styling | Naming: keeps soma's part names (Provider, Row, Cell, SelectionCheckbox). react-aria's "GridListItem" maps to `Row`; soma's vocabulary is more precise and consistent with future Table / TreeGrid in the same family. Variant set: - variant: ControlVariant (surface/outline/ghost), default 'surface' - size: xs..xl (5-step, mirrors Combobox/Listbox) - color: ColorRole (primary/secondary/neutral/affirm/fulfill/risk/threat/loss) - block: stretch to container width Disambiguation (in index.ts comment): use GridList for rows with interactive children; use Listbox for atomic text options (ARIA forbids interactive descendants inside role="listbox"); use Select/Combobox for closed-picker patterns; use Table for tabular data with columns + sort. Visual reuse: - SelectionCheckbox uses the same `mask-image: url(svg-check)` pattern as the shared menu-indicator partial (commit |
5 months ago |
|
|
52627e60bb |
feat(eidos): port NavigationMenu (top-level site navigation w/ mega-menus)
#132. Eidos wrapper + types + recipe + canonical-depth demo. 7 parts: Provider, List, Item, Trigger, Content, Link, Indicator. Soma owns active-item state, hover delays (openDelay/skipDelayDuration/closeDelay), hoverEnabled gate, loop arrow nav, data-motion directional swap, Indicator rect CSS vars. Eidos paints the bar + trigger chrome + Content panel + slide animations. Reference parity (no air baseline — port directly from soma): | Feature | soma | Radix | bits-ui | this port | |---|---|---|---|---| | Provider + List + Item + Trigger + Content + Link + Indicator | ✓ | ✓ | ✓ | ✓ | | Hover open (openDelay/skipDelayDuration/closeDelay) | ✓ | ✓ | ✓ | ✓ | | hoverEnabled (click-only mode) | ✓ | ⚠ | ✓ | ✓ | | orientation horizontal + vertical | ✓ | ✓ | ⚠ | ✓ | | dir / RTL | ✓ | ✓ | ✓ | ✓ | | Indicator CSS vars (x/y/w/h rect) | ✓ | ✗ | ✗ | ✓ | | data-motion directional swap (from-/to-start/end) | ✓ | ✓ | ⚠ | ✓ | | forceMount on Content + Indicator | ✓ | ✓ | ✓ | ✓ | | ARIA <nav> + translated aria-label | ✓ | ⚠ | ⚠ | ✓ | | Keyboard nav (Arrow/Home/End/Enter/Space/Esc, ArrowDown → Content) | ✓ | ✓ | ✓ | ✓ | | Sub (nested viewports) | ✗ | ✓ | ✓ | ⚠ deferred (matches soma) | | Viewport (shared swap pane) | ✗ | ✓ | ✓ | ⚠ deferred | | Modal variant (focus trap) | ✗ | ✓ | ✗ | ⚠ deferred | | SafePolygon hover bridge | ✗ | ✗ | ✗ | ⚠ deferred | Naming: matches soma's part names exactly. Both Trigger (button) and Link (anchor) stay native — NOT routed through <Button> — because top-nav has flat/borderless chrome distinct from action buttons. The Link automatically picks up `aria-current="page"` styling for the active route. Variant set: only `size: 'sm' | 'md' | 'lg'` (no xs/xl). No variant/color — NavigationMenu is chrome surface, not actionable intent. Fresh recipe — does NOT reuse dropdown-menu (floating click-driven) or menubar (roving tabindex bar). NavigationMenu lives inline in the page; per-item Content panels render via Floating but conceptually attach to the bar. Indicator is a 2px underline that slides via CSS vars (--navigation-menu-indicator-{x,y,w,h}) written by soma. Trigger chevron rotates on [data-state='open']. Content animates via data-motion directional slide on sibling swap. Sidebar entry added under Navigation (between Pagination and a future NavigationMenu entry). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
80b21f63ba |
feat(eidos): port Listbox (embedded selection list)
#131. Eidos wrapper + types + recipe + canonical-depth demo. Soma owns single/multi selection (value as string[]), roving-tabindex keyboard nav (Arrow + Home/End + typeahead + Ctrl+A), Field-merged flags (disabled/readonly/required/invalid), hidden form inputs. Eidos adds the visual surface and exposes variant + size + color. Reference parity (no air baseline — port directly from soma): | Feature | soma | react-aria | Headless UI | bits-ui | this port | |---|---|---|---|---|---| | Single + multiple selection | ✓ | ✓ | ✓ | ✓ | ✓ | | bind:value (controlled) | ✓ | ✓ | ✓ | ✓ | ✓ | | Typeahead + Arrow / Home / End | ✓ | ✓ | ✓ | ✓ | ✓ | | Loop nav | ✓ | ✗ | ✗ | ✗ | ✓ | | Section / Group + label | ✓ | ✓ | ✗ | ✓ | ✓ | | Ctrl+A select-all (multi) | ✓ | ✓ | ✗ | ✗ | ✓ | | Form participation (hidden inputs) | ✓ | ✗ | ✓ | ✗ | ✓ | | Field.Provider merge (disabled/invalid/…) | ✓ | ✗ | ✗ | ✗ | ✓ | | Horizontal orientation | ✓ | ✓ | ✓ | ✓ | ✓ | | Item indicator (✓ glyph) | ✓ | ✓ | ✗ | ✓ | ✓ shared partial | | Drag-to-reorder | ✗ | ✓ | ✗ | ✗ | ⚠ deferred | | Async / infinite loading | ✗ | ✓ | ✗ | ✗ | ⚠ deferred | | selectionBehavior (toggle/replace) | ⚠ implicit | ✓ | ✗ | ✓ | ⚠ deferred | | PageUp/Down nav | ✗ | ✓ | ✗ | ✗ | ⚠ deferred | Parts (5): Listbox (Root, replaces soma's Provider name), Item, ItemIndicator, Group, GroupLabel. Variant set: - variant: ControlVariant (`'surface' | 'outline' | 'ghost'`), default 'surface' - size: xs..xl (5-step canonical form-control scale) - color: ColorRole (primary/secondary/neutral/affirm/fulfill/risk/threat/loss) - block: stretch to container width Cross-component reuse: - `ItemIndicator` shares the menu-indicator visual (`a2709476`) — ✓ glyph identical to standalone Checkbox and Dropdown/Context Menu CheckboxItems. CSS-only difference: shown only inside `[data-listbox-item][data-selected]`. - Item row hover / highlight tokens mirror Combobox.Item / Command.Item — the form-control surface family stays coherent. Disambiguation note (in index.ts comment): - Listbox: atomic options, text rows, no inner interactive elements (ARIA forbids interactive descendants inside role="listbox") - GridList: rows with buttons / links / checkboxes inside (2D nav) - Select / Combobox: closed picker surface (Listbox-like dropdown) Gotcha: listbox.svelte and listbox-item.svelte rename `children` to `bodyContent` on destructure to avoid shadowing the inner `{#snippet children}` block (same recursion trap documented in `feedback_button_consumer_pattern.md` Rule 3). Demo: 6 tabs at canonical depth — Live wires all soma + eidos props, all 3 ControlVariant chips, 5 size chips, 8 ColorRole chips, single + multi mode toggle, RTL/horizontal toggle. Sidebar entry added under Forms (between Combobox and Calendar). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
d58b684c5c |
docs(uix sidebar): list LinkPreview, Menubar, Command under Overlays
The 3 newly-ported components (commits |
5 months ago |
|
|
8ab8f9dae1 |
feat(eidos): port Command (⌘K palette)
Closes #130. Eidos wrapper + 13 part wrappers + 2 new sub-parts + Dialog composition + recipe + canonical-depth demo. Soma owns scoring (`computeFuzzyScore`), keyboard nav (Arrow + vim Ctrl+N/P/J/K + typeahead + loop + grid columns + RTL), item visibility, and the imperative `CommandApi`. Eidos adds size + Item.Icon / Item.Shortcut slots + Dialog composition with optional shortcut binding. Reference parity: | Feature | cmdk | Mantine Spotlight | bits-ui Command | this port | |---|---|---|---|---| | Root provider w/ bind:value, bind:search | ✓ | ✓ | ✓ | ✓ | | Input, List, Viewport, Empty | ✓ | ✓ | ✓ | ✓ | | Group + GroupHeading + GroupItems | ✓ | ✓ | ✓ | ✓ | | Item value, keywords, disabled, onSelect | ✓ | ✓ | ✓ | ✓ | | LinkItem (anchor) | ✗ | ✗ | ✓ | ✓ | | Separator, Loading (progress 0-100) | ✓ | partial | ✓ | ✓ | | Custom filter / shouldFilter | ✓ | ✓ | ✓ | ✓ | | Loop, vim bindings, grid columns | ✓ | partial | ✓ | ✓ | | disablePointerSelection | ✓ | ✗ | ✓ | ✓ | | Modal Dialog composition | ✓ | ✓ (Root is modal) | ✓ | ✓ via eidos Dialog | | Built-in ⌘K shortcut binding | ✗ (BYO) | ✓ | ✗ (BYO) | ✓ optional | | Item.Icon / Item.Shortcut slots | BYO | leftSection/rightSection | BYO | ✓ NEW | Naming: `Command.Root` aliased as the default export (cmdk/bits-ui parity, eidos convention). Inner parts as named sub-properties: `Command.Input`, `Command.List`, `Command.Item`, `Command.Item.Icon`, `Command.Item.Shortcut`, `Command.Dialog`, … Variant set: only `size: 'sm' | 'md' | 'lg'` (drops xs/xl — palette UX needs comfortable hit targets, matches air baseline). Shortcut binding: cross-platform `mod` token (Cmd on macOS, Ctrl elsewhere). Parses `'mod+k'`, `'ctrl+shift+p'`, etc. Window keydown listener registered on mount, cleaned up on unmount. Pass `null`/ undefined to disable. Recipe: `command.css` paints palette container, input row, scrolling List/Viewport, item rows (with highlighted [data-selected] state), icon + shortcut slot layout, group heading, separator, empty state. Loading progress is a 2px bar under Input with `--_command-loading- progress` for the fill. Inside Command.Dialog the panel chrome comes from dialog.css. Demo: 6 tabs (Live/API/Morfo/Sema/Recipe/A11y) — Live wires every soma + eidos prop + Dialog mode with shortcut input. Two stages: inline palette + dialog opener with shortcut hint. Three code snippets (soma, eidos inline, eidos dialog). Gotcha note: command.svelte renames the children prop to `bodyContent` on destructure to avoid shadowing the inner `{#snippet children}` — same infinite-recursion trap noted in `feedback_button_consumer_pattern.md`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
9def877dbd |
feat(eidos): port Menubar (desktop File/Edit/View bar)
#129. Eidos wrapper + types + recipe + canonical-depth demo. Soma owns active-menu state, cross-menu ArrowLeft/Right navigation, hover-switch when active, focus return on Escape, typeahead, RTL. Eidos paints the bar surface and trigger states; per-menu Content reuses the dropdown-menu recipe (Item / Sub / Checkbox / Radio chrome + the shared menu-indicator partial from `a2709476`) because soma already re-exports the DropdownMenu parts for those slots. Reference parity: | Feature | Radix | bits-ui | ark-ui | this port | |---|---|---|---|---| | Root provider w/ active-menu binding | ✓ | ✓ | ✗ (Menu only) | ✓ | | Menu / Trigger / Content | ✓ | ✓ | ✓ | ✓ | | Item / Group / GroupHeading / Separator | ✓ | ✓ | ✓ | ✓ | | CheckboxItem / CheckboxGroup / RadioGroup / RadioItem | ✓ | ✓ | ✓ | ✓ | | Sub / SubTrigger / SubContent | ✓ | ✓ | ✓ | ✓ | | Cross-menu ArrowLeft/Right (with loop) | ✓ | ✓ | n/a | ✓ | | Auto-open on hover when active | ✓ | ✓ | n/a | ✓ | | Typeahead | ✓ | ✓ | ✓ | ✓ | | Arrow part | ✓ | ✓ | ✓ | ⚠ dropped — menubar idiom anchors to bar edge | Parts shipped (15): Provider, Menu, Trigger, Portal, Content, Item, Group, GroupHeading, Separator, Arrow (re-exported), CheckboxItem, CheckboxGroup, RadioGroup, RadioItem, Sub, SubTrigger, SubContent. The inner 12 (Item onwards) are re-exports of the eidos DropdownMenu wrappers so the menu-indicator partial applies automatically — no recipe duplication. Variant set: only `size: 'sm' | 'md' | 'lg'` (xs would crowd triggers, xl unnecessary for a chrome bar). Size cascades from Menubar root to per-menu Content via a Svelte context (`menubar-context.ts`, `getMenubarSizeContext` / `setMenubarSizeContext`). Cross-layer: eidos Menubar reuses eidos DropdownMenu internals — the recipe doesn't redefine Item/Sub/Checkbox/Radio. Menubar.Content stamps `data-size` AND inherits `data-dropdown-menu-content` (soma's MenubarContent specializes DropdownMenuContent), so dropdown-menu.css paints the panel chrome and our menubar.css only declares the bar surface + trigger states. Workaround note: menubar-content.svelte casts rest props through `Record<string, unknown>` to bypass a TS "union too complex" depth limit triggered by soma's inline-augmented MenuContentProps + HTML attribute union. Runtime types are correct. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
0afcabee8b |
feat(eidos): port LinkPreview (Radix HoverCard equivalent)
#128. Eidos wrapper + types + recipe + canonical-depth demo. Trigger renders as `<a>` (navigational anchor); on hover/focus after `openDelay` (default 700ms) the Content panel reveals with SafePolygon bridging trigger and content so the pointer can travel without dismissing. Non-modal, no-op on touch (link follows directly). Reference parity: | Feature | Radix HoverCard | bits-ui LinkPreview | ark-ui HoverCard | this port | |---|---|---|---|---| | open with bind: | ✓ | ✓ | ✓ | ✓ | | openDelay (700ms) / closeDelay (300ms) | ✓ | ✓ | ✓ | ✓ | | disabled | ✗ | ✓ | ✓ | ✓ | | Trigger as `<a>` | ✓ | ✓ | ✗ (`<button>`) | ✓ | | Portal + Arrow | ✓ | ✓ | ✓ | ✓ | | Floating layer (side/align/offsets/collision) | ✓ | ✓ | ✓ | ✓ | | customAnchor | ✗ | ✓ | ✗ | ✓ | | ContentStatic | ✗ | ✓ | ✗ | ⚠ deferred | | lazyMount | ✗ | ✗ | ✓ | ⚠ deferred | Naming choice: `LinkPreview` (matches air + bits-ui), not `HoverCard`. Trigger semantic is navigational anchor — for a button-triggered hover-card use `<Popover>` with `openDelay` or pass `child` snippet to bypass the default `<a>`. Variant set: only `size: 'xs'..'xl'` (no ControlVariant — LinkPreview is a surface, not actionable). Defer color/intent per the canon rule that surfaces are uncolored. Parts shipped: Provider, Trigger, Portal, Content, Arrow. Recipe: `link-preview.css` with anchor underline (subtle, intensifies on hover/open), content panel chrome (border + surface bg + shadow), arrow fill matching panel bg, slide+fade entrance per `data-side`, reduced-motion skip. Demo: 6 tabs (Live/API/Morfo/Sema/Recipe/A11y) at canonical depth per DEMO_AUTHORING_GUIDE — Live wires every Provider+Content prop with the right chip set, eidos+soma snippets generated, all 6 ARIA contract rows + keyboard + pointer tables in A11y tab. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
32598d7cf3 |
fix(alert-dialog): narrow Action.variant to canonical SelectionVariant (3)
Stops the chip-count drift introduced in \`c98510af\`. AlertDialog.Action
was exposing all 6 ButtonVariant chips, breaking the 3-chip rhythm the
rest of UIX follows. Per DEMO_AUTHORING_GUIDE §12.7 every chip-group
must enumerate the full union the type declares — so the fix is to
narrow the *type*, not truncate the chips.
\`AlertDialogActionVariant = Extract<ButtonVariant, SelectionVariant>\`
imports the canonical set from \`eidos/lib/types.ts\` (`solid | outline |
ghost`), which is the right shape for a primary action button:
- keeps 'solid' as the CTA default
- 3-chip parity with Cancel (ControlVariant), DropdownMenu.Trigger
(ControlVariant), and the rest of the form-control surface
- no invented vocab — uses the existing canon set named for exactly
this use case ("selection chips / toggles" semantically maps to
"selection / action buttons")
Demo array drops from 6 to 3 chips; API row updated to reflect the
narrowing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
c98510aff7 |
demo(alert-dialog): expose variant/size chips for Action and Cancel
Surfaces the Button visual props that AlertDialog.Action and
AlertDialog.Cancel now forward (post `c6f063dc`) so the demo isn't
lying about the component's API:
- Action: 6 chips (full ButtonVariant — primary CTA), 5 size chips.
Intent is NOT exposed as a separate chip because it inherits from
the dialog's own `intent` prop (already a Live control above).
- Cancel: 3 chips (ControlVariant — narrowed per
DEMO_AUTHORING_GUIDE §12.7), 5 size chips. Always neutral palette
regardless of dialog intent.
Live preview wires both bindings into the rendered Action/Cancel; API
tables list the new props with type, default, and the snippet shape
(`Snippet<[{ props; intent }]>` for Action's `child`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
4b5bd9f35d |
refactor(dropdown-menu): remove unused modal; Trigger→Button; canon vocab fixes
Three related fixes touching the menu surface: 1. **Remove `modal` from DropdownMenu** (#127). The prop was declared in soma but never wired to anything — no overlay, no pointer-events blocker, no scrim. ark-ui / Headless-UI / Chakra all treat menu as non-modal by convention; Dialog/Drawer/AlertDialog cover blocking semantics. Drop it from soma (types, provider opts, component, test fixture), the eidos doc comment, the menubar consumer, and the dropdown-menu demo (state, control, snippet, API table, parity table). 2. **DropdownMenu.Trigger now renders `<Button>` internally** (#120 first consumer). Default variant='surface', size='md', intent='neutral'. `variant` is narrowed to ControlVariant (`'surface' | 'outline' | 'ghost'`) per DEMO_AUTHORING_GUIDE §12.7 so the trigger stays consistent with the form-control surface (Field/Select/Combobox) instead of inheriting Button's wider 6-variant set. The eidos demo exposes variant/size/intent chips that match the canonical narrowed vocab (Intent: neutral · affirm · fulfill · risk · threat · loss). Trigger's per-recipe CSS dropped — chrome comes from button.css. 3. **Menu-item unchecked indicators** (follow-up to #118). CheckboxItem and RadioItem now paint an empty outlined box / circle even when unchecked, so the user can tell at a glance which rows are checkable. Checked state fills with the accent color via the same `::before`. The reserved padding slot (26px) keeps row text from shifting when state flips. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
a804c39208 |
feat(eidos): Button + 9 demo rebuilds + picker recipe polish
Build: - New <Button> compound (morfo + soma + sema + eidos): 6 variants (solid/soft/surface/outline/ghost/plain), 8 colors, 5 sizes, optional icon/endIcon snippets, loading state with built-in Spinner, asChild Slot pattern, intent-driven sema (commit-action with fromProp:intent). Demo rebuilds (canonical depth per DEMO_AUTHORING_GUIDE): - context-menu, dropdown-menu, pin-input, alert-dialog, toggle-group: full per-part Morfo contracts, reference-parity tables, ARIA contract tables, keyboard tables, composition controls. - color-field, time-field, time-picker, time-range-picker, color-picker, month-grid, year-grid: same canonical treatment; per-part data/aria/ keyboard iteration of raw morfo, reference parity vs react-aria/ ark-ui/Chakra/bits-ui/Mantine. Picker recipe polish: - PickerShell.Clear/Cancel/Close refactored to use <Button size="xs"> (affects 5 pickers — date / date-range / time / time-range / color). - TimePicker + TimeRangePicker: popover condensed (min-width 18rem -> 14 / 32 -> 28), per-channel accent (hour=primary, minute=secondary, second=neutral; end variant muted), ruler off by default, hour ruler cleaned to 0/6/12/18/23. - ColorPicker: typography token --_color-picker-trigger-font-size that scales with data-size (10/12/14px); autoFormat default true; Area recipe override for [data-x-channel='hue'] with rainbow gradient + saturation/brightness Y overlays. - ValueText eidos wrapper: fix children snippet bug that suppressed soma's displayValue fallback. - date-field.css: segment editing affordance (cursor + hover + active ring) via new --_date-field-accent-element token across 8 color cascades. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
ec7cdd60b4 |
fix(date-range-field): canonical demo + recipe specificity + DateField overlay
User audit (2026-05-23) caught two real defects in this component:
1. Demo was a 386-line skeleton — switches used the made-up
`data-control-kind="switch"` attribute, props like granularity /
locale / hideTimeZone / per-endpoint readonlySegments were not
wired, API / Morfo / Recipe / A11y tabs were one-sentence stubs.
Canonical reference demos are 700–1200 lines.
2. Recipe rule `[data-date-range-field-input][data-active]
[data-date-field]` matched nothing — soma overlays
`data-date-field-input` on the SAME element as
`data-date-range-field-input` (no nested `[data-date-field]`).
Fixes:
- Eidos `<DateRangeField>` root now overlays
`data-date-field=""` on top of `data-date-range-field`. Lets the
full `date-field.css` cascade (chrome, size, variant, color)
apply inside the range with zero recipe duplication. Documented
in the wrapper comment.
- `date-range-field.css` simplified to layout-only:
[data-date-range-field][data-date-field] (specificity bump
beats date-field's base `display: inline-flex` rule)
grid: 1fr · auto · 1fr × 2 rows
label spans the full first row
separator via `::before` in the middle grid cell
endpoints placed in columns 1 and 3 via `data-endpoint='start' / 'end'`
No recipe duplication; date-field paints input chrome.
- Demo rebuilt to canonical depth (1008 lines, +622):
Live tab: 6 layered subsections (value shape / locale + dir /
flags / per-endpoint readonly segments / eidos chrome /
actions), 11 switches in canonical `<span data-uix-switch>`
style with state-text labels, 9 chip groups (value profile,
kind, granularity, hourCycle, hideTimeZone, locale × 6
locales, dir, size × 5, variant × 3, color × 6), reset /
clear actions, reactive soma + eidos snippets reflecting
every control.
API tab: full prop table per part (Root with 21 props, Label,
Input, Segment), reference parity table vs Bits UI / Ark UI
/ react-aria / Chakra v3.
Morfo tab: header table + parts overview + per-part data /
aria / keyboard tables (driven by raw morfo, cast to a
narrowed shape) + events table.
Sema tab: passive justification + delegation table showing
per-endpoint DateField runtimes.
Recipe tab: selector classification (morfo / eidos) + chrome
attribution.
A11y tab: per-segment keyboard + ARIA contract tables.
- Demo `{#each segments}` keyed by `(i)` (was already fixed, kept).
Lesson registered in
`C:\Users\dev\.claude\projects\G--dev-svelte-vicen\memory\feedback_demos_must_be_canonical_depth.md`
and referenced from `MEMORY.md`: ALWAYS read DEMO_AUTHORING_GUIDE.md
+ a reference demo (date-field 716L, drawer 737L, date-range-picker
1238L) BEFORE writing. Compare against reference libs first.
Verified visually at /uix/components/date-range-field — DOM has
both endpoints side-by-side, em-dash separator in middle column,
date-field chrome applied to each input, canonical control style
across all 6 subsections.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
18edecba5b |
fix(date-range-field demo): key each segments by index, not seg.part
Soma's segments array uses `'literal'` as the part for every visual
separator (e.g. the `/` between dd / mm / yyyy). Keying the {#each}
by `seg.part` produced duplicate keys → `each_key_duplicate` runtime
error at the second `literal` separator. Switch to positional key
`(i)` — segments don't reorder, so positional is stable and correct.
Snippet template strings updated to match so the visible code in
the Live tab reflects the right pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
754754d4a4 |
feat(date-range-field): port from soma to eidos (Tier 2 sprint, 1/3)
Two-endpoint date entry. Mirrors DateField × 2 with shared validation and a single label naming the whole range. Each Input scopes its own DateField context internally, so segments inside re-use the existing date-field recipe automatically. Eidos surface: - variant / color / size — same vocabulary as <DateField>, cascaded via data-* on the root to both endpoint inputs and their segments - Re-uses DateField.Segment (zero duplication) Recipe (date-range-field.css): - Label + control row stack - Em-dash separator rendered via `::before` on the end Input (no manual `<span>—</span>` needed) - Endpoint-aware tint via `data-active` on the holding input Morfo: - scope: ['soma'] → ['soma', 'sema', 'eidos'] - apg URL: Spinbutton (per segment) - Passive at this contract layer (events come from the two endpoint DateField runtimes); README documents the classification Sidebar nav: 'Date range field' added under Forms (next to Date field). Tier 2 sprint progress: 1/3 (date-range-field next: time-range-field, range-calendar). Checks: svelte-check 0 errors, component:audit 91/91 PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
207774714a |
feat(pin-input): port from soma to eidos (Tier 1 sprint, 5/5 — DONE)
Multi-cell OTP / verification code input. Two parts (Provider, Cell) with a snippet-driven cell iteration. Architecture: - A transparent absolutely-positioned <input> captures every keystroke, paste and IME event (soma). - Visible bordered cells follow the hidden input's caret via data-active / data-filled emitted by the morfo. - Single-input approach (shadcn / Bits family) — preserves paste distribution, autocomplete='one-time-code' for SMS autofill on iOS/Android, and IME quality. Eidos surface: - size: 'sm' | 'md' | 'lg' | 'xl' (responsive), cascades cell dimension + glyph size tokens to all cells - Recipe paints square bordered cells with focus ring tracking data-active, subtle bg lift on data-filled, threat-toned border when [data-invalid] cascades from the provider Morfo: - scope: ['soma'] → ['soma', 'sema', 'eidos'] - apg URL added (Textbox pattern — OTP has no formal ARIA pattern) - Passive at the morfo layer (no events array; perceptual feedback via the commit family base when onComplete fires) - README documents the passive classification Comparativa: aligned with shadcn / Bits UI (single hidden input). Ark and Chakra use one input per cell which loses paste / IME quality. Sidebar nav: 'Pin input' added under Forms (next to Tags input). === Sprint Tier 1 — DONE === 1. toggle-group |
5 months ago |
|
|
21eb8a6264 |
feat(context-menu): port from soma to eidos (Tier 1 sprint, 4/5)
14-part compound — DropdownMenu surface minus CheckboxGroup. Same visual recipe vocabulary; the only differences vs DropdownMenu are the trigger surface (a `<span>` wrapping right-clickable content rather than a `<button>` toggle) and cursor-anchored positioning (soma's floating layer handles that). Parts: Provider · Trigger · Content · Arrow · Item · Group · GroupHeading · Separator · CheckboxItem · RadioGroup · RadioItem · Sub · SubTrigger · SubContent. Recipe (context-menu.css): - Panel chrome + item rows identical to dropdown-menu - Trigger gets a subtle `data-state='open'` dashed outline as a "this region summoned the menu" affordance - Recipe duplication over aliasing — each component evolves freely without CSS cascade coupling Sidebar nav: 'Context menu' added under Overlays. Morfo scope: ['soma'] → ['soma', 'sema', 'eidos']. Sprint progress: 4/5 (toggle-group, alert-dialog, dropdown-menu, context-menu). Last is pin-input. Checks: svelte-check 0 errors, component:audit 91/91 PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f19b5c7270 |
docs(uix): add Toggle group · Alert dialog · Dropdown menu to sidebar nav
Wires the three Tier 1 sprint components into the IA: - 'Toggle group' under Controls (next to Toggle) - 'Alert dialog' under Overlays (next to Dialog) - 'Dropdown menu' under Overlays (next to Popover) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
4e477e637b |
feat(dropdown-menu): port from soma to eidos (Tier 1 sprint, 3/5)
Full 15-part compound — pass-through wrappers over soma with the canonical visual recipe. Parts: Provider · Trigger · Content · Arrow · Item · Group · GroupHeading · Separator · CheckboxItem · CheckboxGroup · RadioGroup · RadioItem · Sub · SubTrigger · SubContent. API parity with Bits UI (richest reference): CheckboxGroup + RadioGroup binding arrays / scalars, nested Sub-menus, Arrow pointer. Radix Primitives + Ark UI lack CheckboxGroup; Chakra v3 + Radix Themes lack Arrow. Recipe (dropdown-menu.css): - Uniform chrome across Content + SubContent (panel shadow, border, padding, max-height) - Items share one set of tokens (hover/focus highlight, disabled opacity, indicator slot via `::before` for Checkbox/Radio data-state='checked') - SubTrigger gets a chevron via `::after` - GroupHeading: muted uppercase, micro-tracked - Separator: 1px subtle divider - Enter animation on `data-state='open'` No `variant` / `color` / `size` knobs at the eidos layer — menus are themselves the visual variant of "list of actions" within an app. Per-item destructive coloring deferred to next pass (cascade via item's `data-color` attr). Morfo scope: ['soma'] → ['soma', 'sema', 'eidos']. Sound comes from the family bases (`emerge` for open/close, `commit` for select) — no per-component cascade needed. Demo: 6-tab template, items hooked to `lastSelected` tracker, all three groupings demoed (Items / CheckboxGroup / RadioGroup / Sub). Checks: svelte-check 0 errors, component:audit 91/91 PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
d5630a528f |
feat(alert-dialog): port from soma to eidos (Tier 1 sprint, 2/5)
Modal Dialog variant with role=alertdialog, forced modal=true, and click-outside disabled. Soma delegates to the Dialog runtime (variant='alertdialog') so DOM markers are Dialog's (data-dialog-*) — eidos reuses Dialog's recipe for chrome and adds two alert-dialog-specific buttons: Action + Cancel. Eidos shape: - Provider, Content, Action, Cancel — alert-dialog-specific wrappers - Trigger, Overlay, Title, Description, Header, Footer, Portal — re-exported from Dialog's eidos barrel (zero duplication) Recipe (alert-dialog.css): - Only styles Action + Cancel - Buttons consume Toggle palette tokens (same "interactive button" vocabulary used by <Toggle> and <ToggleGroup.Item>) - Auto-color cascade: Action picks up the dialog's data-color so a risk/threat-flagged dialog gets a red/amber Action button without manual recipe code - Cancel stays neutral so the safe-choice contrast is preserved Morfo: - scope: ['soma'] → ['soma', 'sema', 'eidos'] - The morfo declares Action + Cancel parts only (Provider is virtual, other parts inherited via Dialog runtime delegation) - Classified passive at this contract level (Dialog owns the events); README documents this in §"Passive justification" Demo: 6-tab canonical template with intent / size / position chips, soma + eidos snippets, references comparison table vs Radix Primitives / Bits UI / Ark UI / Chakra v3 / Radix Themes / shadcn. Checks: svelte-check 0 errors, component:audit 91/91 PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
61ffbade46 |
feat(toggle-group): port from soma to eidos (Tier 1 sprint, 1/5)
First component in the soma→eidos porting sprint. Two-part compound (Provider + Item), modeled on radio-group's structure. Eidos surface: - variant: 'solid' | 'outline' | 'ghost' — mirrors <Toggle> - color: 7 tones, propagated to items via data-color cascade - size: xs/sm/md/lg/xl (responsive) - attached: boolean — segmented control affordance (shared borders, active item z-indexed above neighbors) - block: boolean — items grow to fill the inline-size - iconOnly: per-item flag — square aspect, sr-only label Items consume the Toggle palette token vocabulary (--toggle-*) so a standalone <Toggle> and a <ToggleGroup.Item> at the same variant × color × size are visually identical — single source of truth for the "toggle button" recipe. Morfo scope fix: ['soma'] → ['soma', 'sema', 'eidos']. Adds apg URL (WAI-ARIA Button pattern) so the audit's A-1.4 rule passes. The commit-toggle event's sound comes from the `commit` family base — no per-component cascade needed. Demo: canonical 6-tab template (Live / API / Morfo / Sema / Recipe / A11y) with every prop exposed as a chip/switch, soma + eidos snippets update live, sema "▶ play" button on the commit-toggle event. Reference comparison vs Radix Primitives / Bits UI / Ark UI / Chakra v3 / Mantine / Radix Themes / shadcn documented in README §Comparativa. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
4324cb489d |
fix(splitter): register sema scope + cascade so handle-pick/drag actually sound
Two missing registrations meant the splitter sema cascade from the
previous commit never reached the engine — handle-pick and handle-drag
were silent, only commit-set sounded (because the `commit` family has
a sound payload baked into its SEMA_MAP base, while `handle` only has
haptic and depends on the cascade for sound).
Fixes:
1. morfo scope: ['soma'] → ['soma', 'sema', 'eidos']. Without 'sema'
the per-component cascade rules in src/uix/sema/components/splitter.ts
are never honored.
2. web/routes/uix/+layout@.svelte: add splitterSema to the
`components: [...]` array passed to defineEngineSemantic. The
sema engine only loads cascade packs it's explicitly given, so
forgetting this line silently drops all splitter cascade rules.
Verified in browser at /uix/components/splitter via Live tab (real
pointer interaction, AudioContext.createOscillator counter):
handle-pick → 2 oscillators (handle.pickup.air, gain 0.10)
handle-drag → 16 oscillators across 8 throttled emits — the
dynamic resolveSplitterDragSound curve is playing
per-frame as expected
commit-set → 2 oscillators (handle.snap.chime, gain 0.14)
Note: the Sema tab's static "▶ play" button still shows 0 oscillators
for handle-drag — same as Slider's demo. By design: that button calls
engine.emit() directly without the per-emit `signal.overrides` that
soma injects during real drag. The dynamic curve IS the payload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
b504c1c4b7 |
feat(eidos foundation): vertebrate typography via named-style aliases + R-2.7
Single source of truth for typography values that the recipe layer
consumes. The foundation aliases `--font-ui` and `--leading-ui` (read
by ~30 recipe tokens in `lib/recipes/base.ts`) now derive from the
canonical `label` named style instead of carrying duplicate literals:
--style-label-font-family: var(--font-family-primary);
--style-label-line-height: 1.25;
--font-ui: var(--style-label-font-family, var(--font-family-primary));
--leading-ui: var(--style-label-line-height, 1.25);
Chain: typography.ts styles → --style-{name}-* → --leading-ui / --font-ui
→ recipe tokens → component CSS. Editing
`STATIC_TYPOGRAPHY.styles.label.lineHeight` now propagates to every
recipe in one go.
Why not push recipes to consume `--style-{name}-*` directly:
- t-shirt sizes (xs/sm/md/lg/xl) don't map to four semantic buckets
- per-component matices (description/caption/hint) need their own
color / weight / letter-spacing
- ref libraries (Radix Themes, Mantine, MUI, Chakra) all keep
numerical scale for component internals; semantic layer is only for
user-facing typography primitives (`<Text variant="body2">`)
Audit rule R-2.7 (warn): detects literal font-size / font-weight /
line-height / letter-spacing in eidos component CSS. Escape valves:
var(...), numeric identities (0/0px/1), keywords (inherit/initial/
unset), or trailing `/* literal: <reason> */` comment. Current run
flags 6 components with letter-spacing/font-size literals (all
intentional micro-tracking and em-relative; can be annotated case by
case).
Documentation:
- src/uix/eidos/README.md § "Vertebración tipográfica" — two-layer
architecture rationale, alias chain diagram, comparison vs Radix
Themes / Chakra / Mantine / MUI, escape valves
- web/routes/uix/lib/COMPONENT_AUDIT_GUIDE.md § 4.11 — pointer to
R-2.7 + cross-link to the foundation doc
Verified end-to-end in browser at /uix/components/field:
--font-ui → 'Instrument Sans', system-ui, sans-serif
--style-label-font-family → 'Instrument Sans', system-ui, sans-serif
--leading-ui → 1.25
--style-label-line-height → 1.25
computed [data-field-label].line-height → 17.5px (= 14 × 1.25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
e11d3539ef |
feat(scroll-area): add size/radius/scrollbars props + fix scroll-type race
Reference-library parity (Radix Themes, Chakra v3): - size: 'xs' | 'sm' | 'md' | 'lg' (Chakra t-shirt scale) → thickness via --scroll-area-scrollbar-size. - radius: 'none' | 'sm' | 'md' | 'lg' | 'full' (Radix Themes) → thumb curvature via --scroll-area-thumb-radius. - scrollbars: 'vertical' | 'horizontal' | 'both' (Radix Themes) → recipe-level sugar that auto-mounts Scrollbar / Thumb / Corner from the root. Explicit composition still supported (omit the prop). Bug fix — type='scroll' + horizontal sometimes stuck hidden: - `mounted` on ScrollAreaScrollbarProvider is now $state. Previously a plain field, so the `shouldShow` derived didn't invalidate when the requestFrame mount-flag flipped during the first frame. - The reveal-on-scroll $effect now skips its first run via a `firstRun` flag. That first run was the subscription pass with scrollTop/scrollLeft still 0 — it was eagerly calling show() and setting `visible=true` before `mounted` flipped. Subsequent real scrolls then assigned visible=true again (no-op), so shouldShow didn't re-run and the bar stayed hidden until the hide timer expired. Now the bar only reveals on genuine scroll deltas. Demo: chip controls for size + radius, switched to scrollbars prop on the root (drops the explicit Scrollbar/Thumb children), eidos snippet shows the recipe-sugar form, soma snippet still shows the explicit Radix-style composition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
560ce8f66c |
fix(scroll-area, drawer): horizontal scrollbar visibility + drawer drag affordance
- scroll-area soma: defer the mount-frame request to `$effect` so SSR doesn't hit `dom::window_required` during constructor evaluation. - scroll-area recipe: scrollbars now `position: absolute` (overlay style) so the horizontal track lands at the viewport's block-end instead of flowing below it. `:has` rules reserve corresponding edges when both axes mount; corner switched to absolute as well. - drawer demo: drop the ↑↓←→ glyph pop-outs and `overflow: visible` during dragging. The handle's own pill grip is the affordance — the same shape the splitter uses, no extra chrome. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
bd2e40366a |
feat(layout): Layout Batch 3 — scroll-area, separator, splitter + splitter sema rewire
3 new layout primitives close the air port set (15 / 15 done).
`npm run component:audit`: 91 / 91 PASS, all 3 new PASS with 0 errors.
**scroll-area** — eidos compound wrapper over the existing soma:
`<ScrollArea><ScrollArea.Viewport>{content}</ScrollArea.Viewport>
<ScrollArea.Scrollbar orientation="vertical"><ScrollArea.Thumb />
</ScrollArea.Scrollbar><ScrollArea.Corner /></ScrollArea>`. Mirrors
Radix Primitives / Bits UI shape. Demo 667 lines.
**separator** — visual primitive (`scope: ['eidos']`) with conditional
ARIA: `role="separator"` + `aria-orientation` when not decorative,
`aria-hidden="true"` when decorative. New morfo
`src/uix/morfo/components/separator.ts`. Demo 645 lines.
**splitter** — eidos compound over existing soma:
`<Splitter orientation="horizontal"><Splitter.Panel>{a}</Splitter.Panel>
<Splitter.ResizeTrigger /><Splitter.Panel>{b}</Splitter.Panel>
</Splitter>`. Demo 824 lines.
**Splitter sema rewire — match Slider's handle/drag/commit shape:**
The morfo previously declared only `commit-resize`. Direct-manipulation
primitives (Slider, Splitter) should share the same event vocabulary so
audio/haptic projections feel coherent across the family. Renamed +
expanded to mirror Slider:
- `handle-pick` (family: handle, verb: pick, sequence: coincident,
target: resize-trigger) — fires on pointerdown when user grabs the
trigger.
- `handle-drag` (family: handle, verb: drag, sequence: coincident,
target: resize-trigger) — fires on each pointermove frame.
- `commit-set` (family: commit, verb: set, intent: neutral,
sequence: post, target: provider) — fires on pointerup. The runtime
`commit-set` handler is wired to call the consumer's `onResizeEnd`
callback, so sema and JS callback land at the same moment (parity
with Slider's `onValueCommit`).
Soma provider triggers these via `this.runtime.trigger(name)` in
`onpointerdown` / `onpointermove` / `notifyResizeEnd` (the latter
unchanged externally — internally now routes through the runtime
trigger).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
a08a0a9890 |
fix(aspect-ratio): fill inline axis by default + panel demo uses solid colors
Two bugs surfaced when verifying the panel demo visually: 1. **AspectRatio shrunk to content width inside grid/flex containers.** Grid items default to `width: max-content`, so AspectRatio without an explicit width was sizing itself to its child's intrinsic content instead of filling the slot. The ratio still applied but against the collapsed width, producing a tiny box. Fix: recipe sets `inline-size: var(--box-width, 100%)` so the default is "fill the slot". `width=…` from Box still wins; `maxWidth=…` constrains the upper bound exactly as before. Block-level containers are unaffected (100% of the line box matches their natural width). 2. **Panel demo gradient invisible in dark mode.** The demo used `--color-primary-track` + `--color-affirm-track` which are surface tokens (near-black in dark theme). Swapped to `*-solid` tokens with `--color-content-on-solid` text for high contrast in both themes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
d421f1fb5d |
feat(typography): 8 primitives — Heading, Display, Code, CodeBlock, Kbd, Mark, Highlight, Link
Full canon for the rest of the typography family, following Text
(commit `03531709`) as template. Each primitive ships morfo (passive,
scope eidos, 0 events) + full eidos set (svelte/css/types/index +
README with required sections) + canon 6-tab demo (≥400 lines each).
**Heading** — semantic h1..h6 via `level` prop, visual style decoupled
via `style` prop (defaults to match level but `<Heading level={3}
style="h1">` allowed). Demo 594 lines.
**Display** — always-h1 page-title primitive. Always uses
`style="hero"`. Tracking via literal em values until foundation emits
hero-scale tracking tokens. Demo 546 lines.
**Code** — inline code via `<code>`. `variant: 'plain' | 'soft' |
'outline'` for subtle background chrome. Demo 423 lines.
**CodeBlock** — multi-line `<pre><code>` with optional title +
copy button (button utility, NOT a sema verb — kept out of morfo per
2-of-3 rule). Demo 402 lines.
**Kbd** — keyboard shortcut chrome over `style="code"`. Demo 418 lines.
**Mark** — text highlight via `<mark>`. Palette uses canonical
`ColorRole` intents (default `fulfill` ≈ legacy yellow). Demo 402 lines.
**Highlight** — substring highlighter inside Text. Scans children for
`query` and wraps matches. Same intent palette as Mark. Demo 407 lines.
**Link** — text link via `<a>`. `variant: 'default' | 'subtle' |
'plain'` for underline style, `intent: ColorRole` for color. Passive
on the morfo — hover/active is CSS-only, navigation belongs to the
consumer (router). Demo 448 lines.
**Architectural deviations from air documented per README's
Decisiones section:**
- Heading: semantic level vs visual style decoupling
- Code split into inline `<Code>` + block `<CodeBlock>` (was one
component with `variant='block'` in air)
- Mark/Highlight palette swapped from literal hue names to canonical
ColorRole intents (theme contract)
- Link palette opened to full ColorRole; `quiet`/`emphasized` air
variants dropped in favour of composable patterns
`npm run component:audit`: **90 / 90 PASS**, 0 NEEDS-WORK.
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
0353170982 |
feat(typography): Text — POC consuming new --style-* foundation tokens
First typography primitive consuming the named-style tokens added in
`015bd761`. Sets the canon for the remaining typography ports
(Heading, Display, Code, CodeBlock, Kbd, Mark, Highlight, Link).
API:
- `style: 'body' | 'prose' | 'label' | 'caption'` (default `body`).
Selects which `--style-{name}-*` token set the recipe consumes.
- Per-axis overrides via `size`, `family`, `weight`, `color`, `align`.
Each writes a local `--_text-{axis}` CSS variable that wins over
the named style's default via the cascade.
- `italic`, `underline` shorthand booleans.
- `truncate` (single-line ellipsis) + `clamp={N}` (multi-line via
CSS `-webkit-line-clamp`).
- `as` prop switches the tag (default `'span'` — inline composable).
- `tagStyle` escape hatch for one-off inline CSS that the prop
surface doesn't cover (separate from the `style` named-style prop).
Architectural choices documented in README:
- Sizes map to eidos `xxs..xxxl` (not air's `1..12`).
- Families use the eidos slot names (`primary/secondary/mono/display`)
not air's domain aliases (`ui/prose/code`) — coherence with the
foundation contract.
- Colors are restricted to the content-role slots
(`--color-content-*`) to preserve contrast guarantees. Free colors
via `tagStyle`.
- Canvas line-count measurement (air's `text` + bindable `lineCount`)
deferred; CSS line-clamp covers the visual-truncate use case.
Sidebar nav gains a `Typography` group with Text as its first entry.
`npm run component:audit`: 82 / 82 PASS, 0 NEEDS-WORK. svelte-check 0
errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
376f9e33eb |
feat(layout): Layout Batch 2 — aspect-ratio, auto-grid, banner, float
4 new layout primitives, full canon (morfo + eidos full set + canon
demo + README). `npm run component:audit`: 81 / 81 PASS, 0 NEEDS-WORK.
**AspectRatio** — composes through Box; emits single `--aspect-ratio`
CSS var (uses modern CSS `aspect-ratio` property, drops legacy
padding-bottom hack). Accepts numeric (16/9 → 1.777…) or string
("16/9") values.
**AutoGrid** — composes through Grid. Resolves `templateColumns` from
`minItemWidth` to `repeat(auto-fill, minmax(MIN, 1fr))` in JS so the
recipe stays declarative. Uses `auto-fill` (not auto-fit) to preserve
empty tracks when item count is low.
**Banner** — `<header role="banner">` announcement strip with intent
(full 8-role UIX `ColorRole`), variant (`ChipVariant` soft/solid/
outline/ghost), size (sm/md/lg). Dismissal is composition-driven:
consumer wraps in {#if show} and adds `<Banner.Close>`. No
`dismissible` boolean.
**Float** — CSS `float` primitive with logical `inline-start` /
`inline-end` sides. Redefined from air's 9-zone overlay primitive
(which becomes a future `<Positioned>` component if real demand
surfaces). Useful for inline images / pull-quotes / drop caps.
Each component ships morfo (`scope: ['eidos']`, 1 part, 0 events,
justified) + eidos set + README (Baseline / Comparativa /
Decisiones / Eventos Sema / Gaps with disposition markers /
Referencias / Passive justification) + canon demo with 6 tabs.
Sidebar nav appends the 4 entries to the existing Layout group.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
60a50f1c27 |
docs: COMPONENT_AUDIT_GUIDE.md — inviolable pre-flight + audit script
Creates the binding pre-flight checklist every agent / contributor must read before touching any UIX component. AGENTS.md gets a top-banner linking the three required reads (this guide, DEMO_AUTHORING_GUIDE, eidos components README). Sections: 0. Inviolable rule — always read this + the demo guide + components README before coding 1. Reference library matrix (radix-themes, radix-primitives, chakra, mantine, mui, react-aria, ark, bits, shadcn, WAI-ARIA APG) 2. 4-layer ownership recap + 2-of-3 rule for morfo extensions 3. Pre-flight audit template (feature parity matrix, architectural choices, reference comparison, decision log, user sign-off line) 4. Project-wide architectural rules — Radix item/container split, composition over visibility props, chip parity, size category cheatsheet, per-event intent, no re-export facades, persistent label registries, floating layer defaults, combobox keyboard, Chakra band-above-control chips 4.11 `npm run component:audit` is the canonical verification tool — walks every component, parses morfo, cross-checks eidos + demo, emits report. A component is "done" only when its scoreboard row reads PASS with 0 errors. 5. Demo template is locked — points at DEMO_AUTHORING_GUIDE 6. Anti-pattern catalogue — every failed approach from recent sessions with WHY it failed (shallow demos, matchAnchorWidth, visibility booleans, inline chips, onpointerdown picks, flex 100% wrap, unregistering labels on unmount, auto-rendering wrappers, refocus without guard, agent git reset, skipped audit) 7. Canonical canaries per domain 8. Audit log — running table with commit hashes + known gaps from Layout Batch 1 9. Pre-port checklist (now includes README writing + the audit script run + npm run check) 10. "When in doubt, ask the user" closer Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
4d45cfcc82 |
docs: COMPONENT_AUDIT_GUIDE.md — inviolable pre-flight for component work
Creates the binding pre-flight checklist every agent / contributor
must read before touching any UIX component (`src/uix/{morfo, soma,
sema, eidos}/components/{name}` or `web/routes/uix/components/{name}/`).
Sections:
0. Inviolable rule — always read this + DEMO_AUTHORING_GUIDE +
components/README before coding
1. Reference library matrix (radix-themes, chakra, mantine, mui,
react-aria, ark, bits, shadcn, WAI-ARIA APG) with what each is for
2. 4-layer ownership recap (morfo / soma / sema / eidos) + the
2-of-3 rule for morfo extensions
3. Pre-flight audit template — feature parity matrix, architectural
choices, reference comparison, decision log, user sign-off line
4. Project-wide architectural rules (Radix item/container split,
composition over visibility props, chip parity, size category
cheatsheet, per-event intent, no re-export facades, persistent
label registries, floating layer defaults, combobox keyboard,
Chakra band-above-control chips)
5. Demo template lock — points at DEMO_AUTHORING_GUIDE
6. Anti-pattern catalogue — every failed approach from recent
sessions with WHY it failed (shallow demos, matchAnchorWidth,
visibility booleans, inline chips, onpointerdown picks, flex
100% wrap, unregistering labels on unmount, auto-rendering
wrappers, refocus without guard, agent git reset, skipped audit)
7. Canonical canaries per domain (drawer, search-field, box, flex,
date-picker, avatar)
8. Audit log — running table of completed audits with commit hashes
+ the known gaps from Layout Batch 1 to address before the next
round (alignContent on Flex/Grid, columns/rows shorthand on Grid,
grow boolean on Group, fluid on Container, HStack/VStack helpers)
9. Pre-port checklist consumers can copy into task plans
10. "When in doubt, ask the user" closer
AGENTS.md updated with a top-banner ⚠ block linking the three
required reads (this guide, the demo guide, the eidos components
README) so any new agent picks them up before touching code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
591b08853a |
feat(layout): canon demos + morfos for the remaining 6 primitives
Replicates the box / flex canonical pattern across grid, stack, group,
wrap, container and section. Each gets:
- `src/uix/morfo/components/{name}.ts` — scope `eidos`, single Provider
part, no events. Header comment justifies the 0-event surface as a
passive container composing through Box / Flex.
- Rewritten demo following DEMO_AUTHORING_GUIDE: 6 tabs (Live · API ·
Morfo · Sema · Recipe · A11y), `Layout · {Name}` eyebrow, 4 meta
pills, permanent stage between header and tablist, trace strip,
MutationObserver wired to data-event, reactive soma + eidos snippets,
API tables grouped by concern, reference comparison vs radix-themes
/ chakra-ui / mantine.
Cross-references in every lede + API tab:
- Grid → Box (item placement) + Flex (flex containers)
- Stack → Flex (full API) + Group (row cluster)
- Group → Stack / Wrap / Flex
- Wrap → Flex (`wrap="wrap"`) + Group (nowrap)
- Container → Box (arbitrary maxWidth) + Section (block padding)
- Section → Box (non-semantic padding) + Container (max-width)
Demo sizes: grid 479L, stack 426L, group 458L, wrap 460L, container
415L, section 386L (vs 80-95L before — matches drawer/avatar canon
mass).
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
0dc9c523d1 |
feat(layout): canon Flex demo + morfo (mirrors Box pattern)
Second POC for redoing Layout Batch 1 demos. Mirrors `box`'s canonical shape adapted for a flex container: - 7 container-side controls (direction, align, justify, wrap, gap, padding, inline) — chips for unions, number for space tokens, switch for the boolean. - API tab separates "Container props (Flex-only)" from "Inherited from Box" with a forward-link. - Cross-references: lede + API call out that item-side props (alignSelf, gridColumn, …) live on the child `<Box>`, and that Stack / Group are direction-specific helpers. - Reference comparison row reflects the Radix Themes split (item vs container) and notes Chakra's `HStack`/`VStack` map to UIX `Stack`. Adds `src/uix/morfo/components/flex.ts` (scope `eidos`, 1 part, 0 events, mirrors box.ts justification). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
66c6897c61 |
feat(layout): canon Box demo + relocate grid item placement props to Box
POC for redoing Layout Batch 1 demos to follow DEMO_AUTHORING_GUIDE. Box becomes the reference template for the remaining 7 primitives. Demo follows the canon shape: - Header with eyebrow + 4 meta pills (parts, events, props, scope). - Stage permanently rendered with 3 nested boxes + trace strip showing live display/padding/gap state. - 6 tabs in canonical order (Live · API · Morfo · Sema · Recipe · A11y). - Live tab: 7 controls + reactive soma/eidos snippets. - API tab: 6 subsections grouped by concern (sizing / spacing / display+position / flex/grid item / escape hatch / reference comparison vs radix-themes / chakra / mantine). - Morfo / Sema / Recipe / A11y tabs each populated with the contract appropriate to a passive eidos-native primitive. Architectural fixes (heredados de air, ahora correctos): - `gridColumn`, `gridRow`, `gridArea` move from `<Grid>` to `<Box>` — these are item placement props, they belong on the CHILD that declares its slot, not the container. - Add `placeSelf` to Box (grid item shorthand for align-self + justify-self). - Grid recipe + props updated accordingly; comment clarifies that item placement lives on Box. Add `src/uix/morfo/components/box.ts` (scope `eidos`, 1 part, 0 events with justification comment). Box demo updated lede to point at Flex / Grid for container-side props (Radix Themes split, not Chakra everything-on-Box). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
9ab6746c63 |
chore(uix nav): add Layout group to sidebar for the 8 new primitives
Surfaces Box / Flex / Grid / Stack / Group / Wrap / Container / Section
in the docs sidebar. Pages already existed at `/uix/components/{name}/`
from `9ec2a57a` but weren't navigable from the chrome.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
9ec2a57a85 |
feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
20709caf9c |
refactor(combobox): SelectedTags as band above the Control (Chakra pattern)
Switched the multi-mode chips from MUI-style "inline inside the input" to the Chakra v3 multi-select pattern — chips render as a separated band ABOVE the Control. Two wins: - The dropdown popover (opens downward) never covers the selected chips, which sit comfortably above the input. - The Control stays a normal-height single-line input. No layout shifts, no absolute-positioned affordances, no `display: contents` gymnastics, no input flex tuning. Reverts the inside-the-Control CSS gymnastics from `30e9517a` / `64bbe9b1` / `87e3d68f` (flex-wrap, padding-right reservation, absolute clear+trigger, input flex basis). `<Combobox.SelectedTags>` now renders as a plain `flex-wrap` row with chip gap and a margin- block-end separating it from the Control. Demo composes `<Combobox.SelectedTags />` BEFORE `<Combobox.Control>` when `mode === 'multiple'`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
30e9517a9a |
feat(combobox): SelectedTags inline chips for multi mode
New parts `<Combobox.SelectedTags>`, `<Combobox.SelectedTag>` and `<Combobox.SelectedTagRemove>` render the current selection as removable chips inside `<Combobox.Control>` when `type='multiple'` (Material UI Autocomplete pattern): - Morfo: 3 new public parts with kebab names + texts entries. - Langs: es/en entries `selected-tags` / `selected-tag-remove`. - Soma: `ComboboxSelectedTagsProvider` exposes `items` derived from the provider's label registry; `ComboboxSelectedTagProvider` surfaces `data-value` / `data-label`; `ComboboxSelectedTagRemoveProvider` resolves its parent tag via context and calls `provider.removeValue` on click. Provider gains `getSelectedItems()` and `removeValue()` helpers; `selectedTagsEmpty` runtime prop drives `[data-empty]`. - Soma item registry: stop unregistering labels on Item cleanup so chips keep proper labels after the listbox closes/lazy-unmounts. - Eidos: default snippet loops items and renders a chip + X icon per entry; consumer can override the snippet for custom rendering. - CSS: chip styling, wrap-friendly Control with breathing padding when chips are present, ClearButton pinned right via auto margin, input flex basis reduced to 4rem so it shares a row with chips. - Demo: renders `<Combobox.SelectedTags />` inside Control when `mode === 'multiple'`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
26daa9e22e |
feat(search-field demo): full keyboard nav for results popover
Follows the WAI-ARIA combobox pattern via aria-activedescendant — the input keeps DOM focus, highlight moves virtually: - ArrowDown / ArrowUp: open popover if closed, cycle highlight (wrapping at edges). - Home / End: jump to first / last (when open). - Enter: commit the highlighted item, close popover. - Escape: close popover without clearing value; Soma's clearOnEscape fires only when popover is already closed. - pointermove on a result moves the highlight to it (mouse mirrors keyboard). - highlighted item auto-scrolls into view; index resets when result set shrinks past it or after commit/clear. Wires aria-controls / aria-expanded / aria-activedescendant / aria-autocomplete on `<SearchField.Input>` and ids + data-highlighted on each option. Header meta pill updated to 7 keys. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
99a1f05cd7 |
fix(search-field demo): close popover after pick without reopening on refocus
`commitPick()` set `value`, closed the popover, then refocused the input — but the `onfocusin` on `<Popover.Anchor>` reopened it. Guard the anchor's open handler with a `justCommitted` flag cleared on the next microtask so the refocus that follows a pick is ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
82349f15b4 |
feat(search-field): floating results popover in demo + hide native clear
- Demo `/uix/components/search-field` wraps the SearchField in `<Popover.Anchor>`; results render in `<Popover.Content>` (intrinsic width, align="start") wrapped in `<PickerShell.Body>` so the panel inherits the canonical picker chrome. Items are real `<button>`s with `onclick` — committing a selection sets `value`, closes the popover, and restores focus to the input. - Forward `bind:ref` through the eidos `<SearchField.Input>` wrapper so consumers can refocus the input from the demo's pick handler. - Hide the browser's native `::-webkit-search-cancel-button` / `::-webkit-search-decoration` / `::-ms-clear` so only the canonical `<SearchField.ClearTrigger>` X is visible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
afb09c2254 |
feat(picker-shell): extend to Root + Header + Body + Footer layout
Promote PickerShell from action-row-only to a canonical 4-zone shell (Root → Header / Body / Footer) shared by every composite picker so date/time/color popovers get the same visual contract for free. - Add `<PickerShell.Root>` flex-column wrapper (emits data-picker-shell, data-mode) plus `<PickerShell.Header>` and `<PickerShell.Body>` zones matching the existing Footer's spacing + border tokens. - Auto-wrap all 5 *-content.svelte popovers (date / date-range / time / time-range / color) in `<PickerShell.Root>` so consumers only compose Body + Footer. - Refresh all 5 demos to use `<PickerShell.Body>` around picker content; date-picker demo gains a `showShellHeader` toggle showcasing the new Header zone. - CSS scales section gap + header spacing across xs/sm/md/lg picker sizes via [data-popover-content][data-picker-size] scopes, in sync with the existing Footer scale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
8aa6086976 |
docs(search-field demo): add live filterable results panel
The demo only had the SearchField input by itself with a trace strip; nothing actually demonstrated the search outcome. Added a 34-item dataset of UIX components (name + group) and a live results grid that filters by name or group substring as the user types. - Empty input renders 34/34 results. - Typing "form" filters to 17 components from the Forms group. - "drawer" filters to 1 (Drawer). - Empty results state renders a graceful "No matches for ..." message. - Counter shows `results N / total` so the demo doubles as a quick feedback loop for the debounceMs control (count + meta animate together when the debounced onValueChange finally fires). CSS lives inline in the demo (`.search-field-results*` selectors) since this is demo content rather than reusable component visuals. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
9fbf8e48b5 |
feat(number-field, search-field, combobox): audit-driven improvements
Three components closing the 6-component audit batch.
number-field
- N1 RTL scrubber: invert horizontal delta sign when dir='rtl' so
dragging right decrements (visually backward) in RTL layouts. Vertical
unaffected. Added 2 RTL scrub tests; existing scrub test pinned to ltr.
- N2 stepper height: Increment/Decrement now use --_number-field-height
for inline-size/block-size/min-inline-size so they always match the
input row at every size token (xs/sm/md/lg/xl).
- N3 input focus ring: explicit [data-number-field-input]:focus-visible
with --_number-field-accent-border box-shadow, alongside the existing
Provider-level ring.
search-field
- S1 icon tokens: added --search-field-icon-size-{xs..xl} + icon-color +
icon-margin-inline-end + a Morfo Icon part + soma/eidos wrappers.
Demo uses <SearchField.Icon><Search /></SearchField.Icon>.
- S2 debounceMs prop: soma wraps onValueChange in a timers.schedule with
the provided delay. clear() cancels, submit() flushes synchronously.
- S3 loading slot: new LoadingIndicator part (morfo + soma + eidos) +
data-loading on Provider + CSS spinner via @keyframes search-field-spin
honoring prefers-reduced-motion.
combobox
- C1 ClearButton part: new morfo trigger + soma provider + eidos wrapper
with X icon. Hidden via [data-empty] (opacity + pointer-events:none).
Click clears value + inputValue and refocuses input.
- C2 allowsCustomValue: when true and no highlighted item, Enter commits
the current inputValue as the value. Single mode closes after commit;
multi appends + clears input + stays open.
- C4 Backspace removes last tag in multi mode when input is empty.
- C5 Sticky group headers: CSS position:sticky with content-overlay bg.
Plus: fixed search-field demo's brittle parts[1] index reference to a
named lookup (parts.find(p => p.kebab === 'input')).
Verified: svelte-check --threshold error → 0 errors / 0 warnings /
4584 files. All component tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
7bc17485dc |
feat(editable): invalid + required + form + Control + better hover/disabled + defaultValue
Audit-driven improvements (7 items) to Editable:
- E1 invalid + required props
- morfo: Provider gains data-required/data-invalid + aria-required/aria-invalid;
Input emits data-invalid + aria-invalid.
- soma: opts include `invalid` and `required`; runtime registration + provider
props derivation emits the data-attrs.
- eidos css: risk-border on Input + Control when [data-invalid].
- E2 APG: added `apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/textbox/'` to
morfo — Editable is essentially a textbox swap.
- E3 form integration: new HiddenInput part (morfo + soma + eidos). The Provider
wrapper auto-renders `<EditableHiddenInput>` when `name` is set, so consumers
don't have to compose it manually. Submits value as native form data.
- E4 Control re-export: Eidos namespace now exposes `Editable.Control`,
matching the soma surface.
- E7 submit hover: replaced `filter: brightness(1.04)` with
`--_editable-accent-solid-hover` per color variant. Consistent with the
other triggers' bg+border transition pattern.
- E9 disabled state: added `cursor: not-allowed` + `color: content-disabled`
on Preview/Input/triggers, alongside the existing opacity. Clearer
feedback than opacity alone.
- E10 defaultValue: new prop seeds `value` once via untrack when `value`
is uncontrolled. Same pattern as the recent select.defaultValue.
Demo at /uix/components/editable: switches for invalid/required/disabled,
a Form integration section with native form submit + FormData panel,
and a defaultValue uncontrolled example.
Verified: svelte-check --threshold error → 0 errors / 0 warnings / 4578
files. Soma tests: 3/3 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
75136e6de5 |
fix(select) + feat(field): match-anchor width, label persistence, OptionalIndicator, auto-label
select bugs
- The popover content didn't match the trigger width even though
matchAnchorWidth was already implemented in the eidos layer. Root
cause was in soma/layers/floating/floating.svelte.ts: `transformedStyle`
read `this.opts.style` directly without `.current`, so consumer styles
never merged into the floating wrapper. Fixed by reading
`this.opts.style.current` and also returning the value when it's an
object (previously fell through to undefined). All popover-based
components (select, combobox, date/time pickers, color picker) now get
their `style` prop applied.
- The trigger value text reverted from the rendered label (e.g. "Apple")
to the raw value string ("apple") whenever the popover closed. Items
unmount when Content's Presence collapses, and they were unregistering
their label on cleanup — leaving the label registry empty between
opens. Drop the unregister on cleanup; keep registrations alive across
popover open/close cycles. Re-mounts overwrite the entry.
field improvements (F1 + F2 + F3)
- F1: add an explicit `[data-field][data-variant='surface']` CSS block
so the surface variant (declared in the type union) has its own rule
instead of silently falling back to the default style.
- F2: new `OptionalIndicator` part (morfo + soma wrapper + eidos
wrapper). Renders only when `required === false`. Default text comes
from `texts['optional-indicator']` ("(optional)"). Styled muted + 0.85em.
- F3: `<Field.Label />` without children now auto-renders the
localised label from `texts.label`. Reduces boilerplate when consumers
rely on the field name for its label.
- Demo at /uix/components/field gained composition examples for the new
parts.
Verified: svelte-check --threshold error → 0 errors / 0 warnings / 4576
files. In-browser: select trigger and popover widths both 336 px;
closed and open values both show labels (e.g. "Apple, Pear, Orange").
Field demo shows 2 required `*`, 1 `(optional)`, and an empty
`<Field.Label />` falling back to the literal "Field".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
ffdb480403 |
feat(select): invalid state + ItemDescription + defaultValue + visual fixes
Audit-driven improvements to Select: - morfo APG URL fixed (combobox → listbox; Select is a collapsible-listbox per ARIA APG, not a text-input combobox). - `invalid` prop added to soma + morfo data-invalid/aria-invalid on Provider and Trigger. Eidos applies risk-border + tinted focus shadow when invalid. - `defaultValue` prop added (string | string[]) for uncontrolled init. Normalized to string[] inside the soma wrapper via untrack so the reference is intentionally non-reactive. - `ItemDescription` part: new morfo part (kebab item-description, archetype description), new soma wrapper + new eidos wrapper. CSS gives muted color and 0.85em font-size; when the parent item is checked, the description inherits accent color. - Value truncation: ensure `min-inline-size: 0` + `max-inline-size: 100%` on the trigger value so long labels ellipsis instead of overflowing. - Highlighted vs checked distinction: the highlighted-only state now uses a lighter hover background, and an item that is both highlighted and checked gets an additional inset focus ring on top of the accent fill. Demo at /uix/components/select adds an `invalid` switch, and three of the six items now ship with a description (Apple / Pear / Carrot). Verified in browser: - triggerInvalid + aria-invalid = true after toggling switch. - Description on non-checked item: rgb(123,123,123) (content-muted). - Description on checked item: accent purple (inherits). - svelte-check --threshold error → 0 errors / 0 warnings / 4574 files. Deferred: SL3 readonly (rare use case), SL4 virtual scrolling (own task). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
fd3a0b00c9 |
feat(calendar, date-picker, date-range-picker): add xs size + fix duplicate footer
xs propagation - Add [data-size='xs'] blocks to calendar.css, date-picker.css, date-range-picker.css (root + inline range calendar). - Extend CalendarSize union with 'xs'; DatePicker / DateRangePicker inherit it through their type re-exports. - date-picker-content + date-range-picker-content clamp xs to sm when forwarding to PopoverContent (PopoverSize starts at sm; the calendar inside still receives xs via data-size). - size chip groups in calendar / date-picker / date-range-picker demos now list xs / sm / md / lg. date-picker demo: fix duplicate footer - The `calendarBody` snippet had a hardcoded <DatePicker.Footer> that rendered alongside the controllable `pickerFooter` snippet, producing two footer rows when showFooter=true. Removed the hardcoded one. Verified in browser: - date-picker size='xs' -> data-date-field-input height 26px (control-height-xs), padding 8px (space-2), font-size 12px. - date-picker popover now shows a single footer. - svelte-check --threshold error -> 0 errors / 0 warnings / 4572 files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f4707bd0c9 |
feat(month-grid, year-grid): add xs size
Both grids now accept size='xs' alongside sm/md/lg. - recipe: add calendar-padding-xs / control-size-xs / day-size-xs / font-size-xs tokens (space-2, control-height-xxs=22px, font-size-xs). Regenerated generated/base.css. - eidos css: add [data-month-grid][data-size='xs'] and the year-grid equivalent before the existing sm block. - types: MonthGridSize / YearGridSize extend Size with 'xs'. - demos: size chip group now lists xs / sm / md / lg. Verified in browser at /uix/components/month-grid with size='xs': cell block-size = 22px, padding = 8px (space-2), font-size = 12px. svelte-check --threshold error -> 0 errors / 0 warnings / 4572 files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
f7831563a1 |
feat(month-grid, year-grid): standalone primitives parallel to date-picker views
DatePicker.MonthView / YearView require a DatePickerProvider context and
can't be used standalone. This commit introduces MonthGrid and YearGrid
as independent primitives that accept value / placeholder / minValue /
maxValue as props and work anywhere.
morfo
- New morfo/components/month-grid.ts and year-grid.ts.
- scope: ['soma','sema'], apg: grid pattern.
- 7 parts each: Provider (role=application) / Header / Heading /
PrevButton / NextButton / Grid (role=grid) / Cell (role=gridcell).
- Events: commit-set (cell click) + nav-step (prev/next).
- Cell data-attrs: data-value, data-selected, data-current, data-disabled,
data-focused.
- Keyboard map covers arrows / Home / End / PageUp/Down / Enter / Space.
soma
- New soma/components/{month-grid,year-grid}/ with provider class +
langs + types + exports + 7 thin svelte wrappers each.
- MonthGridProvider: 12-cell grid keyed to placeholder.year; localized
month names via Intl.DateTimeFormat with configurable monthFormat
('long'|'short'|'narrow'). Prev/Next step ±1 year.
- YearGridProvider: decadeSize-cell grid (default 12); page anchored at
placeholder.year - (placeholder.year % decadeSize). Prev/Next step ±
decadeSize.
- Both expose Cell sub-provider with snippetProps {selected, disabled,
current, value, label} + roving tabindex + RTL-aware keyboard nav.
eidos
- New eidos/components/{month-grid,year-grid}/ — namespace-style index
with size / variant / color visual props + dedicated CSS reusing the
--calendar-* token family.
- Each grid renders Header (Prev/Heading/Next) + 3-col Grid.
demo
- New /uix/components/month-grid and /uix/components/year-grid demo
pages with the standard live/api/morfo/sema/recipe/a11y tabs,
interactive controls for every public prop, soma+eidos snippets that
reactively rebuild from controls, and sema event play buttons.
Verified: svelte-check --threshold error -> 0 errors / 0 warnings /
4572 files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
dd22cfc2bc |
feat(time-range-picker): eidos wrapper + canonical picker-shell parity
Builds the Eidos time-range-picker layer end-to-end and brings color-picker's morfo into parity with the other pickers. time-range-picker - soma: `mode` prop, `pickerShellContext` registration with commit/cancel/clear and `valueOnOpen` snapshot for revert; Provider now renders a div with data-state/disabled/readonly/required + aria mirrors. - soma: new `Clock` part (mirrors TimePicker.Clock with role="dialog" and data-state). - morfo: scope ['soma','sema'] + apg dialog-modal; promoted Provider to public div with states/data-state; added Clock part; declared the 6 canonical picker events (open / close-commit / close-cancel / close-dismiss / close-dismiss-outside / commit-clear); slider parts declare data-endpoint + data-disabled. - eidos: full namespace (Trigger / Content / Clock / HourSlider / MinuteSlider / SecondSlider / DayPeriodToggle + re-exported PickerShell Footer / Clear / Cancel / Close). Sliders compose generic SliderProvider (N-8) and ship with optional rulers above hour / below minute+second. - eidos css: 2-column endpoint grid in the clock; per-endpoint range tint; AM/PM toggle styling; reuses time-field tokens. - demo: interactive testbed at /uix/components/time-range-picker mirroring the time-picker demo — every soma + eidos prop has a live control, closeOnRangeComplete + start/endReadonlySegments toggles, footer composition switches, sema play buttons, soma+eidos snippets. - nav: added entry between Time picker and Color field. color-picker (audit fixes) - morfo: Provider declares states ['open','closed'] + data-state attr; added the same 6 canonical picker events targeting the `content` part (handle-pick / handle-drag / commit-set preserved). - soma: runtime now feeds open state + disabled/readonly/required/invalid props to the morfo runtime so data-state actually emits. Verified: svelte-check --threshold error → 0 errors / 0 warnings / 4524 files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
8515f5e673 |
fix(color-picker): ValueSwatch now reactively reflects the current color
User report: 'la caja del color seleccionado en el pickercolor no se
actualiza, compruébalo.'
Root cause
----------
The Eidos recipe had an `[data-color-picker-value-swatch]::after`
overlay that painted `var(--cp-trigger-color, currentColor)` on top of
the swatch — a CSS variable set ONLY on the trigger button by the
Eidos wrapper. The popover content is portaled outside the trigger's
subtree, so the var never propagated into the ValueSwatch element.
The overlay therefore stayed at its fallback (`currentColor`) and
covered the soma-set inline `background-color`, hiding the live colour.
Fix
---
1. **Soma `ColorPickerValueSwatchProvider.backgroundStyle`** now emits
BOTH `background-color: <hex>` and `--cp-current-color: <hex>` so:
- Consumers without the eidos recipe still see the colour (inline
background-color, as before).
- Eidos recipes / external CSS can read `--cp-current-color` as a
reactive CSS variable on the element itself.
2. **Eidos CSS** drops the `::after` overlay. Instead the checker
(transparency pattern) lives on a `::before` pseudo with
`z-index: -1` inside an isolated stacking context, so the soma's
inline `background-color` paints ON TOP and the checker shows
through only where alpha < 1. No more colour stomping.
Checks: 67/67 component-audit PASS, svelte-check 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
da19728cb6 |
feat(color-picker): area drag emits handle-pick/handle-drag/commit-set + refresh demo snippets
User report: 'el selector del cuadro de colores, no tiene semántica, es
un drag, etc como el slider, no?'
You're right — the 2D area is a drag interaction with the same shape
as a slider. It was wiring pointer events to soma state but staying
silent to sema; nothing for sounds/haptics/transient styles to hook
onto. Now it speaks the same vocabulary as the slider.
Morfo additions (color-picker)
------------------------------
- `handle-pick` (family: handle, verb: pick, sequence: coincident,
target: area) — fires once at pointer-down.
- `handle-drag` (family: handle, verb: drag, sequence: coincident,
target: area) — fires on each move (pointer or keyboard step).
- `commit-set` already existed — now also emitted on pointer-up and
on each keyboard step (slider parity).
Soma changes (color-picker-provider)
------------------------------------
- ColorPickerAreaProvider:
- `onpointerdown` → trigger `handle-pick` then continue to drag.
- `handlePointerMove` → trigger `handle-drag` after writing the value.
- `onpointerup` → trigger `commit-set` then `commitChange()` for
the onValueChangeEnd callback.
- ColorPickerAreaThumbProvider.onkeydown:
- Each arrow / Home / End / PageUp / PageDown step now triggers
`handle-drag` + `commit-set` + commitChange(). Matches the slider's
keyboard semantics (one step = one drag + one commit).
Demo snippet refresh
--------------------
The `somaSnippet` / `eidosSnippet` in `/uix/components/color-picker`
were stuck on the pre-refactor API (`<ColorPicker.Area>` with explicit
Background+Thumb children, no `Control`, no `ValueSwatch`, no
PickerShell footer). Updated both to mirror the current demo body:
- Eidos: `Label / Control[Trigger + ValueText + HiddenInput] / Portal /
Content[Area / ChannelSlider hue / alpha / ValueSwatch +
ChannelInput + EyeDropper / SwatchGroup / Footer[Clear/Cancel/
Close]]`.
- Soma: same shape but uses `PickerShell.Footer / Clear / Cancel /
Close` explicitly (and the explicit Track/Thumb composition on
channel sliders for the curious).
- Both snippets honor the live `mode / inline / closeOnSelect /
enableAlpha / showFooter / showClear / showCancel / showClose`
flags from the chip controls.
Checks: 67/67 component-audit PASS, 40/40 morfo:check PASS,
svelte-check 0 errors / 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
3e5be857c4 |
fix(color-picker): single-row preset palette of 10 swatches that share container width
User report: 'la paleta dejala en 10 y más pequeños para que solo ocupen una linea, se deben de ajustar a lo ancho'. Demo ---- - Preset palette trimmed from 11 to 10 colors. CSS — swatch-group rewritten to single-row flex layout ------------------------------------------------------ - `display: flex; flex-wrap: nowrap; inline-size: 100%; min-inline-size: 0` so all swatches share the available width. - Each `<SwatchTrigger>` becomes `flex: 1 1 0` + `min-inline-size: 0` + `aspect-ratio: 1` + `inline-size/block-size: auto`, with the recipe token (`--color-picker-swatch-size`) acting as `max-inline- size` instead of a fixed size. Net effect: swatches shrink/grow together to fit any popover width and any number of presets, while capping at 1.5rem so they don't blow up on huge containers. Math at default md popover (~19.5rem content width, ~14px padding each side, 10 swatches × ~23px + 9 × 6px gap ≈ ~284px usable). Each swatch lands at ~23px and the row never wraps. Checks: 67/67 component-audit PASS, svelte-check 0 errors / 0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
aaf35fdbb0 |
fix(color-picker/demo): compact value row — swatch + channel input + eyedropper inline
User feedback: 'se pueden realinear los diferentes elementos para que no sea tan alto, el pick from screen en la misma linea del color seleccionado, o el input del valor al lado del color, etc.' Collapses three previously-stacked rows into one: Before: [preview swatch + value text] ← row 1 [channel input segments + format] ← row 2 [eyedropper button + label] ← row 3 (right-aligned) After: [swatch] [channel input ........] [eyedropper] ← single row The ValueText is dropped because the channel input segments already show the hex/rgb/hsl value digit-by-digit (more useful than a static string). The eyedropper becomes a compact icon button at the end of the row — `aria-label="Pick color from screen"` still announces it. Net effect: ~50px shorter popover. Files ----- - web/routes/uix/components/color-picker/+page.svelte — `.cp-value-row` flex layout with swatch (1.75rem) + ChannelField (flex: 1 1 auto) + EyeDropper. ChannelField input forced `flex-wrap: nowrap` so segments stay inline. Old `.cp-preview-row` / `.cp-eyedropper-row` styles removed. Checks: 67/67 component-audit PASS, svelte-check 0 errors / 0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
709d8a108e |
fix(color-picker): fix slider gradients + add live preview + label eyedropper
User report: 'los sliders son deficitarios, no muestra la banda de color ni de transferencia, faltan textos, el color seleccionado aparte del valor ascii, un texto que indique el gotero, etc.' Three concrete issues addressed. 1. Slider gradients invisible ----------------------------- Root cause: the soma channel-slider div carries BOTH `data-slider` (from SliderProvider.props) and `data-color-picker-channel-slider` (from mergeProps). My CSS targeted `[data-color-picker-channel-slider] [data-slider]::before` — i.e. as a *descendant*. They're the same element, so the selector never matched and the rainbow / alpha gradient stayed hidden. Fix: - New selector `[data-color-picker-channel-slider][data-slider]::before` paints the gradient on the slider's own track strip. - Transparency checker moved to the wrapper's `::after` (with `z-index: -1` + `isolation: isolate`) so it sits behind the gradient — visible only where the alpha gradient ramps to transparent. - Adds a subtle border on the track so the strip is visible against the popover surface. 2. Live preview alongside the hex/rgb/hsl string ------------------------------------------------ The popover used to show only the segmented ChannelInput (digits) and a small swatch dot on the trigger. Now the popover has a `.cp-preview- row` with `<ColorPicker.ValueSwatch>` (2rem) + `<ColorPicker.ValueText>` + uppercase format tag, so the user always sees the actual color visually alongside its numeric representation. 3. Eyedropper label ------------------- Added a textual `Pick from screen` label next to the icon button, plus `aria-label` on the EyeDropper. The button is no longer a mystery glyph. 4. Channel slider row labels ---------------------------- Each ChannelSlider now sits in a `.cp-slider-row` with a leading uppercase label (Hue / Alpha) so the user knows what each track drives without hovering or reading aria. 5. Section header for swatches ------------------------------ 'Preset colors' header above the swatch grid for visual hierarchy inside the popover. Files ----- - src/uix/eidos/components/color-picker/color-picker.css — slider gradient routing + checker-on-::after layering. - web/routes/uix/components/color-picker/+page.svelte — preview row, slider row labels, eyedropper label, section headers + local scoped CSS in <style>. Checks: 67/67 component-audit PASS, 40/40 morfo:check PASS, svelte-check 0 errors / 0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
714ee3735c |
feat(picker-shell): extract shared Footer/Clear/Cancel/Close + apply to color-picker
Introduces PickerShell — a soma context + eidos parts shared by every
composite picker (date, time, color, future range pickers). Aligns with
norm N-8 (reuse components across complex composites) so the
Footer/Clear/Cancel/Close behavior lives in ONE place instead of being
duplicated per picker family.
New files
---------
- src/uix/soma/components/picker-shell/
picker-shell-handle.svelte.ts → pickerShellContext +
PickerShellHandle interface
(getMode / commit / cancel / clear).
exports.ts, index.ts → public surface.
- src/uix/eidos/components/picker-shell/
picker-shell-footer.svelte → [data-picker-footer][data-mode]
picker-shell-clear.svelte → [data-picker-clear][data-action]
picker-shell-cancel.svelte → [data-picker-cancel][data-action]
picker-shell-close.svelte → [data-picker-close][data-action]
picker-shell.css → canonical action-row recipe.
index.ts, types.ts → PickerShell namespace + prop types.
- Registered in src/uix/eidos/index.css.
Color-picker now consumes the shell
-----------------------------------
- ColorPickerProvider gets `mode: 'inline' | 'modal'` opt + a
`valueOnOpen` snapshot via watch(open) so cancel() can revert.
- New `commit()` / `cancel()` / `clear()` methods with shell-picker
semantics. The previous `commit()` (fires onValueChangeEnd) was
renamed `commitChange()` since the picker-shell `commit()` means
"save + close". All internal callers (area-thumb pointerup,
swatch click, eyedropper open, channel-slider drop) updated.
- Constructor registers the handle:
`pickerShellContext.set(this.pickerShellHandle)`.
- PopoverProvider receives `modal: mode === 'modal'` so outside-click
+ Escape get blocked when modal.
- Eidos `ColorPicker.Footer / .Clear / .Cancel / .Close` re-export
the shared parts. The old `ColorPicker.Close = PopoverClose` was
renamed `ColorPicker.PopoverClose`; the canonical `Close` is now
the picker-shell save action.
Demo
----
- New `mode` chip (inline | modal).
- Footer composition switches (render footer / clear / cancel / close).
- Renders <ColorPicker.Footer> with shell parts inside <Content>.
Visual fixes
------------
- Horizontal overflow on the popover content: added `min-inline-size: 0
/ max-inline-size: 100% / box-sizing: border-box / overflow-x: hidden`
on `[data-popover-content][data-color-picker-content]` and on its
direct children. Inner ChannelInput allowed to wrap segments instead
of pushing the popover wider.
- ColorField segments inside the popover gain `flex: 0 1 auto`.
Norms
-----
- N-8 case study appended to PENDIENTES.md documenting the
PickerShell extraction alongside the channel-slider refactor.
Checks
------
- 67/67 component-audit PASS
- 40/40 morfo:check PASS
- svelte-check 0 errors / 0 warnings
Follow-up (task #81): migrate date-picker / time-picker Footer to use
PickerShell too — drop the duplicate `date-picker-footer/clear/
cancel/close.svelte` and `time-picker-footer/clear/cancel/close.svelte`
files; update CSS selectors to `[data-picker-footer/...]`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
2731ee567b |
feat(eidos): add ColorPicker wrapper + demo
Composite popover: 2D saturation/brightness area, hue + alpha channel
sliders, preset swatches, eye-dropper, and a segmented channel input
composed from ColorField. Builds on the existing soma color-picker
(23 parts) — eidos layer is mostly thin pass-throughs adding visual
chrome.
What landed
-----------
- New `src/uix/eidos/components/color-picker/`:
- Root visual (Provider) + visual-context (size/variant/color)
- Trigger with a default swatch dot tinted from
`--cp-trigger-color` (the air pattern from morfo-runtime)
- Content (popover wrapper), Area (+ Background + Thumb),
TransparencyGrid
- ChannelSlider (+ Track + Thumb) — generic per-channel slider;
default-composes Track+Thumb when no children
- SwatchGroup + SwatchTrigger + Swatch + SwatchIndicator
- EyeDropper (Pipette icon by default)
- ChannelInput + ChannelSegment + FormatSelect (composed from
ColorField parts; same provider injected automatically)
- Label, Control, ValueSwatch, ValueText, HiddenInput
- Recipe section `color-picker` in `recipes/base.ts` (regenerated
`generated/base.css`) — trigger, content, area, channel slider,
swatches, eye-dropper tokens.
- CSS reuses color-field tokens for the segmented input portion and
adds picker-specific visuals. Transparency checker on trigger swatch
+ value swatch + alpha slider track via conic-gradient pattern.
- Morfo updates: scope `['soma','sema']`, `apg: dialog-modal`, added
`commit-set` event so audit classifies it as interactive.
- Soma exports: ColorPickerProvider class so eidos Trigger can read
the current value for the swatch tint.
- Demo at `/uix/components/color-picker` with the standard 6 tabs;
live stage exercises area drag + channel sliders + preset swatches
+ eye-dropper + format switch.
- README documents the surface, comparativa vs air/Ark/Chakra, and
flags the picker contract (mode/Footer/commit/cancel) as gap to
align with date/time-picker in a follow-up.
Checks
------
- 67/67 component-audit PASS
- 40/40 morfo:check PASS (new color-picker demo validates against
morfo DOM)
- svelte-check 0 errors / 0 warnings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
2f147897d9 |
feat(morfo/time-picker): declare HourSlider/MinuteSlider/SecondSlider as parts + ruler opt-out prop
User pointed out that TimePicker exposes <TimePicker.HourSlider> / .MinuteSlider / .SecondSlider in its public API but those weren't declared in the time-picker morfo — the contract was implicit (data- slider attrs from the slider morfo). Now made explicit. Morfo additions (all optional, archetype: 'group'): - HourSlider → data-time-picker-hour-slider - MinuteSlider → data-time-picker-minute-slider - SecondSlider → data-time-picker-second-slider Eidos slider wrappers stamp the corresponding data-attr on the outer wrapper div so morfo:check finds the parts in the DOM. The inner slider DOM (data-slider, data-slider-thumb, data-slider-range) keeps its own contract via the slider morfo — composition, not duplication. Also: - Hour ticks now end-inclusive: 24h shows 0/3/6/9/12/15/18/21/23 (was 0..21, missing the upper cap). 12h shows 1/3/6/9/12. - Minute / Second scale now ends with 59 so the upper bound is shown. - New optional `ruler` prop on <TimePicker> (default `true`) and matching per-slider `ruler` opt-out. Propagated via visual context. - Demo: new switch to toggle the ruler scale. 67/67 component-audit PASS, 39/39 morfo:check PASS, svelte-check 0 errors / 0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
391de6e04c |
feat(eidos): add TimePicker wrapper + demo following P-1..P-5 picker contract
Builds on the existing soma TimePicker slider-based clock (HourSlider / MinuteSlider / SecondSlider + DayPeriodToggle). Adds the canonical picker shell: - mode='inline' | 'modal' on the Provider, propagated to popover.modal - Provider helpers commit() / cancel() / clear() with watch(open) snapshot for cancel-revert - Footer composition (TimePicker.Footer / Clear / Cancel / Close as pure-composition Eidos parts; no *Button root props) - New TimePicker.Clock soma component that carries the data-time-picker-clock contract attr, wraps the slider stack - Eidos shell: Provider visual root, Trigger (clock icon), Content (popover content), Clock, Footer + action parts, slider/toggle pass-throughs Renews the time-picker morfo to declare Provider / Trigger / Clock / DayPeriodToggle / DayPeriodItem / Footer / Clear / Cancel / Close, plus all 6 commit/emerge events (open + close-commit/cancel/dismiss/ dismiss-outside + commit-clear). apg=dialog-modal. Demo at /uix/components/time-picker exercises all controls, Footer composition switches, soma + eidos code snippets, morfo / sema / recipe / a11y tabs. README documents the picker contract. 67/67 component-audit PASS, 39/39 morfo:check PASS, svelte-check 0 errors / 0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
ff279ae828 |
feat(eidos): add TimeField + ColorField wrappers and demos
Mirrors the date-field pattern (Provider visual root + Label / Input / Segment / HiddenInput parts; ColorField also exposes FormatSelect). Soma owns segment editing, ARIA spinbuttons, validation, format switching and form submission; Eidos adds size (xs..xl), control variant (surface/outline/ghost) and intent color recipes. Adds time-field and color-field recipe sections to recipes/base.ts (regenerated generated/base.css) and registers both routes in the sidebar nav. apg=spinbutton declared on both morfos. READMEs follow the Baseline / Superficie / Comparativa / Decisiones / Gaps / Referencias template required by audit F-1.x. All 67 component-audit components PASS, 38/38 morfo:check PASS, svelte-check 0 errors/0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
72ff90b71f |
docs: consolidate kind contract + composition norms (sweep)
After several commits on pickers, the docs lagged behind the actual
contract. This sweep aligns PENDIENTES + eidos README +
DEMO_AUTHORING_GUIDE with what landed.
PENDIENTES.md:
- 'Pickers' section rewritten as a consolidated state table.
Everything done is marked hecho; the two big items
(MonthPicker/YearPicker, MonthRangePicker/YearRangePicker as
separate components) are explicitly **descartar** because they're
achieved via <DatePicker kind='X'> / <DateRangePicker kind='X'>.
Duplicating component surfaces for what a prop captures is
doctrinally rejected.
- Promotion of YearView/MonthView to standalone <YearCalendar> /
<MonthCalendar> is **diferir** — currently coupled to picker
provider context, no real use case outside picker yet.
- Time picker / color picker propagation of modal+Footer pattern
marked **implementar**.
- Playwright browser tests for the picker flows marked
**implementar** — range state machine + kind chip + modal need
coverage.
- Range view: 'differentiate start/end vs in-range visually' added
to theming backlog (currently all 3 use primary-solid, range
tint not visible).
- Two new norms N-6 and N-7:
* N-6 picker kind = single source for input + view. Filtering
lives at DateFieldProvider (soma), consumers iterate the
segments output. Views are canonical Eidos parts.
* N-7 composition over visibility props. Parts opt-in by inclusion,
not by boolean prop. Demo wraps parts in {#if showX} with local
state so the UI toggles still work without leaking demo logic
into the parts.
eidos/README.md:
- New 'Cambios 2026-05-21 — pickers: kind + composition' section
summarising kind + Footer composition + provider helpers + the
'composition wins, no separate variant components' decision.
DEMO_AUTHORING_GUIDE.md:
- §12.9 'Composition over visibility props': right vs wrong example
for <DatePicker.Footer> with the Clear/Cancel/Close children.
- §12.10 'Chakra-style kind for picker variants': demo skeleton
for the Input snippet (no filter) and the Content {#if} branch.
Task list: #29 retired (MonthRangePicker/YearRangePicker as separate
components — replaced by <DateRangePicker kind='X'>).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
05a432afc0 |
date-range-picker: YearRangeView + MonthRangeView (kind grids)
Completes the kind contract for the range picker. The popover used to
render the day-grid range calendar regardless of kind; now it switches
to a year-grid or month-grid view that handles range selection on the
appropriate granularity.
New Eidos parts:
- <DateRangePicker.YearView>: 3×4 decade grid with prev/next decade
buttons. Selection state machine (mirrors the day-grid range
selection):
* empty → click sets startValue (and clears endValue)
* pending (start set, no end) → click sets endValue. Swap if the
clicked year is before the existing start.
* complete (both set) → click resets to a fresh start + clears end.
Year endpoints normalise to Jan 1 (start) / Dec 31 (end).
- <DateRangePicker.MonthView>: 3×4 month grid for the placeholder's
year, with prev/next year navigation. Same state machine using
`year*12 + month` as the comparison key, so ranges span multi-year
boundaries cleanly. Start = day 1; end = last day of the month
(computed via Date(y, m, 0).getDate()).
- Localised month names via DateFormatter(locale, { month: 'short' }).
Index + CSS:
- index.ts: registers YearView + MonthView on the DateRangePicker
namespace.
- date-range-picker.css: shared grid layout (3 columns, gap-2),
hover surface-overlay, selected cells get primary-solid +
content-on-solid, in-range cells get color-mix(primary-solid 22%,
transparent) as a stripe with border-radius:0 (start/end keep
their outer corner radius). Reduced-motion respected.
Demo:
- {#if kind === 'year'} → <YearView>; {:else if 'month'} → <MonthView>;
{:else} → existing day calendar.
- pickerFooter snippet rendered once at the Content level (removed
the inline render at the end of calendarBody to avoid duplication).
Verified in browser:
1. kind='year' chip → popover shows year grid '2020 – 2031'.
2. Click 2024 → cell marked start (purple solid).
3. Click 2027 → cell marked end, 2025-2026 marked in-range. trace
shows 'range 2024-01-01 -> 2027-12-31'. Popover stays open
(closeOnRangeSelect=false in the demo).
0 type errors, 67/67 component:audit PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
9baccecda9 |
picker demos: footer composition switches + popover closed by default
Two follow-ups from user feedback:
(1) User: 'en los demos no existen para definir las propiedades y
visibilidad del pie y los botones' — after the previous refactor that
moved visibility from props to composition, the demos lost their UI
switches and there was no way to toggle the parts on/off.
Restored toggles but with the correct semantics: the switches now
decide whether each part is INCLUDED in the markup, not whether the
part renders internally. Composition first, demo UX second.
- Both demos: new state vars showFooter / showClear / showCancel /
showClose (default all true).
- New `{#snippet pickerFooter()}` snippet that renders
<Footer><Clear/><Cancel/><Close/></Footer> with {#if show*}
guards on each child. Rendered once via {@render pickerFooter()}
at the end of every calendar branch (Calendar / MonthView /
YearView for single; the rangeCalendar for range).
- New 'footer' control row with 4 switches per demo, labelled with
the structural intent ('compose parts inside <Footer>').
(2) User: 'el popover del calendar aparece, eso es un error' — the
previous commit defaulted open=true on both demos so the new Footer
was visible at first paint. That conflicted with the natural flow
(user expects to click the trigger to open). Reverted to open=false
by default.
Also removed the auto-reopen $effect that re-opened the popover
whenever `kind` changed. The chip click can close the popover via
outside-click in inline mode; the user re-opens deliberately. No more
magic.
Verification: 0 type errors, 67/67 component:audit PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
4a866cf86a |
date-range-picker: propagate kind to start/end inputs
User: 'lo has trasladado da daterangepicker?' — only the Footer pure-
composition refactor had landed in range; the `kind` prop (Chakra
date/month/year variant) was still single-picker only. The range
demo's input segments stayed MM/DD/YYYY regardless of the chip.
This commit propagates `kind` end-to-end through the range stack.
Calendar view-mode (year-grid / month-grid for ranges) is still
pending — that's the next commit.
soma:
- DateRangePickerKind type exported alongside DateRangePickerMode.
- DateRangePickerProvider opts gain `kind`. Root .svelte threads it
through readableActive both to the picker context AND to the
DateRangeFieldProvider it creates.
- DateRangeFieldProvider opts gain `kind` (single value — there's no
use case for kind='year' on start + kind='month' on end; the range
picker enforces a uniform kind on both endpoints).
- date-range-field-input forwards provider.opts.kind to each endpoint
DateField it spawns. The constant `kind: 'date'` placeholder is
gone.
- types.ts (range-picker + range-field): JSDoc + prop declarations.
- exports.ts: surface DateRangePickerKind alongside DateRangePickerMode.
- Test fixtures (range-picker + range-field): extended with the new opt.
demo:
- `let kind = $state<'date' | 'month' | 'year'>('date')` + chip control
next to the `mode` radio + prop pass-through to <DateRangePicker>.
Browser confirmed: clicking 'year' chip collapses BOTH start and end
inputs to a single `2026` segment. 'month' shows MM/YYYY on both.
'date' restores MM/DD/YYYY.
What's still pending (next commit):
- Year-grid and month-grid views for the range popover. Currently
the day calendar still renders regardless of kind. The range
picker's popover view-mode needs RangeCalendar to gain a `view`
prop and the eidos surface to dispatch to YearRangeView /
MonthRangeView components (range selection state machine: first
click sets start, second click sets end).
Verification: 0 type errors, 18/18 tests across the date-* soma
suite, 67/67 component:audit PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
6299e6ebc6 |
pickers: Footer parts = pure composition (drop *Button props)
User correctly pointed out: button visibility should not be exposed as
properties on the picker root. It should be expressed via composition
— if you include <DatePicker.Clear/> inside <DatePicker.Footer/>, it
shows; if you omit it, it doesn't. Same as how Header parts work, and
extensible to derivatives (date-range-picker follows the same rule).
This commit removes the visibility props + visibility checks. Pure
composition wins.
soma:
- DatePickerProvider opts: drop clearButton / cancelButton /
closeButton. The root soma component drops the props + the
readableActive passes. Same for DateRangePickerProvider.
- types.ts: drop the prop declarations + JSDoc.
- Test fixtures: drop the state() entries for the removed opts.
eidos parts:
- date-picker-clear / cancel / close: drop the `visible` $derived
and the {#if visible} guard. Render unconditionally.
- date-range-picker-clear / cancel / close: same.
- date-picker-footer / date-range-picker-footer: also drop the
combined `visible` $derived. The Footer container always renders
whatever children are composed inside.
Modal mode: previously the Close part forced itself visible whenever
mode='modal'. That magic is gone too — the consumer is now responsible
for including <Close/> if mode='modal'; otherwise the modal has no
exit affordance (and that's documented in the Close part's comment).
demos:
- Drop clearButton/cancelButton/closeButton state vars.
- Drop the prop pass-through on <DatePicker> / <DateRangePicker>.
- Drop the 'footer buttons' switch group.
- Drop snippet code refs to those props.
- Keep the same <Footer><Clear/><Cancel/><Close/></Footer> markup
inside the calendar branches — now visibility is purely structural.
Verification: 0 type errors, 7/7 date-picker + date-range-picker
soma tests, 67/67 component:audit PASS. The picker still renders
with all three buttons by default (because the demos compose them).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
0956aa1c37 |
date-picker: kind derives segments at soma layer + year/month views
User correctly pointed out: the input segments depend on the kind of calendar — they're *derived* values, not independently controlled. Filtering segments in the demo snippet (commit A) was wrong; the derivation belongs in the DateField provider. This commit refactors the contract + lands the year/month-grid views (combining commit B + C into one). Architecture fix (segments derive from kind): - DateFieldProvider opts gain `kind: 'date' | 'month' | 'year'`. - `segmentContents` filters `allSegmentContent.arr` by a derived `visibleDatePartsByKind` set, collapsing runs of literals and trimming leading/trailing separators. Time segments (hour/minute/ second/dayPeriod) are passed through untouched — `kind` is orthogonal to `granularity`. - DateField root component accepts `kind` prop and threads it. - DatePicker root forwards its `kind` to the DateField provider it creates internally. - DateRangeField passes `kind: 'date'` as a constant for now; range propagation lands in commit D. - Test fixtures extended with the new opt. New Eidos parts (commit B + C in one shot): - `<DatePicker.YearView>`: 3×4 decade grid centred on placeholder's decade boundary. Header shows the decade range with prev/next decade buttons. Click on a year sets value to (year, 1, 1) and calls handleDateSelect (closes if closeOnDateSelect=true inline). - `<DatePicker.MonthView>`: 3×4 month grid for the placeholder's year. Localised month names via DateFormatter. Header shows the year with prev/next year buttons. Click sets (year, month, 1). - CSS for both: shared 3-column grid layout, hover surface-overlay background, selected cell uses primary-solid + content-on-solid. focus-visible outline. prefers-reduced-motion honoured. Demo wiring: - Removed the local `filterByKind` helper — soma derives it now. - The snippet just iterates `segments` as it comes. - The popover content branches on `kind`: Calendar for 'date', MonthView for 'month', YearView for 'year'. The Footer renders in all three branches. - An $effect re-opens the popover whenever `kind` changes (clicking the chip outside the popover would otherwise close it in inline mode). What this commit DOES NOT do (commit D): - Propagate `kind` to date-range-picker (start + end inputs + the range-calendar popover view-mode). - date-range-field consumes `kind: 'date'` only for now. Verification: 0 type errors, 11/11 date-field + date-picker tests, 67/67 component:audit PASS. Browser confirmed: - kind=date → MM/DD/YYYY input + day calendar - kind=month → MM/YYYY input + month grid (2026) - kind=year → YYYY input + year grid (2020 – 2031) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
46ce1d800d |
date-picker: kind prop (date/month/year) — commit A scaffolding
User wants Chakra-style behavior: the chip in the demo should drive
both the visible segments in the input AND the calendar view
(year-only grid, month-only grid, day calendar). The previous
'readonly segments' chip was a Bits-style input lock, not a picker
kind switch — wrong contract.
This commit lays the scaffolding for the Chakra model, in four
incremental landings (A → D). It's the FIRST landing.
What changes:
- DatePickerKind type ('date' | 'month' | 'year') exported from soma.
- DatePickerProvider opts gain a `kind` slot; the root .svelte
defaults to 'date' and threads it through readableActive.
- exports.ts surfaces the new type alongside DatePickerMode.
- The root <div data-date-picker> emits data-kind for downstream
CSS / picker parts to consume in subsequent commits.
- Test fixture extended with the new opt.
Demo (date-picker single):
- Chip control renamed from 'readonly segments' to 'kind' with values
date / month / year. A hint shows the current input format
(YYYY / MM/YYYY / MM/DD/YYYY).
- New `visibleDateParts` derived set drives a `filterByKind` helper
applied to the segment snippet, so the input renders the right
subset on first selection. Literals (separators) between dropped
parts are removed; leading/trailing literals are trimmed.
- Snippet code refs updated: closeButton && readonlySegments line
replaced with kind !== 'date' && ` kind="${kind}"`.
What this commit DOES NOT do (next commits):
- B: render the year-grid in the calendar popover when data-kind=year.
- C: render the month-grid when data-kind=month.
- D: propagate to date-range-picker + wire the soma date-field to
drop segments based on kind (currently the filter lives in the
demo snippet — works for single-picker but isn't a contract for
third-party consumers).
Verification: 0 type errors. Browser-confirmed: clicking 'year' chip
collapses the input to a single '2026' segment and stamps
data-kind='year' on the picker root. Calendar still shows day-grid
(that's commit B's scope). 67/67 component:audit PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
3956a5d39e |
picker demos: open by default + footer buttons enabled
User report: 'no veo que haga nada nuevo en la demo' — the new canonical Footer/Clear/Cancel/Close parts existed but were invisible on first load because every *Button toggle defaulted to false and the popovers opened closed. The user reloaded, saw the same baseline as before, and concluded nothing had landed. Fix: - date-picker demo: open=true, closeOnDateSelect=false (so the user can pick a date without the popover collapsing while exploring), clearButton=true, cancelButton=true, closeButton=true. Result: on first load the calendar is visible with 'Borrar fecha · Cancelar · Cerrar' below it, in the active locale. - date-range-picker demo: same — open=true, clear/cancel/close all default to true. The comment claiming the popover should stay closed because 'segments are for keyboard entry' is replaced; the demo's purpose is to surface the API, and the segments are still reachable by Tab-focus even when the popover is open. These changes are demo-only — the actual provider defaults stay inline/false. Production consumers must opt in. 0 type errors. Browser verified: Footer renders with all three i18n-driven labels at the bottom of the calendar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
bf9dc8934d |
pickers: visible readonly segment styling + date-picker demo control
User report: setting startReadonlySegments/endReadonlySegments in the date-range-picker demo didn't change anything visible, and the date-picker demo had no equivalent control. Root cause #1 (visible): the readonly segment styling was background: var(--color-neutral-track) — primitive-neutral-3 — too close to the surface background in both themes. Mechanically the lock was working (data-readonly stamped, keyboard updates blocked) but the visual delta was invisible. Fix: add segment-readonly-color + segment-readonly-decoration tokens to the recipe. Read-only segments now render with - background: var(--color-surface-overlay) (clearer step from default) - color: var(--color-content-secondary) (slightly faded) - text-decoration: underline dotted var(--color-content-muted) - cursor: default both in date-field.css and the date-range-picker per-component override. Root cause #2 (consistency): date-picker demo lacked the segment-lock chip group that the range demo has. Added segmentLock state + readonly-segments derived array (year / monthYear / date presets) + chip control + snippet integration. Now both pickers expose the prop identically. Verification (browser): clicking the 'monthYear' chip in date-picker demo stamps data-readonly on the month + year segments; the dotted underline + lighter color are visible. Range demo: clicking 'year' under 'start segments' affects only the start year segment, end stays editable. 0 type errors, 67/67 component:audit PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
394aeaa476 |
pickers: promote Footer/Clear/Cancel/Close to canonical Eidos parts
The footer affordances (Clear / Cancel / Close) lived inline in each
demo as picker-actions.svelte. Promoted to canonical Eidos surface so
the API is consistent and the contract is auditable.
morfo:
- date-picker + date-range-picker: added Footer (archetype 'footer'),
Clear/Cancel/Close (archetype 'trigger', kind 'public', optional).
Each part declares aria-label via idlangref and data-action="…".
- types.ts: added 'footer' to ARCHETYPE_VOCABULARY array + the
MorfoArchetype union (the type had it, the runtime list did not).
- lang catalogs: added clear / cancel / close idlangref entries to
date-picker.ts and date-range-picker.ts.
eidos:
- Created date-picker-footer/clear/cancel/close.svelte and the range
mirrors. Each part:
- Pulls the provider from context via DatePickerProvider.require()
(analogous DateRangePickerProvider.require() for the range).
- Renders nothing when the corresponding *Button opt is false; the
Close part stays visible whenever mode === 'modal' (modal pickers
need a way out — outside-click and Escape are blocked).
- Emits data-{component}-{part} + data-action so the recipe selector
matches the morfo declaration.
- aria-label resolves via uix.langs.ts('#?components.X.{action}|…').
- onclick calls provider.clear() / cancel() / commit() then forwards
any consumer-supplied onclick.
- index.ts barrels: registered Footer/Clear/Cancel + replaced
Close (was popover-close.svelte) with the new picker-aware Close.
Soma popover-close still drives the old aria; date-picker's Close
adds the modal-conditional visibility + commit semantics.
- *.css: folded the inline picker-actions styles into the recipes —
data-{name}-footer flex row + data-{name}-{clear,cancel,close}
buttons (clear/cancel margin-inline-end: auto so close sits flush
right). prefers-reduced-motion already covered.
soma:
- date-picker exports.ts: surfaces DatePickerProvider + DatePickerMode
+ datePickerAttrs so the Eidos parts can consume them (mirrors what
date-range-picker already exposed). No new behavior — just plumbing.
demo:
- date-picker + date-range-picker demos: replaced
<PickerActions /> with <DatePicker.Footer>
<DatePicker.Clear /><DatePicker.Cancel /><DatePicker.Close />
</DatePicker.Footer>. The two picker-actions.svelte files are
deleted.
Verification: 0 type errors, morfo:check PASS (Footer parts not
required in DOM; conditional visibility honored), component:audit
67/67 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
9bd9db408d |
date-picker: mode='modal' + footer (clear/cancel/closeButton)
Replicates the date-range-picker pattern on the single date-picker:
soma:
- DatePickerMode type ('inline' | 'modal') exported.
- Provider gains mode/clearButton/cancelButton/closeButton opts plus
commit() / cancel() / clear() action helpers; cancel() reverts the
value snapshot taken via watch() on the OPEN edge of opts.open.
- handleDateSelect now early-returns when mode === 'modal' so modal
pickers don't auto-close on selection.
- exports.ts surfaces DatePickerProvider + DatePickerMode for demo /
footer wiring (mirrors date-range-picker's barrel).
- Test fixture extended with the new opts (closeOnDateSelect stays
true by default; mode='inline', all buttons false).
morfo:
- scope=['soma','sema'], apg=dialog-modal.
- 6 events: open / close-commit / close-cancel / close-dismiss /
close-dismiss-outside / commit-clear. prewrite stamps data-last-action
with the causal exit reason so Sema can tint the exit animation.
- Calendar part declares the state machine (open/closed,
data-last-action, data-starting-style, data-ending-style) and the
modal-keyboard surface (Escape / Tab / Shift+Tab).
demo:
- New picker-actions.svelte mirrors the range demo: pulls provider via
context, renders Clear / Cancel / Close conditionally, forces Close in
modal mode. CSS is local to the file (uses --color-primary-* +
--color-surface-overlay tokens, no raw colors).
- +page.svelte adds mode radio chip group + 3 footer-button switches and
passes mode/clearButton/cancelButton/closeButton through to the Eidos
wrapper (which spreads to soma).
The Eidos wrapper needs no change — it already spreads everything via
...rest, so the new soma opts reach Soma without further wiring.
Verification: 0 type errors, 3/3 soma tests pass, morfo:check PASS,
component:audit 67/67 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
dfb64511de |
eidos: theming fixes + size/variant parity batch + docs
- theme: add surface.muted + content.muted to color contract (neutral-3 + neutral-10). Plugs 17 broken --color-content-muted and 3 broken --color-surface-muted references in recipes/components. - form.css: fix --color-neutral-element-hover typo → --color-neutral-hover. - archetypes.css + events.css: replace raw hsl/rgba indigo with color-mix(var(--color-primary-solid) …) — no raw colors left in eidos. - combobox dark scrollbar: unscope ::-webkit-scrollbar rules in uix.css and duplicate --uix-line on :root + :root[data-mode='dark'] so portaled overlays (Combobox listbox, Popover, Dialog, Drawer) inherit the theme. - sizes: 20 components expand from sm/md/lg to xs..xl (form controls, text inputs, progress/meter, field/form) or xs..lg (nav controls: breadcrumb, pagination, tag-group, toolbar). Composite panels keep sm/md/lg deliberately. - variants: field + toolbar drop arbitrary ControlVariant narrowings; both expose all 3 (surface | outline | ghost) with new outline CSS. - pagination demo: disambiguate siblingCount/boundaryCount as "per side" in label + API table (Radix/MUI convention). - docs: CHECKLIST §C-2.6 (contract token presence) + §D-7.4 (chip parity) + §D-7.5 (size category) added. DEMO_AUTHORING §12.7 (chip parity) + §12.8 (size category cheatsheet) added. eidos/README + active_architecture.md sync texts/migration nomenclature. - PENDIENTES.md: normas N-1..N-5 implantadas en esta sesión. Verification: 88/88 eidos tests, 0 type errors, 67/67 component:audit PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
5d31645939 |
search-field demo: clear button is an X icon, not a word
The clear affordance is the standard search-input pattern: an X glyph inside the input, with the localized "Clear" / "Borrar" string moved to `aria-label`. Showing the word "Borrar" inline and then a hover button on top read as two redundant affordances. Replaced the visible text with `<X size="1em" decorative />` from the icon set. The aria-label still pulls from `'#?components.search-field.clear|Clear'` so screen readers get the localized affordance name. Code snippets in the API panel updated to match the new pattern (`<SearchField.ClearTrigger aria-label="Clear"><X ... /></...>`) instead of the text-only variant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
fd9fb633e0 |
search-field demo: clear button text follows the locale
The demo hardcoded the visible Clear button label inline:
<SearchField.ClearTrigger>Clear</SearchField.ClearTrigger>
So the toolbar's locale toggle (ES/EN) only swapped the aria-label
via the morfo's translationRef, not the visible text. With locale=ES
the panel showed a Spanish UI everywhere except the literal "Clear".
Replace with `uix.langs.ts('#?components.search-field.clear|Clear')`
so the visible button picks up the active catalog entry. Catalog
strings shortened to just `Borrar` / `Clear` (one-word action label
that fits the inline-button affordance — the previous "Borrar
búsqueda" was too long for the visible chrome and wrapped in two
lines). The full phrase still reads naturally for aria-label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
332ef63177 |
demos: theme scrollbars + add missing data-uix-radio chrome
Two visible inconsistencies in dark theme that the user flagged: 1. Scrollbars in code blocks (and any other scrollable inside the demos) rendered with the user-agent default chrome — bright white track + thumb on a dark surface. Only `[data-uix-rail]` (the left navigation column) had a themed scrollbar. Added a universal WebKit scrollbar theme scoped to `[data-uix-docs]`: transparent track, `--uix-line` thumb with 2px padding-clipped border so it reads as a thin rounded handle that hovers to `--uix-line-strong`. Both axes (10px width). Firefox handled via `scrollbar-color` + `scrollbar-width: thin` on the root. 2. `[data-uix-radio]` had zero CSS rules — the date-range-picker demo's `mode` (inline / modal) selector fell back to native radio inputs and an unstyled label, while its sibling controls used the themed `[data-uix-switch]`. Two switches-as-controls + one set of native radios in the same panel. Mirrored the data-uix-switch chrome at radio size: 16x16 circle, `--uix-line` border, `--uix-surface-sunken` background, inner pill in `--uix-accent` when checked, focus ring matches the rest. Label uses the same color + size as data-uix-switch-label. Verified in browser on date-range-picker demo: the `mode` radios now read as the same control family as the surrounding switches, and the code-snippet scrollbar is dark-themed instead of bright white. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
8ccd9de033 |
demos: homogenize input chrome across all demo Controls
The `[data-uix-control] input[type='text|number']` rule only covered two input types, so demos that used `<input type='date'>` (date- and date-range pickers' min/max/start/end inputs, calendar bounds, etc.) or `<input type='email|search|tel|url|password|time|month|week| datetime-local'>` (form/field demos) fell back to the user-agent chrome — different padding, borders, fonts, and the native calendar/clock indicator with no theme alignment. Replaced the type-specific selector with an exclusion list: [data-uix-control] input:not([type='checkbox']):not([type='radio']):not([type='range']) Every text-like input now picks up the demo's standard chrome (6px / 32px / `--uix-line` border / `--uix-surface` bg / focus ring). Checkbox + radio keep their bespoke toggle treatments; range keeps its native track with the demo accent. Added: - Consistent `min-block-size: 32px` + `box-sizing: border-box` so inputs line up vertically with the demo's button-style pill controls. - Disabled state: 0.55 opacity + `not-allowed` cursor. - `::-webkit-calendar-picker-indicator` recolored for date/datetime-local/month/time/week inputs — inverted 70% so the calendar/clock icon reads on dark theme and matches the field text tone, with full opacity on hover. - `::-webkit-search-cancel-button` recolored. Verified in browser: date-range-picker demo's start/end/minValue/ maxValue inputs now match minDays/maxDays (number) and label/name (text) inputs. Form demo Field-rendered inputs and the controls panel both read as one consistent surface. date-field controls panel (locale select + name text + min/max date inputs) matches. Closes #47 in the task list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
491e4be75c |
file-upload demo: ship valid PNG bytes so previews render
The demo seeded `cover.png` with `new Uint8Array(size)` — empty bytes. `URL.createObjectURL` then handed a blob the browser couldn't decode as PNG, so the preview rendered the user-agent broken-image icon next to the row. Fix: when the demo file type starts with `image/`, prepend a valid 1x1 transparent PNG header before the size-padding bytes. The browser decodes a 1px image cleanly and the preview slot stays empty-looking (which is the intended visual) instead of showing a broken-image affordance. atob lives inside the `typeof File` runtime check so SSR doesn't choke on it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
383e2ff7f7 |
date-range-picker: mode prop + footer button props + revert group chrome
User feedback batch from incidencia 2026-05-20:
1. Modal vs inline mode
- New `mode: 'inline' | 'modal'` prop on the soma `DateRangePicker.Provider`.
- Modal wires the popover `modal: true` → outside-click and Escape are
ignored; user must commit via the footer Close button (or revert via
Cancel).
- Provider exposes helpers `clear()`, `cancel()`, `commit()` for the
footer. `cancel()` restores the value snapshot taken on the OPEN edge
(captured via a `watch` on `opts.open` true-edge transition).
2. Footer buttons as boolean props
- `clearButton`, `cancelButton`, `closeButton` props on the picker.
The footer renders only if at least one is true. In `mode='modal'`
the closeButton is forced on (the user always needs an exit).
- `picker-actions.svelte` in the demo reads the picker context via
`DateRangePickerProvider.require()` and renders the enabled buttons
against `provider.clear/cancel/commit`.
3. Range field shape (revert to Chakra-style two boxes)
- Removed the `data-date-range-field-group` wrapper from the demo so
the start and end inputs are rendered as two separate boxed fields
with the icon embedded in the end box, matching Chakra's layout.
- The recipe CSS rules for `data-date-range-field-group` stay
available as an opt-in for consumers who prefer the unified pill.
4. Demo defaults
- `open` starts at `false` so the picker exercises the real
open/close flow when the user clicks the trigger — the segments are
for direct keyboard entry, the popover is for visual exploration.
- Mode toggle (inline / modal) + footer button switches surfaced as
controls in the demo.
5. Plumbing
- DateRangePickerOpts gains `mode`, `clearButton`, `cancelButton`,
`closeButton` (StateProps for mode, ActiveProps for the booleans).
- `DateRangePickerProvider` and `DateRangePickerMode` are now re-
exported from the soma barrel for consumers that wire footer
actions in the calendar tree.
- Test factory updated to seed the new opts.
Verified in browser: trigger opens; click-outside in modal mode is
ignored; Close commits & closes; Cancel reverts to snapshot & closes;
Clear empties the range & keeps open. Heading "May – June 2026" (year
collapse) and centered per-calendar titles still working from the
previous commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
0d164b54de |
date-range-picker: unify start + end into a single input chrome
Closes incidencia point B (range field shouldn't show two separate
boxes). New CSS contract: `[data-date-range-field-group]` on the
wrapper element collapses the chrome around its descendants:
- The group owns the border, background, focus ring, invalid state,
variant/ghost treatment, and disabled opacity.
- Each `[data-date-range-field-input]` inside loses its own border,
background, and padding; it just contributes its segments to the
shared row.
- The "to" separator (.range-separator or `[data-range-separator]`)
picks up the field's literal tone.
- The embedded trigger from the previous commit sits flush at the
trailing edge of the group, since `data-date-field-input` already
detects the embedded case.
Demo: add `data-date-range-field-group` to the `.range-field-row`
wrapper. Visually now reads as one input pill: `05 / 20 / 2026 to
06 / 03 / 2026 📅`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
3b464ee361 |
range-calendar: smart heading + per-calendar title in header
Closes incidencia points D + E:
- Heading collapses the shared year when both visible months are the
same year: "May – June 2026" instead of "May 2026 – June 2026".
Cross-year still renders both ends explicitly ("June 2025 – July
2026"). Implemented in `RangeCalendarProvider.headingValue`.
- date-range-picker demo: drop the per-month title that previously
rendered BELOW each grid (duplicate of the main heading). Instead,
render one centered title per visible calendar INSIDE the header
bar between the prev/next buttons. The grid below now shows just
the weeks. A `range-header-titles` grid container splits the
available header space equally across `month-count` titles, so
each label sits centered over its calendar grid.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
5f8adbeca6 |
date-picker + date-range-picker: trigger embedded inside the input
The calendar icon now lives INSIDE the date input as a flush trailing affordance, not as a separate sibling button. The change is markup + recipe CSS only — no API change. - Demo (date-picker): drop the `.picker-control` grid wrapper; put `<DatePicker.Trigger />` as the last child of the Input snippet, after the segments. Codeblock snippets updated to match. - Demo (date-range-picker): keep the dual-input + separator layout but move `<DateRangePicker.Trigger />` into the END input, since the trigger represents the trailing affordance of the whole range. Both code snippets updated. - Recipe CSS (both): add nested rules that detect the embedded case (`[data-date-field-input] > [data-date-picker-trigger]`, `[data-date-range-field-input] > [data-date-range-picker-trigger]`). Embedded trigger drops its own border/background, matches the segment height, sits flush at the trailing edge with `margin-inline-start: auto`, and uses the segment focus ring on hover/focus instead of the standalone field ring. The standalone (sibling) treatment stays available for consumers who prefer the icon as a separate control. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
39eb0eec86 |
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo + Soma provider hardening) plus a checklist-driven audit pipeline that scores all 67 morfo components against doctrinal completion criteria. New components: - date-picker: full popover-anchored picker over date-field + calendar, with calendar/content/trigger parts and demo route. - date-range-picker: standalone wrapper with own calendar/grid/segment surface, demo route, and recipe CSS. - Both wrappers follow Option C disciplined (root + parts attached via explicit assignment, no Object.assign). Supporting Soma changes: - range-calendar provider tightened (211 LOC of behavior, 167 LOC of tests), README brought up to component doctrine. - date-field, date-picker, date-range-field, date-range-picker Soma providers + READMEs updated for new wrappers. - popover provider/close gain props needed by the picker wrappers. Morfo updates: - date-picker / date-range-picker / range-calendar morfos refined for the new APIs (parts, events, ARIA). Audit infrastructure (new): - src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across morfo / eidos wrapper / recipe CSS / demo / README / cross-layer scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION and DEMO_AUTHORING_GUIDE. - scripts/component-audit.ts + `npm run component:audit` — regex parser over all 67 components, emits tmp/component-audit.md with summary scoreboard + per-component findings. Validates against canonical SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS. - Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip, date-range-picker). Top systemic gaps: translations.label (49), README Gaps/Comparativa/Baseline sections (87 combined), keyboard /event ratio under-declaration (15), apg URL absent (19). Misc: - src/uix/kimi-audit-eidos.md — supplementary audit notes. - .gitignore: ignore .codex-* agent scratch artifacts at repo root. - continue.md + READMEs updated through the migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
d6f550554d |
Add Eidos calendar component
|
5 months ago |
|
|
fb5dd0919c |
Advance Eidos demos and form validation docs
|
5 months ago |
|
|
09e2618787 |
Continue Eidos component migration
|
5 months ago |
|
|
bddfff9e39 |
Add UIX permutation axis coverage
|
5 months ago |
|
|
277f1f24dc |
Expand UIX permutation route coverage
|
5 months ago |
|
|
6508c7e562 |
Instrument UIX permutation demos
|
5 months ago |
|
|
fbacd1133d |
Remove frontend art
|
5 months ago |
|
|
e6e438b62a |
Tighten uix audit contracts
|
5 months ago |
|
|
2df58074a1 |
Use local fonts in UIX docs
|
5 months ago |
|
|
d822597ffe |
Generate Eidos recipe tokens from config
|
5 months ago |
|
|
b121554918 |
Fix UIX docs theme and popover dismiss
|
5 months ago |
|
|
501f55b0cc |
Align Active docs with prefs projection
|
5 months ago |
|
|
26b9b83eb5 |
Move prefs projection out of ActiveUix
|
5 months ago |
|
|
c12b1c48c8 |
Refactor active uix architecture
|
5 months ago |
|
|
cb5097b3c3 |
Add active eidos runtime and morfo lang registry
|
5 months ago |
|
|
7214ae7940 |
uix: drawer/popover/toast/accordion eidos wrappers + sema packs + /uix docs scaffold
- eidos: migrate drawer / popover / toast / accordion to wrapper subdirectory pattern (provider + parts + tokens + recipe per component); avatar wrapper added; toggle/switch/collapsible recipe polish. - sema: per-component packs (accordion, drawer, popover, toast); engine / resolver / event / sema-map updates; visual channel hold refinements. - morfo: drawer event surface (variant, dragToDismiss, isDragging, minSize/ maxSize, onDragStart); popover/toast/accordion event refinements; schema + types updates. - soma: drawer provider rewrite (variant, dragToDismiss, isDragging binding, drag callbacks); toast provider/toaster restructure with toast-main + toast-status parts; popover content/arrow polish; gesture/floating layer updates; manipulation layer scaffold; avatar removed (eidos owns it now). - web/routes: legacy demos updated for new APIs; new /uix docs scaffold (web/routes/uix/) with Radix-style shell, theme/lang/format/semantic prefs, and standardized component template (Drawer page first — 6 tabs, live controls, drag-hint arrows, scroll-safe body wrapper). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
e6862265e2 |
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color
sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
29ce812f94 |
collapsible: flat <Collapsible> ergonomic default + compound parts
Per the doctrine update in GUIA §13: eidos for multi-part components
exposes BOTH a flat default that auto-composes Provider/Trigger/Content
AND the compound parts for advanced cases. Soma + morfo remain the
universal composable foundation underneath.
Why: with the previous "compound only" rule for multi-part, eidos
collapsible was a pure pass-through that added nothing operational —
the consumer wrote the same import + same shape as soma. The flat
default makes eidos earn its layer by collapsing the 90% case to a
single call:
// 90% case — flat ergonomic
<Collapsible bind:open>
{#snippet trigger()}Show details{/snippet}
<p>Body content.</p>
</Collapsible>
// Advanced — compound (parts in different subtrees, multi-trigger,
// conditional content)
<header><Collapsible.Provider bind:open>
<Collapsible.Trigger>Show</Collapsible.Trigger>
</Collapsible.Provider></header>
<aside>
<Collapsible.Content>...</Collapsible.Content>
</aside>
The architectural virtue: if the flat doesn't fit, a developer drops
to soma + morfo and composes freely. No vendor lock-in in the visual
layer; eidos is the design system convention, soma is the universal
primitive.
Files:
- src/uix/eidos/components/collapsible/collapsible.svelte (NEW): flat
default that auto-composes Provider + Trigger + Content via a
`trigger` snippet for the toggle label and `children` for the body.
- src/uix/eidos/components/collapsible/types.ts: adds
`CollapsibleFlatProps = SomaCollapsibleProps & { trigger?: Snippet }`.
- src/uix/eidos/components/collapsible/index.ts: exports `default`
(flat) plus named `Provider`/`Trigger`/`Content` (compound).
- src/uix/eidos/components/collapsible/{trigger,content,provider}.svelte:
fixed children destructuring so snippets propagate cleanly to soma
(was previously spreading $props() blindly which can lose snippet
bindings in Svelte 5).
- src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md: NEW §13 "Doctrina del API:
soma compound, eidos opinionado" formalises the two-shape rule and
the architectural virtue (soma+morfo as universal foundation).
Renumbered §13/§14 → §14/§15.
- web/routes/collapsible/+page.svelte: live preview now uses the flat
form; eidos snippet shows the flat shape; soma snippet keeps the
compound shape; Notes tab updated.
- src/uix/eidos/components/collapsible/README.md: rewrote API section
with both shapes + the "if neither fits, use soma+morfo" escape.
Tests 218/218 green. npm run check baseline 39.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d1850a7de4 |
demos/collapsible: add soma snippet alongside eidos in Live tab
The collapsible demo only rendered the eidos snippet, which made the
"compound shape is the same" doctrinal point invisible: a reader
couldn't see that soma and eidos expose the same multi-part API,
differing only in import path and what each layer guarantees.
Now both code blocks stack in the Live tab:
- soma: includes the `<script>` boilerplate (`let open = $state(false)`)
to make the headless/structural-only nature explicit
- eidos: same shape, different import, no setup boilerplate (consumer
binds open directly with `onOpenChange={handleChange}` for the demo
trace)
Per doctrine §10, only single-part components (toggle/switch) get a
flat default export — multi-part keeps compound on both layers, so the
two snippets share shape. That's correct. The DIFFERENCE is the import
path, the visible boilerplate, and what each layer adds (soma:
structural contract; eidos: same surface + recipe CSS).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
a9ad71118b |
fix: dark mode + collapsible demo layout shift
Two real bugs reported on the collapsible demo:
1. **Dark mode broken across all migrated components.**
- The docs site layout sets `document.documentElement.dataset.theme =
'light'|'dark'` — those are the selector names the docs CSS uses.
- The eidos themes (themes/base/{light,dark}.css) only respond to
`[data-theme='base-light'|'base-dark']`. Different namespace.
- Result: docs grays swap in dark mode but eidos doctrinal palette
(primary/secondary/neutral/affirm/fulfill/risk/threat/loss) stays
in light values, so component text renders dark on dark.
- Fix: each eidos theme now accepts BOTH selectors. `[data-theme=
'base-light'], [data-theme='light'], :root { ... }` and equivalent
for dark. Existing consumers that use `base-*` keep working; the
short `light/dark` aliases handle the docs site convention.
2. **Collapsible trigger jumps from center to left when expanded.**
- The preview-stage uses `display: flex; justify-content: center;
align-items: center;` — natural for buttons/switches that don't
grow.
- `Collapsible.Provider` is `display: block` so it shrink-fits its
content as a flex item. Closed = trigger-width (centered visually);
open = trigger + content width (longer, so the visual center
anchors trigger at the left edge).
- Fix: collapsible demo overrides the preview-stage style with
`justify-content: flex-start; align-items: flex-start` and wraps
the provider in a `max-inline-size: 32rem` panel. Disclosure
ergonomics need anchor-left + grow-down, not center.
- Also swapped the body's `--gray-50` (docs site token) for
`--color-surface-raised` + `--color-content-primary` (eidos
doctrinal tokens) so the panel inherits the dark-mode swap.
npm run check holds at the 39-error baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
d10281bc94 |
collapsible: migrate to eidos wrapper pattern (third pilot)
Third component migrated to the eidos wrapper subdirectory pattern,
following the new "check air baseline first" discipline.
Air baseline recovered from commit 0a391408^:
- air/components/collapsible: 3 wrappers (Provider/Trigger/Content)
with `class` passthrough, `bind:open`, `onOpenChange`, manual
`air.interaction.play('expansion', enter|exit)` perceptual emit.
- No size, intent, color, or chevron slot.
Migration decisions (presented BEFORE coding per scope-approval rule):
- Two doctrinal events (`expand` + `collapse`, both emerge family,
sequence='pre', no intent — emerge is transitional). Replaces air's
manual `air.interaction.play` with `runtime.trigger('expand'|
'collapse')` which atravieses visual + sound + vibra channels.
- No intent/color/size — air didn't have them and disclosure is
non-evaluative per guide §3.1.
- Multi-part API: Provider + Trigger + Content (no flat default per
doctrine §10).
Files:
- src/uix/morfo/components/collapsible.ts: scope expanded to ['soma',
'sema'], events array added with two emerge events.
- src/uix/soma/components/collapsible/collapsible-provider.svelte.ts:
runtime.events handlers `expand`/`collapse` mutate state; toggle()
fires runtime.trigger(next ? 'expand' : 'collapse').
- src/uix/eidos/components/collapsible/{collapsible.css,
collapsible-provider.svelte, collapsible-trigger.svelte,
collapsible-content.svelte, types.ts, index.ts, README.md}.
- src/uix/eidos/components/collapsible.css (legacy flat) deleted.
- src/uix/eidos/index.css: import path updated to subdirectory.
- web/routes/collapsible/+page.svelte: full interactive testbed with
tabs (Live, Soma, Morfo, Sema, Eidos, Notes) mirroring switch's
structure. Sema tab shows per-channel signature cards for both
expand and collapse events.
Disciplina formalizada en CLAUDE.md hand-off + memory entry: "Check
air baseline first" before any future eidos migration. Switch shipped
without this check and lost ResponsiveProp<Size> (regression). Don't
repeat with collapsible/dialog/drawer/popover/toast/avatar.
npm run check holds at the 39-error baseline. Tests 218/218 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
48b646ecc4 |
demos/switch: full Sema tab visualisations matching toggle
The earlier switch demo Sema tab was a stub linking to the toggle docs page. Now it ships the full per-channel signature cards (motion preview with x8 amplification, sound spectrum bars, color affects, presence preview), the live intent picker, and the Play button that fires on the live switch via uix.semantic.emit. Mirrors the structure used in toggle so the two pilots stay symmetric for future migrations. The play-button targets data-switch (was data-toggle in the toggle demo). The intent-picker drives the same `intent` state that the soma-driven recipe consumes through data-color, so Play replays motion + sound + the data-color theme on the actual track without flipping state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
4d26761263 |
docs + switch: align active docs to canon, migrate switch to eidos wrapper
Phase 4 (docs alignment with the canonical guide):
- active_architecture.md — Dialog example refactored to the new event
shape (target inside semantic, explicit verb + sequence). Verbs
table grouped by family with the post-canon vocabulary (select/
toggle moved to commit, edit removed from handle, new verbs added).
Section 10 (Estado actual) updated to 2026-05-08 with current
progress: doctrina cerrada, eidos en producción, SoundChannel
eager-init, switch migrated, etc. "Para profundizar" §14 points at
the canonical guide.
- morfo/README.md — Step 5.5 added covering the doctrinal event
shape with all fields (semantic.family, .verb, .target, .sequence,
.intent) and the two naming shapes ({verb}-{variant} and
{family}-{verb}).
- sema/README.md — verbs section restructured to Record-by-family
matching the runtime; documents the verbs that moved between
families per the canon. Naming shapes section updated with both
forms and validateEventName signature.
- CLAUDE.md — "Session hand-off" rewritten with the post-canon
state. Pointer at GUIA_IMPLEMENTACION_SEMAUIX.md as the canonical
source. Pending list refreshed: switch is the immediate next
component, persistence/a11ySemantic/polymorphism deferred until
real consumers appear.
Switch migration to the eidos wrapper pattern:
- morfo/components/switch.ts — adds events: [{ commit-toggle }] with
the doctrinal shape (verb=toggle, sequence=post, intent fromProp
binding to the 4 supported intents). New data-* (data-color with
the 6-token Toggle/Switch subset, data-readonly, data-invalid,
data-required) and corresponding aria-*. scope expanded to
['soma', 'sema'].
- soma/components/switch/types.ts — adds intent, color, readonly,
invalid, aria-labelledby props with full JSDoc. Subset constrained
per guide §3.1 (Toggle/Switch row).
- soma/components/switch/switch-provider.svelte.ts — runtime-direct,
Field integration via FieldProvider.get(), isDisabled/isReadonly/
isRequired/isInvalid OR-merge derivations, intent ↔ color
resolution (intent wins when evaluative), runtime.trigger(
'commit-toggle') in onclick.
- soma/components/switch/components/switch.svelte — wires the new
props through bindProps.
- eidos/components/switch/{switch.css, switch.svelte, types.ts,
index.ts, README.md} — full subdirectory pattern, mirroring
toggle. Recipe drops legacy success/warning/danger/info selectors;
declares the 6 doctrinal data-color values directly. Wrapper
auto-renders Switch.Thumb inside the track and exposes an optional
`thumb` snippet for in-thumb glyphs.
- eidos/components/switch.css (legacy flat) deleted; eidos/index.css
updated to import the subdirectory recipe.
- web/routes/switch/+page.svelte — interactive testbed with all soma
props as live controls + tabs (Live, Soma, Morfo, Sema, Eidos,
Notes). Mirrors toggle's structure.
npm run check holds at the 39-error baseline. Tests 217/217 green.
Pending:
- Migrate collapsible / dialog / drawer / popover / toast / avatar
to the eidos wrapper pattern.
- Persistence + holds-by-intent — defer until first signal.warn /
signal.alert consumer.
- a11ySemantic + polymorphic events — defer until concrete need.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
f5a2a7fb49 |
eidos: pilot wrapper pattern + doctrinal API conventions
Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper +
types + index + README) replacing the flat CSS-only form. Pattern is
documented in eidos/components/README.md and the toggle README.
Shared types live in eidos/lib/types.ts. First export is `Size` (8 values
xxs..xxl + full); components narrow with `Extract<Size, ...>` per the
per-component-subset doctrine. No `Eidos` prefix on types — module path
already conveys the layer.
API doctrine:
- soma stays compound (Toggle.Provider) for symmetry with multi-part
- eidos exports both default + Provider so single-part components
accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>`
(compound-style consumers)
SoundChannel eager-init fixes the autoplay race: AudioContext is created
+ resumed synchronously on the first user gesture (capture-phase
listener registered in the constructor), avoiding the previous race
where the first emit() scheduled the resume in a microtask outside the
gesture window.
Demo page (web/routes/toggle/+page.svelte) restructured so the live
preview renders ALWAYS above the tablist — Sema-tab Play buttons can
fire on the real toggle. Motion preview amplifies scale ×8 visually
only; doctrinal values stay in the <dl>.
Conventions 7-13 added to src/docs/sema-implementation-guide.md
covering: directory structure, wrapper composition, no Eidos prefix,
soma compound vs eidos flat, iconOnly sr-only body, sound eager-init,
docs-preview amplification.
CLAUDE.md gets a session hand-off block listing where things stand and
next concrete steps (migrate switch/collapsible/dialog/drawer/popover/
toast/avatar; wire topbar sound mute to masterGain; rename theme
tokens to drop the success/warning/danger fallback aliases).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
4546e448bd |
demos: web/routes/* for the 8 migrated soma components
First demos under the new web/routes location (configured by phase C
of the cleanup as `kit.files.routes`). One demo per component:
toggle, switch, collapsible, avatar, dialog, drawer, popover, toast.
Layout (web/routes/+layout.svelte):
- Bootstraps the UIX via createActiveUix({ lang: { schema: {},
defaultLocale: 'es' } }) — minimal config, full UIX surface.
- Wraps in <Uix> which sets the active-uix context and creates the
Soma scope so providers can require it.
- Imports the eidos index.css so the visual layer is applied.
- Tiny demo-only stylesheet (web/routes/layout.css) for the page
chrome (topbar, demo grid, sections) — explicitly NOT part of
eidos.
Each demo page exercises the public API:
- toggle: bound, initially-pressed, disabled
- switch: bound, disabled
- collapsible: bound show/hide, disabled trigger
- avatar: loaded, error fallback, delayed
- dialog: basic with overlay + focus trap + escape dismissal
- drawer: right / left / bottom (with handle)
- popover: click + hover trigger
- toast: 4 intents (fulfill, risk, threat, neutral) via createToaster
Verified end-to-end with scripts/demo-probe.ts (Playwright):
- All 9 routes (landing + 8 demos) serve 200.
- Zero console errors except avatar's pravatar.cc network failure
(sandbox limitation, not a real bug).
- Interactive checks: dialog open shows modal + overlay backdrop +
focused Cancel button; popover open positions floating content
next to trigger; toast Fulfill shows the green-tinted shadow
driven by data-event-intent='fulfill' from sema's visual channel
reacting to the morfo's announce event semantics — confirms the
full morfo → sema → eidos pipeline works end-to-end on a migrated
soma component.
scripts/demo-probe.ts: small Playwright runner that loads each route,
captures console errors, takes a screenshot of the static state, then
clicks the trigger on dialog/collapsible/popover/toast for a second
"open" screenshot. Output goes to g:/tmp/demos/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
8e800031fe |
cleanup phase C: reconfig SvelteKit + aliases + web/routes skeleton
svelte.config.js / vite.config.ts:
- Drop dead aliases: \$lib, \$glob, \$langs, @/ling, @/logr, @/glob,
@/actx, @/uiux, \$terra (their target dirs were removed in phase B).
- Add \$reactive at the top level (was previously hidden under the
"legacy" block; the target src/libs/reactive is current).
- Set kit.files.routes to "web/routes" — demos move out of src/ and
will be re-authored from scratch alongside the new architecture.
- vite.config.ts: extract the alias map into a single `aliases` const
shared by both Vite resolve and the server-test project, instead of
duplicating it.
web/routes/+layout.svelte + +page.svelte: minimal SvelteKit boot so
`npm run dev` and `npm run build` find a routes tree. Real demos
(active-uix, soma, morfo, sema, eidos) will be added as sibling routes
once the runtime is wired.
Verified: `npx svelte-kit sync` regenerates .svelte-kit/tsconfig.json
without any of the dead aliases. .svelte-kit/types/web/routes/ is
populated, confirming the new routes path is active.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |