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.
172 lines
5.0 KiB
172 lines
5.0 KiB
import type { Morfo } from '../types';
|
|
import { v } from '../types';
|
|
|
|
export const splitterMorfo = {
|
|
name: 'Splitter',
|
|
kebab: 'splitter',
|
|
// Sema is in scope: the splitter's three-event drag shape ships a
|
|
// per-component cascade in `src/uix/sema/components/splitter.ts`
|
|
// (pickup sound on `handle-pick`, dynamic per-emit overrides on
|
|
// `handle-drag`, snap chime on `commit-set`). Without `'sema'`
|
|
// here the cascade rules would never load and `handle-*` events
|
|
// would fall through to the family base — which for `handle` has
|
|
// no `sound` slice, so drag would be silent. Eidos is in scope too
|
|
// since the components/splitter eidos wrappers reference the morfo
|
|
// in their CSS recipe (`data-event='commit-set'` pulse).
|
|
scope: ['soma', 'sema', 'eidos'],
|
|
expression: 'pack',
|
|
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/',
|
|
texts: {
|
|
label: '#?components.splitter.label|Splitter',
|
|
resize: '#?components.splitter.resize|Resize panels'
|
|
},
|
|
events: [
|
|
// Splitter mirrors Slider's three-event grip/drag/commit shape. Both
|
|
// are direct-manipulation primitives where the user grabs a handle,
|
|
// drags it, and releases — the sema rendering should match so
|
|
// audio/haptic projections feel coherent across the family.
|
|
{
|
|
// User grabs the resize trigger (pointerdown). Coincident with
|
|
// the gesture start — no settled value yet.
|
|
name: 'handle-pick',
|
|
semantic: {
|
|
family: 'handle',
|
|
verb: 'pick',
|
|
target: v.partRef('resize-trigger'),
|
|
sequence: 'coincident'
|
|
}
|
|
},
|
|
{
|
|
// Continuous resize during drag (pointermove). Coincident with
|
|
// each frame of motion — the value is in flux.
|
|
name: 'handle-drag',
|
|
semantic: {
|
|
family: 'handle',
|
|
verb: 'drag',
|
|
target: v.partRef('resize-trigger'),
|
|
sequence: 'coincident'
|
|
}
|
|
},
|
|
{
|
|
// Value committed on release (pointerup). The settled panel
|
|
// sizes are now the source of truth.
|
|
name: 'commit-set',
|
|
semantic: {
|
|
family: 'commit',
|
|
verb: 'set',
|
|
target: v.partRef('provider'),
|
|
intent: 'neutral',
|
|
sequence: 'post'
|
|
}
|
|
}
|
|
],
|
|
direction: {},
|
|
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-disabled', value: v.propRef('disabled'), severity: 'optional' },
|
|
{ attr: 'data-resizing', value: v.propRef('resizing'), severity: 'optional' }
|
|
],
|
|
aria: []
|
|
},
|
|
{
|
|
name: 'Panel',
|
|
kebab: 'panel',
|
|
archetype: 'content',
|
|
kind: 'public',
|
|
defaultElement: 'div',
|
|
optional: false,
|
|
states: ['collapsed', 'expanded'],
|
|
data: [
|
|
{
|
|
attr: 'data-state',
|
|
values: ['collapsed', 'expanded'],
|
|
value: v.propRef('state')
|
|
},
|
|
{
|
|
attr: 'data-orientation',
|
|
values: ['horizontal', 'vertical'],
|
|
value: v.propRef('orientation')
|
|
},
|
|
{ attr: 'data-splitter-panel-id', value: v.propRef('panelId'), emit: 'value' }
|
|
],
|
|
aria: []
|
|
},
|
|
{
|
|
name: 'ResizeTrigger',
|
|
kebab: 'resize-trigger',
|
|
archetype: 'trigger',
|
|
kind: 'public',
|
|
defaultElement: 'div',
|
|
role: 'separator',
|
|
optional: false,
|
|
states: ['dragging', 'hover', 'inactive'],
|
|
data: [
|
|
{
|
|
attr: 'data-state',
|
|
values: ['dragging', 'hover', 'inactive'],
|
|
value: v.propRef('state')
|
|
},
|
|
{
|
|
attr: 'data-orientation',
|
|
values: ['horizontal', 'vertical'],
|
|
value: v.propRef('orientation')
|
|
},
|
|
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' },
|
|
{ attr: 'data-dragging', value: v.propRef('dragging'), severity: 'optional' }
|
|
],
|
|
aria: [
|
|
{
|
|
attr: 'aria-label',
|
|
value: v.translationRef('#?components.splitter.resize|Resize panels'),
|
|
severity: 'recommended'
|
|
},
|
|
{ attr: 'aria-orientation', value: v.propRef('orientation') },
|
|
{ attr: 'aria-valuenow', value: v.propRef('size') },
|
|
{ attr: 'aria-valuemin', value: v.propRef('minSize') },
|
|
{ attr: 'aria-valuemax', value: v.propRef('maxSize') }
|
|
],
|
|
// Conditions added in the P1·CONTRATO keyboard wave: four arrows were
|
|
// declared unconditionally while only TWO ever resize. The provider
|
|
// resolves its pair through `getDirectionalKeys(dir, orientation)`
|
|
// (splitter-provider :675-677), so the cross-axis pair is inert.
|
|
keyboard: [
|
|
{
|
|
key: 'ArrowRight',
|
|
action: 'resize',
|
|
condition: { when: 'prop-equals', prop: 'orientation', value: 'horizontal' }
|
|
},
|
|
{
|
|
key: 'ArrowLeft',
|
|
action: 'resize',
|
|
condition: { when: 'prop-equals', prop: 'orientation', value: 'horizontal' }
|
|
},
|
|
{
|
|
key: 'ArrowUp',
|
|
action: 'resize',
|
|
condition: { when: 'prop-equals', prop: 'orientation', value: 'vertical' }
|
|
},
|
|
{
|
|
key: 'ArrowDown',
|
|
action: 'resize',
|
|
condition: { when: 'prop-equals', prop: 'orientation', value: 'vertical' }
|
|
},
|
|
{ key: 'Home', action: 'minimize' },
|
|
{ key: 'End', action: 'maximize' },
|
|
{ key: 'Enter', action: 'toggle' }
|
|
]
|
|
}
|
|
]
|
|
} as const satisfies Morfo;
|