You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/web/routes/uix/components/checkbox/+page.svelte

598 lines
27 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script lang="ts">
import {
Checkbox,
type CheckboxSize,
type CheckboxVariant,
type CheckboxColor,
type CheckboxIconSnippetArgs
} from '$uix/eidos/components/checkbox';
import { compileMorfo } from '$uix/morfo';
import { checkboxMorfo } from '@/uix/morfo/components/checkbox';
import { getActiveUix } from '$active-uix';
import { page } from '$app/state';
const uix = getActiveUix();
const params = page.url.searchParams;
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
let tab = $state<Tab>('live');
// ── State ──────────────────────────────────────────────────────────────
let checked = $state(false);
let indeterminate = $state(false);
let disabled = $state(params.get('perm.disabled') === 'true');
let readonly = $state(false);
let required = $state(false);
let invalid = $state(params.get('perm.invalid') === 'true');
let size = $state<CheckboxSize>('md');
let variant = $state<CheckboxVariant>('solid');
let color = $state<CheckboxColor>('primary');
let labelText = $state('Subscribe to updates');
let nameAttr = $state('subscribe');
let valueAttr = $state('on');
let useCustomIcon = $state(false);
let lastFormSubmit = $state<Record<string, unknown> | null>(null);
const groupOptions = [
{ value: 'email', label: 'Email' },
{ value: 'sms', label: 'SMS' },
{ value: 'push', label: 'Push' },
{ value: 'inapp', label: 'In-app' }
];
let groupValue = $state<string[]>(['email']);
let groupOrientation = $state<'vertical' | 'horizontal'>('vertical');
let groupColumns = $state<number | undefined>(undefined);
let groupDisabled = $state(false);
type TraceEntry = { event: string; family: string; intent?: string; at: number };
let trace = $state<TraceEntry[]>([]);
let stageRef = $state<HTMLElement | null>(null);
$effect(() => {
const el = stageRef;
if (!el) return;
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.attributeName !== 'data-event') continue;
const target = m.target as Element;
const ev = target.getAttribute('data-event');
if (!ev) continue;
trace = [
{ event: ev, family: target.getAttribute('data-event-family') ?? '—', intent: target.getAttribute('data-event-intent') ?? undefined, at: Date.now() },
...trace
].slice(0, 6);
}
});
obs.observe(el, { attributes: true, subtree: true, attributeFilter: ['data-event'] });
return () => obs.disconnect();
});
const compiled = compileMorfo(checkboxMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
const events = $derived([...compiled.actions.byName.values()]);
function fmtTime(at: number): string {
const d = new Date(at);
return `${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`;
}
function handleSubmit(e: SubmitEvent) {
e.preventDefault();
const data = new FormData(e.currentTarget as HTMLFormElement);
const obj: Record<string, unknown> = {};
for (const [k, v] of data.entries()) {
if (k in obj) {
const existing = obj[k];
obj[k] = Array.isArray(existing) ? [...existing, v] : [existing, v];
} else obj[k] = v;
}
lastFormSubmit = obj;
}
const somaSnippet = $derived(
[
"<script lang='ts'>",
" import { Checkbox } from '$soma/components/checkbox';",
' let checked = $state(false);',
'</' + 'script>',
'',
'<Checkbox.Provider',
' bind:checked',
indeterminate && ' bind:indeterminate',
disabled && ' disabled',
readonly && ' readonly',
required && ' required',
invalid && ' invalid',
nameAttr && ` name="${nameAttr}"`,
'>',
' <Checkbox.Indicator />',
nameAttr && ' <Checkbox.HiddenInput />',
'</Checkbox.Provider>'
].filter(Boolean).join('\n')
);
const eidosSnippet = $derived(
[
"<script lang='ts'>",
" import { Checkbox } from '$uix/eidos/components/checkbox';",
' let checked = $state(false);',
'</' + 'script>',
'',
'<Checkbox',
' bind:checked',
indeterminate && ' bind:indeterminate',
variant !== 'solid' && ` variant="${variant}"`,
size !== 'md' && ` size="${size}"`,
color !== 'primary' && ` color="${color}"`,
disabled && ' disabled',
readonly && ' readonly',
required && ' required',
invalid && ' invalid',
nameAttr && ` name="${nameAttr}"`,
'>',
' <Checkbox.Indicator' + (useCustomIcon ? ' icon={customIcon}' : '') + ' />',
nameAttr && ' <Checkbox.HiddenInput />',
'</Checkbox>'
].filter(Boolean).join('\n')
);
</script>
<svelte:head>
<meta name="perm-axes" content="disabled,invalid" />
</svelte:head>
{#snippet customIcon({ checked: c, indeterminate: i }: CheckboxIconSnippetArgs)}
{#if i}
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="12" r="3" /></svg>
{:else if c}
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2l2.39 6.95H22l-6.31 4.58L18.07 21 12 16.55 5.93 21l2.38-7.47L2 8.95h7.61L12 2z" /></svg>
{/if}
{/snippet}
<div data-uix-canvas-inner>
<header>
<div data-uix-eyebrow>Controls · Checkbox</div>
<h1 data-uix-page-title>Checkbox</h1>
<p data-uix-page-lede>
Boolean control with full APG semantics, indeterminate state, native form integration via <code>HiddenInput</code>, three variants (<code>solid</code> / <code>outline</code> / <code>ghost</code>), color subset (<code>primary | secondary | neutral | affirm</code> — no risk/threat/loss; use <code>invalid</code> for validation), <code>commit-check</code> / <code>commit-uncheck</code> sema events.
</p>
<div data-uix-page-meta>
<span data-uix-meta-pill><span data-uix-meta-key>parts</span>{compiled.parts.order.length}</span>
<span data-uix-meta-pill><span data-uix-meta-key>events</span>{events.length}</span>
<span data-uix-meta-pill><span data-uix-meta-key>variants</span>3</span>
<span data-uix-meta-pill><span data-uix-meta-key>colors</span>4</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>apg</span>
<a href={checkboxMorfo.apg} target="_blank" rel="noreferrer">checkbox ↗</a>
</span>
</div>
</header>
<div data-uix-stage>
<div data-uix-stage-area bind:this={stageRef} style="display: flex; flex-direction: column; gap: 1.5rem; align-items: flex-start;">
<form onsubmit={handleSubmit} style="display: flex; flex-direction: column; gap: 0.75rem; align-items: flex-start;">
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
<Checkbox
bind:checked
bind:indeterminate
{disabled}
{readonly}
{required}
{invalid}
{size}
{variant}
{color}
name={nameAttr || undefined}
value={valueAttr}
data-perm-step={disabled ? undefined : '0'}
data-perm-label={disabled ? undefined : 'toggle main checkbox'}
>
<Checkbox.Indicator icon={useCustomIcon ? customIcon : undefined} />
{#if nameAttr}<Checkbox.HiddenInput />{/if}
</Checkbox>
{#if labelText}<span>{labelText}</span>{/if}
</label>
<button type="submit" data-uix-play>Submit form</button>
</form>
<Checkbox.Group
bind:value={groupValue}
orientation={groupOrientation}
columns={groupColumns}
disabled={groupDisabled}
name="notifications"
>
<Checkbox.GroupLabel>Notification channels</Checkbox.GroupLabel>
{#each groupOptions as opt (opt.value)}
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
<Checkbox value={opt.value}>
<Checkbox.Indicator />
<Checkbox.HiddenInput />
</Checkbox>
<span>{opt.label}</span>
</label>
{/each}
</Checkbox.Group>
</div>
<div data-uix-stage-trace>
<span data-uix-stage-trace-key>trace</span>
{#if trace.length === 0}
<span>toggle the checkbox to see commit events</span>
{:else}
{#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each}
{/if}
<span style="margin-inline-start: auto;">
<span data-uix-stage-trace-key>state</span> {indeterminate ? 'indeterminate' : checked ? 'checked' : 'unchecked'}
</span>
</div>
{#if lastFormSubmit !== null}
<div data-uix-stage-trace style="margin-top: 0.5rem;">
<span data-uix-stage-trace-key>FormData</span>
<code style="font-size: 0.85em;">{JSON.stringify(lastFormSubmit)}</code>
</div>
{/if}
</div>
<div data-uix-tabs role="tablist">
<button data-uix-tab data-active={tab === 'live'} onclick={() => (tab = 'live')}>Live</button>
<button data-uix-tab data-active={tab === 'api'} onclick={() => (tab = 'api')}>API <span data-uix-tab-count>22</span></button>
<button data-uix-tab data-active={tab === 'morfo'} onclick={() => (tab = 'morfo')}>
<span data-uix-layer-badge="morfo">morfo</span>
<span data-uix-tab-count>{partsList.length}p · {events.length}e</span>
</button>
<button data-uix-tab data-active={tab === 'sema'} onclick={() => (tab = 'sema')}>
<span data-uix-layer-badge="sema">sema</span>
<span data-uix-tab-count>{events.length}</span>
</button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
{#if tab === 'live'}
<section data-uix-section>
<h2 data-uix-section-title>Controls</h2>
<p data-uix-section-desc>Props grouped by layer: <span data-uix-layer-badge="soma">soma</span> headless behavior + form, <span data-uix-layer-badge="eidos">eidos</span> visual treatment. Sema events live in the <a href="#sema" onclick={(e) => { e.preventDefault(); tab = 'sema'; }}>Sema</a> tab.</p>
<div data-uix-subsection-head><span data-uix-layer-badge="soma">soma</span> props · headless behavior + form</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>checked <span data-uix-control-hint>bindable</span></span>
<span data-uix-switch><input type="checkbox" bind:checked={checked} /><span data-uix-switch-label>{checked ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>indeterminate <span data-uix-control-hint>bindable</span></span>
<span data-uix-switch><input type="checkbox" bind:checked={indeterminate} /><span data-uix-switch-label>{indeterminate ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>disabled</span>
<span data-uix-switch><input type="checkbox" bind:checked={disabled} /><span data-uix-switch-label>{disabled ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>readonly</span>
<span data-uix-switch><input type="checkbox" bind:checked={readonly} /><span data-uix-switch-label>{readonly ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>required</span>
<span data-uix-switch><input type="checkbox" bind:checked={required} /><span data-uix-switch-label>{required ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>invalid</span>
<span data-uix-switch><input type="checkbox" bind:checked={invalid} /><span data-uix-switch-label>{invalid ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>name</span>
<input type="text" bind:value={nameAttr} placeholder="(no HiddenInput)" />
</label>
<label data-uix-control>
<span data-uix-control-label>value</span>
<input type="text" bind:value={valueAttr} />
</label>
</div>
<div data-uix-subsection-head><span data-uix-layer-badge="eidos">eidos</span> props · visual treatment</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>variant</span>
<span data-uix-chips role="radiogroup">
{#each ['solid', 'outline', 'ghost'] as v}
<button data-uix-chip data-active={variant === v} onclick={() => (variant = v as CheckboxVariant)}>{v}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>size</span>
<span data-uix-chips role="radiogroup">
{#each ['sm', 'md', 'lg'] as s}
<button data-uix-chip data-active={size === s} onclick={() => (size = s as CheckboxSize)}>{s}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>color</span>
<span data-uix-chips role="radiogroup">
{#each ['primary', 'secondary', 'neutral', 'affirm'] as c}
<button data-uix-chip data-active={color === c} onclick={() => (color = c as CheckboxColor)}>{c}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>custom icon snippet</span>
<span data-uix-switch><input type="checkbox" bind:checked={useCustomIcon} /><span data-uix-switch-label>{useCustomIcon ? 'star' : 'default ✓ / —'}</span></span>
</label>
</div>
<div data-uix-subsection-head>Group · <span data-uix-layer-badge="soma">soma</span> + <span data-uix-layer-badge="eidos">eidos</span></div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>orientation <span data-uix-tag data-kind="soma">soma</span></span>
<span data-uix-chips role="radiogroup">
{#each ['vertical', 'horizontal'] as o}
<button data-uix-chip data-active={groupOrientation === o} onclick={() => (groupOrientation = o as 'vertical' | 'horizontal')}>{o}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>columns <span data-uix-tag data-kind="eidos">eidos</span></span>
<span data-uix-chips role="radiogroup">
{#each [undefined, 2, 3, 4] as c}
<button data-uix-chip data-active={groupColumns === c} onclick={() => (groupColumns = c)}>{c === undefined ? 'flex' : c + ' cols'}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>group disabled</span>
<span data-uix-switch><input type="checkbox" bind:checked={groupDisabled} /><span data-uix-switch-label>{groupDisabled ? 'on' : 'off'}</span></span>
</label>
<div data-uix-control>
<span data-uix-control-label>selected <span data-uix-control-hint>bindable</span></span>
<span style="font-size: 0.85em; color: var(--uix-text-muted);">[{groupValue.join(', ') || '∅'}]</span>
</div>
</div>
<div data-uix-subsection-head>Demo content</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>label text</span>
<input type="text" bind:value={labelText} />
</label>
</div>
<div data-uix-code>
<div data-uix-code-head>
<span data-uix-layer-badge="soma">soma</span>
<span>headless · ARIA + behavior only</span>
<span data-uix-code-lang>svelte</span>
</div>
<pre><code>{somaSnippet}</code></pre>
</div>
<div data-uix-code style="margin-top: var(--uix-space-3);">
<div data-uix-code-head>
<span data-uix-layer-badge="eidos">eidos</span>
<span>visual · adds variant, size, color, icon snippet</span>
<span data-uix-code-lang>svelte</span>
</div>
<pre><code>{eidosSnippet}</code></pre>
</div>
</section>
{/if}
{#if tab === 'api'}
<section data-uix-section>
<h2 data-uix-section-title>API reference</h2>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
<tbody>
<tr><td class="name">checked / indeterminate <span data-uix-tag data-kind="bindable">bindable</span></td><td class="type">boolean</td><td class="default">false</td><td>Checkbox state.</td></tr>
<tr><td class="name">disabled / readonly / required / invalid</td><td class="type">boolean</td><td class="default">false</td><td>Field-integration flags.</td></tr>
<tr><td class="name">name / value</td><td class="type">string</td><td class="default">— / 'on'</td><td>Form field name + value (requires HiddenInput child).</td></tr>
<tr><td class="name">size <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">ResponsiveProp&lt;'sm'|'md'|'lg'&gt;</td><td class="default">'md'</td><td>Box + indicator size preset.</td></tr>
<tr><td class="name">variant <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">'solid'|'outline'|'ghost'</td><td class="default">'solid'</td><td>Visual treatment.</td></tr>
<tr><td class="name">color <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">'primary'|'secondary'|'neutral'|'affirm'</td><td class="default">'primary'</td><td>Palette subset (canon §3.1: no risk/threat/loss — use invalid).</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Indicator / HiddenInput / Group / GroupLabel</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Part</th><th>Notes</th></tr></thead>
<tbody>
<tr><td class="name">Indicator</td><td>Defaults to SvgCheck/SvgDash. Pass <code>icon</code> snippet for custom glyph.</td></tr>
<tr><td class="name">HiddenInput</td><td>Native &lt;input type="checkbox"&gt; for FormData. No props (auto-derived).</td></tr>
<tr><td class="name">Group</td><td>value (bindable string[]), orientation, columns (eidos), disabled, required, name.</td></tr>
<tr><td class="name">GroupLabel</td><td>Group-level label.</td></tr>
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'morfo'}
<section data-uix-section>
<h2 data-uix-section-title><span data-uix-layer-badge="morfo">morfo</span> · declarative contract</h2>
<p data-uix-section-desc>Source: <code>src/uix/morfo/components/checkbox.ts</code>.</p>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Field</th><th>Value</th></tr></thead>
<tbody>
<tr><td class="name">name</td><td class="type">"{checkboxMorfo.name}"</td></tr>
<tr><td class="name">kebab</td><td class="type">"{checkboxMorfo.kebab}"</td></tr>
<tr><td class="name">scope</td><td class="type">[{checkboxMorfo.scope.map((s) => `"${s}"`).join(', ')}]</td></tr>
<tr><td class="name">apg</td><td><a href={checkboxMorfo.apg} target="_blank" rel="noreferrer">{checkboxMorfo.apg}</a></td></tr>
<tr><td class="name">parts.length</td><td class="default">{checkboxMorfo.parts.length}</td></tr>
<tr><td class="name">events.length</td><td class="default">{checkboxMorfo.events?.length ?? 0}</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Parts</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Part</th><th>Marker</th><th>Element</th><th>Role</th><th>Archetype</th><th>States</th><th>Optional</th></tr></thead>
<tbody>
{#each partsList as part}
<tr>
<td class="name">{part.kebab}</td>
<td><code data-uix-part-marker>[{part.marker}]</code></td>
<td class="type">&lt;{part.defaultElement}&gt;</td>
<td class="type">{part.role ?? '—'}</td>
<td class="default">{part.archetype ?? '—'}</td>
<td class="default">{part.states.length ? part.states.join(' | ') : '—'}</td>
<td class="default">{part.optional ? 'yes' : 'no'}</td>
</tr>
{/each}
</tbody>
</table>
</div>
{#each checkboxMorfo.parts as rawPart}
{@const partAny = rawPart as unknown as { kebab: string; aria?: ReadonlyArray<{ attr: string; value: { kind: string }; condition?: { when: string; prop?: string }; severity?: string }>; keyboard?: ReadonlyArray<{ key: string; action: string }> }}
{@const dataAttrs = compiled.contracts.dataAttrsByPart.get(partAny.kebab) ?? []}
{@const ariaAttrs = partAny.aria ?? []}
{@const keyboard = partAny.keyboard ?? []}
{#if dataAttrs.length || ariaAttrs.length || keyboard.length}
<div data-uix-subsection-head>{partAny.kebab}</div>
{#if dataAttrs.length}
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>data-attr</th><th>Values</th><th>Source</th></tr></thead>
<tbody>
{#each dataAttrs as attr}
<tr><td class="name">{attr.attr}</td><td class="type">{attr.values ? attr.values.join(' | ') : '—'}</td><td class="default">{'value' in attr && attr.value ? (attr as { value: { kind: string } }).value.kind : '—'}</td></tr>
{/each}
</tbody>
</table>
</div>
{/if}
{#if ariaAttrs.length}
<div data-uix-table-wrap style="margin-top: var(--uix-space-2);">
<table data-uix-table>
<thead><tr><th>aria-attr</th><th>Source</th><th>Condition</th><th>Severity</th></tr></thead>
<tbody>
{#each ariaAttrs as a}
<tr><td class="name">{a.attr}</td><td class="type">{a.value.kind}</td><td class="default">{a.condition ? `when ${a.condition.when}${a.condition.prop ? ` (${a.condition.prop})` : ''}` : 'always'}</td><td class="default">{a.severity ?? 'required'}</td></tr>
{/each}
</tbody>
</table>
</div>
{/if}
{#if keyboard.length}
<div data-uix-table-wrap style="margin-top: var(--uix-space-2);">
<table data-uix-table>
<thead><tr><th>key</th><th>action</th></tr></thead>
<tbody>
{#each keyboard as kb}
<tr><td><span data-uix-kbd>{kb.key === ' ' ? 'Space' : kb.key}</span></td><td class="default">{kb.action}</td></tr>
{/each}
</tbody>
</table>
</div>
{/if}
{/if}
{/each}
<div data-uix-subsection-head>Events declaration</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>name</th><th>family</th><th>verb</th><th>sequence</th><th>intent</th><th>target</th></tr></thead>
<tbody>
{#each events as action}
{@const sem = action.semantic}
{@const intentDecl = 'intent' in sem ? sem.intent : undefined}
{@const intentStr = typeof intentDecl === 'string' ? intentDecl : intentDecl ? `fromProp:${(intentDecl as { fromProp?: string }).fromProp ?? '?'}` : '—'}
<tr><td class="name">{action.name}</td><td class="type">{sem.family}</td><td>{sem.verb ?? '—'}</td><td>{sem.sequence ?? 'pre'}</td><td class="default">{intentStr}</td><td>{action.target}</td></tr>
{/each}
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'sema'}
<section data-uix-section id="sema">
<h2 data-uix-section-title><span data-uix-layer-badge="sema">sema</span> · events + perceptual signature</h2>
<p data-uix-section-desc>Two events. <code>commit-check</code> rides intent <code>affirm</code>; <code>commit-uncheck</code> rides <code>neutral</code>. Sema pack adds sub-audible cues + tap haptic.</p>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Name</th><th>Family</th><th>Verb</th><th>Sequence</th><th>Intent</th><th>Play</th></tr></thead>
<tbody>
{#each events as action}
{@const intentDecl = 'intent' in action.semantic ? action.semantic.intent : undefined}
{@const effectiveIntent = typeof intentDecl === 'string' ? intentDecl : undefined}
<tr>
<td class="name">{action.name}</td>
<td class="type">{action.semantic.family}</td>
<td>{action.semantic.verb ?? '—'}</td>
<td>{action.semantic.sequence ?? 'pre'}</td>
<td class="default">{effectiveIntent ?? '—'}</td>
<td>
<button data-uix-play onclick={() => {
const target = stageRef?.querySelector('[data-checkbox]') as HTMLElement | null;
if (!target) return;
void uix.events?.emit({ name: action.name, family: action.semantic.family, target, ...(effectiveIntent ? { intent: effectiveIntent } : {}) });
}}>▶ play</button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'recipe'}
<section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2>
<p data-uix-section-desc>Selectors at <code>src/uix/eidos/components/checkbox/checkbox.css</code>. Indicator uses SvgCheck/SvgDash with stroke-dasharray draw animation. Honours <code>prefers-reduced-motion</code>.</p>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Selector</th><th>Source</th></tr></thead>
<tbody>
<tr><td class="name">[data-checkbox][data-state]</td><td><span data-uix-tag data-kind="soma">morfo</span></td></tr>
<tr><td class="name">[data-checkbox][data-disabled / readonly / invalid]</td><td><span data-uix-tag data-kind="soma">morfo</span></td></tr>
<tr><td class="name">[data-checkbox][data-size / data-variant / data-color]</td><td><span data-uix-tag data-kind="eidos">eidos</span></td></tr>
<tr><td class="name">[data-checkbox-indicator] svg[data-svg='check'/'dash']</td><td><span data-uix-tag data-kind="eidos">eidos · svg primitives</span></td></tr>
<tr><td class="name">[data-checkbox-group][data-orientation]</td><td><span data-uix-tag data-kind="soma">morfo</span></td></tr>
<tr><td class="name">[data-checkbox-group][data-columns]</td><td><span data-uix-tag data-kind="eidos">eidos</span></td></tr>
<tr><td class="name">@media (prefers-reduced-motion: reduce)</td><td><span data-uix-tag data-kind="eidos">eidos · a11y</span></td></tr>
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'a11y'}
<section data-uix-section>
<h2 data-uix-section-title>Accessibility</h2>
<p data-uix-section-desc>APG <a href={checkboxMorfo.apg} target="_blank" rel="noreferrer">checkbox pattern</a>. Tri-state via <code>aria-checked="mixed"</code>. Form integration via separate HiddenInput.</p>
<div data-uix-subsection-head>Keyboard</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Key</th><th>Action</th></tr></thead>
<tbody>
<tr><td><span data-uix-kbd>Space</span></td><td>Toggle. Suppressed when disabled or readonly.</td></tr>
<tr><td><span data-uix-kbd>Tab</span> / <span data-uix-kbd>Shift+Tab</span></td><td>Move focus.</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>ARIA contract</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Part</th><th>Attribute</th><th>Value</th></tr></thead>
<tbody>
<tr><td class="name">provider</td><td>role / aria-checked</td><td class="type">"checkbox" / "true"|"false"|"mixed"</td></tr>
<tr><td class="name">provider</td><td>aria-required / aria-readonly / aria-invalid</td><td class="type">"true" (when set)</td></tr>
<tr><td class="name">indicator</td><td>aria-hidden</td><td class="type">"true"</td></tr>
<tr><td class="name">hidden-input</td><td>aria-hidden + tabindex=−1</td><td class="type">excluded from AT + focus</td></tr>
<tr><td class="name">group</td><td>role / aria-labelledby</td><td class="type">"group" / {`<group-label id>`}</td></tr>
</tbody>
</table>
</div>
</section>
{/if}
</div>

Powered by TurnKey Linux.