refactor(sema): share item selection cascade

astra
dev 2 weeks ago
parent 2dbf471b3f
commit 3abf2686d8

@ -223,6 +223,10 @@ Morfo y la demo congelada avisa `D-1.2`; el contrato requiere una decisión apar
[CssField y NumberField](./sema-spin-fields.md) sí comparten ahora tres reglas
idénticas, mientras la advertencia de CssField sigue local (cinco líneas netas
menos de producción).
[Combobox, Select, Listbox y TagGroup](./sema-selection-cascade.md) comparten
las dos reglas hápticas de selección de `item`; TagGroup mantiene después sus
reglas propias de retirada y reset. Los cuatro packs conservan sus Morfos y
selectores tipados.
## Eidos

@ -0,0 +1,58 @@
---
title: UIX — auditoría Sema de selección
type: notes
audience: human + agent
authority: process — evaluación, no contrato de componente
status: reviewed
---
# Sema de Combobox, Select, Listbox y TagGroup
Fecha: 2026-09-23. Se releyeron las cuatro guías obligatorias de componente y
la arquitectura Sema. El alcance autorizado por el usuario es deduplicación
interna sin modificar comportamiento; `web/` queda excluido.
## Pre-flight
| Característica | Combobox | Select | Listbox | TagGroup | Decisión |
| ------------------------- | --------------------------- | --------------------- | --------------------- | -------------------------------------------------------- | ------------------------------------------ |
| Selección confirmada | `commit-select` en `item` | Igual | Igual | Igual | Compartir la construcción de la regla Sema |
| Deselección confirmada | `commit-unselect` en `item` | Igual | Igual | Igual | Compartir la construcción de la regla Sema |
| Firma de ambas reglas | `tap` | `tap` | `tap` | `tap` | Mantener la firma exacta |
| Reglas adicionales | Ninguna | Ninguna | Ninguna | `commit-remove` en `item` y `commit-reset` en `provider` | Mantenerlas locales y en su orden actual |
| Navegación, foco y estado | Propios de Soma/Morfo | Propios de Soma/Morfo | Propios de Soma/Morfo | Propios de Soma/Morfo | Fuera del cambio |
| Referencia oficial | Equivalente cercano | Diferencia frente a UIX | Consecuencia |
| ------------------------------------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------ | ------------------------------------------------ |
| [WAI-ARIA APG Listbox](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/) | Selección simple o múltiple de opciones | Distingue foco y selección; no prescribe háptica | No unificar navegación con selección |
| [Ark UI Combobox](https://ark-ui.com/docs/components/combobox) | Selección y resaltado de ítems, modo múltiple | Gestiona estado en otra arquitectura | Mantener Soma independiente |
| [Radix Primitives Select](https://www.radix-ui.com/primitives/docs/components/select) | Ítem seleccionado y valor controlado | Select simple con composición propia | No alterar partes, props ni Morfo |
| [Bits UI Select](https://bits-ui.com/docs/components/select) | Ítems seleccionables, opción múltiple | Implementación Svelte con contrato propio | Compartir sólo la proyección perceptiva idéntica |
Morfo conserva partes, eventos, ARIA y teclado; Soma conserva selección,
deselección, foco y emisiones; Sema posee sólo estas dos reglas hápticas;
Eidos no cambia. Los selectores se construyen con `semaSelector` y el Morfo de
cada componente. No se añaden props, tamaños, variantes, colores, canales ni
eventos. La extracción es privada de Sema.
## Hipótesis y prueba
Los cuatro packs repiten las dos reglas `commit-select` y `commit-unselect`
con `haptic: { kind: 'tap' }`. Un constructor privado debe aceptar el selector
tipado del `item` y devolver esas dos reglas en el mismo orden. TagGroup debe
conservar sus otras dos reglas después de ellas. Una prueba de caracterización
fijará los cuatro arrays completos antes de modificar producción. La misma
prueba, el censo Sema, resolver, engine y la auditoría de los cuatro componentes
deben pasar después. Si el tipado o la salida cambia, se abandona la extracción.
## Resultado
Se extrajo únicamente el par de reglas a `selection-toggle-cascade.ts`.
Combobox, Select y Listbox llaman al constructor con su `onItem` tipado;
TagGroup lo extiende con sus reglas locales de retirada y reset. La prueba
de caracterización pasó 172/172 junto con censo, resolver y engine antes y
después. `component:audit` terminó en PASS para los cuatro componentes.
`check:gate` terminó sin errores nuevos en `src/` ni `scripts/` (los 89
errores registrados siguen en `web/`). No se modificaron Morfo, Soma, Eidos,
API, DOM, CSS, ARIA ni temporización. No se hizo comparación visual manual
porque esta extracción sólo construye datos de Sema.

@ -1,6 +1,7 @@
import { semaSelector } from '$uix/morfo';
import { comboboxMorfo } from '$uix/morfo/components/combobox';
import type { Sema } from '../sema-map';
import { selectionToggleCascade } from './selection-toggle-cascade';
/**
* Combobox perceptual defaults.
@ -15,16 +16,5 @@ const onItem = (matchers?: Parameters<typeof semaSelector<typeof comboboxMorfo>>
export const comboboxSema: Sema = {
name: 'combobox',
cascade: [
{
selector: onItem({ eventName: 'commit-select' }),
haptic: { kind: 'tap' }
},
{
selector: onItem({ eventName: 'commit-unselect' }),
haptic: { kind: 'tap' }
}
]
cascade: selectionToggleCascade(onItem)
};

@ -1,6 +1,7 @@
import { semaSelector } from '$uix/morfo';
import { listboxMorfo } from '$uix/morfo/components/listbox';
import type { Sema } from '../sema-map';
import { selectionToggleCascade } from './selection-toggle-cascade';
// Same selection semantics as Select → same criterion, same voice (checkpoint
// S3b ascent, 2026-07-07 — the standalone clones its composed sibling's pack).
@ -9,16 +10,5 @@ const onItem = (matchers?: Parameters<typeof semaSelector<typeof listboxMorfo>>[
export const listboxSema: Sema = {
name: 'listbox',
cascade: [
{
selector: onItem({ eventName: 'commit-select' }),
haptic: { kind: 'tap' }
},
{
selector: onItem({ eventName: 'commit-unselect' }),
haptic: { kind: 'tap' }
}
]
cascade: selectionToggleCascade(onItem)
};

@ -1,6 +1,7 @@
import { semaSelector } from '$uix/morfo';
import { selectMorfo } from '$uix/morfo/components/select';
import type { Sema } from '../sema-map';
import { selectionToggleCascade } from './selection-toggle-cascade';
/**
* Select perceptual defaults.
@ -16,16 +17,5 @@ const onItem = (matchers?: Parameters<typeof semaSelector<typeof selectMorfo>>[2
export const selectSema: Sema = {
name: 'select',
cascade: [
{
selector: onItem({ eventName: 'commit-select' }),
haptic: { kind: 'tap' }
},
{
selector: onItem({ eventName: 'commit-unselect' }),
haptic: { kind: 'tap' }
}
]
cascade: selectionToggleCascade(onItem)
};

@ -0,0 +1,17 @@
import type { Sema } from '../sema-map';
type OnItem = (matchers: { eventName: 'commit-select' | 'commit-unselect' }) => string;
/** Shared perceptual rules for committed item selection. */
export function selectionToggleCascade(onItem: OnItem): Sema['cascade'] {
return [
{
selector: onItem({ eventName: 'commit-select' }),
haptic: { kind: 'tap' }
},
{
selector: onItem({ eventName: 'commit-unselect' }),
haptic: { kind: 'tap' }
}
];
}

@ -1,6 +1,7 @@
import { semaSelector } from '$uix/morfo';
import { tagGroupMorfo } from '$uix/morfo/components/tag-group';
import type { Sema } from '../sema-map';
import { selectionToggleCascade } from './selection-toggle-cascade';
const onProvider = (matchers?: Parameters<typeof semaSelector<typeof tagGroupMorfo>>[2]) =>
semaSelector(tagGroupMorfo, 'provider', matchers);
@ -11,16 +12,7 @@ const onItem = (matchers?: Parameters<typeof semaSelector<typeof tagGroupMorfo>>
export const tagGroupSema: Sema = {
name: 'tag-group',
cascade: [
{
selector: onItem({ eventName: 'commit-select' }),
haptic: { kind: 'tap' }
},
{
selector: onItem({ eventName: 'commit-unselect' }),
haptic: { kind: 'tap' }
},
...selectionToggleCascade(onItem),
{
selector: onItem({ eventName: 'commit-remove' }),

@ -0,0 +1,45 @@
import { describe, expect, it } from 'vitest';
import { semaSelector } from '$uix/morfo';
import { comboboxMorfo } from '$uix/morfo/components/combobox';
import { listboxMorfo } from '$uix/morfo/components/listbox';
import { selectMorfo } from '$uix/morfo/components/select';
import { tagGroupMorfo } from '$uix/morfo/components/tag-group';
import { comboboxSema } from './components/combobox';
import { listboxSema } from './components/listbox';
import { selectSema } from './components/select';
import { tagGroupSema } from './components/tag-group';
describe('selection perceptual packs', () => {
it.each([
['Combobox', comboboxMorfo, comboboxSema],
['Select', selectMorfo, selectSema],
['Listbox', listboxMorfo, listboxSema],
['TagGroup', tagGroupMorfo, tagGroupSema]
] as const)('keeps %s item selection signatures and rule order', (_name, morfo, pack) => {
expect(pack.cascade.slice(0, 2)).toEqual([
{
selector: semaSelector(morfo, 'item', { eventName: 'commit-select' }),
haptic: { kind: 'tap' }
},
{
selector: semaSelector(morfo, 'item', { eventName: 'commit-unselect' }),
haptic: { kind: 'tap' }
}
]);
expect(pack.cascade).toHaveLength(pack === tagGroupSema ? 4 : 2);
});
it('keeps TagGroup removal on the item and reset on the provider', () => {
expect(tagGroupSema.cascade.slice(2)).toEqual([
{
selector: semaSelector(tagGroupMorfo, 'item', { eventName: 'commit-remove' }),
haptic: { kind: 'tap' }
},
{
selector: semaSelector(tagGroupMorfo, 'provider', { eventName: 'commit-reset' }),
haptic: { kind: 'tap' }
}
]);
});
});
Loading…
Cancel
Save

Powered by TurnKey Linux.