soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
New components (10):
- Tabs: roving tabindex, automatic/manual activation, onFocusChange
- ToggleGroup: single/multiple, roving tabindex, deselectable
- Toast: imperative API (createToaster), swipe-to-dismiss, hotkey, promise()
- ScrollArea: custom scrollbars, visibility modes, thumb drag, position tracking
- Splitter: resizable panels, collapsible, flex-grow sizing, double-click toggle
- Stepper: multi-step wizard, linear mode, step state, PrevTrigger/NextTrigger
- TreeView: WAI-ARIA tree pattern, expand/collapse, typeahead, * expand siblings
- Pagination: page range algorithm, siblingCount/boundaryCount, slice() utility
- Toolbar: WAI-ARIA toolbar, roving tabindex, integrated ToggleGroup, Separator
- Editable: inline edit, activation/submit modes, draft tracking, auto-resize
Documentation:
- README.md for all 22 components with anatomy, ARIA, data-*, keyboard, usage
- Comparison tables vs ark-ui, bits-ui, radix-ui for every component
Infrastructure:
- Added PrimitiveAnchorAttributes to $soma/types/html.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
<script lang="ts">
|
|
|
|
|
import * as Tabs from '$soma/components/tabs';
|
morfo: permutation runner v2 — URL-driven state seeds + RTL variant
v1 shipped click-based state cycles. v2 adds URL-seeded initial state so
the runner can reach any state the component exposes without walking a
click sequence, and runs every instrumented demo TWICE in CI (LTR + RTL)
to catch direction-specific regressions like the slider thumb transform
that landed this week.
## New test-route infrastructure
- `src/routes/test/soma/_perms.svelte.ts` — parses `?perm.X=Y` query
params into a typed `Perms` record (naive coercion: `'true'` → boolean,
digits → number, else string). Exposed via `Perms.getOr({})` context.
Underscore prefix so SvelteKit does not route it.
- `+layout.svelte` — parses perms once at mount, sets context, honours
`?perm.dir=rtl` and `?perm.locale=en` by routing through
`App.setDir` / `App.setLocale` so the existing control strip stays in
sync.
## Demo opt-in
Three demos seed initial state from URL params (v1 + v2 combined):
- `dialog` — `?perm.open=true` lands open.
- `tabs` — `?perm.value=tab-2` lands on that tab.
- `toolbar` — `?perm.format=bold,italic` + `?perm.align=center` preselect
toggles.
## Runner extension
`scripts/permutation-check.ts` now runs each instrumented demo under a
matrix of URL variants (currently `ltr` + `rtl`), validates morfo at the
URL-seeded state BEFORE any clicks (logged as `[initial seed]`), then
runs the v1 click cycle. Output format updated to show per-variant
blocks.
Result: 3 instrumented demos × 2 variants = 22 step validations per CI
run. RTL seeding reproduces the specific state machine the slider
`translate(50%, -50%)` bug needed to fail — had v2 been in place that
day, the regression would have fired before shipping.
## Docs
`src/uix/morfo/PERMUTATION_RUNNER.md` gains the v2 section (layout
plumbing, demo opt-in, URL variant table, `[initial seed]` step
semantics) and marks v1 + v2 as shipped 2026-04-22. v3 (morfo-inferred
per-component axis matrix) and v4 (MutationObserver ordering for Sema)
remain on the roadmap.
## Verification
- `npm run check`: 0 errors in soma/morfo/test-route scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 demos · 22 steps across 2 variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
import { Perms } from '../_perms.svelte';
|
soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
New components (10):
- Tabs: roving tabindex, automatic/manual activation, onFocusChange
- ToggleGroup: single/multiple, roving tabindex, deselectable
- Toast: imperative API (createToaster), swipe-to-dismiss, hotkey, promise()
- ScrollArea: custom scrollbars, visibility modes, thumb drag, position tracking
- Splitter: resizable panels, collapsible, flex-grow sizing, double-click toggle
- Stepper: multi-step wizard, linear mode, step state, PrevTrigger/NextTrigger
- TreeView: WAI-ARIA tree pattern, expand/collapse, typeahead, * expand siblings
- Pagination: page range algorithm, siblingCount/boundaryCount, slice() utility
- Toolbar: WAI-ARIA toolbar, roving tabindex, integrated ToggleGroup, Separator
- Editable: inline edit, activation/submit modes, draft tracking, auto-resize
Documentation:
- README.md for all 22 components with anatomy, ARIA, data-*, keyboard, usage
- Comparison tables vs ark-ui, bits-ui, radix-ui for every component
Infrastructure:
- Added PrimitiveAnchorAttributes to $soma/types/html.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
|
morfo: permutation runner v3 — per-demo axis matrix via `<meta name="perm-axes">`
v2 ran every demo under a hard-coded `ltr` + `rtl` matrix. v3 lets each
demo declare which additional axes it honours — `orientation`, `disabled`,
`readonly`, `invalid`, `loading` — and the runner adds ONE variant per
declared axis (axis-at-a-time, not cartesian, so the matrix stays bounded
at O(N) instead of O(2^N)).
## Convention
```svelte
<svelte:head>
<meta name="perm-axes" content="dir,orientation,disabled" />
</svelte:head>
```
`dir` is universal (always runs). The other axes are opt-in: they only
produce variants when the demo both lists them in the meta AND wires the
corresponding state from `Perms.getOr(...)`. Without the meta, only
`ltr` + `rtl` run.
## Runner changes
- New `discoverAxes(base, morfoKebab)` pass: loads the baseline URL, reads
`<meta name="perm-axes">`, returns the declared axis list.
- `buildVariants(declaredAxes)` composes `ltr` + `rtl` + one `?perm.X=Y`
variant per declared axis from the `AXIS_FLIP` map.
- `[initial seed]` step validates morfo at each URL before any click,
so URL-seeded state alone is exercised (catches seed regressions even
when the click cycle never fires).
## Demo opt-in (this commit)
- `dialog` — `perm-axes="dir"` (no orientation / disabled / loading axes
apply to Dialog).
- `tabs` — `perm-axes="dir,orientation,disabled"` + wires `orientation` +
`disabled` to the first `Tabs.Provider`.
- `toolbar` — `perm-axes="dir,orientation"` + wires `orientation` to the
first `Toolbar.Provider`.
Result: 3 demos, 9 URL variants, 34 step validations per CI run
(v2 was 22 steps across 6 variants).
## Docs
`src/uix/morfo/PERMUTATION_RUNNER.md` v3 section: supported axis table,
3-step opt-in recipe, roadmap updated (v4 = morfo-inferred value cycling,
v5 = MutationObserver ordering for Sema).
## Verification
- `npm run check`: 0 errors in soma/morfo/test-route scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 demos · 34 steps · 9 variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
// Perm-runner v2/v3 seed. `?perm.value=tab-2` preselects, `?perm.orientation=vertical`
|
|
|
|
|
// flips axis, `?perm.disabled=true` disables the first Tabs.Provider.
|
morfo: permutation runner v2 — URL-driven state seeds + RTL variant
v1 shipped click-based state cycles. v2 adds URL-seeded initial state so
the runner can reach any state the component exposes without walking a
click sequence, and runs every instrumented demo TWICE in CI (LTR + RTL)
to catch direction-specific regressions like the slider thumb transform
that landed this week.
## New test-route infrastructure
- `src/routes/test/soma/_perms.svelte.ts` — parses `?perm.X=Y` query
params into a typed `Perms` record (naive coercion: `'true'` → boolean,
digits → number, else string). Exposed via `Perms.getOr({})` context.
Underscore prefix so SvelteKit does not route it.
- `+layout.svelte` — parses perms once at mount, sets context, honours
`?perm.dir=rtl` and `?perm.locale=en` by routing through
`App.setDir` / `App.setLocale` so the existing control strip stays in
sync.
## Demo opt-in
Three demos seed initial state from URL params (v1 + v2 combined):
- `dialog` — `?perm.open=true` lands open.
- `tabs` — `?perm.value=tab-2` lands on that tab.
- `toolbar` — `?perm.format=bold,italic` + `?perm.align=center` preselect
toggles.
## Runner extension
`scripts/permutation-check.ts` now runs each instrumented demo under a
matrix of URL variants (currently `ltr` + `rtl`), validates morfo at the
URL-seeded state BEFORE any clicks (logged as `[initial seed]`), then
runs the v1 click cycle. Output format updated to show per-variant
blocks.
Result: 3 instrumented demos × 2 variants = 22 step validations per CI
run. RTL seeding reproduces the specific state machine the slider
`translate(50%, -50%)` bug needed to fail — had v2 been in place that
day, the regression would have fired before shipping.
## Docs
`src/uix/morfo/PERMUTATION_RUNNER.md` gains the v2 section (layout
plumbing, demo opt-in, URL variant table, `[initial seed]` step
semantics) and marks v1 + v2 as shipped 2026-04-22. v3 (morfo-inferred
per-component axis matrix) and v4 (MutationObserver ordering for Sema)
remain on the roadmap.
## Verification
- `npm run check`: 0 errors in soma/morfo/test-route scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 demos · 22 steps across 2 variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
const perms = Perms.getOr({} as Record<string, string | number | boolean>);
|
|
|
|
|
let value1 = $state((perms.value as string) ?? 'tab-1');
|
soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
New components (10):
- Tabs: roving tabindex, automatic/manual activation, onFocusChange
- ToggleGroup: single/multiple, roving tabindex, deselectable
- Toast: imperative API (createToaster), swipe-to-dismiss, hotkey, promise()
- ScrollArea: custom scrollbars, visibility modes, thumb drag, position tracking
- Splitter: resizable panels, collapsible, flex-grow sizing, double-click toggle
- Stepper: multi-step wizard, linear mode, step state, PrevTrigger/NextTrigger
- TreeView: WAI-ARIA tree pattern, expand/collapse, typeahead, * expand siblings
- Pagination: page range algorithm, siblingCount/boundaryCount, slice() utility
- Toolbar: WAI-ARIA toolbar, roving tabindex, integrated ToggleGroup, Separator
- Editable: inline edit, activation/submit modes, draft tracking, auto-resize
Documentation:
- README.md for all 22 components with anatomy, ARIA, data-*, keyboard, usage
- Comparison tables vs ark-ui, bits-ui, radix-ui for every component
Infrastructure:
- Added PrimitiveAnchorAttributes to $soma/types/html.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
let value2 = $state('tab-1');
|
|
|
|
|
let value3 = $state('tab-1');
|
|
|
|
|
let value4 = $state('tab-1');
|
morfo: permutation runner v3 — per-demo axis matrix via `<meta name="perm-axes">`
v2 ran every demo under a hard-coded `ltr` + `rtl` matrix. v3 lets each
demo declare which additional axes it honours — `orientation`, `disabled`,
`readonly`, `invalid`, `loading` — and the runner adds ONE variant per
declared axis (axis-at-a-time, not cartesian, so the matrix stays bounded
at O(N) instead of O(2^N)).
## Convention
```svelte
<svelte:head>
<meta name="perm-axes" content="dir,orientation,disabled" />
</svelte:head>
```
`dir` is universal (always runs). The other axes are opt-in: they only
produce variants when the demo both lists them in the meta AND wires the
corresponding state from `Perms.getOr(...)`. Without the meta, only
`ltr` + `rtl` run.
## Runner changes
- New `discoverAxes(base, morfoKebab)` pass: loads the baseline URL, reads
`<meta name="perm-axes">`, returns the declared axis list.
- `buildVariants(declaredAxes)` composes `ltr` + `rtl` + one `?perm.X=Y`
variant per declared axis from the `AXIS_FLIP` map.
- `[initial seed]` step validates morfo at each URL before any click,
so URL-seeded state alone is exercised (catches seed regressions even
when the click cycle never fires).
## Demo opt-in (this commit)
- `dialog` — `perm-axes="dir"` (no orientation / disabled / loading axes
apply to Dialog).
- `tabs` — `perm-axes="dir,orientation,disabled"` + wires `orientation` +
`disabled` to the first `Tabs.Provider`.
- `toolbar` — `perm-axes="dir,orientation"` + wires `orientation` to the
first `Toolbar.Provider`.
Result: 3 demos, 9 URL variants, 34 step validations per CI run
(v2 was 22 steps across 6 variants).
## Docs
`src/uix/morfo/PERMUTATION_RUNNER.md` v3 section: supported axis table,
3-step opt-in recipe, roadmap updated (v4 = morfo-inferred value cycling,
v5 = MutationObserver ordering for Sema).
## Verification
- `npm run check`: 0 errors in soma/morfo/test-route scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 demos · 34 steps · 9 variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
let orientation = $state<'horizontal' | 'vertical'>(
|
|
|
|
|
(perms.orientation as 'horizontal' | 'vertical') ?? 'horizontal'
|
|
|
|
|
);
|
|
|
|
|
let disabled = $state((perms.disabled as boolean) ?? false);
|
soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
New components (10):
- Tabs: roving tabindex, automatic/manual activation, onFocusChange
- ToggleGroup: single/multiple, roving tabindex, deselectable
- Toast: imperative API (createToaster), swipe-to-dismiss, hotkey, promise()
- ScrollArea: custom scrollbars, visibility modes, thumb drag, position tracking
- Splitter: resizable panels, collapsible, flex-grow sizing, double-click toggle
- Stepper: multi-step wizard, linear mode, step state, PrevTrigger/NextTrigger
- TreeView: WAI-ARIA tree pattern, expand/collapse, typeahead, * expand siblings
- Pagination: page range algorithm, siblingCount/boundaryCount, slice() utility
- Toolbar: WAI-ARIA toolbar, roving tabindex, integrated ToggleGroup, Separator
- Editable: inline edit, activation/submit modes, draft tracking, auto-resize
Documentation:
- README.md for all 22 components with anatomy, ARIA, data-*, keyboard, usage
- Comparison tables vs ark-ui, bits-ui, radix-ui for every component
Infrastructure:
- Added PrimitiveAnchorAttributes to $soma/types/html.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:head>
|
|
|
|
|
<title>Tabs · Soma</title>
|
morfo: permutation runner v3 — per-demo axis matrix via `<meta name="perm-axes">`
v2 ran every demo under a hard-coded `ltr` + `rtl` matrix. v3 lets each
demo declare which additional axes it honours — `orientation`, `disabled`,
`readonly`, `invalid`, `loading` — and the runner adds ONE variant per
declared axis (axis-at-a-time, not cartesian, so the matrix stays bounded
at O(N) instead of O(2^N)).
## Convention
```svelte
<svelte:head>
<meta name="perm-axes" content="dir,orientation,disabled" />
</svelte:head>
```
`dir` is universal (always runs). The other axes are opt-in: they only
produce variants when the demo both lists them in the meta AND wires the
corresponding state from `Perms.getOr(...)`. Without the meta, only
`ltr` + `rtl` run.
## Runner changes
- New `discoverAxes(base, morfoKebab)` pass: loads the baseline URL, reads
`<meta name="perm-axes">`, returns the declared axis list.
- `buildVariants(declaredAxes)` composes `ltr` + `rtl` + one `?perm.X=Y`
variant per declared axis from the `AXIS_FLIP` map.
- `[initial seed]` step validates morfo at each URL before any click,
so URL-seeded state alone is exercised (catches seed regressions even
when the click cycle never fires).
## Demo opt-in (this commit)
- `dialog` — `perm-axes="dir"` (no orientation / disabled / loading axes
apply to Dialog).
- `tabs` — `perm-axes="dir,orientation,disabled"` + wires `orientation` +
`disabled` to the first `Tabs.Provider`.
- `toolbar` — `perm-axes="dir,orientation"` + wires `orientation` to the
first `Toolbar.Provider`.
Result: 3 demos, 9 URL variants, 34 step validations per CI run
(v2 was 22 steps across 6 variants).
## Docs
`src/uix/morfo/PERMUTATION_RUNNER.md` v3 section: supported axis table,
3-step opt-in recipe, roadmap updated (v4 = morfo-inferred value cycling,
v5 = MutationObserver ordering for Sema).
## Verification
- `npm run check`: 0 errors in soma/morfo/test-route scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 demos · 34 steps · 9 variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
<!-- Axes the perm-runner v3 should generate URL variants for. -->
|
|
|
|
|
<meta name="perm-axes" content="dir,orientation,disabled" />
|
soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
New components (10):
- Tabs: roving tabindex, automatic/manual activation, onFocusChange
- ToggleGroup: single/multiple, roving tabindex, deselectable
- Toast: imperative API (createToaster), swipe-to-dismiss, hotkey, promise()
- ScrollArea: custom scrollbars, visibility modes, thumb drag, position tracking
- Splitter: resizable panels, collapsible, flex-grow sizing, double-click toggle
- Stepper: multi-step wizard, linear mode, step state, PrevTrigger/NextTrigger
- TreeView: WAI-ARIA tree pattern, expand/collapse, typeahead, * expand siblings
- Pagination: page range algorithm, siblingCount/boundaryCount, slice() utility
- Toolbar: WAI-ARIA toolbar, roving tabindex, integrated ToggleGroup, Separator
- Editable: inline edit, activation/submit modes, draft tracking, auto-resize
Documentation:
- README.md for all 22 components with anatomy, ARIA, data-*, keyboard, usage
- Comparison tables vs ark-ui, bits-ui, radix-ui for every component
Infrastructure:
- Added PrimitiveAnchorAttributes to $soma/types/html.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
</svelte:head>
|
|
|
|
|
|
|
|
|
|
<div class="page">
|
|
|
|
|
<h1>Soma Tabs</h1>
|
|
|
|
|
<p>Headless tabs with roving tabindex, automatic/manual activation, orientation support.</p>
|
|
|
|
|
|
|
|
|
|
<!-- ── 1. Basic horizontal ────────────────────────────────────────────────── -->
|
|
|
|
|
<section>
|
|
|
|
|
<h2>1. Horizontal (automatic)</h2>
|
|
|
|
|
<p class="state">value: {value1}</p>
|
|
|
|
|
|
morfo: permutation runner v3 — per-demo axis matrix via `<meta name="perm-axes">`
v2 ran every demo under a hard-coded `ltr` + `rtl` matrix. v3 lets each
demo declare which additional axes it honours — `orientation`, `disabled`,
`readonly`, `invalid`, `loading` — and the runner adds ONE variant per
declared axis (axis-at-a-time, not cartesian, so the matrix stays bounded
at O(N) instead of O(2^N)).
## Convention
```svelte
<svelte:head>
<meta name="perm-axes" content="dir,orientation,disabled" />
</svelte:head>
```
`dir` is universal (always runs). The other axes are opt-in: they only
produce variants when the demo both lists them in the meta AND wires the
corresponding state from `Perms.getOr(...)`. Without the meta, only
`ltr` + `rtl` run.
## Runner changes
- New `discoverAxes(base, morfoKebab)` pass: loads the baseline URL, reads
`<meta name="perm-axes">`, returns the declared axis list.
- `buildVariants(declaredAxes)` composes `ltr` + `rtl` + one `?perm.X=Y`
variant per declared axis from the `AXIS_FLIP` map.
- `[initial seed]` step validates morfo at each URL before any click,
so URL-seeded state alone is exercised (catches seed regressions even
when the click cycle never fires).
## Demo opt-in (this commit)
- `dialog` — `perm-axes="dir"` (no orientation / disabled / loading axes
apply to Dialog).
- `tabs` — `perm-axes="dir,orientation,disabled"` + wires `orientation` +
`disabled` to the first `Tabs.Provider`.
- `toolbar` — `perm-axes="dir,orientation"` + wires `orientation` to the
first `Toolbar.Provider`.
Result: 3 demos, 9 URL variants, 34 step validations per CI run
(v2 was 22 steps across 6 variants).
## Docs
`src/uix/morfo/PERMUTATION_RUNNER.md` v3 section: supported axis table,
3-step opt-in recipe, roadmap updated (v4 = morfo-inferred value cycling,
v5 = MutationObserver ordering for Sema).
## Verification
- `npm run check`: 0 errors in soma/morfo/test-route scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 demos · 34 steps · 9 variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
<Tabs.Provider bind:value={value1} {orientation} {disabled}>
|
soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
New components (10):
- Tabs: roving tabindex, automatic/manual activation, onFocusChange
- ToggleGroup: single/multiple, roving tabindex, deselectable
- Toast: imperative API (createToaster), swipe-to-dismiss, hotkey, promise()
- ScrollArea: custom scrollbars, visibility modes, thumb drag, position tracking
- Splitter: resizable panels, collapsible, flex-grow sizing, double-click toggle
- Stepper: multi-step wizard, linear mode, step state, PrevTrigger/NextTrigger
- TreeView: WAI-ARIA tree pattern, expand/collapse, typeahead, * expand siblings
- Pagination: page range algorithm, siblingCount/boundaryCount, slice() utility
- Toolbar: WAI-ARIA toolbar, roving tabindex, integrated ToggleGroup, Separator
- Editable: inline edit, activation/submit modes, draft tracking, auto-resize
Documentation:
- README.md for all 22 components with anatomy, ARIA, data-*, keyboard, usage
- Comparison tables vs ark-ui, bits-ui, radix-ui for every component
Infrastructure:
- Added PrimitiveAnchorAttributes to $soma/types/html.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
<Tabs.List>
|
morfo: add permutation runner (A37) — third validation layer for state transitions
## Why
`morfo:check` validates single-state DOM at page load; `smoke` catches
hydration-time errors. Neither ejercices state TRANSITIONS — and that is
where every reactivity bug we caught this week actually lived:
- toolbar A35 loop (mount-time, not caught by either)
- form A36 microtask loop (mount-time with onChange)
- slider RTL transform (static value, off-by-thumb-width)
All passed the existing CI. All would have failed a "click, re-validate"
pass.
## What
- **`scripts/permutation-check.ts`** — Playwright runner. For each demo,
collects `data-perm-step="N"` elements, executes them in ascending
order (re-discovering after each step so portal-gated controls get
counted), and re-validates the component's morfo + captures any
`pageerror` / `console.error` between steps.
- **`data-perm-*` authoring convention** — step index, human label, mode
(click / focus / type / key), settle delay, skip-validate flag. Demos
opt in incrementally; the runner SKIPs annotation-less pages without
failing.
- **`src/uix/morfo/PERMUTATION_RUNNER.md`** — full design doc covering
what it validates, the annotation convention, example, failure format,
and v2–v4 roadmap (URL-driven states → morfo-inferred cycles →
MutationObserver ordering for Sema).
- **COMPONENT_GUIDE A37 + checklist item 40** — instrumentation is now a
ship-gate rule; doc lists coverage targets (overlays / toggleables /
composite roving / forms / RTL) and explicit skips (alerts, file
pickers).
- **`npm run perm:check`** — pipeline entry, exit codes 0/1/2
parallelling `morfo:check`.
## Instrumented demos (v1 seed)
- dialog — 2 steps: open via trigger → close via Close button. Step 1 is
inside `{#if open}` + Portal; runner's dynamic re-discovery handles it.
- tabs — 3 steps: initial tab 1 → switch to tab 2 → switch to tab 3.
- toolbar — 3 steps: toggle bold / italic / underline GroupItems
(exercises the A35 pattern that previously looped).
Result: 8/8 permutations pass; 63 demos SKIPPED pending instrumentation.
## Verification
- `npm run check`: 0 errors in soma/morfo/scripts scope.
- `npm run smoke`: 65/65 routes.
- `npm run morfo:check`: 66/66 morfos.
- `npm run perm:check`: 3/3 instrumented demos, 8/8 permutations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
<Tabs.Trigger value="tab-1" data-perm-step="0" data-perm-label="tab 1 active (initial)">Account</Tabs.Trigger>
|
|
|
|
|
<Tabs.Trigger value="tab-2" data-perm-step="1" data-perm-label="switch to tab 2">Password</Tabs.Trigger>
|
|
|
|
|
<Tabs.Trigger value="tab-3" data-perm-step="2" data-perm-label="switch to tab 3">Settings</Tabs.Trigger>
|
soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
New components (10):
- Tabs: roving tabindex, automatic/manual activation, onFocusChange
- ToggleGroup: single/multiple, roving tabindex, deselectable
- Toast: imperative API (createToaster), swipe-to-dismiss, hotkey, promise()
- ScrollArea: custom scrollbars, visibility modes, thumb drag, position tracking
- Splitter: resizable panels, collapsible, flex-grow sizing, double-click toggle
- Stepper: multi-step wizard, linear mode, step state, PrevTrigger/NextTrigger
- TreeView: WAI-ARIA tree pattern, expand/collapse, typeahead, * expand siblings
- Pagination: page range algorithm, siblingCount/boundaryCount, slice() utility
- Toolbar: WAI-ARIA toolbar, roving tabindex, integrated ToggleGroup, Separator
- Editable: inline edit, activation/submit modes, draft tracking, auto-resize
Documentation:
- README.md for all 22 components with anatomy, ARIA, data-*, keyboard, usage
- Comparison tables vs ark-ui, bits-ui, radix-ui for every component
Infrastructure:
- Added PrimitiveAnchorAttributes to $soma/types/html.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
</Tabs.List>
|
|
|
|
|
|
|
|
|
|
<Tabs.Content value="tab-1">
|
|
|
|
|
<div class="panel">Manage your account details and profile information.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
<Tabs.Content value="tab-2">
|
|
|
|
|
<div class="panel">Update your password and security preferences.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
<Tabs.Content value="tab-3">
|
|
|
|
|
<div class="panel">Configure application settings and notifications.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
</Tabs.Provider>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ── 2. Manual activation ───────────────────────────────────────────────── -->
|
|
|
|
|
<section>
|
|
|
|
|
<h2>2. Manual activation</h2>
|
|
|
|
|
<p class="desc">Arrow keys move focus, Enter/Space activates.</p>
|
|
|
|
|
<p class="state">value: {value2}</p>
|
|
|
|
|
|
|
|
|
|
<Tabs.Provider bind:value={value2} activationMode="manual">
|
|
|
|
|
<Tabs.List>
|
|
|
|
|
<Tabs.Trigger value="tab-1">Overview</Tabs.Trigger>
|
|
|
|
|
<Tabs.Trigger value="tab-2">Analytics</Tabs.Trigger>
|
|
|
|
|
<Tabs.Trigger value="tab-3">Reports</Tabs.Trigger>
|
|
|
|
|
</Tabs.List>
|
|
|
|
|
|
|
|
|
|
<Tabs.Content value="tab-1">
|
|
|
|
|
<div class="panel">Dashboard overview with key metrics.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
<Tabs.Content value="tab-2">
|
|
|
|
|
<div class="panel">Detailed analytics and usage patterns.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
<Tabs.Content value="tab-3">
|
|
|
|
|
<div class="panel">Generated reports and export options.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
</Tabs.Provider>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ── 3. Vertical ────────────────────────────────────────────────────────── -->
|
|
|
|
|
<section>
|
|
|
|
|
<h2>3. Vertical orientation</h2>
|
|
|
|
|
<p class="desc">Up/Down arrows navigate.</p>
|
|
|
|
|
<p class="state">value: {value3}</p>
|
|
|
|
|
|
|
|
|
|
<Tabs.Provider bind:value={value3} orientation="vertical">
|
|
|
|
|
<div class="vertical-layout">
|
|
|
|
|
<Tabs.List>
|
|
|
|
|
<Tabs.Trigger value="tab-1">General</Tabs.Trigger>
|
|
|
|
|
<Tabs.Trigger value="tab-2">Appearance</Tabs.Trigger>
|
|
|
|
|
<Tabs.Trigger value="tab-3">Notifications</Tabs.Trigger>
|
|
|
|
|
</Tabs.List>
|
|
|
|
|
|
|
|
|
|
<div class="vertical-panels">
|
|
|
|
|
<Tabs.Content value="tab-1">
|
|
|
|
|
<div class="panel">General application settings.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
<Tabs.Content value="tab-2">
|
|
|
|
|
<div class="panel">Theme, colors, and layout options.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
<Tabs.Content value="tab-3">
|
|
|
|
|
<div class="panel">Email and push notification preferences.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Tabs.Provider>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<!-- ── 4. Disabled ────────────────────────────────────────────────────────── -->
|
|
|
|
|
<section>
|
|
|
|
|
<h2>4. Disabled tab</h2>
|
|
|
|
|
<p class="desc">Middle tab is disabled — skipped in keyboard nav.</p>
|
|
|
|
|
<p class="state">value: {value4}</p>
|
|
|
|
|
|
|
|
|
|
<Tabs.Provider bind:value={value4}>
|
|
|
|
|
<Tabs.List>
|
|
|
|
|
<Tabs.Trigger value="tab-1">Active</Tabs.Trigger>
|
|
|
|
|
<Tabs.Trigger value="tab-2" disabled>Disabled</Tabs.Trigger>
|
|
|
|
|
<Tabs.Trigger value="tab-3">Also active</Tabs.Trigger>
|
|
|
|
|
</Tabs.List>
|
|
|
|
|
|
|
|
|
|
<Tabs.Content value="tab-1">
|
|
|
|
|
<div class="panel">This tab works normally.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
<Tabs.Content value="tab-2">
|
|
|
|
|
<div class="panel">You should not see this (disabled tab).</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
<Tabs.Content value="tab-3">
|
|
|
|
|
<div class="panel">This tab also works normally.</div>
|
|
|
|
|
</Tabs.Content>
|
|
|
|
|
</Tabs.Provider>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.page {
|
|
|
|
|
max-width: 640px;
|
|
|
|
|
margin: 2rem auto;
|
|
|
|
|
font-family: system-ui, sans-serif;
|
|
|
|
|
}
|
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
h1 {
|
|
|
|
|
margin-bottom: 0.25rem;
|
|
|
|
|
}
|
|
|
|
|
h2 {
|
|
|
|
|
margin-top: 2rem;
|
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
|
}
|
|
|
|
|
p {
|
|
|
|
|
color: #555;
|
|
|
|
|
}
|
|
|
|
|
.state {
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
color: #64748b;
|
|
|
|
|
}
|
|
|
|
|
.desc {
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
margin-bottom: 0.25rem;
|
|
|
|
|
}
|
soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
New components (10):
- Tabs: roving tabindex, automatic/manual activation, onFocusChange
- ToggleGroup: single/multiple, roving tabindex, deselectable
- Toast: imperative API (createToaster), swipe-to-dismiss, hotkey, promise()
- ScrollArea: custom scrollbars, visibility modes, thumb drag, position tracking
- Splitter: resizable panels, collapsible, flex-grow sizing, double-click toggle
- Stepper: multi-step wizard, linear mode, step state, PrevTrigger/NextTrigger
- TreeView: WAI-ARIA tree pattern, expand/collapse, typeahead, * expand siblings
- Pagination: page range algorithm, siblingCount/boundaryCount, slice() utility
- Toolbar: WAI-ARIA toolbar, roving tabindex, integrated ToggleGroup, Separator
- Editable: inline edit, activation/submit modes, draft tracking, auto-resize
Documentation:
- README.md for all 22 components with anatomy, ARIA, data-*, keyboard, usage
- Comparison tables vs ark-ui, bits-ui, radix-ui for every component
Infrastructure:
- Added PrimitiveAnchorAttributes to $soma/types/html.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
|
soma: gesture layer, drawer component, full audit fixes across 26 components
Gesture Layer:
- Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking
- VelocityTracker with ring buffer, staleness check, projection
- 52 tests (velocity + gesture integration)
- setPointerCapture deferred until moveBuffer exceeded (prevents click stealing)
Drawer Component:
- 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close
- Gesture.drag() integration for swipe dismiss
- Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle
- Modal/non-modal/non-dismissible modes
- Logical directions (start/end) RTL-aware via soma presentation
- CSS custom properties (--drawer-progress, --drawer-offset-x/y)
Architecture:
- T1: 219 files migrated from $soma/ alias to relative paths
- T2: 35 wrappers — noop replaced with () => {} inline
- Translation system: componentLangs + common.buttons.* + idlangref constants
- COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns
- contracts.ts: case-normalized registerContract/assertContract
Audit Fixes (P0 critical):
- DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super
- TB1: data-table attr on wrapper
- CH1/SW1/NF4: hidden input rendering fixes
- SP2: panel cleanup on unmount
- RG1: roving tabindex fallback to first item
- CO1: aria-labelledby on collapsible content
Audit Fixes (P1 high):
- SafePolygon integrated in dropdown-menu SubProvider + tooltip
- 9 langs.ts files with idlangref constants
- Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards
- Stepper: count decrement + private, aria-label on List
- ToggleGroup: roving tabindex in multiple mode
- Select: pure aria-activedescendant (no mixed focus), label registry
- NumberField: startValue respects min, scrubber commit on pointerup only
- Toolbar: element-based focus tracking (not index)
Audit Fixes (P2 medium):
- A6: timer/listener cleanup in 6 components
- A12: getDirectionalKeys in 4 components
- A10: DOM queries scoped to root in 3 components
- Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern
- Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup
- Pagination: reactive page clamping, Set-based computePageRange
- Scroll-area: thumbSize zero-guard, mount flash prevention
- Splitter: collapse/expand respects adjacent constraints, immediate pointer capture
5 issues remain (design decisions): stepper registry, table sub-row IDs,
tags-input ARIA model, tags-input paste, toast exit animation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
section {
|
|
|
|
|
margin-bottom: 2rem;
|
|
|
|
|
}
|
soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
New components (10):
- Tabs: roving tabindex, automatic/manual activation, onFocusChange
- ToggleGroup: single/multiple, roving tabindex, deselectable
- Toast: imperative API (createToaster), swipe-to-dismiss, hotkey, promise()
- ScrollArea: custom scrollbars, visibility modes, thumb drag, position tracking
- Splitter: resizable panels, collapsible, flex-grow sizing, double-click toggle
- Stepper: multi-step wizard, linear mode, step state, PrevTrigger/NextTrigger
- TreeView: WAI-ARIA tree pattern, expand/collapse, typeahead, * expand siblings
- Pagination: page range algorithm, siblingCount/boundaryCount, slice() utility
- Toolbar: WAI-ARIA toolbar, roving tabindex, integrated ToggleGroup, Separator
- Editable: inline edit, activation/submit modes, draft tracking, auto-resize
Documentation:
- README.md for all 22 components with anatomy, ARIA, data-*, keyboard, usage
- Comparison tables vs ark-ui, bits-ui, radix-ui for every component
Infrastructure:
- Added PrimitiveAnchorAttributes to $soma/types/html.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
|
|
|
|
|
|
|
|
/* Minimal tab styling for testing */
|
|
|
|
|
:global([data-tabs-list]) {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 2px;
|
|
|
|
|
border-bottom: 1px solid #e2e8f0;
|
|
|
|
|
}
|
|
|
|
|
:global([data-tabs-trigger]) {
|
|
|
|
|
padding: 0.5rem 1rem;
|
|
|
|
|
border: none;
|
|
|
|
|
background: none;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
border-bottom: 2px solid transparent;
|
|
|
|
|
color: #64748b;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
}
|
|
|
|
|
:global([data-tabs-trigger][data-state='active']) {
|
|
|
|
|
color: #1e293b;
|
|
|
|
|
border-bottom-color: #3b82f6;
|
|
|
|
|
}
|
|
|
|
|
:global([data-tabs-trigger]:hover:not([disabled])) {
|
|
|
|
|
color: #1e293b;
|
|
|
|
|
}
|
|
|
|
|
:global([data-tabs-trigger][disabled]) {
|
|
|
|
|
opacity: 0.4;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
|
|
|
|
:global([data-tabs-trigger]:focus-visible) {
|
|
|
|
|
outline: 2px solid #3b82f6;
|
|
|
|
|
outline-offset: -2px;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel {
|
|
|
|
|
padding: 1rem;
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
border-top: none;
|
|
|
|
|
border-radius: 0 0 6px 6px;
|
|
|
|
|
color: #334155;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Vertical layout */
|
|
|
|
|
.vertical-layout {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 0;
|
|
|
|
|
}
|
|
|
|
|
.vertical-layout :global([data-tabs-list]) {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
border-right: 1px solid #e2e8f0;
|
|
|
|
|
min-width: 140px;
|
|
|
|
|
}
|
|
|
|
|
.vertical-layout :global([data-tabs-trigger]) {
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
border-right: 2px solid transparent;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
.vertical-layout :global([data-tabs-trigger][data-state='active']) {
|
|
|
|
|
border-right-color: #3b82f6;
|
|
|
|
|
}
|
|
|
|
|
.vertical-panels {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
.vertical-panels .panel {
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
border-left: none;
|
|
|
|
|
border-radius: 0 6px 6px 0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|