feat(soma): wire runtime.trigger for batch C (gestural + wrappers)

Cierra el sprint de cabling. De los 6 originales del batch C, 3 son
wrappers sin events propios (time-range-field, link-preview,
alert-dialog) que delegan en su componente compuesto — zero wiring
necesario. Los 3 restantes:

- drag-drop:
  - startDrag(...) → handle-pick en draggable con params.source
    como fallbackTarget.
  - commitDrop(target) → handle-drop en droppable con target.
  - cancelDrag() → commit-cancel en draggable con drag.source.

- virtual-list:
  - scrollToIndex(...) → shift-navigate-to-index en viewport
    (programmatic discrete-navigation moment).
  - observeResize callback → commit-set-resize en provider
    cuando el clientWidth/Height realmente cambia (skip initial).
  - NO emite handle-scroll: family.handle es haptic-only, el
    scroll listener fires en cada píxel — emitiría buzz nonstop.
    Documentado como decisión consciente; apps con scroll-feedback
    wiran su propio throttle.

- virtual-grid: análogo virtual-list, con scrollToCell →
  shift-navigate-to-cell. NO emite handle-scroll-row /
  handle-scroll-column por la misma razón.

Verificación:
- npm run morfo:vocabulary → EXIT 0
- npx vitest run src/uix/sema src/uix/morfo → 195/195 pass
- npx vitest run src/uix/soma → 419/419 pass

Tras este commit: TODOS los morfos con scope:sema + events declarados
emiten desde soma (excepto los 4 `handle-scroll*` de virtual-list/grid
intencionalmente saltados). Cierra el caveat "soma no emite" del
proyecto memory.

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

@ -159,6 +159,9 @@ export class DragDropProvider {
};
this.keyboard = params.keyboard;
this.announceTemplate(DRAG_DROP_LANGS.DRAG_STARTED, { item: params.label });
// Morfo: handle-pick on `draggable`. The source element is the
// draggable that just initiated the drag — pass it as fallback.
void this.runtime.trigger('handle-pick', { fallbackTarget: params.source });
if (params.keyboard) {
this.setupKeyboardNav();
@ -213,6 +216,9 @@ export class DragDropProvider {
item: drag.label,
target: targetLabel.trim()
});
// Morfo: handle-drop on `droppable`. The target is the droppable
// that received the dropped item.
void this.runtime.trigger('handle-drop', { fallbackTarget: target });
this.finish();
}
@ -225,6 +231,9 @@ export class DragDropProvider {
outcome: 'cancel'
});
this.announce(DRAG_DROP_LANGS.DRAG_CANCELLED);
// Morfo: commit-cancel on `draggable`. The source still holds
// the dragged-from element; use it as fallback target.
void this.runtime.trigger('commit-cancel', { fallbackTarget: drag.source });
// Return focus to the source for keyboard drags.
const source = drag.source;
this.finish();

@ -183,6 +183,12 @@ export class VirtualGridProvider {
left: left ?? this.scrollLeft,
behavior: opts.behavior ?? 'auto'
});
// Morfo: shift-navigate-to-cell on `viewport`. Programmatic scroll-
// to-cell is the discrete-navigation moment. `handle-scroll-row` /
// `handle-scroll-column` are intentionally NOT emitted here for
// the same fatigue reason as virtual-list (haptic-only family,
// pixel-frequency events would buzz nonstop).
void this.runtime.trigger('shift-navigate-to-cell', { fallbackTarget: vp });
};
readonly snippetProps = $derived.by(() => ({
@ -230,9 +236,22 @@ export class VirtualGridViewportProvider {
if (!el) return;
const updateSize = () => {
const prevH = this.provider.viewportHeight;
const prevW = this.provider.viewportWidth;
this.provider.viewportHeight = el.clientHeight;
this.provider.viewportWidth = el.clientWidth;
// Morfo: commit-set-resize on `provider`. Only emit on real
// dimension change (ResizeObserver fires on every layout);
// skip the initial measurement (prevH === prevW === 0).
if ((prevH !== el.clientHeight || prevW !== el.clientWidth) && (prevH !== 0 || prevW !== 0)) {
void this.provider.runtime.trigger('commit-set-resize');
}
};
// NOTE: `handle-scroll-row` / `handle-scroll-column` declared in
// the morfo but NOT emitted here. See virtual-list note —
// pixel-frequency scroll with haptic-only family = device buzz.
// Apps that want scroll-driven sema should wire their own
// throttled emit.
const updateScroll = () => {
this.provider.scrollTop = el.scrollTop;
this.provider.scrollLeft = el.scrollLeft;

@ -261,6 +261,16 @@ export class VirtualListProvider {
target = Math.max(0, Math.min(this.totalSize - vpSize, target));
// Morfo: shift-navigate-to-index on `viewport`. Programmatic
// scroll-to is a discrete navigation moment (vs the continuous
// `handle-scroll` which is intentionally NOT emitted from this
// provider — see note in `VirtualListViewportProvider`).
const vpForEmit = this.viewportRef;
void this.runtime.trigger(
'shift-navigate-to-index',
vpForEmit ? { fallbackTarget: vpForEmit } : undefined
);
// Internal viewport scroll.
const vp = this.viewportRef;
if (vp) {
@ -337,8 +347,21 @@ export class VirtualListViewportProvider {
const horizontal = this.provider.isHorizontal;
const updateSize = () => {
this.provider.viewportSize = horizontal ? el.clientWidth : el.clientHeight;
const prev = this.provider.viewportSize;
const next = horizontal ? el.clientWidth : el.clientHeight;
this.provider.viewportSize = next;
// Morfo: commit-set-resize on `provider`. Only emit on real
// size change (ResizeObserver fires on every layout pass).
if (prev !== next && prev !== 0) {
void this.provider.runtime.trigger('commit-set-resize');
}
};
// NOTE: `handle-scroll` is declared in the morfo but NOT emitted
// here by default. The scroll listener below fires on every
// pixel; family.handle.activeChannels=['haptic'] so emitting on
// each event would buzz nonstop on mobile. Apps that want
// scroll-driven sema feedback should wire it themselves with
// their own throttle / debounce policy.
const updateScroll = () => {
this.provider.scrollOffset = horizontal ? el.scrollLeft : el.scrollTop;
};

Loading…
Cancel
Save

Powered by TurnKey Linux.