import type { Morfo } from '../types'; import { v } from '../types'; /** * Collapsible — disclosure pattern (header + body). * * Per `src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md` §4.1 (Accordion = * emerge.expand): the disclosure flip has two perceptual directions * with distinct signatures, so it declares TWO events instead of one * `commit-toggle`. Both belong to the `emerge` family (transitional — * no intent). `emerge-expand` is post-sequence so the content exists * visually before Eidos reacts to `data-event='emerge-expand'`; * `emerge-collapse` is post too (collapsible-NEW-001) so the conceal * plays against a content the flip has not yet hidden. * * No intent / color subset — disclosure is non-evaluative. Air had a * manual `air.interaction.play('expansion', enter|exit)` that the * sema engine now subsumes via runtime.trigger. */ export const collapsibleMorfo = { name: 'Collapsible', kebab: 'collapsible', scope: ['soma', 'sema', 'eidos'], // expression resolved via the sema pack at // `src/uix/sema/components/collapsible.ts` — mirrors accordion // (`emerge.soft` expand + `emerge.exit` collapse). Coherence with // the sibling disclosure pattern. expression: 'pack', texts: { label: '#?components.collapsible.label|Collapsible' }, apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/', events: [ { name: 'emerge-expand', semantic: { family: 'emerge', verb: 'expand', target: v.partRef('content'), sequence: 'post' } }, { name: 'emerge-collapse', semantic: { family: 'emerge', verb: 'collapse', target: v.partRef('content'), // `post` (not `pre`): the provider sets `open = false` inside the // runtime HANDLER, so `pre` deferred the close behind the full // perceptual hold (~250ms measured dead delay — the Checkbox-lag // class, collapsible-NEW-001). With `post` the content closes // immediately; the CSS exit animation is keyed on `data-state`. sequence: 'post' } } ], parts: [ { name: 'Provider', kebab: 'provider', archetype: 'provider', kind: 'public', defaultElement: 'div', optional: false, states: ['open', 'closed'], data: [ { attr: 'data-state', values: ['open', 'closed'], value: v.stateRef('open') }, { attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional', condition: { when: 'prop-truthy', prop: 'disabled' } } ], aria: [] }, { name: 'Trigger', kebab: 'trigger', archetype: 'trigger', kind: 'public', defaultElement: 'button', role: 'button', optional: false, states: ['open', 'closed'], data: [ { attr: 'data-state', values: ['open', 'closed'], value: v.stateRef('open') }, { attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional', condition: { when: 'prop-truthy', prop: 'disabled' } } ], aria: [ { attr: 'type', value: v.literal('button') }, { attr: 'aria-expanded', value: v.stateRef('open') }, { attr: 'aria-controls', value: v.partRef('content'), condition: { when: 'part-present', part: 'content' }, severity: 'recommended' } ] }, { name: 'Content', kebab: 'content', archetype: 'content', kind: 'public', defaultElement: 'div', role: 'region', optional: false, states: ['open', 'closed'], data: [ { attr: 'data-state', values: ['open', 'closed'], value: v.stateRef('open') }, { attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional', condition: { when: 'prop-truthy', prop: 'disabled' } } ], aria: [ { attr: 'aria-labelledby', value: v.partRef('trigger'), condition: { when: 'part-present', part: 'trigger' }, severity: 'recommended' } ] } ] } as const satisfies Morfo;