docs(soma): add Sema events section to 23 component READMEs

Documenta los cambios doctrinales del sprint Lectura C + cabling
soma. Cada README añade una sección `## Sema events` con tabla
(event / family / verb / target / intent / when) + referencia al
pack si existe + notas doctrinales de las correcciones aplicadas.

Componentes:
- Packs creados este sprint (11): switch, toggle, toggle-group,
  menubar, navigation-menu, dropdown-menu, context-menu, tree-view,
  tree-grid, tooltip, collapsible.
- Componentes con emisión cableada sin pack (8): listbox, grid-list,
  table, feed, command, carousel, announce, clipboard.
- Componentes gestuales con notas sobre handle-scroll no-emitido
  (3): drag-drop, virtual-list, virtual-grid.
- Checkbox: documenta la doctrina de dos eventos direccionales
  (commit-toggle-check/uncheck con intent affirm/neutral).

Correcciones doctrinales documentadas:
- announce: commit-announce-* → signal.announce / signal.alert
- tree-view: target item → branch para emerge-expand/collapse
- table: shift.sort → commit.set; shift.expand-row → emerge.expand
- drag-drop: commit.drag-start/drop → handle.pick/drop
- clipboard: commit.copy → commit.save; commit.copy-error +
  threat → commit.fail + risk
- virtual-list/grid: handle-scroll declarado pero NO emitido
  (decisión consciente — buzz nonstop con family.handle haptic-only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent cbf76b66c8
commit 7afa7057d4

@ -90,6 +90,18 @@ Snippet props: `{ announce, clear }`.
| Region | `data-role` | `status \| alert \| log \| timer` | | Region | `data-role` | `status \| alert \| log \| timer` |
| Region | `data-live` | `polite \| assertive` | | Region | `data-live` | `polite \| assertive` |
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ------------------ | -------- | ---------- | ---------- | --------- | --------------------------------------------- |
| `signal-announce` | `signal` | `announce` | `provider` | `neutral` | `announce(message, 'polite')` called. |
| `signal-alert` | `signal` | `alert` | `provider` | `threat` | `announce(message, 'assertive')` called. |
| `commit-reset` | `commit` | `reset` | `provider` | `neutral` | `clear()` called — both live regions emptied. |
Doctrinal classification per book cap. 24 §5.1: `signal.announce + neutral` = "esto existe, sin urgencia"; `signal.alert + threat` = "atiende ahora". Previously misclassified as `commit.submit` — corrected in the Capa 2 sprint.
No per-component pack — falls back to `family.signal` base (gain 0.4, arc contour). Loud by family-design because announcements ARE the perceptual surface (alert with threat intent gets the system's strongest signal).
## Dual-region A/B trick ## Dual-region A/B trick
Screen readers dedupe consecutive identical strings — writing "Saved" twice in a row is silent. The Provider alternates between two internal regions (A/B) on each `announce()` call; the AT sees a change in the "other" region and re-reads the message. Screen readers dedupe consecutive identical strings — writing "Saved" twice in a row is silent. The Provider alternates between two internal regions (A/B) on each `announce()` call; the AT sees a change in the "other" region and re-reads the message.

@ -130,6 +130,16 @@ Focus the Provider to enable keyboard control.
| `Home` | Jump to first slide. | | `Home` | Jump to first slide. |
| `End` | Jump to last slide. | | `End` | Jump to last slide. |
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ---------------------- | ------- | ---------- | ------ | ------ | --------------------------------------------------------------- |
| `shift-navigate-slide` | `shift` | `navigate` | `item` | — | Slide change (any path: button, indicator, keyboard, drag-snap, autoplay). |
Centralized in `commit(target)` — the single state-mutator that every navigation path funnels through. Slide element resolved by `data-index` so the cascade matches the specific newly-active slide.
No per-component pack — falls back to `family.shift` base (gain 0.18 ascending, no haptic), which reads as the "context shift" the book canon describes.
## Drag ## Drag
When `dragEnabled` is `true`, pointer events on `ItemGroup` drive the drag. On release: When `dragEnabled` is `true`, pointer events on `ItemGroup` drive the drag. On release:

@ -52,6 +52,17 @@ A control that allows the user to toggle between checked, unchecked, and optiona
| ------- | -------------------- | | ------- | -------------------- |
| `Space` | Toggle checked state | | `Space` | Toggle checked state |
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ----------------------- | -------- | -------- | ---------- | --------- | --------------------------------------------- |
| `commit-toggle-check` | `commit` | `toggle` | `provider` | `affirm` | Transition from unchecked → checked. |
| `commit-toggle-uncheck` | `commit` | `toggle` | `provider` | `neutral` | Transition from checked / indeterminate → unchecked. |
Two events instead of a single `commit-toggle`: the direction matters perceptually. `affirm` (the user confirms inclusion / agreement) maps to a brighter, slightly higher cue; `neutral` (revoking a state) has no evaluative load. Per book cap. 22 §10 (literal: "Toggle: contact.press → commit.toggle + affirm — el press registra; el toggle fija") — both share the canonical verb `commit.toggle`; direction is communicated by intent + `data-state`.
**Pack**: [`src/uix/sema/components/checkbox.ts`](../../../sema/components/checkbox.ts) — `form.commit.soft` (gain 0.05) + tap haptic on check; `form.commit.subtle` (gain 0.03) + tap on uncheck. Discrimination rides on the family/intent base — the pack does NOT override pitch / contour / gain from intent.deltas to preserve the per-intent perceptual signature.
## Usage ## Usage
### Basic ### Basic

@ -75,6 +75,19 @@ Snippet props: `{ value, copied, copy }`.
| ---------------- | ---------------------------------- | | ---------------- | ---------------------------------- |
| `Enter` / `Space` | Clicks the Trigger → calls `copy()` | | `Enter` / `Space` | Clicks the Trigger → calls `copy()` |
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ------------------ | -------- | ------ | --------- | -------- | --------------------------------------------------- |
| `commit-save-copy` | `commit` | `save` | `trigger` | `affirm` | `navigator.clipboard.writeText()` resolved (success). |
| `commit-fail-copy` | `commit` | `fail` | `trigger` | `risk` | `writeText` rejected (permission denied, blur, etc.). |
Both events stamp on the Trigger button. `copy()` accepts an optional `triggerEl` so the Trigger's onclick handler passes `e.currentTarget` as fallback target.
Doctrinal corrections vs earlier drafts: `commit.copy` → `commit.save` (per book cap. 23, save = "algo queda aplicado o guardado"); `commit.copy-error + threat` → `commit.fail + risk` (risk = recoverable; threat overstated for a copy failure the user can retry). The internal reset timer that flips `copied` back to false used to emit `shift-reset` — removed (timer bookkeeping, not a perceptual event).
No per-component pack — falls back to `family.commit` base. Intent `risk` adds roughness + pitch lower on the fail variant via `intent.deltas`.
## i18n ## i18n
| Key | English | Spanish | | Key | English | Spanish |

@ -48,6 +48,17 @@ A component that expands and collapses a content section.
| ----------------- | ------------------------------- | | ----------------- | ------------------------------- |
| `Enter` / `Space` | Toggle the content (on trigger) | | `Enter` / `Space` | Toggle the content (on trigger) |
## Sema events
| Event | Family | Verb | Target | Sequence | When |
| ---------- | -------- | ---------- | --------- | -------- | ----------------------------- |
| `expand` | `emerge` | `expand` | `content` | `post` | Content opens (trigger click). |
| `collapse` | `emerge` | `collapse` | `content` | `pre` | Content closes. |
`expand` is post-sequence so the content exists visually before Eidos reacts; `collapse` is pre so the exit signal plays while content is still visible. No intent — disclosure is non-evaluative.
**Pack**: [`src/uix/sema/components/collapsible.ts`](../../../sema/components/collapsible.ts) — mirrors Accordion (`emerge.soft` expand + `emerge.exit` collapse @ gain 0.06). The single-panel sibling shares the perceptual signature of the multi-panel parent for system coherence.
## Usage ## Usage
### Basic ### Basic

@ -120,6 +120,20 @@ A command palette with fuzzy scoring, keyboard navigation, groups, empty/loading
Horizontal arrow keys are direction-inverted in RTL mode. Horizontal arrow keys are direction-inverted in RTL mode.
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ---------------------- | -------- | -------- | ------ | -------- | ------------------------------------------------------------------- |
| `commit-submit-invoke` | `commit` | `submit` | `item` | `affirm` | Item activated (mouse click or Enter on highlighted item). |
Two emission paths:
- Mouse: `CommandItemProvider.onclick` fires with `e.currentTarget` as fallback target.
- Keyboard: `Enter` calls `selectCurrent()` on the provider; emission looks up the item by `data-value` via `querySelector`.
Intent is `affirm` (not `fulfill`) per book cap. 22 §8 — the actual command outcome (success / failure / completion) fires downstream; celebrating at the click is "celebrate before time" (same precedent as Button activation).
No per-component pack — falls back to `family.commit` base.
## Usage ## Usage
### Basic command palette ### Basic command palette

@ -150,6 +150,18 @@ A right-click menu of actions. Supports submenus, checkbox items, radio groups,
All directional keys are flipped in RTL mode. All directional keys are flipped in RTL mode.
## Sema events
| Event | Family | Verb | Target | Intent | Sequence | When |
| --------------- | -------- | -------- | --------- | -------- | -------- | --------------------------------------------------------------- |
| `open` | `emerge` | `open` | `content` | — | `pre` | Menu opens at the anchored point (right-click / long-press). |
| `close` | `emerge` | `close` | `content` | — | `pre` | Menu closes (outside click, Escape, item activation). |
| `commit-select` | `commit` | `select` | `item` | `affirm` | `post` | User activates an item. |
Identical perceptual signature to `dropdown-menu` so the system reads as one consistent "menu sound" regardless of how the menu was triggered. Emission lives in `openAt()`, `handleClose()`, and `ContextMenuItemProvider.onclick/onkeydown`.
**Pack**: [`src/uix/sema/components/context-menu.ts`](../../../sema/components/context-menu.ts) — mirrors dropdown-menu (open: `emerge.soft`, close: `emerge.exit.soft`, item: `form.commit.subtle` + tap).
## Usage ## Usage
### Basic ### Basic

@ -114,6 +114,20 @@ Each drag step is announced via the `Announce` global API. Component-owned annou
Droppables are explicitly added/removed from the tab order based on the accept filter — a disabled or non-accepting target is silently skipped during keyboard navigation. Droppables are explicitly added/removed from the tab order based on the accept filter — a disabled or non-accepting target is silently skipped during keyboard navigation.
## Sema events
| Event | Family | Verb | Target | Intent | When |
| --------------- | -------- | -------- | ----------- | --------- | ------------------------------------------------------------------- |
| `handle-pick` | `handle` | `pick` | `draggable` | — | `startDrag()` succeeded — user grabbed an item. |
| `handle-drop` | `handle` | `drop` | `droppable` | — | `commitDrop(target)` — item released on an accepting target. |
| `commit-cancel` | `commit` | `cancel` | `draggable` | `neutral` | `cancelDrag()` — user pressed Escape during carry or non-accepting drop. |
Doctrinal classification per book cap. 25 §4 (literal): pick / drop / drag / resize / rotate / reorder all belong to `handle` (direct manipulation). The actual consequence of the drop — `commit.reorder + affirm`, `commit.delete + loss`, etc. — fires from whichever morfo owns the moved/deleted entity, NOT this one.
Doctrinal corrections vs earlier drafts: `commit.drag-start` → `handle.pick`, `commit.drop` → `handle.drop` (Capa 2 sprint).
No per-component pack — falls back to `family.handle` base (haptic only, no sound — gestures are tactile).
## Comparison ## Comparison
| Feature | Soma | Radix | Ark UI | react-aria | dnd-kit | | Feature | Soma | Radix | Ark UI | react-aria | dnd-kit |

@ -138,6 +138,21 @@ A menu of actions triggered by a button. Supports submenus, checkbox items, radi
| `ArrowLeft` (LTR) / `Escape` | Close submenu, focus SubTrigger | | `ArrowLeft` (LTR) / `Escape` | Close submenu, focus SubTrigger |
| `ArrowDown` / `ArrowUp` | Navigate items within submenu | | `ArrowDown` / `ArrowUp` | Navigate items within submenu |
## Sema events
| Event | Family | Verb | Target | Intent | Sequence | When |
| --------------- | -------- | -------- | --------- | -------- | -------- | ------------------------------------------------- |
| `open` | `emerge` | `open` | `content` | — | `pre` | Menu opens (`handleOpen` central state mutator). |
| `close` | `emerge` | `close` | `content` | — | `pre` | Menu closes (`handleClose`). |
| `commit-select` | `commit` | `select` | `item` | `affirm` | `post` | User activates an item (mouse click or keyboard). |
`open` / `close` use sequence `pre` so the perceptual signal lands at the gesture moment, before the structural flip (the content is still in DOM, the cascade matches `[data-dropdown-menu-content]`). `commit-select` fires from MenuItem `onclick` and `onkeydown` with `e.currentTarget` as fallback target.
**Pack**: [`src/uix/sema/components/dropdown-menu.ts`](../../../sema/components/dropdown-menu.ts):
- `open` → `emerge.soft` (soft chime, gain 0.08).
- `close` → `emerge.exit.soft` (descending pitch + gain 0.05).
- `commit-select` → `form.commit.subtle` + light `tap` haptic. Softer than radio-group because close-emerge + select-commit + downstream-surface fire in quick succession; spreading intensity avoids double-tap fatigue.
## Usage ## Usage
### Basic ### Basic

@ -128,6 +128,17 @@ Per [the APG](https://www.w3.org/WAI/ARIA/apg/patterns/feed/):
| `Ctrl+End` | Focus last Article. | | `Ctrl+End` | Focus last Article. |
| `Tab` (within Article) | Enters inner interactive elements per native tab order. | | `Tab` (within Article) | Enters inner interactive elements per native tab order. |
## Sema events
| Event | Family | Verb | Target | Intent | When |
| --------------------------- | -------- | ---------- | ---------- | --------- | --------------------------------------------------------------------- |
| `commit-submit-load-more` | `commit` | `submit` | `provider` | `neutral` | User requested more items (`PageDown` at end, or `Sentinel` intersected). |
| `shift-navigate-focus-item` | `shift` | `navigate` | `article` | — | Keyboard navigation moved focus to a different article (`focusAt(index)`). |
`commit-submit-load-more` fires from both keyboard PageDown-at-end and the intersection observer's sentinel — both go through `onLoadMore` handler. `shift-navigate-focus-item` is centralized in `focusAt()` so every keyboard movement (PageDown/PageUp/Ctrl+Home/End) produces one stamp.
No per-component pack — falls back to family bases. The downstream outcome (load succeeded → `commit.complete + affirm`, failed → `commit.fail + risk`) fires from the app, not this morfo.
## Virtualization ## Virtualization
Compose with [`VirtualList`](../virtual-list/README.md) for long streams: Compose with [`VirtualList`](../virtual-list/README.md) for long streams:

@ -140,6 +140,15 @@ Rows with interactive descendants (buttons, links, checkboxes) support a WAI-ARI
This matches [react-aria's GridList pattern](https://react-spectrum.adobe.com/react-aria/GridList.html) and the APG `grid` keyboard contract. This matches [react-aria's GridList pattern](https://react-spectrum.adobe.com/react-aria/GridList.html) and the APG `grid` keyboard contract.
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ----------------- | -------- | ---------- | ------ | -------- | -------------------------------------------------------- |
| `commit-select` | `commit` | `select` | `row` | `affirm` | Row moves into the selected set (single, toggle, or range). |
| `commit-unselect` | `commit` | `unselect` | `row` | `affirm` | Row moves out of the selected set (multi-toggle). |
Same pattern as listbox: emission from `select(value, mode)` with direction detection by prior inclusion. Row resolved by `data-value`. No per-component pack — falls back to `family.commit` base.
## Virtualization ## Virtualization
Compose with [`VirtualList`](../virtual-list/README.md) when the dataset is large: Compose with [`VirtualList`](../virtual-list/README.md) when the dataset is large:

@ -135,6 +135,17 @@ Exactly one `Item` holds `tabindex=0` at any time: the first selected + enabled
Disabled items are skipped entirely by arrow nav and typeahead. Disabled items are skipped entirely by arrow nav and typeahead.
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ----------------- | -------- | ---------- | ------ | -------- | -------------------------------------------------------- |
| `commit-select` | `commit` | `select` | `item` | `affirm` | Item moves into the selected set (single or multi mode). |
| `commit-unselect` | `commit` | `unselect` | `item` | `affirm` | Item moves out of the selected set (multi-toggle). |
Emission lives in the central `select(value, mode)` method. Direction is detected via prior inclusion: `wasSelected ? unselect : select`. The item element is looked up by `data-value` so the cascade matches the specific item.
No per-component pack — falls back to `family.commit` base (gain 0.3 sound + tap haptic). Apps that want softer list-selection can add an app-level cascade rule, e.g. `[data-listbox-item] { sound: { gain: 0.05 } }`.
## i18n ## i18n
| Key | English | Spanish | | Key | English | Spanish |

@ -122,6 +122,18 @@ Horizontal arrow keys switch between menus while a menu is open. Directional key
Triggers use roving tabindex: one trigger has `tabindex="0"`, all others have `tabindex="-1"`. Triggers use roving tabindex: one trigger has `tabindex="0"`, all others have `tabindex="-1"`.
## Sema events
| Event | Family | Verb | Target | Intent | When |
| --------------- | -------- | -------- | --------- | -------- | --------------------------------------------------------------------------------- |
| `commit-select` | `commit` | `select` | `trigger` | `affirm` | A top-level trigger becomes active (click, keyboard activate, hover-follow, arrow-nav). |
Emission is centralized in `MenubarProvider.open(value)` so every path (mouse, keyboard, hover-follow) produces one stamp. The fallback target is the trigger element resolved through `triggerRegistry`.
**Pack**: [`src/uix/sema/components/menubar.ts`](../../../sema/components/menubar.ts) — `form.commit.subtle` (gain 0.03) + light `tap` haptic. High-frequency top-level traversal; softer than radio-group to avoid fatigue during multi-trigger scrubbing. See `LIBRO_VARIACIONES_Y_EXTENSIONES.md` D.6.
The menu's open/close and item selection live in the nested `DropdownMenu` (see its own README) — those events fire independently from this morfo's emission.
## Usage ## Usage
### Basic ### Basic

@ -221,6 +221,16 @@ Arrow navigation between triggers always closes any open submenu — this keeps
Disabled triggers and Links with no interactivity are included in navigation order but Links simply navigate on Enter via native anchor behaviour. The list is NOT a roving-tabindex pattern — each Trigger/Link is `tabindex=0` so users can Tab out of the nav directly. Disabled triggers and Links with no interactivity are included in navigation order but Links simply navigate on Enter via native anchor behaviour. The list is NOT a roving-tabindex pattern — each Trigger/Link is `tabindex=0` so users can Tab out of the nav directly.
## Sema events
| Event | Family | Verb | Target | Intent | When |
| --------------- | -------- | -------- | ------ | -------- | -------------------------------------------------------------------------- |
| `commit-select` | `commit` | `select` | `item` | `affirm` | An item becomes active (Trigger opens its content, or hover/keyboard activate). |
Emission lives in `NavigationMenuProvider.openNow(value)` — the central state-mutator covers every path (click, keyboard, hover-follow). Fallback target is the trigger element from `triggerRefs`; the cascade selector `[data-navigation-menu-item]` matches via `closest()`.
**Pack**: [`src/uix/sema/components/navigation-menu.ts`](../../../sema/components/navigation-menu.ts) — `form.commit.subtle` + light `tap` haptic. Same soft signature as `menubar` and items in `dropdown-menu` / `context-menu` for system-wide menu coherence.
## i18n ## i18n
| Key | English | Spanish | | Key | English | Spanish |

@ -50,6 +50,14 @@ switch state outside Soma.
| `Enter` | Toggle the switch | | `Enter` | Toggle the switch |
| `Space` | Toggle the switch (native button behavior) | | `Space` | Toggle the switch (native button behavior) |
## Sema events
| Event | Family | Verb | Target | When |
| --------------- | -------- | -------- | ---------- | --------------------------------------------------------------------- |
| `commit-toggle` | `commit` | `toggle` | `provider` | Switch toggled on/off. Intent from prop: `neutral`, `affirm`, `risk`, `threat`. |
**Pack**: [`src/uix/sema/components/switch.ts`](../../../sema/components/switch.ts) — silent-by-default (`form.toggle.silent`) + light `tap` haptic. High-frequency settings panels; intent-loaded toggles (threat) still surface audibly via `intent.deltas`.
## Usage ## Usage
### Basic ### Basic

@ -205,6 +205,20 @@ interface ColumnDef<TData> {
| RowDetail | `data-table-row-detail` | Always present | | RowDetail | `data-table-row-detail` | Always present |
| RowDetail | `data-state` | `open \| closed` (hidden from DOM when closed) | | RowDetail | `data-state` | `open \| closed` (hidden from DOM when closed) |
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ------------------- | -------- | -------- | --------------- | --------- | ----------------------------------------------------- |
| `commit-set-sort` | `commit` | `set` | `column-header` | `neutral` | Sort criterion changed (`SortTrigger` click/Enter/Space). |
| `commit-select` | `commit` | `select` | `row` | `affirm` | Row selected (single or multi mode). |
| `emerge-expand-row` | `emerge` | `expand` | `row` | — | Detail panel opens (closed → open only — no collapse counterpart in morfo). |
The morfo declares the doctrinal contract; emission lives in `TableSortTriggerProvider` (onclick/onkeydown), `TableRowProvider` (onclick/onkeydown), and `TableRowDetailTriggerProvider` (onclick/onkeydown gated on `wasOpen === false`).
Doctrinal corrections vs earlier drafts: `shift.sort` → `commit.set`, `shift.expand-row` → `emerge.expand` (per book cap. 22-26).
No per-component pack — falls back to family bases.
## Sorting ## Sorting
Sorting is **opt-in per column** (`enableSorting: true`). The provider emits `data-sortable` and `aria-sort` but does **not** add click handlers. The consumer renders a sort button. Sorting is **opt-in per column** (`enableSorting: true`). The provider emits `data-sortable` and `aria-sort` but does **not** add click handlers. The consumer renders a sort button.

@ -55,6 +55,14 @@ Standalone Toggle (a single button with `aria-pressed`) is air-native and does n
Arrow keys move focus only. Pressing the item (click, Enter, Space) toggles its state. Only pressed items have `tabindex=0` (roving tabindex). Arrow keys move focus only. Pressing the item (click, Enter, Space) toggles its state. Only pressed items have `tabindex=0` (roving tabindex).
## Sema events
| Event | Family | Verb | Target | When |
| --------------- | -------- | -------- | ------ | ------------------------------------------------------------------- |
| `commit-toggle` | `commit` | `toggle` | `item` | Item clicked. Intent from prop: `neutral`, `affirm`, `risk`, `threat`. |
**Pack**: [`src/uix/sema/components/toggle-group.ts`](../../../sema/components/toggle-group.ts) — silent-by-default sound (`form.toggle.silent`) + light `tap` haptic. High-frequency segmented controls; intent-driven gain still surfaces (threat +0.1 audible). See `LIBRO_VARIACIONES_Y_EXTENSIONES.md` D.5.
## Usage ## Usage
### Single mode (default) ### Single mode (default)

@ -63,6 +63,14 @@ Snippet props: `{ pressed: boolean }`.
| ----------------- | ------------------------------ | | ----------------- | ------------------------------ |
| `Enter` / `Space` | Toggle (native `<button>` behaviour). | | `Enter` / `Space` | Toggle (native `<button>` behaviour). |
## Sema events
| Event | Family | Verb | Target | When |
| --------------- | -------- | -------- | ---------- | --------------------------------------------------------------------- |
| `commit-toggle` | `commit` | `toggle` | `provider` | Toggle flipped on/off. Intent from prop: `neutral`, `affirm`, `risk`, `threat`. |
**Pack**: [`src/uix/sema/components/toggle.ts`](../../../sema/components/toggle.ts) — same signature as Switch (silent-by-default + light tap haptic). Sequence is `post` so the perceptual closure lands after the state flip.
## Comparison ## Comparison
| Feature | Soma | Radix | Ark UI | bits-ui | | Feature | Soma | Radix | Ark UI | bits-ui |

@ -67,6 +67,18 @@ Wrap multiple tooltips in `Tooltip.Group` to share open delay across a group.
Tooltips open on `pointerenter` and `focus`, close on `pointerleave` and `blur`. Tooltips open on `pointerenter` and `focus`, close on `pointerleave` and `blur`.
## Sema events
| Event | Family | Verb | Target | Sequence | When |
| --------------- | -------- | --------- | --------- | -------- | ------------------------------------------ |
| `open` | `emerge` | `open` | `content` | `pre` | Pointer-enter or focus revealed the tooltip. |
| `close` | `emerge` | `close` | `content` | `pre` | Pointer-leave or blur closed it. |
| `close-dismiss` | `emerge` | `dismiss` | `content` | `pre` | Escape key dismissal. |
**Pack**: [`src/uix/sema/components/tooltip.ts`](../../../sema/components/tooltip.ts) — **silent-by-default**. The pack tuning (`tooltip.silent`) subtracts the family.emerge gain entirely, so neutral tooltips emit at gain 0 (no sound) and no haptic. Intent threat / fulfill still surface via `intent.deltas` for the rare error/success tooltip variant.
Tooltips are auxiliary presence (not signal). Hovering is a continuous, ambient gesture — emitting audible feedback on every cursor sweep across a dense UI would cause immediate fatigue. The silence is doctrinal, not absence (see `LIBRO_VARIACIONES_Y_EXTENSIONES.md` D.8 — tooltips as canonical example of "auxiliary, not signal").
## Usage ## Usage
### Basic ### Basic

@ -141,6 +141,18 @@ Per the [APG treegrid spec](https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/):
| `Escape` | Clear selection. | | `Escape` | Clear selection. |
| `a–z` / `0–9` | Typeahead. | | `a–z` / `0–9` | Typeahead. |
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ----------------- | -------- | ---------- | ------ | -------- | ----------------------------------------------------- |
| `commit-select` | `commit` | `select` | `row` | `affirm` | Row selected (`select(value, mode)`). |
| `emerge-expand` | `emerge` | `expand` | `row` | — | Row toggled open, revealing children (`toggleExpand`). |
| `emerge-collapse` | `emerge` | `collapse` | `row` | — | Row toggled closed (`toggleExpand`). |
Unlike tree-view, tree-grid uses a single `Row` part for both leaves and branches — `data-has-children` + `data-expanded` disambiguate. The row element is resolved via `querySelector('[data-tree-grid-row][data-value="..."]')`.
**Pack**: [`src/uix/sema/components/tree-grid.ts`](../../../sema/components/tree-grid.ts) — same signature as tree-view (emerge.soft expand / emerge.exit.soft collapse / form.commit.subtle + tap on select).
## Comparison ## Comparison
| Feature | Soma | Radix | Ark UI | react-aria | APG | | Feature | Soma | Radix | Ark UI | react-aria | APG |

@ -114,6 +114,21 @@ Branches can nest arbitrarily deep. Leaf items use `Item`, expandable folders us
| `*` | Expand all siblings of the focused item | | `*` | Expand all siblings of the focused item |
| Character keys | Typeahead — focus first matching item by text content | | Character keys | Typeahead — focus first matching item by text content |
## Sema events
| Event | Family | Verb | Target | Intent | When |
| ----------------- | -------- | ---------- | -------- | -------- | ---------------------------------------------------------- |
| `commit-select` | `commit` | `select` | `item` | `affirm` | Item or branch selected (leaves or parent nodes). Fired via `select(value, fromEl)`. |
| `emerge-expand` | `emerge` | `expand` | `branch` | — | Branch toggled open (`toggleExpand`). |
| `emerge-collapse` | `emerge` | `collapse` | `branch` | — | Branch toggled closed (`toggleExpand`). |
**Target split**: expand/collapse target `branch` (items are leaves — they don't expand). The DOM marker on a branch is `data-tree-view-branch`, distinct from `data-tree-view-item`. `select` accepts an optional `fromEl` parameter passed by BranchControl / Item handlers so the stamp lands on the right element. Doctrinal correction in commit `66318c47`.
**Pack**: [`src/uix/sema/components/tree-view.ts`](../../../sema/components/tree-view.ts):
- `emerge-expand` → `emerge.soft` (gain 0.08), same as Accordion expand.
- `emerge-collapse` → `emerge.exit.soft` (descending pitch + gain 0.05).
- `commit-select` → `form.commit.subtle` + light `tap` haptic. Two cascade rules (one on `item`, one on `branch`) so both leaf and branch selection share the perceptual signature.
## Behavior ## Behavior
### Expand/collapse ### Expand/collapse

@ -96,6 +96,17 @@ Pass every field of the `virtualCell` snippet value through to the Cell — `row
VirtualGrid is a rendering optimisation; it carries no ARIA roles. For a semantic table / grid, wrap the `Provider` in the appropriate outer element (e.g. `role="grid"`) and set `role="row"` / `role="gridcell"` on `Cell` yourself. VirtualGrid is a rendering optimisation; it carries no ARIA roles. For a semantic table / grid, wrap the `Provider` in the appropriate outer element (e.g. `role="grid"`) and set `role="row"` / `role="gridcell"` on `Cell` yourself.
## Sema events
| Event | Family | Verb | Target | Intent | Emitted? | When |
| ------------------------ | -------- | ---------- | ---------- | --------- | -------- | -------------------------------------------------- |
| `handle-scroll-row` | `handle` | `scroll` | `viewport` | — | NO | Row-axis scroll. Same buzz reasoning as virtual-list — soma does NOT emit. |
| `handle-scroll-column` | `handle` | `scroll` | `viewport` | — | NO | Column-axis scroll. Same as above. |
| `shift-navigate-to-cell` | `shift` | `navigate` | `viewport` | — | YES | `scrollToCell(rowIndex, columnIndex)` — programmatic discrete navigation. |
| `commit-set-resize` | `commit` | `set` | `provider` | `neutral` | YES | `rowCount` or `columnCount` changed (real dimension change, not initial). |
See virtual-list README for the rationale on skipping `handle-scroll*` emission. No per-component pack — falls back to family bases.
## Comparison ## Comparison
| Feature | Soma | Ark UI | TanStack | React Aria | | Feature | Soma | Ark UI | TanStack | React Aria |

@ -105,6 +105,18 @@ The Provider exposes a `scrollToIndex(index, { align })` method via snippet prop
| `center` | Centre the target vertically (or horizontally). | | `center` | Centre the target vertically (or horizontally). |
| `end` | Align the target to the viewport's end edge. | | `end` | Align the target to the viewport's end edge. |
## Sema events
| Event | Family | Verb | Target | Intent | Emitted? | When |
| ------------------------- | -------- | ---------- | ---------- | --------- | -------- | ----------------------------------------------------- |
| `handle-scroll` | `handle` | `scroll` | `viewport` | — | NO | Declared in morfo as the contract surface for user-initiated scroll. Soma does NOT emit by default (see note below). |
| `shift-navigate-to-index` | `shift` | `navigate` | `viewport` | — | YES | `scrollToIndex()` — programmatic discrete-navigation moment. |
| `commit-set-resize` | `commit` | `set` | `provider` | `neutral` | YES | `count` changed → `totalSize` recomputed (real dimension change, not initial measurement). |
**`handle-scroll` is intentionally NOT wired**: family.handle activates only the haptic channel, and the scroll listener fires on every pixel — emitting on each event would buzz the device nonstop on mobile. Apps that want scroll-driven sema feedback should wire their own throttled / debounced emit at the appropriate cadence (e.g. on `scrollend` or rAF-throttled).
No per-component pack — falls back to family bases.
## Comparison ## Comparison
| Feature | Soma | Radix | Ark UI | bits-ui | TanStack Virtual | React Aria | | Feature | Soma | Radix | Ark UI | bits-ui | TanStack Virtual | React Aria |

Loading…
Cancel
Save

Powered by TurnKey Linux.