soma test: instrument 7 more demos for permutation runner (composite + toggleables batch)

Adds `data-perm-step` + `<meta name="perm-axes">` + perms seed to:

- **accordion** — expand item-1 → switch to item-2. `?perm.value=item-1`
  seeds the Provider's value array. `dir` axis.
- **listbox** — select apple (first) → select apricot. `?perm.orientation`
  flips the layout, already wired. Items in a loop use conditional
  `data-perm-step` by index. `dir,orientation` axes.
- **tree-view** — select readme → select license under the expanded docs
  branch. `?perm.expanded=docs&perm.selected=readme` seed. `dir` axis.
- **combobox** — open via trigger (Combobox.Trigger). `dir` axis only —
  typing / selection is left to v4 per-component value enumeration.
- **menubar** — open File menu → close via Escape (mode=key, 500ms settle).
  `dir` axis.
- **toggle** — press Bold toggle (inside Field.Provider integration
  example). `dir` axis.
- **toggle-group** — press left → press center (single mode). Added
  `aria-label="Text alignment"` to silence the A4 DEV warning that now
  fires (shipped in batch 10 of audit 2026-04-21). `?perm.value=bold`
  preselects. `dir,orientation` axes.

## Deferred in this batch

- **context-menu** — needs `contextmenu` event (right-click). Requires a
  `data-perm-mode='contextmenu'` extension to the runner. Future batch.
- **navigation-menu** — hover-driven only. Same future-batch treatment as
  Tooltip has with `data-perm-mode="focus"` + long settle.

## Coverage delta

- Before: 8 demos · 19 variants · 62 steps.
- After:  15 demos · 35 variants · 106 steps.

~1.7× jump in a single batch. Tabs still leads with 4 variants (adds
`orientation` + `disabled` axes); ToggleGroup, Toolbar, Listbox each have
3 variants.

## 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`: 15/15 demos · 106 steps · 35 variants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
semantuix
dev 6 months ago
parent 1e8500bfac
commit b68b831320

@ -1,7 +1,14 @@
<script lang="ts">
import * as Accordion from '$soma/components/accordion';
import { Perms } from '../_perms.svelte';
let value1 = $state<string[]>([]);
const perms = Perms.getOr({} as Record<string, string | number | boolean>);
function parseList(v: unknown, fallback: string[]): string[] {
if (typeof v !== 'string' || v.length === 0) return fallback;
return v.split(',').map((s) => s.trim()).filter(Boolean);
}
let value1 = $state<string[]>(parseList(perms.value, []));
let value2 = $state<string[]>(['item-1']);
let value3 = $state<string[]>([]);
let value4 = $state<string[]>([]);
@ -9,6 +16,7 @@
<svelte:head>
<title>Accordion · Soma</title>
<meta name="perm-axes" content="dir" />
</svelte:head>
<div class="page">
@ -26,7 +34,9 @@
<Accordion.Provider bind:value={value1} collapsible>
<Accordion.Item value="item-1">
<Accordion.Header>
<Accordion.Trigger>What is soma?</Accordion.Trigger>
<Accordion.Trigger data-perm-step="0" data-perm-label="expand item-1">
What is soma?
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>
<div class="content-body">
@ -38,7 +48,9 @@
<Accordion.Item value="item-2">
<Accordion.Header>
<Accordion.Trigger>How does it compare?</Accordion.Trigger>
<Accordion.Trigger data-perm-step="1" data-perm-label="switch to item-2">
How does it compare?
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>
<div class="content-body">

@ -31,6 +31,7 @@
<svelte:head>
<title>Combobox · Soma</title>
<meta name="perm-axes" content="dir" />
</svelte:head>
<div class="page">
@ -44,7 +45,7 @@
<Combobox.Provider bind:value={value1} bind:inputValue={input1}>
<div class="input-wrapper">
<Combobox.Input placeholder="Search fruits..." />
<Combobox.Trigger>▾</Combobox.Trigger>
<Combobox.Trigger data-perm-step="0" data-perm-label="open combobox">▾</Combobox.Trigger>
</div>
<Portal>
<Combobox.Content>

@ -3,6 +3,9 @@
import * as Field from '$soma/components/field';
import type { ListboxSelectionMode } from '$soma/components/listbox';
import type { Orientation } from '$soma/types';
import { Perms } from '../_perms.svelte';
const perms = Perms.getOr({} as Record<string, string | number | boolean>);
// ── Example 1 — interactive testbed (every public prop wired) ────────────
const fruits = [
@ -22,7 +25,7 @@
let selectionMode = $state<ListboxSelectionMode>('single');
let loop = $state(true);
let typeahead = $state(true);
let orientation = $state<Orientation>('vertical');
let orientation = $state<Orientation>((perms.orientation as Orientation) ?? 'vertical');
let disabled = $state(false);
let readonly = $state(false);
let required = $state(false);
@ -66,6 +69,7 @@
<svelte:head>
<title>Listbox · Soma</title>
<meta name="perm-axes" content="dir,orientation" />
</svelte:head>
<div class="page">
@ -118,8 +122,17 @@
aria-label="Fruits"
class="list {orientation}"
>
{#each fruits as fruit (fruit.value)}
<Listbox.Item value={fruit.value} disabled={fruit.value === 'grape'}>
{#each fruits as fruit, i (fruit.value)}
<Listbox.Item
value={fruit.value}
disabled={fruit.value === 'grape'}
data-perm-step={i === 0 ? '0' : i === 1 ? '1' : undefined}
data-perm-label={i === 0
? 'select apple'
: i === 1
? 'select apricot'
: undefined}
>
{#snippet children({ isSelected })}
<span class="label">{fruit.label}</span>
<Listbox.ItemIndicator>✓</Listbox.ItemIndicator>

@ -38,6 +38,7 @@
<svelte:head>
<title>Menubar · Soma</title>
<meta name="perm-axes" content="dir" />
</svelte:head>
<div class="page">
@ -61,9 +62,16 @@
<Menubar.Provider bind:value={openValue}>
<Menubar.Menu value="file">
<Menubar.Trigger>File</Menubar.Trigger>
<Menubar.Trigger data-perm-step="0" data-perm-label="open File menu">File</Menubar.Trigger>
<Portal>
<Menubar.Content sideOffset={4} align="start">
<Menubar.Content
sideOffset={4}
align="start"
data-perm-step="1"
data-perm-mode='key="Escape"'
data-perm-label="close via Escape"
data-perm-settle="500"
>
<Menubar.Item onSelect={() => log('File → New')}>
<span class="label">New</span><kbd class="shortcut">⌘N</kbd>
</Menubar.Item>

@ -1,14 +1,25 @@
<script lang="ts">
import * as ToggleGroup from '$soma/components/toggle-group';
import { Perms } from '../_perms.svelte';
let value1 = $state<string[]>([]);
const perms = Perms.getOr({} as Record<string, string | number | boolean>);
function parseList(v: unknown, fallback: string[]): string[] {
if (typeof v !== 'string' || v.length === 0) return fallback;
return v.split(',').map((s) => s.trim()).filter(Boolean);
}
let value1 = $state<string[]>(parseList(perms.value, []));
let value2 = $state<string[]>(['bold']);
let value3 = $state<string[]>([]);
let value4 = $state<string[]>([]);
let orientation = $state<'horizontal' | 'vertical'>(
(perms.orientation as 'horizontal' | 'vertical') ?? 'horizontal'
);
</script>
<svelte:head>
<title>ToggleGroup · Soma</title>
<meta name="perm-axes" content="dir,orientation" />
</svelte:head>
<div class="page">
@ -21,9 +32,9 @@
<p class="desc">One item pressed at a time. Click again to deselect.</p>
<p class="state">value: {JSON.stringify(value1)}</p>
<ToggleGroup.Provider bind:value={value1}>
<ToggleGroup.Item value="left">Left</ToggleGroup.Item>
<ToggleGroup.Item value="center">Center</ToggleGroup.Item>
<ToggleGroup.Provider bind:value={value1} {orientation} aria-label="Text alignment">
<ToggleGroup.Item value="left" data-perm-step="0" data-perm-label="press left">Left</ToggleGroup.Item>
<ToggleGroup.Item value="center" data-perm-step="1" data-perm-label="press center">Center</ToggleGroup.Item>
<ToggleGroup.Item value="right">Right</ToggleGroup.Item>
</ToggleGroup.Provider>
</section>

@ -24,6 +24,7 @@
<svelte:head>
<title>Toggle · Soma</title>
<meta name="perm-axes" content="dir" />
</svelte:head>
<div class="page">
@ -80,7 +81,13 @@
<Field.Provider invalid={fieldInvalid}>
<Field.Label>Bold</Field.Label>
<Field.Control>
<Toggle.Provider bind:pressed={bold} aria-label="Bold" class="tog">
<Toggle.Provider
bind:pressed={bold}
aria-label="Bold"
class="tog"
data-perm-step="0"
data-perm-label="press bold toggle"
>
{#snippet children({ pressed: p })}
<strong class:pressed={p}>B</strong>
{/snippet}

@ -1,8 +1,15 @@
<script lang="ts">
import * as TreeView from '$soma/components/tree-view';
import { Perms } from '../_perms.svelte';
let expanded1 = $state<string[]>(['docs']);
let selected1 = $state<string[]>([]);
const perms = Perms.getOr({} as Record<string, string | number | boolean>);
function parseList(v: unknown, fallback: string[]): string[] {
if (typeof v !== 'string' || v.length === 0) return fallback;
return v.split(',').map((s) => s.trim()).filter(Boolean);
}
let expanded1 = $state<string[]>(parseList(perms.expanded, ['docs']));
let selected1 = $state<string[]>(parseList(perms.selected, []));
let expanded2 = $state<string[]>([]);
let selected2 = $state<string[]>([]);
@ -10,6 +17,7 @@
<svelte:head>
<title>TreeView · Soma</title>
<meta name="perm-axes" content="dir" />
</svelte:head>
<div class="page">
@ -36,8 +44,12 @@
<TreeView.Label>Documents</TreeView.Label>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.Item value="readme" depth={1}>README.md</TreeView.Item>
<TreeView.Item value="license" depth={1}>LICENSE</TreeView.Item>
<TreeView.Item value="readme" depth={1} data-perm-step="0" data-perm-label="select readme">
README.md
</TreeView.Item>
<TreeView.Item value="license" depth={1} data-perm-step="1" data-perm-label="select license">
LICENSE
</TreeView.Item>
<TreeView.Item value="changelog" depth={1}>CHANGELOG.md</TreeView.Item>
</TreeView.BranchContent>
</TreeView.Branch>

Loading…
Cancel
Save

Powered by TurnKey Linux.