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/splitter/+page.svelte

825 lines
29 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 {
Splitter,
type SplitterProps
} from '$uix/eidos/components/splitter';
import { compileMorfo } from '$uix/morfo';
import { splitterMorfo } from '@/uix/morfo/components/splitter';
import { getActiveUix } from '$active-uix';
const uix = getActiveUix();
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number };
let tab = $state<Tab>('live');
let trace = $state<TraceEntry[]>([]);
let stageRef = $state<HTMLElement | null>(null);
// ── Live state ───────────────────────────────────────────────────────
type Orientation = 'horizontal' | 'vertical';
const orientations: Orientation[] = ['horizontal', 'vertical'];
let orientation = $state<Orientation>('horizontal');
let disabled = $state(false);
let keyboardStep = $state<number>(1);
let collapsible = $state(false);
let panelACount = $state<number>(40);
let panelBCount = $state<number>(60);
const splitterProps = $derived<Partial<SplitterProps>>({
orientation,
disabled,
keyboardStep
});
// ── Compiled morfo ───────────────────────────────────────────────────
const compiled = compileMorfo(splitterMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
const events = $derived([...compiled.actions.byName.values()]);
$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();
});
function fmtTime(at: number): string {
const d = new Date(at);
return `${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`;
}
// ── Snippets ─────────────────────────────────────────────────────────
const somaSnippet = $derived(
[
"<script lang='ts'>",
" import * as Splitter from '$soma/components/splitter';",
'</' + 'script>',
'',
'<Splitter.Provider',
orientation !== 'horizontal' && ` orientation="${orientation}"`,
disabled && ' disabled',
keyboardStep !== 1 && ` keyboardStep={${keyboardStep}}`,
'>',
' <Splitter.Panel panelId="a" defaultSize={50} minSize={20}' +
(collapsible ? ' collapsible collapsedSize={0}' : '') +
'>',
' {a}',
' </Splitter.Panel>',
' <Splitter.ResizeTrigger panelBefore="a" panelAfter="b" />',
' <Splitter.Panel panelId="b" defaultSize={50} minSize={20}>',
' {b}',
' </Splitter.Panel>',
'</Splitter.Provider>'
]
.filter(Boolean)
.join('\n')
);
const eidosSnippet = $derived(
[
"<script lang='ts'>",
" import { Splitter } from '$uix/eidos/components/splitter';",
'</' + 'script>',
'',
'<Splitter',
orientation !== 'horizontal' && ` orientation="${orientation}"`,
disabled && ' disabled',
keyboardStep !== 1 && ` keyboardStep={${keyboardStep}}`,
'>',
' <Splitter.Panel panelId="a" defaultSize={50} minSize={20}' +
(collapsible ? ' collapsible collapsedSize={0}' : '') +
'>',
' {a}',
' </Splitter.Panel>',
' <Splitter.ResizeTrigger panelBefore="a" panelAfter="b" />',
' <Splitter.Panel panelId="b" defaultSize={50} minSize={20}>',
' {b}',
' </Splitter.Panel>',
'</Splitter>'
]
.filter(Boolean)
.join('\n')
);
</script>
<div data-uix-canvas-inner>
<header>
<div data-uix-eyebrow>Layout · Splitter</div>
<h1 data-uix-page-title>Splitter</h1>
<p data-uix-page-lede>
Two-or-more resizable panels separated by a draggable handle. Headless soma owns the resize
math (percentages, min / max clamping, collapsed state, keyboard arrow steps). The morfo
declares a single semantic event — <code>commit-resize</code> in the <code>commit</code>
family — fired on drag-end so perceptual channels react to the settled value, not to every
pixel of the drag. Eidos paints chrome plus a pulse on the handle when the commit fires.
</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>orientation</span>{orientation}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>scope</span>{splitterMorfo.scope.join(', ')}
</span>
</div>
</header>
<!-- Live preview always rendered -->
<div data-uix-stage>
<div data-uix-stage-area bind:this={stageRef}>
<div
style="inline-size: 100%; max-inline-size: 36rem; block-size: 18rem;"
>
<Splitter {...splitterProps} style="inline-size: 100%; block-size: 100%;">
<Splitter.Panel
panelId="a"
defaultSize={50}
minSize={20}
collapsible={collapsible}
collapsedSize={0}
>
<div
style="block-size: 100%; padding: var(--space-3); overflow: auto; color: var(--color-content-primary); background: var(--color-primary-track);"
>
<strong>Panel A</strong>
<p style="margin: var(--space-2) 0; color: var(--color-content-secondary); font-size: var(--font-size-sm);">
Drag the handle to resize, use arrow keys when focused. Home jumps to min, End
to max. {collapsible ? 'Enter toggles collapse.' : ''}
</p>
<ul style="margin: 0; padding-inline-start: var(--space-5);">
{#each Array.from({ length: panelACount }, (_, i) => i + 1) as i}
<li>Row A-{i}</li>
{/each}
</ul>
</div>
</Splitter.Panel>
<Splitter.ResizeTrigger panelBefore="a" panelAfter="b" />
<Splitter.Panel panelId="b" defaultSize={50} minSize={20}>
<div
style="block-size: 100%; padding: var(--space-3); overflow: auto; color: var(--color-content-primary); background: var(--color-affirm-track);"
>
<strong>Panel B</strong>
<p style="margin: var(--space-2) 0; color: var(--color-content-secondary); font-size: var(--font-size-sm);">
The handle paints in the primary color while dragging; on commit (mouse up) it
pulses for the sema signal-hold duration.
</p>
<ul style="margin: 0; padding-inline-start: var(--space-5);">
{#each Array.from({ length: panelBCount }, (_, i) => i + 1) as i}
<li>Row B-{i}</li>
{/each}
</ul>
</div>
</Splitter.Panel>
</Splitter>
</div>
</div>
<div data-uix-stage-trace>
<span data-uix-stage-trace-key>trace</span>
{#if trace.length === 0}
<span>drag the handle or hit ▶ play on the Sema tab to see the commit-resize event</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>orientation</span> {orientation} ·
<span data-uix-stage-trace-key>step</span> {keyboardStep}%
</span>
</div>
</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>3</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>
Soma owns every knob — the eidos layer adds zero evaluative props. Sema events are
surfaced in the Sema tab; the stage trace shows them too once a drag commits or the play
button fires.
</p>
<div data-uix-subsection-head>
<span data-uix-layer-badge="soma">soma</span> props · headless behavior
</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>orientation</span>
<span data-uix-chips role="radiogroup">
{#each orientations as opt}
<button
data-uix-chip
data-active={orientation === opt}
onclick={() => (orientation = opt)}>{opt}</button
>
{/each}
</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
>keyboardStep <span data-uix-control-hint
>percent per arrow-key press</span
></span
>
<input type="range" min="1" max="10" step="1" bind:value={keyboardStep} />
<span data-uix-control-value>{keyboardStep}%</span>
</label>
<label data-uix-control>
<span data-uix-control-label
>collapsible (panel A) <span data-uix-control-hint
>Enter toggles collapse to <code>collapsedSize=0</code></span
></span
>
<span data-uix-switch>
<input type="checkbox" bind:checked={collapsible} />
<span data-uix-switch-label>{collapsible ? 'on' : 'off'}</span>
</span>
</label>
</div>
<div data-uix-subsection-head>Demo content</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>panel A rows</span>
<input type="range" min="10" max="120" step="10" bind:value={panelACount} />
<span data-uix-control-value>{panelACount}</span>
</label>
<label data-uix-control>
<span data-uix-control-label>panel B rows</span>
<input type="range" min="10" max="120" step="10" bind:value={panelBCount} />
<span data-uix-control-value>{panelBCount}</span>
</label>
</div>
<!-- ── Code snippets per layer ─────────────────────────────────── -->
<div data-uix-code>
<div data-uix-code-head>
<span data-uix-layer-badge="soma">soma</span>
<span>headless · resize math + keyboard + collapse</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 · pure pass-through; recipe pulses on commit-resize</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>
<p data-uix-section-desc>
Pass-through from soma — no evaluative additions on the eidos layer.
</p>
<div data-uix-subsection-head>Splitter (root)</div>
<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">orientation</td>
<td class="type">'horizontal' | 'vertical'</td>
<td class="default">'horizontal'</td>
<td>Layout direction.</td>
</tr>
<tr>
<td class="name">disabled</td>
<td class="type">boolean</td>
<td class="default">false</td>
<td>Disables all resize triggers.</td>
</tr>
<tr>
<td class="name">keyboardStep</td>
<td class="type">number</td>
<td class="default">1</td>
<td>Percentage of resize per arrow-key press.</td>
</tr>
<tr>
<td class="name">onResize</td>
<td class="type">(sizes: number[]) =&gt; void</td>
<td class="default empty">—</td>
<td>Live callback during drag (every frame).</td>
</tr>
<tr>
<td class="name">onResizeEnd</td>
<td class="type">(sizes: number[]) =&gt; void</td>
<td class="default empty">—</td>
<td>Drag-end callback. Also fires <code>commit-resize</code> in sema.</td>
</tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Splitter.Panel</div>
<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">panelId</td>
<td class="type">string</td>
<td class="default empty">—</td>
<td>Required. Links the panel with its surrounding resize triggers.</td>
</tr>
<tr>
<td class="name">defaultSize</td>
<td class="type">number (0–100)</td>
<td class="default empty">—</td>
<td>Required. Initial size as a percentage.</td>
</tr>
<tr>
<td class="name">minSize</td>
<td class="type">number</td>
<td class="default">0</td>
<td>Lower clamp on the panel's size.</td>
</tr>
<tr>
<td class="name">maxSize</td>
<td class="type">number</td>
<td class="default">100</td>
<td>Upper clamp on the panel's size.</td>
</tr>
<tr>
<td class="name">collapsible</td>
<td class="type">boolean</td>
<td class="default">false</td>
<td>Allows double-click and Enter to toggle collapse.</td>
</tr>
<tr>
<td class="name">collapsedSize</td>
<td class="type">number</td>
<td class="default">0</td>
<td>Size when collapsed (percentage).</td>
</tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Splitter.ResizeTrigger</div>
<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">panelBefore</td>
<td class="type">string</td>
<td class="default empty">—</td>
<td>Required. Panel ID that grows when the handle is dragged forward.</td>
</tr>
<tr>
<td class="name">panelAfter</td>
<td class="type">string</td>
<td class="default empty">—</td>
<td>Required. Panel ID that shrinks when the handle is dragged forward.</td>
</tr>
<tr>
<td class="name">disabled</td>
<td class="type">boolean</td>
<td class="default">false</td>
<td>Disables this specific resize trigger.</td>
</tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Reference comparison</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead>
<tr><th>Library</th><th>Closest equivalent</th><th>Difference</th></tr>
</thead>
<tbody>
<tr>
<td class="name">radix-themes</td>
<td><code>&lt;Splitter&gt;</code></td>
<td>Identical compound shape. UIX adds collapsible panels + sema commit event.</td>
</tr>
<tr>
<td class="name">ark-ui</td>
<td><code>Splitter</code> headless</td>
<td>Soma layer mirrors Ark UI's headless contract closely.</td>
</tr>
<tr>
<td class="name">react-resizable-panels</td>
<td><code>&lt;PanelGroup&gt; / &lt;Panel&gt; / &lt;PanelResizeHandle&gt;</code></td>
<td>
UIX wraps the equivalent shape with a more compact naming
(<code>Panel</code> / <code>ResizeTrigger</code>).
</td>
</tr>
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'morfo'}
<section data-uix-section>
<h2 data-uix-section-title>Morfo contract</h2>
<p data-uix-section-desc>
Source: <code>src/uix/morfo/components/splitter.ts</code>. Scope is
<code>['soma']</code> — the headless layer owns state. Three parts, one event.
</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>{splitterMorfo.name}</td></tr>
<tr><td class="name">kebab</td><td><code>{splitterMorfo.kebab}</code></td></tr>
<tr><td class="name">scope</td><td>{splitterMorfo.scope.join(', ')}</td></tr>
<tr><td class="name">parts.length</td><td>{partsList.length}</td></tr>
<tr><td class="name">events.length</td><td>{events.length}</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Parts</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead>
<tr>
<th>kebab</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="type">{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 splitterMorfo.parts as rawPart}
{@const partAny = rawPart as unknown as {
kebab: string;
aria?: ReadonlyArray<{
attr: string;
value: { kind: string };
condition?: { when: string; prop?: string; part?: 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 kind</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 kind</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.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 k}
<tr>
<td><span data-uix-kbd>{k.key}</span></td>
<td>{k.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>
A single <code>commit-resize</code> event in the <code>commit</code> family. Sequence is
<em>post</em> — the perceptual signal lands AFTER the resize math has settled. Press ▶
play to emit the event on the live preview's resize trigger; the stage trace strip records
it and the recipe pulses the handle for the signal-hold duration.
</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 sem = action.semantic}
{@const intentDecl = 'intent' in sem ? sem.intent : undefined}
{@const effectiveIntent =
typeof intentDecl === 'string'
? intentDecl
: intentDecl
? undefined
: undefined}
<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">{typeof intentDecl === 'string' ? intentDecl : '—'}</td>
<td>
<button
data-uix-play
onclick={() => {
const target = (stageRef?.querySelector(
'[data-splitter-resize-trigger]'
) ?? stageRef?.querySelector('[data-splitter]')) 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>
Recipe lives in <code>src/uix/eidos/components/splitter/splitter.css</code>. The
<code>data-orientation</code> attribute drives the flex direction; the
<code>data-dragging</code> presence flag swaps the handle to the active color; the
<code>data-event='commit-resize'</code> hook pulses on commit.
</p>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Selector</th><th>Owner</th><th>Purpose</th></tr></thead>
<tbody>
<tr>
<td class="name"><code>[data-splitter]</code></td>
<td><span data-uix-tag data-kind="morfo">morfo</span></td>
<td>Provider marker. Sets up the flex container.</td>
</tr>
<tr>
<td class="name"
><code>[data-splitter][data-orientation='vertical']</code></td
>
<td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>Swaps to <code>flex-direction: column</code>.</td>
</tr>
<tr>
<td class="name"><code>[data-splitter-panel]</code></td>
<td><span data-uix-tag data-kind="morfo">morfo</span></td>
<td>Panel marker. Soma sets the inline / block size via inline style.</td>
</tr>
<tr>
<td class="name"><code>[data-splitter-resize-trigger]</code></td>
<td><span data-uix-tag data-kind="morfo">morfo</span></td>
<td>Handle marker. Paints the drag affordance.</td>
</tr>
<tr>
<td class="name"
><code>[data-splitter-resize-trigger][data-dragging]</code></td
>
<td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>Active drag — paint in <code>--color-primary-solid</code>.</td>
</tr>
<tr>
<td class="name"
><code>[data-splitter-resize-trigger][data-event='commit-resize']</code></td
>
<td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>
Sema hook — pulse the handle during the signal-hold (sequence:
<em>post</em>).
</td>
</tr>
<tr>
<td class="name"
><code>[data-splitter-resize-trigger]::before</code></td
>
<td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>Center grip — small pill sized to match orientation.</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>
The resize trigger advertises <code>role="separator"</code> with
<code>aria-valuenow</code> / <code>aria-valuemin</code> / <code>aria-valuemax</code>
bound to the soma-tracked size, plus <code>aria-orientation</code> matching the
splitter's layout.
</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>ArrowLeft</span> / <span data-uix-kbd>ArrowRight</span></td><td>Resize by <code>keyboardStep</code> percent (horizontal).</td></tr>
<tr><td><span data-uix-kbd>ArrowUp</span> / <span data-uix-kbd>ArrowDown</span></td><td>Resize by <code>keyboardStep</code> percent (vertical).</td></tr>
<tr><td><span data-uix-kbd>Home</span></td><td>Jump to the minimum size.</td></tr>
<tr><td><span data-uix-kbd>End</span></td><td>Jump to the maximum size.</td></tr>
<tr><td><span data-uix-kbd>Enter</span></td><td>Toggle collapse (when <code>collapsible</code>).</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">resize-trigger</td><td>role</td><td class="type">'separator'</td></tr>
<tr><td class="name">resize-trigger</td><td>aria-orientation</td><td class="type">'horizontal' | 'vertical'</td></tr>
<tr><td class="name">resize-trigger</td><td>aria-valuenow</td><td class="type">current size %</td></tr>
<tr><td class="name">resize-trigger</td><td>aria-valuemin</td><td class="type">minSize</td></tr>
<tr><td class="name">resize-trigger</td><td>aria-valuemax</td><td class="type">maxSize</td></tr>
<tr><td class="name">resize-trigger</td><td>aria-label</td><td class="type">'Resize panels'</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Other concerns</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Concern</th><th>Contract</th></tr></thead>
<tbody>
<tr>
<td class="name">Focus visible</td>
<td>
<code>:focus-visible</code> on the resize trigger surfaces the canonical UIX
focus ring.
</td>
</tr>
<tr>
<td class="name">Pointer / touch</td>
<td>
Cursor flips to <code>col-resize</code> / <code>row-resize</code> on the
handle. Soma owns pointer-down / pointer-move / pointer-up.
</td>
</tr>
<tr>
<td class="name">Reduced motion</td>
<td>
The handle's hover / drag transition uses <code>--duration-fast</code>; the
commit pulse honours the sema visual channel's reduced-motion override.
</td>
</tr>
<tr>
<td class="name">RTL</td>
<td>
Soma's <code>dir</code> prop swaps the direction of <code>ArrowLeft</code> /
<code>ArrowRight</code> in horizontal splitters so the user always
"grows-forward" with the trailing-edge key.
</td>
</tr>
</tbody>
</table>
</div>
</section>
{/if}
</div>

Powered by TurnKey Linux.