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/eidos/components
dev 6f42eebfe8
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
2 months ago
..
accordion fix(direction): getDir() no era reactivo, y 33 componentes estampaban su valor congelado 2 months ago
alert-dialog feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
anchor-nav uix(anchor-nav): F1.6 · scrollspy TOC (soma+eidos) + hardening 3 months ago
announce docs(components): S4 lotes 2-5 + drag-drop — 24 consumer-door dossiers, E-2.3 = 0 (P7) 3 months ago
aspect-ratio fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers 5 months ago
audio-player fix(media-player): de 2x no se podia bajar, y la etiqueta movia la botonera 2 months ago
aura revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
auto-grid fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers 5 months ago
avatar feat(direction): un guard para el eje logico-fisico, y el <main> que congelaba las 51 demos 2 months ago
backdrop revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
badge fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar 2 months ago
banner fix(color): un solo vocabulario de rol, y la documentación del color reconciliada 2 months ago
barcode feat(barcode): el ISBN es un PERFIL de entrada sobre EAN-13, no una simbología 2 months ago
box eidos(box): las variables de cascada de Box dejan de heredarse 3 months ago
breadcrumb feat(uix): --state-* rollout — 19 neutral-hover components use the state layer 3 months ago
button fix(color): un solo vocabulario de rol, y la documentación del color reconciliada 2 months ago
button-group uix(color): abre `color` en button-group + highlight (delegan a Button/Mark) 3 months ago
calendar feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
callout fix(color): un solo vocabulario de rol, y la documentación del color reconciliada 2 months ago
card fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar 2 months ago
card-group feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
carousel fix(carousel): las flechas apuntaban hacia adentro en RTL 2 months ago
cascade revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
chart feat(direction): eidos comparte el contexto, y el corpus registra el endgame 2 months ago
chat-composer uix(chat): bloque de mensajería chat-* v1 (log · message · composer · typing) 3 months ago
chat-list uix(chat): v2 · chat-list — panel de conversaciones (compone Listbox) 3 months ago
chat-log feat(direction): un guard para el eje logico-fisico, y el <main> que congelaba las 51 demos 2 months ago
chat-message uix(chat): v2 · jump-to-message highlight en chat-message 3 months ago
chat-typing uix(chat): v2 · typing avatars — slot Avatars en chat-typing 3 months ago
checkbox revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
chronos docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy 2 months ago
clipboard revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
code revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
code-block feat(uix): calendar-nav unification, TimePicker datetime blocks, A31 perf 3 months ago
collapsible feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
color-field feat(direction): el eslabon de contexto — la composicion hereda por fisica 2 months ago
color-picker feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
color-swatch revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
combobox feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
command docs(components): S4 lotes 2-5 + drag-drop — 24 consumer-door dossiers, E-2.3 = 0 (P7) 3 months ago
container feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) 3 months ago
context-menu feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) 3 months ago
count-up feat(count-up): animated STAT — prefix/suffix + currency/percent/compact (not a bare counter) 3 months ago
cropper feat(direction): un guard para el eje logico-fisico, y el <main> que congelaba las 51 demos 2 months ago
css-field revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
date-field feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
date-picker feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
date-range-field revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
date-range-picker feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
dialog feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
display revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
drag-drop revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
drawer feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
dropdown-menu feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
editable revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
emoji-picker feat(direction): el eslabon de contexto — la composicion hereda por fisica 2 months ago
empty-state uix(empty-state): F1.2 · componente base display (morfo+eidos) al suelo del dossier 3 months ago
fab fix(census): R-1.x focus/disabled census — 9 components cleared, evidence-checked (129 PASS) 3 months ago
feed fix(direction): dos reglas :dir(rtl) volteaban lo que ya estaba volteado 2 months ago
field feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
field-langs feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
file-upload feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
flex fix(eidos): las variables de `Flex` y `Grid` dejan de heredarse 2 months ago
float perf(eidos): code-split 35 component recipes (Phase 3, batch 1) 4 months ago
float-panel fix(float-panel): el resize se calculaba al reves en RTL, y se salia de sus bounds 2 months ago
form feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
format-date fix(uix): clear the catalog's clean functional-error flags + reframe C10 tiers 3 months ago
format-number fix(uix): clear the catalog's clean functional-error flags + reframe C10 tiers 3 months ago
gradient-builder feat(gradient): S9 gradient-* — graduate from ACTIVE_DEV_TRACK + apg + dossiers (initiative close) 3 months ago
gradient-picker feat(gradient): S9 gradient-* — graduate from ACTIVE_DEV_TRACK + apg + dossiers (initiative close) 3 months ago
grid fix(eidos): las variables de `Flex` y `Grid` dejan de heredarse 2 months ago
grid-list revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
group fix(cleanroom): F2+F3+F4-C+SEM-4s1 — lote mecánico, censos con guard, corpus documental y el close polimórfico de los pickers VIVO 3 months ago
heading feat(blocks): content-section cierra F2 — la rejilla de escape, y las escalas tipograficas salen de Text 2 months ago
highlight revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
icon test(eidos): triage the 15 long-red suite failures — 289/289 green, check 61→59 3 months ago
icon-button feat(eidos): IconButton primitive + Lote 0 iconOnly->IconButton cascade 4 months ago
image feat(direction): un guard para el eje logico-fisico, y el <main> que congelaba las 51 demos 2 months ago
image-adjustments feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) 3 months ago
image-picker feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) 3 months ago
kbd fix(eidos): component-coherence audit FASE 2 — focus-ring, fields, segments, langs, API nits 4 months ago
knob docs(components): S4 lote 1 (forms) — consumer-door dossiers + field-langs soma contract (P7) 3 months ago
label revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
link fix(blocks,canon): el ledger dice la verdad y el arco perceptivo vuelve a sonar 2 months ago
link-preview fix(morfo+docs): approved one-liner pass — delegation/apg declarations + doc drift (P7 close-out) 3 months ago
listbox revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
mark fix(color): un solo vocabulario de rol, y la documentación del color reconciliada 2 months ago
mask-field docs: el corpus entero deja de describir un sistema que ya no existe 2 months ago
media-player feat(direction): el eslabon de contexto — la composicion hereda por fisica 2 months ago
menu-dial feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
menubar feat(uix): Menubar.Panel — una entrada puede abrir un panel (role=dialog) 2 months ago
meter perf(eidos): code-split 35 component recipes (Phase 3, batch 1) 4 months ago
metrics fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente 2 months ago
mockup revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
month-grid feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
motion revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
natural-time-picker feat(direction): el eslabon de contexto — la composicion hereda por fisica 2 months ago
nav-tree docs: el corpus entero deja de describir un sistema que ya no existe 2 months ago
navigation-menu revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
number-field revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
onion-menu feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
pagination feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) 3 months ago
palabras fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente 2 months ago
password-field revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
picker-shell fix(cleanroom): F2+F3+F4-C+SEM-4s1 — lote mecánico, censos con guard, corpus documental y el close polimórfico de los pickers VIVO 3 months ago
pin-input fix(census): R-1.x focus/disabled census — 9 components cleared, evidence-checked (129 PASS) 3 months ago
popover revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
progress feat(eidos): recipe contract R-4.x + motion-channel migration 15/15 + theme-builder fixes + inventory decisions 3 months ago
proof-of-human feat(direction): un guard para el eje logico-fisico, y el <main> que congelaba las 51 demos 2 months ago
prose uix(prose): F1.5 · tipografía editorial con auto-exclusión de componentes embebidos 3 months ago
qr-code fix(census): R-1.x focus/disabled census — 9 components cleared, evidence-checked (129 PASS) 3 months ago
radio-cards revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
radio-group revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
range-calendar feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
rating-group fix(uix): C2b — invalid/disabled/readonly states: real fixes + cited exceptions 3 months ago
relative-time fix(uix): clear the catalog's clean functional-error flags + reframe C10 tiers 3 months ago
result uix(result): F1.3 · estado terminal de flujo/pagina (morfo+eidos) al suelo del dossier 3 months ago
s-text revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
s-text-virtual-list fix(morfo+docs): approved one-liner pass — delegation/apg declarations + doc drift (P7 close-out) 3 months ago
scroll-area fix(uix): focus ring on surfaces — migrate 5 box-shadow rings to outline 3 months ago
scroll-frames docs(components): final lote 6 — the LAST five dossiers; full matrix GREEN (134/0/0) 3 months ago
search-field revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
section docs(layout): READMEs for 8 layout primitives (audit PASS) 5 months ago
select revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
separator feat(eidos): recipe contract R-4.x + motion-channel migration 15/15 + theme-builder fixes + inventory decisions 3 months ago
sidebar docs: el corpus entero deja de describir un sistema que ya no existe 2 months ago
skeleton revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
slider fix(direction): getDir() no era reactivo, y 33 componentes estampaban su valor congelado 2 months ago
spin-field docs(eidos): close S7 paperwork — passive justification + spin-field layer contract (P7) 3 months ago
spinner revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
split-button feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
splitter feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
stack fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers 5 months ago
stepper revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
sticky fix(sticky): sentinelRef is State + eidos types import ProviderProps 3 months ago
surface revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
svg feat(eidos): IconButton primitive + Lote 0 iconOnly->IconButton cascade 4 months ago
switch feat(direction): un guard para el eje logico-fisico, y el <main> que congelaba las 51 demos 2 months ago
table revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
tabs refactor(tabs): el indicador pasa a MeasuredIndicator, y aparece un punto ciego del guard 2 months ago
tag-group revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
tags-input revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
text feat(blocks): content-section cierra F2 — la rejilla de escape, y las escalas tipograficas salen de Text 2 months ago
text-blur feat(packs+text): incorporate the animation collection — Ambient pack + text-effects family + docs 3 months ago
text-circular feat(packs+text): incorporate the animation collection — Ambient pack + text-effects family + docs 3 months ago
text-focus feat(packs+text): incorporate the animation collection — Ambient pack + text-effects family + docs 3 months ago
text-gradient feat(text-gradient): `colors` discriminates canonical name vs stop list (folds `preset`) 3 months ago
text-scramble feat(packs+text): incorporate the animation collection — Ambient pack + text-effects family + docs 3 months ago
textarea revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
time-field revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
time-picker revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
time-range-field revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
time-range-picker revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
timeline revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
toast feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
toggle uix(color): abre `color` al sistema completo (role·intent·paleta·valor) en la familia interactiva + rail segmentado de radio-group 3 months ago
toggle-group revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
toolbar docs(direction): los 55 componentes que aceptan `dir` ya lo documentan 2 months ago
tooltip feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
trans fix(uix): clear the catalog's clean functional-error flags + reframe C10 tiers 3 months ago
tree-grid feat(direction): un guard para el eje logico-fisico, y el <main> que congelaba las 51 demos 2 months ago
tree-view fix(direction): dos reglas :dir(rtl) volteaban lo que ya estaba volteado 2 months ago
virtual-grid revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
virtual-list revert: deshacer la auditoría entera — se hizo sin leer la doctrina 2 months ago
waveform docs(direction): los 55 componentes que aceptan `dir` ya lo documentan 2 months ago
wrap fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers 5 months ago
year-grid feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve 2 months ago
README.md docs(direction): el contrato pasa a ser canon, y el corpus deja de contradecirlo 2 months ago

README.md

eidos/components/

Recipes + wrappers visuales por componente. Cada componente vive en su subdirectorio con la forma canónica documentada abajo. Los .css sueltos al nivel raíz son legacy de la fase "eidos = solo CSS" y se migran progresivamente.

⚠️ Contrato de construcción — leer antes de escribir un recipe/wrapper. La fuente de verdad de qué tokens/arquetipos consume un componente visual es la tabla «Build contract» de docs/guides/component-guide.md (migrada allí el 2026-07-11, DOC-1; el audit de 2026-06-19 que la sembró queda como historia): elevación vía data-depth (bundle completo) · estado vía --state-* · radio vía factor global + [data-shape-nest] · foco outline --focus-ring-* (§32) · label de campo canónico · bundle --size-* · touch-target §37 · color por token (cero hex) · densidad --space-*/--control-height-* · RTL lógico + :dir(rtl) (nunca [dir='rtl']), y el provider estampa el dir crudo si el recipe ramifica con :dir() — sin estampar, :dir() sólo ve la dirección heredada (contrato de dirección) · i18n langs.ts · portal-safe · composición. Todos los ejes están LIVE con su guard (R-*, contracts, elevation-plane).


Forma canónica (disciplined option C — vigente desde 2026-05-10)

Convención unificada para TODOS los componentes multi-part. Sigue la estructura de air (la rama legacy, ya retirada del árbol) y la ergonomía moderna de bits-ui / shadcn-svelte: una sola entidad mental — <Drawer> — con hijos accesibles como propiedades — <Drawer.Trigger>, <Drawer.Content>, etc.

Reglas duras

  1. Un solo punto de entrada por componente: la default export es el componente root visual. Se llama igual que el componente (<Drawer>, <Tabs>, <Checkbox>, …) — no <Drawer.Provider>, no <Drawer.Root>.
  2. El root vive en {name}.svelte — NO en {name}-provider.svelte. El nombre del fichero coincide con el nombre del componente.
  3. No exportar Provider públicamente. La separación "Provider compound vs flat" es una invención previa que se ha retirado: hay UNA forma compound — el root + sus hijos atados.
  4. Los hijos siguen el naming de air / headless: Trigger, Content, Overlay, Title, Description, Close, Portal, Header, Footer, Item, Indicator, HiddenInput, Group, Label, etc. No inventar nombres nuevos.
  5. Portal se incluye donde air lo tenía (Dialog, Drawer, Popover, Tooltip — overlays portaled). Importado de $soma/components/internal.
  6. No flat con snippet slots como API principal. La invención <Drawer trigger={...} title={...} actions={...}> está retirada — esconde decisiones de composición que deberían ser explícitas en componentes con portal/overlay/content/close.
  7. Los hijos se atan al root con asignación explícita, no Object.assign (que en Svelte 5 puede causar issues sutiles de hidratación):
    const Drawer = DrawerComponent as DrawerNamespace;
    Drawer.Trigger = Trigger;
    Drawer.Content = Content;
    // ...
    

Estructura de directorio

drawer/
  drawer.svelte                ← root (lo que era air's Provider)
  drawer-trigger.svelte
  drawer-overlay.svelte
  drawer-content.svelte
  drawer-handle.svelte
  drawer-title.svelte
  drawer-description.svelte
  drawer-close.svelte
  drawer-header.svelte         ← eidos-only layout shell (cuando aplique)
  drawer-footer.svelte         ← idem
  drawer.css                   ← recipe
  types.ts                     ← extiende las props públicas de Soma
  index.ts                     ← compone Drawer + hijos

{name}.svelte — el root

Wrapper directo sobre el provider público de Soma. Setea el contexto headless, acepta los bindables del estado (open, value, pressed, etc.), añade los data-attrs visuales propios de eidos (data-size, data-variant, data-color, …), y renderiza {@render children?.()} para que los hijos se compongan dentro.

<script lang="ts">
	/**
	 * Eidos `<Drawer>` — root component. Wraps the Soma
	 * to set up the component context.
	 */
	import * as Drawer from '$soma/components/drawer';
	import type { DrawerProps } from './types';

	let {
		open = $bindable(false),
		activeSnapPoint = $bindable(null),
		isDragging = $bindable(false),
		children,
		...rest
	}: DrawerProps = $props();
</script>

<Drawer.Provider {...rest} bind:open bind:activeSnapPoint bind:isDragging>
	{@render children?.()}
</Drawer.Provider>

Patrón de import interno: import * as Drawer from '$soma/components/drawer'. Eidos no crea una fachada headless propia por componente; envuelve las partes públicas de Soma directamente como <Drawer.Provider>, <Drawer.Trigger>, etc.

Prohibiciones de naming interno

El namespace interno que apunta a Soma se llama igual que el componente. No se prefija ni se renombra:

<!-- Correcto -->
import * as Collapsible from '$soma/components/collapsible';

<Collapsible.Provider>
	<Collapsible.Trigger />
</Collapsible.Provider>

Formas prohibidas:

  • import * as Parts from '$soma/components/collapsible'
  • import * as CollapsibleBase from '$soma/components/collapsible'
  • import { Provider as SomaCollapsibleProvider } from '$soma/components/collapsible'
  • etiquetas sueltas <Provider>, <Trigger>, <Content> dentro de Eidos
  • tipos o aliases internos SomaXxxProvider, XxxBase, XxxRoot

Motivo: Eidos ya declara la capa en el path. El import interno debe expresar la entidad headless concreta de Soma, no una abstraccion inventada. Si un wrapper Eidos necesita el provider de Soma, escribe <Collapsible.Provider>; si necesita una parte, escribe <Collapsible.Trigger>, <Collapsible.Content>, etc.

Guardia de componentes compuestos de fecha

Incidencia 2026-05-20: DateField, DatePicker y DateRangePicker no pueden cerrarse con wrappers Eidos si Morfo/Soma y la demo no estan cerrados primero. Para componentes compuestos de fecha:

  • Comparar antes con morfo-runtime (air / terra) y referencias React Aria, Ark UI, Bits UI y shadcn-svelte.
  • Completar Morfo con partes, data-*, ARIA, estados, keyboard y eventos observables de la superficie compuesta.
  • La demo no puede inventar data-calendar-* o data-range-calendar-*; si la receta los necesita, pertenecen a Morfo/Soma.
  • Cada control visible (granularity, hourCycle, segmentos, min/max, pagedNavigation, modal/no-modal, clear) debe cambiar algo visible en el stage y estar reflejado en los snippets.
  • Verificar visualmente popup, uno/dos meses, seleccion, deseleccion parcial, limites y accion de borrado antes de documentarlo como listo.

{name}-{part}.svelte — hijos passthrough

Cada hijo es un wrapper delgado sobre la part del Soma. Si añade visual-only data-attrs, los stamp aquí (ej. data-size en Content). Si es passthrough puro (Trigger, Close), trivial.

<!-- drawer-trigger.svelte -->
<script lang="ts">
	import * as Drawer from '$soma/components/drawer';
	import type { DrawerTriggerProps } from './types';

	let { children, ...rest }: DrawerTriggerProps = $props();
</script>

<Drawer.Trigger {...rest}>
	{@render children?.()}
</Drawer.Trigger>

index.ts — compone el namespace

Asignación explícita per-property sobre el root component. NO usar Object.assign(DrawerComponent, { Trigger, ... }) — Svelte 5 puede manejar mal la mutación bulk del component constructor durante hidratación, causando que los hijos aparezcan y desaparezcan después de mount.

import DrawerComponent from './drawer.svelte';
import Trigger from './drawer-trigger.svelte';
import Overlay from './drawer-overlay.svelte';
import Content from './drawer-content.svelte';
import Title from './drawer-title.svelte';
import Description from './drawer-description.svelte';
import Close from './drawer-close.svelte';
import Header from './drawer-header.svelte';
import Footer from './drawer-footer.svelte';
import Handle from './drawer-handle.svelte';
import { Portal } from '$soma/components/internal';

type DrawerNamespace = typeof DrawerComponent & {
	Trigger: typeof Trigger;
	Portal: typeof Portal;
	Overlay: typeof Overlay;
	Content: typeof Content;
	Handle: typeof Handle;
	Title: typeof Title;
	Description: typeof Description;
	Close: typeof Close;
	Header: typeof Header;
	Footer: typeof Footer;
};

const Drawer = DrawerComponent as DrawerNamespace;
Drawer.Trigger = Trigger;
Drawer.Portal = Portal;
Drawer.Overlay = Overlay;
Drawer.Content = Content;
Drawer.Handle = Handle;
Drawer.Title = Title;
Drawer.Description = Description;
Drawer.Close = Close;
Drawer.Header = Header;
Drawer.Footer = Footer;

export { Drawer };
export default Drawer;

export type {
	DrawerProps,
	DrawerTriggerProps as TriggerProps,
	DrawerOverlayProps as OverlayProps,
	DrawerContentProps as ContentProps
	// … etc
} from './types';

types.ts — passthrough + adiciones eidos

import type {
	ProviderProps,
	TriggerProps,
	ContentProps
	// …
} from '$soma/components/drawer';

export type DrawerProps = ProviderProps;
export type DrawerTriggerProps = TriggerProps;

// Eidos añade props visuales que el recipe consume vía data-attrs
export type DrawerContentProps = ContentProps & {
	size?: ResponsiveProp<DrawerSize>;
};

Sin XxxFlatProps, sin XxxProviderProps as ProviderProps aliases. Hay un solo XxxProps (el del root) y los XxxPartProps per hijo.

Cuando el tipo root de Soma se importa como ProviderProps, se usa solo como tipo local dentro de types.ts. No se exporta al consumidor con nombre ProviderProps desde Eidos ni se crea un alias SomaXxxProviderProps.

Tipos visuales canonicos

No se redeclaran variantes, colores ni tamanos a mano en cada componente. La unica fuente para las props visuales compartidas es src/uix/eidos/lib/types.ts:

  • Size y ResponsiveProp<T> para escalas responsivas.
  • ControlVariant, SelectionVariant, ChipVariant, MarkerVariant, SurfaceVariant, etc. para variantes canonicas por familia visual.
  • ColorRole para la paleta de intents UIX (primary, secondary, neutral, affirm, fulfill, risk, threat, loss).

Cuando un componente necesita restringir una familia compartida, usa Extract<> sobre el tipo canonico. No se crean unions locales con literales duplicados:

import type { ColorRole, ControlVariant, Size } from '$uix/eidos/lib/types';

export type DateFieldSize = Extract<Size, 'sm' | 'md' | 'lg'>;
export type DateFieldVariant = ControlVariant;
export type DateFieldColor = ColorRole;

Partes Eidos-only

Algunas partes existen solo para componer la superficie visual: Header, Footer, Status, Main, Handle, filas/labels planas, indicadores decorativos o primitives svg. No necesitan morfo propio mientras cumplan las cuatro reglas:

  1. No crean comportamiento ni estado.
  2. No son target de eventos perceptivos.
  3. No poseen ARIA obligatoria ni relaciones accesibles propias.
  4. Solo emiten estructura, clase/estilo passthrough o data-* visuales que el recipe consume.

Si cualquiera de esas reglas deja de cumplirse, la parte deja de ser Eidos-only y debe subir al contrato correspondiente: primero morfo, luego Soma si necesita runtime.

El agregador scripts/eidos-lint-all.ts mantiene una allowlist explícita de estos attrs/parts visuales. El lint base sigue siendo estricto con valores inválidos de enums morfo; la allowlist solo evita que el reporte de drift se llene de partes visuales intencionales.

src/uix/eidos/component-api-contract.test.ts protege la forma pública del barrel: sin Object.assign, sin Provider público, root en {component}.svelte y miembros del XxxNamespace sincronizados con sus asignaciones explícitas (Drawer.Trigger = Trigger, etc.).

La guardia src/uix/eidos/component-visual-attrs.test.ts fija el cableado mínimo entre wrapper y recipe: si un wrapper visual declara una prop que se consume como data-* (size, variant, color, position, columns, etc.), el fichero debe seguir estampando ese atributo. Esto no añade comportamiento a Eidos; solo evita que la capa visual trague props en silencio.

Uso del consumidor

<script lang="ts">
	import { Drawer } from '$uix/eidos/components/drawer';
	let open = $state(false);
</script>

<Drawer bind:open variant="overlay" direction="right">
	<Drawer.Trigger>Open</Drawer.Trigger>
	<Drawer.Portal>
		<Drawer.Overlay />
		<Drawer.Content>
			<Drawer.Header>
				<Drawer.Title>Title</Drawer.Title>
				<Drawer.Description>Subtitle</Drawer.Description>
			</Drawer.Header>
			<p>body</p>
			<Drawer.Footer>
				<Drawer.Close>Close</Drawer.Close>
			</Drawer.Footer>
		</Drawer.Content>
	</Drawer.Portal>
</Drawer>

Componentes single-part (toggle, icon)

Sin hijos compound. El default export ES el componente entero. Sin namespace, sin asignación de propiedades, sin Provider alias.

// toggle/index.ts
export { default } from './toggle.svelte';
export type { ToggleProps, ToggleVariant, ToggleSize } from './types';
import Toggle from '$uix/eidos/components/toggle';
<Toggle bind:pressed variant="solid">Bold</Toggle>

Partes visuales por defecto

Algunos componentes multi-part necesitan una parte visual minima para no renderizar una superficie rota. Switch es el caso canonico: el root sigue exponiendo Switch.Thumb, pero si el consumidor no aporta children, <Switch /> monta un thumb visual por defecto. Esto no crea una API flat ni esconde el contrato de Soma; solo evita que el uso minimo renderice un track sin indicador.


Toast — caso especial (dos roots independientes)

Toast tiene dos roots públicos que NO se anidan:

  • <Toast> — manual compound (Provider+Viewport+Item iteración del consumer). Children attached: Viewport, Item, Status, Main, Title, Description, Action, Close.
  • <Toaster /> — imperative auto-mount. Renderiza Provider+Viewport+ Item-loop con un template default. Pasas toaster de createToaster(). Export separado, no Toast.Toaster porque es un root competidor, no un hijo.
import { Toast, Toaster, createToaster } from '$uix/eidos/components/toast';

const t = createToaster();

// Imperative
<Toaster toaster={t} />

// Manual
<Toast toaster={t}>
  <Toast.Viewport>
    {#each t.toasts as toast}
      <Toast.Item {toast}>
        <Toast.Title>{toast.title}</Toast.Title>
      </Toast.Item>
    {/each}
  </Toast.Viewport>
</Toast>

Convenciones de naming de tokens CSS

Los tokens CSS llevan el nombre del componente, no de la capa. Sin prefijos de capa — ni --eidos-, ni --air-, ni --terra-, ni --soma-.

Forma Uso Ejemplo
--{component}-… tokens públicos (sobreescribibles por el consumer) --dialog-content-bg, --toggle-height-md
--_{component}-… tokens internos del recipe (no parte del API público) --_tabs-trigger-height, --_toggle-bg

Los [data-{component}] y [data-{component}-{part}] selectors son la única vía pública para que el recipe se acople al runtime — toda información cross-layer pasa por data-attrs declarados en el morfo. Cada --{component}-* consumido por CSS debe salir de EidosConfig.recipes; si un alias publico queda sin consumidor real, el test src/uix/eidos/recipe-css-contract.test.ts falla.


Comparativa obligatoria por componente

Ningun componente Eidos se declara cerrado solo por envolver Soma. Antes de implementar o revisar un componente:

  1. Leer Air en la rama anterior (morfo-runtime:src/uix/air/components/{name}) cuando exista. Air es la primera baseline visual.
  2. Leer Soma/Morfo actuales para separar comportamiento, ARIA, estado, traducciones y data-attrs de la superficie visual Eidos.
  3. Auditar Morfo/Sema. El morfo no se considera correcto solo porque compile:
    • Clasificar el componente como pasivo, interactivo o mixto.
    • Justificar cualquier 0 events de forma explicita.
    • Para cada accion real de usuario revisar family, verb, sequence, intent, target, prewrite y commit.
    • Verificar que Soma dispara esas ocurrencias con runtime.trigger(...).
    • Evitar eventos de alta frecuencia para cambios continuos; normalmente se modelan inicio/drag confirmado/commit, no cada frame.
  4. Comparar contra referentes externos relevantes: Radix/Radix Themes, Ark UI, Bits UI, shadcn-svelte y React Aria cuando aplique.
  5. Crear/actualizar components/{name}/README.md con tabla de funcionalidades, tabla Morfo/Sema, gaps y decisiones. Cada ⚠️ / ❌ debe acabar en una decision explicita: implementar ahora, diferir a Morfo/Soma, diferir a v2 o descartar por no pertenecer a Eidos.
  6. Si hay demo en web/routes/uix/components/{name}, sus snippets forman parte de la arquitectura del componente: deben reproducir el mismo contrato visible que el preview. En componentes schema-driven (form, auto-fields, date/time con formatos, etc.) no se permite un schema reducido que omita campos visibles, validators o defaults reales.
  7. Solo despues tocar wrapper, recipe o tokens.

El objetivo no es copiar APIs, sino que Eidos no quede por debajo de Air ni de los referentes en funcionalidades reales. Si una capacidad pertenece a Soma, la tabla debe decirlo; si es visual, Eidos debe cubrirla o justificar el gap.

Reglas especificas para demos de formularios

  • La validacion en tiempo real se modela como validationBehaviour: 'onChange'.
  • Si una demo arranca en onChange, los defaults iniciales deben ser validos salvo que el README y la UI digan explicitamente que se esta mostrando un formulario inicialmente invalido.
  • El snippet de Soma y el snippet de Eidos deben declarar los mismos campos visibles que el preview: imports SIUM, schema, defaults, Form + Field y Form.AutoFields. No se admiten snippets que omiten age, role, campos de fecha, arrays u otros datos que el preview valida.
  • Form.AutoFields es renderer reflectivo de SIUM. Si necesita comportamiento nuevo, se cambia Soma / $libs/forms / Morfo antes que Eidos.

Forma legacy (CSS-only, retired)

Hubo una fase anterior cuando eidos sólo emitía CSS (accordion.css, dialog.css, etc., al nivel raíz de components/). Todos los componentes han sido migrados a la forma canónica del subdirectorio. Si encuentras un .css suelto, es un descuido — debe vivir dentro de su subdirectorio.


Inventario

El inventario vivo de wrappers es el árbol de subdirectorios de components/ + npm run component:audit. La tabla de estado de la migración 2026-05 (completada) se conserva en docs/process/handoffs-2026-05.md.

Powered by TurnKey Linux.