import type { Morfo } from '../types'; import { v } from '../types'; export const cssFieldMorfo = { name: 'CssField', kebab: 'css-field', scope: ['soma', 'sema'], texts: { label: '#?components.css-field.label|CSS Value', invalid: '#?components.css-field.invalid|Invalid CSS value' }, apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/', events: [ { name: 'commit-set', semantic: { family: 'commit', verb: 'set', target: v.partRef('provider'), intent: 'neutral', sequence: 'post' } }, { name: 'handle-pick', semantic: { family: 'handle', verb: 'pick', target: v.partRef('scrubber'), sequence: 'pre', channels: ['sound', 'haptic'] } }, { name: 'handle-drag-scrub', semantic: { family: 'handle', verb: 'drag', target: v.partRef('scrubber'), sequence: 'coincident', channels: ['haptic'] } }, { // Book §6.2 — signal.risk = untilFix. Typing a value whose unit is not // in `values` (or otherwise unparseable) keeps the warning visible until // the user fixes the input. Provider clears via `runtime.clearTarget(input)` // before each commit/blur validation pass — the next valid value is the fix. name: 'signal-warn-invalid', semantic: { family: 'signal', verb: 'warn', target: v.partRef('input'), intent: 'risk', sequence: 'post', persistence: 'untilFix' }, a11ySemantic: { requiresPersistentTrace: true, reducedMotionFallback: 'text' } } ], parts: [ { name: 'Provider', kebab: 'provider', archetype: 'provider', kind: 'public', defaultElement: 'div', optional: false, data: [ // Structural identity: CssField IS a spin-field. These presence attrs // let the shared spin-field eidos visual style it without cloning CSS // per component (see eidos/components/spin-field). { attr: 'data-spin-field', value: v.literal(''), severity: 'required' }, { attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }, { attr: 'data-readonly', value: v.propRef('readonly'), severity: 'optional' }, { attr: 'data-required', value: v.propRef('required'), severity: 'optional' }, { attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' }, { attr: 'data-focused', value: v.propRef('focused'), severity: 'optional' }, { attr: 'data-scrubbing', value: v.propRef('scrubbing'), severity: 'optional' } ], aria: [] }, { name: 'Input', kebab: 'input', archetype: 'input', kind: 'public', defaultElement: 'input', role: 'spinbutton', optional: false, data: [ { attr: 'data-spin-field-input', value: v.literal(''), severity: 'required' }, { attr: 'data-disabled', severity: 'optional' }, { attr: 'data-readonly', severity: 'optional' }, { attr: 'data-invalid', severity: 'optional' } ], aria: [ { attr: 'aria-valuenow', value: v.propRef('value'), severity: 'optional' }, { attr: 'aria-valuetext', value: v.propRef('value'), severity: 'recommended' }, { attr: 'aria-required', value: v.propRef('required'), severity: 'optional', ariaBoolean: true }, { attr: 'aria-disabled', value: v.propRef('disabled'), severity: 'optional', ariaBoolean: true }, { attr: 'aria-readonly', value: v.propRef('readonly'), severity: 'optional', ariaBoolean: true }, { attr: 'aria-invalid', value: v.propRef('invalid'), severity: 'optional', ariaBoolean: true } ], keyboard: [ { key: 'ArrowUp', action: 'increment' }, { key: 'ArrowDown', action: 'decrement' }, { key: 'Shift+ArrowUp', action: 'increment-large' }, { key: 'Shift+ArrowDown', action: 'decrement-large' }, { key: 'PageUp', action: 'increment-large' }, { key: 'PageDown', action: 'decrement-large' }, { key: 'Home', action: 'first-item' }, { key: 'End', action: 'last-item' }, { key: 'Enter', action: 'commit' } ] }, { name: 'IncrementTrigger', kebab: 'increment-trigger', archetype: 'trigger', kind: 'public', defaultElement: 'button', role: 'button', optional: true, data: [ { attr: 'data-spin-field-increment-trigger', value: v.literal(''), severity: 'required' }, { attr: 'data-disabled', severity: 'optional' } ], aria: [ { attr: 'type', value: v.literal('button') }, { attr: 'aria-label', value: v.commonRef('buttons.increment', 'Increment'), severity: 'recommended' }, { attr: 'aria-disabled', value: v.literal('true'), severity: 'optional', condition: { when: 'prop-truthy', prop: 'disabled' } } ] }, { name: 'DecrementTrigger', kebab: 'decrement-trigger', archetype: 'trigger', kind: 'public', defaultElement: 'button', role: 'button', optional: true, data: [ { attr: 'data-spin-field-decrement-trigger', value: v.literal(''), severity: 'required' }, { attr: 'data-disabled', severity: 'optional' } ], aria: [ { attr: 'type', value: v.literal('button') }, { attr: 'aria-label', value: v.commonRef('buttons.decrement', 'Decrement'), severity: 'recommended' }, { attr: 'aria-disabled', value: v.literal('true'), severity: 'optional', condition: { when: 'prop-truthy', prop: 'disabled' } } ] }, { name: 'Scrubber', kebab: 'scrubber', archetype: 'thumb', kind: 'public', defaultElement: 'div', role: 'presentation', optional: true, data: [ { attr: 'data-spin-field-scrubber', value: v.literal(''), severity: 'required' }, { attr: 'data-scrubbing', severity: 'optional' }, { attr: 'data-disabled', severity: 'optional' } ], aria: [{ attr: 'aria-hidden', value: v.literal('true') }] } ] } as const satisfies Morfo;