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.
199 lines
6.0 KiB
199 lines
6.0 KiB
import type { Morfo } from '../types';
|
|
import { v } from '../types';
|
|
|
|
export const fieldMorfo = {
|
|
name: 'Field',
|
|
kebab: 'field',
|
|
scope: ['soma', 'sema', 'eidos'],
|
|
// The base shell stays on the family voice: the concrete fields carry the
|
|
// packs; Field.Input's Enter submit speaks with the commit family's base
|
|
// signature (adoption of the written family doctrine — see the event note).
|
|
expression: 'family-default',
|
|
// Field.Input is the single-line text input — same pattern the password /
|
|
// textarea siblings cite.
|
|
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/textbox/',
|
|
texts: {
|
|
label: '#?components.field.label|Field',
|
|
'required-indicator': '#?components.field.required-indicator|*',
|
|
'optional-indicator': '#?components.field.optional-indicator|(optional)'
|
|
},
|
|
events: [
|
|
// User pressed Enter on the plain-text Field.Input. ADOPTION of the
|
|
// family doctrine written (with incident) in password-field's morfo:
|
|
// per-keystroke typing is NOT a commit — `commit` is for terminal
|
|
// actions, and Enter-to-submit IS the text-input pattern (aligned with
|
|
// password / textarea / editable / number-field / search / combobox).
|
|
// This closed the text-input pair's asymmetry (audit ficha field F-2:
|
|
// textarea had `commit-submit`, the base Input did not).
|
|
{
|
|
name: 'commit-submit',
|
|
semantic: {
|
|
family: 'commit',
|
|
verb: 'submit',
|
|
target: v.partRef('input'),
|
|
intent: 'neutral',
|
|
sequence: 'post'
|
|
}
|
|
}
|
|
],
|
|
parts: [
|
|
{
|
|
name: 'Provider',
|
|
kebab: 'provider',
|
|
archetype: 'provider',
|
|
kind: 'public',
|
|
defaultElement: 'div',
|
|
optional: false,
|
|
// data-state (5-value aggregate) was pruned 2026-07-07 (checkpoint
|
|
// S10): zero CSS consumers catalog-wide and redundant with the flags
|
|
// below — one representation per concept. The resolved state still
|
|
// drives Input's aria-busy via its state condition.
|
|
data: [
|
|
{ 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-dirty', value: v.propRef('dirty'), severity: 'optional' },
|
|
{ attr: 'data-touched', value: v.propRef('touched'), severity: 'optional' },
|
|
{ attr: 'data-field-name', value: v.propRef('name'), emit: 'value', severity: 'optional' }
|
|
],
|
|
aria: []
|
|
},
|
|
{
|
|
name: 'Label',
|
|
kebab: 'label',
|
|
archetype: 'label',
|
|
kind: 'public',
|
|
defaultElement: 'label',
|
|
optional: true,
|
|
data: [
|
|
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' },
|
|
{ attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' },
|
|
{ attr: 'data-required', value: v.propRef('required'), severity: 'optional' }
|
|
],
|
|
attrs: [
|
|
{
|
|
attr: 'for',
|
|
value: v.partRef('input'),
|
|
condition: { when: 'part-present', part: 'input' },
|
|
severity: 'recommended'
|
|
}
|
|
],
|
|
aria: []
|
|
},
|
|
{
|
|
name: 'RequiredIndicator',
|
|
kebab: 'required-indicator',
|
|
archetype: 'indicator',
|
|
kind: 'public',
|
|
defaultElement: 'span',
|
|
optional: true,
|
|
data: [{ attr: 'data-required', value: v.propRef('required'), severity: 'optional' }],
|
|
aria: [{ attr: 'aria-hidden', value: v.literal('true') }]
|
|
},
|
|
{
|
|
name: 'OptionalIndicator',
|
|
kebab: 'optional-indicator',
|
|
archetype: 'indicator',
|
|
kind: 'public',
|
|
defaultElement: 'span',
|
|
optional: true,
|
|
data: [{ attr: 'data-required', value: v.propRef('required'), severity: 'optional' }],
|
|
aria: [{ attr: 'aria-hidden', value: v.literal('true') }]
|
|
},
|
|
{
|
|
name: 'Control',
|
|
kebab: 'control',
|
|
kind: 'public',
|
|
defaultElement: 'div',
|
|
optional: true,
|
|
data: [
|
|
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' },
|
|
{ attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' }
|
|
],
|
|
aria: []
|
|
},
|
|
{
|
|
name: 'Input',
|
|
kebab: 'input',
|
|
archetype: 'input',
|
|
kind: 'public',
|
|
defaultElement: 'input',
|
|
optional: true,
|
|
// `states` stays: the resolved field state drives aria-busy below
|
|
// (state-equals 'loading'). Its data-state attr was pruned with the
|
|
// other two (checkpoint S10, 2026-07-07).
|
|
states: ['state'],
|
|
data: [],
|
|
attrs: [{ attr: 'type', value: v.propRef('type') }],
|
|
aria: [
|
|
{
|
|
attr: 'aria-labelledby',
|
|
value: v.partRef('label'),
|
|
condition: { when: 'part-present', part: 'label' },
|
|
severity: 'recommended'
|
|
},
|
|
{ attr: 'aria-invalid', value: v.propRef('invalid'), ariaBoolean: true },
|
|
{
|
|
attr: 'aria-describedby',
|
|
value: v.partRef('helper-text'),
|
|
condition: { when: 'part-present', part: 'helper-text' },
|
|
severity: 'recommended'
|
|
},
|
|
{
|
|
attr: 'aria-busy',
|
|
value: v.literal('true'),
|
|
severity: 'optional',
|
|
condition: { when: 'state-equals', state: 'state', value: 'loading' }
|
|
},
|
|
{
|
|
attr: 'aria-required',
|
|
value: v.literal('true'),
|
|
severity: 'optional',
|
|
condition: { when: 'prop-truthy', prop: 'required' }
|
|
}
|
|
],
|
|
keyboard: [{ key: 'Enter', action: 'submit' }]
|
|
},
|
|
{
|
|
name: 'HelperText',
|
|
kebab: 'helper-text',
|
|
archetype: 'description',
|
|
kind: 'public',
|
|
defaultElement: 'p',
|
|
optional: true,
|
|
data: [{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }],
|
|
aria: []
|
|
},
|
|
{
|
|
name: 'ErrorText',
|
|
kebab: 'error-text',
|
|
archetype: 'description',
|
|
kind: 'public',
|
|
defaultElement: 'p',
|
|
role: 'alert',
|
|
optional: true,
|
|
data: [],
|
|
aria: [{ attr: 'aria-live', value: v.literal('polite') }]
|
|
},
|
|
{
|
|
name: 'Prefix',
|
|
kebab: 'prefix',
|
|
kind: 'public',
|
|
defaultElement: 'span',
|
|
optional: true,
|
|
data: [],
|
|
aria: [{ attr: 'aria-hidden', value: v.literal('true') }]
|
|
},
|
|
{
|
|
name: 'Suffix',
|
|
kebab: 'suffix',
|
|
kind: 'public',
|
|
defaultElement: 'span',
|
|
optional: true,
|
|
data: [],
|
|
aria: [{ attr: 'aria-hidden', value: v.literal('true') }]
|
|
}
|
|
]
|
|
} as const satisfies Morfo;
|