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/src/uix/morfo/components/stepper.ts

258 lines
6.5 KiB

import type { Morfo } from '../types';
import { v } from '../types';
export const stepperMorfo = {
name: 'Stepper',
kebab: 'stepper',
scope: ['soma', 'sema', 'eidos'],
expression: 'pack',
apg: 'none — no official APG pattern for process steppers; triggers follow button semantics with a list landmark (checkpoint C5, 2026-07-07)',
texts: {
label: '#?components.stepper.label|Stepper',
list: '#?components.stepper.list|Steps'
},
events: [
{
name: 'shift-step',
semantic: {
family: 'shift',
verb: 'step',
target: v.partRef('provider'),
sequence: 'post'
}
},
{
name: 'commit-complete',
semantic: {
family: 'commit',
verb: 'complete',
target: v.partRef('provider'),
intent: 'fulfill',
sequence: 'post'
}
}
],
direction: {},
focus: {
kind: 'roving',
// Linear gating (non-selectable future steps) is candidate FILTERING,
// not a different movement model.
parts: ['trigger'],
orientation: 'horizontal'
},
parts: [
{
name: 'Provider',
kebab: 'provider',
archetype: 'provider',
kind: 'public',
defaultElement: 'div',
optional: false,
data: [
{
attr: 'data-orientation',
values: ['horizontal', 'vertical'],
value: v.propRef('orientation')
},
{ attr: 'data-linear', value: v.propRef('linear'), severity: 'optional' }
],
aria: []
},
{
name: 'List',
kebab: 'list',
archetype: 'group',
kind: 'public',
defaultElement: 'div',
role: 'tablist',
optional: false,
data: [],
aria: [
{
attr: 'aria-label',
value: v.translationRef('#?components.stepper.list|Steps'),
severity: 'recommended'
},
{ attr: 'aria-orientation', value: v.propRef('orientation') }
]
},
{
name: 'Item',
kebab: 'item',
archetype: 'item',
kind: 'public',
// A PRESENTATIONAL wrapper, not the tab. The pattern here is
// `list (role=tablist) > item > trigger (role=tab)`, and a generic
// element between a tablist and its tabs breaks the owns relationship —
// so the wrapper steps out of the tree, the same way an `<li>` inside a
// `role='menu'` does. Declared `li` until 2026-08-12 while the component
// rendered a role-less `div` inside the tablist: the declaration was
// wrong AND the a11y was (ledger D11).
defaultElement: 'div',
role: 'presentation',
optional: false,
states: ['complete', 'current', 'incomplete'],
data: [
{
attr: 'data-state',
values: ['complete', 'current', 'incomplete'],
value: v.propRef('state')
},
{
attr: 'data-orientation',
values: ['horizontal', 'vertical'],
value: v.propRef('orientation')
}
],
aria: []
},
{
name: 'Trigger',
kebab: 'trigger',
archetype: 'trigger',
kind: 'public',
defaultElement: 'button',
role: 'tab',
optional: true,
states: ['complete', 'current', 'incomplete'],
data: [{ attr: 'data-state', values: ['complete', 'current', 'incomplete'] }],
attrs: [{ attr: 'type', value: v.literal('button') }],
aria: [
{
attr: 'aria-controls',
value: v.partRef('content'),
condition: { when: 'part-present', part: 'content' },
severity: 'recommended'
},
{
attr: 'aria-current',
value: v.literal('step'),
severity: 'optional',
condition: { when: 'state-equals', state: 'current', value: 'true' }
},
{
attr: 'aria-disabled',
value: v.literal('true'),
severity: 'optional',
condition: { when: 'prop-truthy', prop: 'disabled' }
}
],
// Only TWO of these four arrows ever move: the shared roving primitive
// resolves its pair through `getDirectionalKeys(dir, orientation)`
// (stepper-provider :304), so the cross-axis pair is inert. Left
// UNCONDITIONED in the P1·CONTRATO keyboard wave, unlike its six
// siblings: the condition needs `orientation` on THIS part, and the
// trigger neither declares `data-orientation` (its Item parent does)
// nor registers the prop. The dead pair is aspirational until the
// part carries the axis — inventing the source to satisfy the
// condition would be the drift this wave exists to remove.
keyboard: [
{ key: 'ArrowRight', action: 'next-item' },
{ key: 'ArrowLeft', action: 'prev-item' },
{ key: 'ArrowDown', action: 'next-item' },
{ key: 'ArrowUp', action: 'prev-item' },
{ key: 'Home', action: 'first-item' },
{ key: 'End', action: 'last-item' }
]
},
{
name: 'Indicator',
kebab: 'indicator',
archetype: 'indicator',
kind: 'public',
defaultElement: 'div',
optional: true,
states: ['complete', 'current', 'incomplete'],
data: [
{
attr: 'data-state',
values: ['complete', 'current', 'incomplete'],
value: v.propRef('state')
}
],
aria: [{ attr: 'aria-hidden', value: v.literal('true') }]
},
{
name: 'Separator',
kebab: 'separator',
archetype: 'separator',
kind: 'public',
defaultElement: 'div',
role: 'separator',
optional: true,
states: ['complete', 'current', 'incomplete'],
data: [
{
attr: 'data-state',
values: ['complete', 'current', 'incomplete'],
value: v.propRef('state')
},
{
attr: 'data-orientation',
values: ['horizontal', 'vertical'],
value: v.propRef('orientation')
}
],
aria: []
},
{
name: 'Content',
kebab: 'content',
archetype: 'content',
kind: 'public',
defaultElement: 'div',
role: 'tabpanel',
optional: true,
states: ['complete', 'current', 'incomplete'],
data: [
{
attr: 'data-state',
values: ['complete', 'current', 'incomplete'],
value: v.propRef('state')
}
],
aria: []
},
{
name: 'CompletedContent',
kebab: 'completed-content',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [
{
attr: 'data-state',
values: ['complete', 'current', 'incomplete'],
value: v.propRef('state')
}
],
aria: []
},
{
name: 'PrevTrigger',
kebab: 'prev-trigger',
archetype: 'trigger',
kind: 'public',
defaultElement: 'button',
role: 'button',
optional: true,
data: [{ attr: 'data-disabled', severity: 'optional' }],
attrs: [{ attr: 'type', value: v.literal('button') }],
aria: []
},
{
name: 'NextTrigger',
kebab: 'next-trigger',
archetype: 'trigger',
kind: 'public',
defaultElement: 'button',
role: 'button',
optional: true,
data: [{ attr: 'data-disabled', severity: 'optional' }],
attrs: [{ attr: 'type', value: v.literal('button') }],
aria: []
}
]
} as const satisfies Morfo;

Powered by TurnKey Linux.