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.
svelte-kit-vice/src/uix/morfo/components/field-langs.ts

163 lines
6.2 KiB

import type { Morfo } from '../types';
import { v } from '../types';
/**
* FieldLangs — a localized-input wrapper around `Field` that holds one value
* per language and switches which language is being edited through a compact
* switcher docked INSIDE the field control (like a trailing unit selector).
*
* Surface (composition, not a fork — the picker pattern A27): the root owns the
* shared `values` map + `activeLang`; it composes the eidos `<Field>` and, at
* the logical `switcherSide` of the control, an in-control switcher whose form
* adapts to the language count — a segmented `ToggleGroup` for ≤3 languages,
* the design-system `Select` (ghost) for more (or forced via `switcher`).
*
* <FieldLangs languages={['en','es','fr']} required={['en']} bind:values>
* <FieldLangs.Label>Product name</FieldLangs.Label>
* <FieldLangs.Control><FieldLangs.Input /></FieldLangs.Control>
* <FieldLangs.ErrorText />
* </FieldLangs>
*
* The `Field` and `ToggleGroup` surfaces keep their own morfo contracts; this
* morfo only declares FieldLangs' own parts (root, switcher, per-language
* option) plus the language-switch event.
*/
export const fieldLangsMorfo = {
name: 'FieldLangs',
kebab: 'field-langs',
scope: ['soma', 'sema', 'eidos'],
// The language switch fires `shift-navigate`; its perceptual signature is
// the `shift` family base (a "noticed" visual settle, no sound by default).
// The switch-press tap comes from the composed ToggleGroup's own pack — no
// per-component tuning needed here.
expression: 'family-default',
apg: 'none — composite CMS pattern with no single APG entry; the composed Field carries textbox and the switcher is a WAI-ARIA radiogroup in both modes',
texts: {
label: '#?components.field-langs.label|Localized field',
'switcher-label': '#?components.field-langs.switcher-label|Select language'
},
events: [
{
// The active editing language changed — a context/régimen shift
// between the per-language values (book cap. 27: `shift`, not a state
// fixation). In segmented mode the composed ToggleGroup also fires its
// own `commit-toggle` for the switch press; this event marks the
// higher level "you moved to another language context". `shift` takes
// no intent (only `commit`/`signal` require one).
name: 'shift-navigate',
semantic: {
family: 'shift',
verb: 'navigate',
target: v.partRef('provider'),
sequence: 'post'
}
}
],
parts: [
{
name: 'Provider',
kebab: 'provider',
archetype: 'provider',
kind: 'public',
defaultElement: 'div',
optional: false,
data: [
{ attr: 'data-active-lang', value: v.propRef('activeLang'), emit: 'value' },
{
attr: 'data-switcher-side',
values: ['start', 'end'],
value: v.propRef('switcherSide')
},
{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' },
{ attr: 'data-required', value: v.propRef('required'), severity: 'optional' },
{ attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' }
],
aria: []
},
{
// The in-control switcher. Segmented mode = FieldLangs' OWN radiogroup
// of `LangOption` buttons (NOT the composed ToggleGroup — its
// `commit-toggle` semantics are wrong for a language-context
// navigation, which is `shift-navigate`; composing it would also leak
// its chrome into the control). Select mode = the design-system
// `<Select>` (ghost). FieldLangs adds its identity marker + `data-side`
// (which control edge it docks on, logical / RTL-aware) + `data-mode`.
// Private: the consumer never composes it — the Control injects it.
name: 'Switcher',
kebab: 'switcher',
archetype: 'group',
kind: 'private',
defaultElement: 'div',
optional: false,
data: [
{ attr: 'data-side', values: ['start', 'end'], value: v.propRef('side') },
{ attr: 'data-mode', values: ['segmented', 'select'], value: v.propRef('mode') }
],
aria: [
{
attr: 'aria-label',
value: v.translationRef('switcher-label', 'Select language'),
severity: 'recommended'
}
]
},
{
// One radio button per supported language in SEGMENTED mode — a
// FieldLangs-owned `<button role="radio">`. Carries the per-language
// identity + completion metadata the recipe styles (the set/unset dot,
// the solid-primary active pill). Optional — absent in select mode.
// Private — generated from `languages`, never composed.
name: 'LangOption',
kebab: 'lang-option',
archetype: 'option',
kind: 'private',
defaultElement: 'button',
role: 'radio',
optional: true,
data: [
{ attr: 'data-lang', value: v.propRef('lang'), emit: 'value' },
{ attr: 'data-active', value: v.propRef('active'), severity: 'optional' },
{ attr: 'data-required', value: v.propRef('required'), severity: 'optional' },
{ attr: 'data-complete', value: v.propRef('complete'), severity: 'optional' }
],
aria: [{ attr: 'aria-checked', value: v.propRef('active'), ariaBoolean: true }]
},
{
// The multiline control shell — a `position: relative` wrapper around a
// composed `TextArea` with the switcher docked in its top corner (so
// the field's default floating label keeps working). Public: the
// consumer composes `<FieldLangs.Textarea>` instead of
// `<FieldLangs.Control><FieldLangs.Input/></FieldLangs.Control>` for a
// multiline localized field. Optional — the single-line path omits it.
name: 'Textarea',
kebab: 'textarea',
kind: 'public',
defaultElement: 'div',
optional: true,
data: [],
aria: []
},
{
// The dropdown list in SELECT mode — a vertical radiogroup of
// `LangOption` buttons inside a composed `Popover`. FieldLangs-owned,
// so selecting a language fires only `shift-navigate`; no `Select`
// commit semantics leak into the field. Optional — absent in segmented
// mode. Private — generated from `languages`.
name: 'Menu',
kebab: 'menu',
archetype: 'group',
kind: 'private',
defaultElement: 'div',
optional: true,
data: [],
aria: [
{
attr: 'aria-label',
value: v.translationRef('switcher-label', 'Select language'),
severity: 'recommended'
}
]
}
]
} as const satisfies Morfo;

Powered by TurnKey Linux.