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/soma/layers/presence.svelte.ts

339 lines
12 KiB

import { type Active, type ActiveProps } from '$libs/reactive';
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
import { watch } from 'runed';
import type { ActiveDom } from '$adom';
import type { EngineMotion } from '$motion';
import type { PresenceGroup, PresenceMember, PresencePhase, PresenceRole } from './presence-group';
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
// ── Types ────────────────────────────────────────────────────────────────────
export interface PresenceOptions extends ActiveProps<{ open: boolean; ref: HTMLElement | null }> {
dom: ActiveDom;
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
/** Called after open/close animation completes. Receives the current open state. */
onComplete?: (open: boolean) => void;
/** When false, skips animation waiting — unmounts immediately on close. @default true */
enabled?: boolean;
/**
* The motion runtime (`uix.motion` / `soma.motion`). When present, the cycle
* runs `motion.run(node, phase)` and awaits its `finished` ALONGSIDE
* `getAnimations()` — so a JS preset (a `spring`, invisible to
* `getAnimations()`) still gates the unmount. CSS presets settle immediately
* (their generated CSS is awaited via `getAnimations()`). The component passes
* `this.soma.motion`; the engine reads the node's `data-animation-style`.
*/
motion?: EngineMotion;
/**
* Presence-coordination group (RFC: eidos/MOTION_SERVICE_RFC.md §7). When
* present, this surface CEDES its enter timing to the group: it mounts and
* waits to be released instead of self-driving (the group sequences releases
* per the morfo's `animation.children.{enter,exit}` and aggregates `finished`).
* Absent (the common case) ⇒ this `Presence` is an island, exactly as before.
*/
group?: PresenceGroup;
/**
* Role within the group. The parent/coordinating surface is `'owner'`; nested
* surfaces are `'child'` (the default). Only the owner triggers the group's
* coordinated enter; children mount and wait to be released.
*/
groupRole?: PresenceRole;
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
}
export type TransitionStatus = 'starting' | 'ending' | undefined;
// ── Presence ─────────────────────────────────────────────────────────────────
/**
* Animation-aware presence manager.
*
* Lifecycle:
*
* OPENING:
* open=true → shouldRender=true + transitionStatus='starting'
* → next rAF: transitionStatus=undefined (removes data-starting-style, triggers CSS transition)
* → getAnimations().finished → onComplete(true)
*
* CLOSING:
* open=false → transitionStatus='ending' (element stays in DOM!)
* → getAnimations().finished
* → shouldRender=false + transitionStatus=undefined → onComplete(false)
*/
export class Presence implements PresenceMember {
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
readonly opts: PresenceOptions;
shouldRender = $state(false);
transitionStatus = $state<TransitionStatus>(undefined);
private runId = 0;
private frameIds = new Set<number>();
/** Run id captured when a grouped surface mounts and waits to be released (RFC §7). */
private pendingRunId = 0;
/** Deregister callback from the coordination group, if any. */
private deregister: (() => void) | undefined;
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
constructor(opts: PresenceOptions) {
this.opts = opts;
this.shouldRender = opts.open.current;
// RFC §7.1: cede to a coordination group if one is in scope. Register now
// (synchronously, in the provider's init) and deregister on teardown so the
// group's member set tracks mount/unmount. No group ⇒ island (below).
if (opts.group) {
this.deregister = opts.group.register(this);
$effect(() => () => this.deregister?.());
}
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
watch(
() => opts.open.current,
(isOpen) => {
if (isOpen) {
this.handleOpen();
} else {
this.handleClose();
}
soma: gesture layer, drawer component, full audit fixes across 26 components Gesture Layer: - Gesture.base(), Gesture.drag(), Gesture.resize() — shared pointer tracking - VelocityTracker with ring buffer, staleness check, projection - 52 tests (velocity + gesture integration) - setPointerCapture deferred until moveBuffer exceeded (prevents click stealing) Drawer Component: - 8 parts: Provider, Trigger, Content, Overlay, Handle, Title, Description, Close - Gesture.drag() integration for swipe dismiss - Snap points (fractional + px), fadeFromIndex, handle tap-to-cycle - Modal/non-modal/non-dismissible modes - Logical directions (start/end) RTL-aware via soma presentation - CSS custom properties (--drawer-progress, --drawer-offset-x/y) Architecture: - T1: 219 files migrated from $soma/ alias to relative paths - T2: 35 wrappers — noop replaced with () => {} inline - Translation system: componentLangs + common.buttons.* + idlangref constants - COMPONENT_GUIDE.md + README.md fully updated to reflect current patterns - contracts.ts: case-normalized registerContract/assertContract Audit Fixes (P0 critical): - DI1/PO1/TO1: 'provider' → 'root' in parts/contract/super - TB1: data-table attr on wrapper - CH1/SW1/NF4: hidden input rendering fixes - SP2: panel cleanup on unmount - RG1: roving tabindex fallback to first item - CO1: aria-labelledby on collapsible content Audit Fixes (P1 high): - SafePolygon integrated in dropdown-menu SubProvider + tooltip - 9 langs.ts files with idlangref constants - Dialog: aria-label removed (WCAG 2.5.3), handleOpen/Close guards - Stepper: count decrement + private, aria-label on List - ToggleGroup: roving tabindex in multiple mode - Select: pure aria-activedescendant (no mixed focus), label registry - NumberField: startValue respects min, scrubber commit on pointerup only - Toolbar: element-based focus tracking (not index) Audit Fixes (P2 medium): - A6: timer/listener cleanup in 6 components - A12: getDirectionalKeys in 4 components - A10: DOM queries scoped to root in 3 components - Dropdown-menu: typeahead + Home/End in SubContent, activate() pattern - Checkbox: PrimitiveLabelAttributes, getAriaChecked helper, stale value cleanup - Pagination: reactive page clamping, Set-based computePageRange - Scroll-area: thumbSize zero-guard, mount flash prevention - Splitter: collapse/expand respects adjacent constraints, immediate pointer capture 5 issues remain (design decisions): stepper registry, table sub-row IDs, tags-input ARIA model, tags-input paste, toast exit animation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
}
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
);
}
/** Whether the element should be in the DOM. Use in template: `{#if presence.isPresent}` */
readonly isPresent = $derived.by(() => this.shouldRender);
/** Data attrs to spread onto the element for CSS transition support. */
readonly transitionAttrs = $derived.by(() => {
if (this.transitionStatus === 'starting') return { 'data-starting-style': '' } as const;
if (this.transitionStatus === 'ending') return { 'data-ending-style': '' } as const;
return {} as const;
});
// ── Open ─────────────────────────────────────────────────────────────────
private handleOpen() {
// Grouped surface: cede the release timing to the group (RFC §7). The
// island path below is unchanged.
if (this.opts.group) {
this.handleOpenGrouped();
return;
}
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
this.cleanup();
this.shouldRender = true;
this.transitionStatus = 'starting';
const runId = ++this.runId;
// Remove data-starting-style on next frame to trigger CSS transition
this.requestFrame(() => {
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
if (runId !== this.runId) return;
this.transitionStatus = undefined;
// Start any JS-driven motion, then wait for CSS + JS to complete.
const extra = this.startMotion('enter');
this.waitForAnimations(runId, extra, () => {
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
this.opts.onComplete?.(true);
});
});
}
// ── Close ────────────────────────────────────────────────────────────────
private handleClose() {
// Grouped surface: cede the exit to the group, which coordinates the tree's
// exit and unmounts after (DOM retention §8.1). The island path is unchanged.
if (this.opts.group) {
this.handleCloseGrouped();
return;
}
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
this.cleanup();
const enabled = this.opts.enabled ?? true;
if (!enabled) {
this.shouldRender = false;
this.transitionStatus = undefined;
this.opts.onComplete?.(false);
return;
}
this.transitionStatus = 'ending';
const runId = ++this.runId;
// Start any JS-driven exit motion, then wait for CSS + JS, then unmount.
const extra = this.startMotion('exit');
this.waitForAnimations(runId, extra, () => {
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
if (runId !== this.runId) return;
this.shouldRender = false;
this.transitionStatus = undefined;
this.opts.onComplete?.(false);
});
}
// ── Grouped lifecycle (RFC §7) — only active when `opts.group` is set ──────
/**
* Grouped ENTER: mount immediately, then WAIT to be released by the group.
* Only the owner triggers the group's coordinated enter; children mount and
* stay in `data-starting-style` until the group calls `release('enter')`.
*/
private handleOpenGrouped() {
this.cleanup();
this.shouldRender = true;
this.transitionStatus = 'starting';
this.pendingRunId = ++this.runId;
if (this.role === 'owner') this.opts.group?.requestEnter();
}
/**
* Grouped EXIT: cede to the group (RFC §8.1). Mark `ending` and WAIT — the
* group runs the tree's exit in `when` order and calls `unmount()` here only
* after the whole exit settles (DOM retention). Only the owner triggers it.
*/
private handleCloseGrouped() {
this.cleanup();
const enabled = this.opts.enabled ?? true;
if (!enabled) {
this.unmount();
return;
}
// Stay in the OPEN visual state (no `data-ending-style` yet) until the group
// RELEASES this surface. That is what makes `when: 'after'` exit the children
// before the owner — and the owner stay visibly intact while its children
// leave — instead of everyone's exit animation firing at once. `release`
// stamps `ending` when our turn comes (RFC §8.1).
this.pendingRunId = ++this.runId;
if (this.role === 'owner') this.opts.group?.requestExit();
}
/** This surface's role within its group (`PresenceMember`). */
get role(): PresenceRole {
return this.opts.groupRole ?? 'child';
}
/**
* Release this surface's motion for the phase — the group calls this in
* coordinated order (`PresenceMember`). Removes `data-starting-style` on enter,
* starts the JS motion, and resolves when this surface's own animation finishes
* (CSS via `getAnimations()` + any JS `finished`). The visual stagger is CSS
* (eidos); soma only awaits.
*/
release(phase: PresencePhase): Promise<void> {
const runId = this.pendingRunId;
return new Promise<void>((resolve) => {
this.requestFrame(() => {
if (runId !== this.runId) {
resolve();
return;
}
// Enter: drop `starting` → the CSS transition plays. Exit: stamp
// `ending` NOW (not at close) so the surface holds its open state until
// the group reaches it, giving `when: 'after'` its sequencing (§8.1).
if (phase === 'enter') this.transitionStatus = undefined;
else this.transitionStatus = 'ending';
const extra = this.startMotion(phase);
this.waitForAnimations(runId, extra, () => {
if (phase === 'enter') this.opts.onComplete?.(true);
resolve();
});
});
});
}
/**
* Tear down after the group's coordinated exit settles (`PresenceMember`, RFC
* §8.1). Mirrors the island unmount: drop the surface and notify. The group
* calls this once the whole tree's exit has aggregated — never before, so the
* owner's DOM (and any retained subtree) survives the children's exit.
*/
unmount(): void {
this.shouldRender = false;
this.transitionStatus = undefined;
this.opts.onComplete?.(false);
}
/**
* Cancel in-flight motion (interruption — RFC §8.3, M4). `cleanup()` bumps the
* runId so the in-flight async tail (frames, `waitForAnimations`) becomes a
* no-op, and `motion.cancel(node)` stops any JS-driven run so a reversal does
* not stack a second animation on the node (CSS presets are untracked → no-op;
* the reversed CSS transition continues from its current value). The group calls
* this on every member when a reversal supersedes the in-flight phase.
*/
cancel(): void {
this.cleanup();
const node = this.opts.ref.current;
if (node) this.opts.motion?.cancel(node);
}
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
// ── Animation waiting ────────────────────────────────────────────────────
/** Start the JS-driven motion for this phase, if a `motion` engine is wired. */
private startMotion(phase: 'enter' | 'exit'): Promise<void> | undefined {
const node = this.opts.ref.current;
if (!node) return undefined;
return this.opts.motion?.run(node, phase)?.finished;
}
private waitForAnimations(
runId: number,
extra: Promise<void> | undefined,
onDone: () => void
) {
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
// getAnimations() needs a frame to see the active animations
this.requestFrame(() => {
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
if (runId !== this.runId) return;
const node = this.opts.ref.current;
if (!node) {
onDone();
return;
}
// CSS animations (via getAnimations) + any JS-driven `finished`. A spring
// is invisible to getAnimations(), so its `finished` joins the wait here.
const finishers: Promise<unknown>[] = node.getAnimations().map((a) => a.finished);
if (extra) finishers.push(extra);
if (finishers.length === 0) {
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
onDone();
return;
}
Promise.all(finishers)
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
.then(() => {
if (runId !== this.runId) return;
onDone();
})
.catch(() => {
// An awaited animation was CANCELLED (its `.finished` rejects).
// This is not only rapid toggling (stale runId → no-op below): a
// visual exit animation can be interrupted WITHOUT a state change —
// e.g. sema unstamps the `data-event-*` that drives the close
// `dismiss-fade` when its hold ends BEFORE the animation's own
// duration, cancelling it. A cancelled exit MUST still finalize the
// lifecycle; otherwise a closed overlay stays mounted forever and
// its FocusScope traps focus (no block can be focused to type).
// Stale runs are filtered by the runId guard, exactly as in `then`.
if (runId !== this.runId) return;
onDone();
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
});
});
}
// ── Cleanup ──────────────────────────────────────────────────────────────
private requestFrame(callback: () => void) {
const frame = this.opts.dom.requestFrame(() => {
this.frameIds.delete(frame);
callback();
}, this.opts.ref.current);
this.frameIds.add(frame);
}
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
private cleanup() {
this.runId++;
for (const frame of this.frameIds) {
this.opts.dom.cancelFrame(frame, this.opts.ref.current);
}
this.frameIds.clear();
Refactor soma: consolidate layers, add Soma class, Dialog component, animation system Layers: - Consolidate 28 files/10 dirs → 13 files/2 dirs - Separate behavior layers (classes) from internal components - Normalize naming: Dismissal, ScrollLock, TextSelection, FocusScope - Adapt floating layer from terra to soma (imports, CSS vars --soma--) - Presence rewritten: data-starting/ending-style, forceMount, onComplete, getAnimations API Architecture: - Replace config/ with Soma class (root instance with services) - Add SomaFormat namespace (date, number, currency, unit formatters) - Add generic resolve() for prop → config → fallback chain - Provider supports optional ref (ProviderOpts vs WithRefOpts) - Hierarchical component barrel: import { Dialog } from '$soma/components' - Descriptive IDs: soma-dialog-trigger-c12 Dialog component (7 parts): - Provider, Trigger, Content, Overlay, Title, Description, Close - Content integrates all behavior layers as classes (no template nesting) - modal prop controls interactOutsideBehavior automatically - Nested dialog support: depth, data-nested, --soma-dialog-depth CSS vars - Animation support: forceMount, data-starting/ending-style, onOpenChangeComplete - Translator integration for aria-labels - JSDoc documentation on all props Bug fixes: - Presence mount without ref (chicken-and-egg) - ScrollLock unlock path restores body style - Nested dialog infinite loop (untrack mutation) - pointer-events: none blocking dialog interaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 months ago
}
}

Powered by TurnKey Linux.