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-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
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. |
| `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
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 |
## 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
### Basic

@ -75,6 +75,19 @@ Snippet props: `{ value, copied, 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
| Key | English | Spanish |

@ -48,6 +48,17 @@ A component that expands and collapses a content section.
| ----------------- | ------------------------------- |
| `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
### 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.
## 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
### 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.
## 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
### 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.
## 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
| 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 |
| `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
### Basic

@ -128,6 +128,17 @@ Per [the APG](https://www.w3.org/WAI/ARIA/apg/patterns/feed/):
| `Ctrl+End` | Focus last Article. |
| `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
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.
## 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
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.
## 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
| 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"`.
## 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
### 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.
## 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
| Key | English | Spanish |

@ -50,6 +50,14 @@ switch state outside Soma.
| `Enter` | Toggle the switch |
| `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
### Basic

@ -205,6 +205,20 @@ interface ColumnDef<TData> {
| RowDetail | `data-table-row-detail` | Always present |
| 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 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).
## 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
### Single mode (default)

@ -63,6 +63,14 @@ Snippet props: `{ pressed: boolean }`.
| ----------------- | ------------------------------ |
| `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
| 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`.
## 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
### Basic

@ -141,6 +141,18 @@ Per the [APG treegrid spec](https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/):
| `Escape` | Clear selection. |
| `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
| 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 |
| 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
### 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.
## 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
| 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). |
| `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
| Feature | Soma | Radix | Ark UI | bits-ui | TanStack Virtual | React Aria |

Loading…
Cancel
Save

Powered by TurnKey Linux.