import type { Morfo } from '../types'; import { v } from '../types'; import { INTENTS } from '../../intent'; export const toastMorfo = { name: 'Toast', kebab: 'toast', scope: ['soma', 'sema'], apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/alert/', texts: { label: '#?components.toast.label|Toast' }, events: [ { name: 'present', semantic: { family: 'emerge', verb: 'present', target: v.partRef('item'), sequence: 'pre' } }, { // Persistence: explicit `transient` even though the book ยง6.2 // canon would imply `untilFix` / `untilAction` for `signal + // risk` / `signal + threat`. The Toast component owns its own // lifecycle via `Toast.Provider`'s auto-dismiss timer + user // dismiss; the `data-event-*` projection is a one-shot pulse // when the toast enters. Persistence of the toast surface // itself lives in `data-state` (open/closed) on the Item, not // in the signal projection. name: 'announce', semantic: { family: 'signal', verb: 'announce', target: v.partRef('item'), sequence: 'coincident', intent: { fromProp: 'intent', default: 'neutral', supported: INTENTS }, persistence: 'transient' } }, { name: 'dismiss', semantic: { family: 'emerge', verb: 'dismiss', target: v.partRef('item'), sequence: 'pre' } } ], parts: [ { name: 'Provider', kebab: 'provider', archetype: 'provider', kind: 'virtual', defaultElement: 'none', optional: false, data: [], aria: [] }, { name: 'Viewport', kebab: 'viewport', archetype: 'viewport', kind: 'public', defaultElement: 'div', role: 'region', optional: false, data: [], aria: [ { attr: 'aria-label', value: v.propRef('label'), severity: 'recommended' }, { attr: 'aria-live', value: v.literal('polite') } ] }, { name: 'Item', kebab: 'item', archetype: 'item', kind: 'public', defaultElement: 'div', optional: false, states: ['open', 'closed'], data: [ { attr: 'data-state', values: ['open', 'closed'], value: v.stateRef('open') }, { attr: 'data-starting-style', severity: 'optional' }, { attr: 'data-ending-style', severity: 'optional' }, { attr: 'data-intent', values: INTENTS, value: v.propRef('intent') }, { attr: 'data-loading', severity: 'optional', value: v.propRef('loading') }, { attr: 'data-swipe', values: ['start', 'move', 'cancel', 'end'], severity: 'optional', value: v.propRef('swipeState') }, { attr: 'data-swipe-direction', values: ['up', 'down', 'left', 'right'], value: v.propRef('swipeDirection') } ], aria: [ { attr: 'role', value: v.mapRef(v.propRef('intent'), { neutral: 'status', affirm: 'status', fulfill: 'status', risk: 'alert', threat: 'alert', // `loss` is a consummated negative consequence (no urgency // to interrupt) โ€” `status` is the appropriate ARIA role. loss: 'status' }) }, { attr: 'aria-live', value: v.mapRef(v.propRef('intent'), { neutral: 'polite', affirm: 'polite', fulfill: 'polite', risk: 'assertive', threat: 'assertive', loss: 'polite' }), severity: 'recommended' }, { attr: 'aria-atomic', value: v.literal('true') }, { attr: 'aria-labelledby', value: v.partRef('title'), condition: { when: 'part-present', part: 'title' }, severity: 'recommended' }, { attr: 'aria-describedby', value: v.partRef('description'), condition: { when: 'part-present', part: 'description' }, severity: 'optional' } ], keyboard: [{ key: 'Escape', action: 'close' }] }, { name: 'Title', kebab: 'title', archetype: 'title', kind: 'public', defaultElement: 'div', optional: true, data: [], aria: [] }, { name: 'Description', kebab: 'description', archetype: 'description', kind: 'public', defaultElement: 'div', optional: true, data: [], aria: [] }, { /** * Status disc โ€” the leading-edge icon area whose color follows * the Item's `data-intent`. Optional visual structural part: * eidos's flat template ships one by default, but a consumer * compounding the parts can omit it (the recipe's grid auto- * collapses) or replace its contents. */ name: 'Status', kebab: 'status', archetype: 'indicator', kind: 'public', defaultElement: 'span', optional: true, data: [], aria: [{ attr: 'aria-hidden', value: v.literal('true') }] }, { /** * Main column โ€” stack container for title + description + action. * Pure layout part (no semantics, no states); declared so the * recipe's `[data-toast-main]` selector is morfo-backed instead * of an off-contract data-attr. */ name: 'Main', kebab: 'main', kind: 'public', defaultElement: 'div', optional: true, data: [], aria: [] }, { name: 'Action', kebab: 'action', archetype: 'action', kind: 'public', defaultElement: 'button', role: 'button', optional: true, data: [], aria: [ { attr: 'type', value: v.literal('button') }, { attr: 'aria-label', value: v.propRef('altText'), severity: 'recommended' } ] }, { name: 'Close', kebab: 'close', archetype: 'close', kind: 'public', defaultElement: 'button', role: 'button', optional: true, data: [], aria: [ { attr: 'type', value: v.literal('button') }, { attr: 'aria-label', value: v.commonRef('buttons.close', 'Close'), severity: 'recommended' } ] } ] } as const satisfies Morfo;