@vectojs/dom 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,112 @@
1
+ import type { AffineTransform, Entity, ProjectionBackend } from '@vectojs/core';
2
+ import { type DOMBridgeOptions } from './eventBridge';
3
+ /** Layer of the DOM-visual root: the `portalRoot` family, below a11y (RFC §7). */
4
+ export declare const DOM_VISUAL_Z_INDEX = "9";
5
+ /** Attribute marking the DOM-visual root and every projected element. */
6
+ export declare const DOM_ROOT_ATTR = "data-vecto-dom-root";
7
+ /** Telemetry counters (also the dirty-check assertions tests pin). */
8
+ export interface DOMProjectionStats {
9
+ mounts: number;
10
+ unmounts: number;
11
+ transformWrites: number;
12
+ opacityWrites: number;
13
+ sizeWrites: number;
14
+ contentWrites: number;
15
+ poolHits: number;
16
+ poolMisses: number;
17
+ }
18
+ /**
19
+ * Dirty-checked field writer handed to kind specs (RFC §4.2,
20
+ * `A11yAttributes` precedent: `undefined` removes, values write only on
21
+ * change). Returns true when the DOM was actually touched.
22
+ */
23
+ export interface DOMSyncFields {
24
+ write(key: string, next: string | undefined, apply: (value: string | undefined) => void): boolean;
25
+ }
26
+ /**
27
+ * Creation + content contract for one `domKind` (RFC §4.1 step 1).
28
+ *
29
+ * This registry is the boundary-rule answer to "custom nodes supply
30
+ * `createDOMElement()`": that method cannot live on core `Entity` (it would be
31
+ * new `HTMLElement` surface in core), so custom kinds register here per
32
+ * projection instance. `sync` must touch the DOM only on change (via
33
+ * `fields`) so steady-state frames write nothing (RFC §4.2).
34
+ */
35
+ export interface DOMKindSpec {
36
+ /** Element tag (pool key; must match what `create` returns). */
37
+ tag: string;
38
+ /** Create (not pool — pooling is the backend's job) the element for `node`. */
39
+ create(node: Entity): HTMLElement;
40
+ /** Sync node content/attributes into `el`, dirty-checked via `fields`. */
41
+ sync(node: Entity, el: HTMLElement, fields: DOMSyncFields): void;
42
+ }
43
+ /**
44
+ * Single-field dirty check behind {@link DOMSyncFields}. `undefined` means
45
+ * "remove" and is cached distinctly from any string, so a removed-then-absent
46
+ * field does not re-apply every frame.
47
+ */
48
+ export declare function writeField(cached: Map<string, string>, key: string, next: string | undefined, apply: (value: string | undefined) => void, onWrite?: () => void): boolean;
49
+ /**
50
+ * The `DOMProjection` (`'dom'`) backend (RFC §§4/5/6/7, P1 prototype).
51
+ *
52
+ * The scene keeps owning nodes, layout, visibility, lifecycle, and events;
53
+ * this backend owns _how a `'dom'`-policy node appears_: one live
54
+ * `HTMLElement` per resident node under a dedicated root, positioned per frame
55
+ * by the node's world matrix as CSS `matrix3d()` with `transform-origin: 0 0`.
56
+ *
57
+ * Layer rule (RFC §7): cross-backend ordering is layer-based, never
58
+ * pixel-depth-based. The root joins the `portalRoot` family at `zIndex 9`,
59
+ * below `a11yRoot` at 10 — a projected node at `position.z = -500` still
60
+ * paints over canvas content, by design, with no per-pixel occlusion.
61
+ *
62
+ * Only 100% browser/display zoom is supported (inherited CSS3DRenderer limit,
63
+ * RFC §5 rule 5): non-100% zoom misaligns `matrix3d` spacing (upstream
64
+ * `mrdoob/three.js#3225`). Documented, not fixed.
65
+ */
66
+ export declare class DOMProjection implements ProjectionBackend {
67
+ readonly kind: 'dom';
68
+ private root;
69
+ private sentinel;
70
+ private readonly states;
71
+ private readonly pools;
72
+ private readonly kinds;
73
+ private readonly bridgeOptions;
74
+ /** Nodes owning a live press: `pointerId`s per node id (RFC4 §5 pin source). */
75
+ private readonly activeGestures;
76
+ private mountSeq;
77
+ private readonly stats;
78
+ /**
79
+ * @param canvas - The scene's canvas. The root is appended to its
80
+ * `parentElement` (inserted before `a11yRoot` when present so the
81
+ * below-a11y layer holds even on `zIndex` ties). Without a DOM (SSR) or
82
+ * without a parent, the backend constructs but never goes resident — the
83
+ * walk then falls through to canvas paint.
84
+ */
85
+ constructor(canvas: HTMLCanvasElement, bridgeOptions?: DOMBridgeOptions);
86
+ /** Register (or replace) a custom `domKind` (RFC §4.1 custom nodes). */
87
+ registerKind(kind: string, spec: DOMKindSpec): void;
88
+ /** Telemetry counters (cumulative). */
89
+ getStats(): DOMProjectionStats;
90
+ /** The DOM-visual root, if constructed (null under SSR). */
91
+ getRoot(): HTMLDivElement | null;
92
+ /** Live element for a resident node, if any. */
93
+ getElement(nodeId: string): HTMLElement | undefined;
94
+ /**
95
+ * Whether `node` currently owns an active press on its live element
96
+ * (`ProjectionBackend.hasActiveGesture`, RFC4 §5). The scene consults this
97
+ * during `'auto'` negotiation so a node never flips backends mid-gesture.
98
+ */
99
+ hasActiveGesture(node: Entity): boolean;
100
+ /** Last ResizeObserver-measured intrinsic size, if observed. */
101
+ getIntrinsicSize(nodeId: string): {
102
+ width: number;
103
+ height: number;
104
+ } | undefined;
105
+ /** Resolve the creation spec for a node (custom kinds win; unknown = plain div). */
106
+ private specFor;
107
+ mount(node: Entity): void;
108
+ update(node: Entity, worldMatrix: AffineTransform): void;
109
+ unmount(node: Entity): void;
110
+ /** Unmount every resident node and remove the root (scene teardown). */
111
+ dispose(): void;
112
+ }
@@ -0,0 +1,57 @@
1
+ import { type Entity } from '@vectojs/core';
2
+ /**
3
+ * Interaction policy for projected elements (RFC §6 layer 2).
4
+ *
5
+ * - `'selection'`: content-interaction mode. Presses mean "select text / press
6
+ * button / type" and never reach the scene's camera/gesture handlers (the
7
+ * demo's Selection mode: left goes to content).
8
+ * - `'orbit'`: scene-gesture mode. Presses pass through to camera handlers
9
+ * (the demo's Orbit mode: left rotates).
10
+ */
11
+ export type DOMInteractionMode = 'selection' | 'orbit';
12
+ /** Options for {@link attachDOMBridge}. */
13
+ export interface DOMBridgeOptions {
14
+ /**
15
+ * Native `input`/`change` (and IME composition) on the element forward as
16
+ * `VectoJSEvent('change')`, mirroring the a11y input-mirror contract whose
17
+ * payload is `{ value, ... }`. Set for editable kinds only.
18
+ */
19
+ editable?: boolean;
20
+ /**
21
+ * Called with the element's current value on every native `input` event
22
+ * (before the `'change'` dispatch), so the node can sync without the bridge
23
+ * knowing the node's shape.
24
+ */
25
+ onNativeInput?: (value: string) => void;
26
+ /** Starting interaction mode (default `'selection'`). */
27
+ mode?: DOMInteractionMode;
28
+ /**
29
+ * Called when a press elects this node as the gesture owner for a
30
+ * `pointerId` (RFC4 §5 never-flip-inside-gesture: the scene pins the node's
31
+ * backend until the matching end). Paired with {@link onGestureEnd}.
32
+ */
33
+ onGestureStart?: (nodeId: string, pointerId: number) => void;
34
+ /**
35
+ * Called when the owning gesture releases (`pointerup`/`pointercancel`),
36
+ * unpinning what {@link onGestureStart} pinned. A release can arrive
37
+ * without a start on this element (press began outside, released inside),
38
+ * so the consumer must tolerate an unmatched end.
39
+ */
40
+ onGestureEnd?: (nodeId: string, pointerId: number) => void;
41
+ }
42
+ /** Handle returned by {@link attachDOMBridge}. */
43
+ export interface DOMBridgeHandle {
44
+ /** Switch the §6 layer-2 policy at runtime. */
45
+ setInteractionMode(mode: DOMInteractionMode): void;
46
+ /** Current policy. */
47
+ getInteractionMode(): DOMInteractionMode;
48
+ /** Remove every listener this bridge installed. Idempotent. */
49
+ release(): void;
50
+ }
51
+ /** Test/contract introspection: who owns `pointerId`, if anyone. */
52
+ export declare function getGestureOwner(pointerId: number): string | undefined;
53
+ /**
54
+ * Bridge native DOM events on a projected element into the Vecto event system.
55
+ * Idempotent release; safe to re-attach after an unmount cycle.
56
+ */
57
+ export declare function attachDOMBridge(el: HTMLElement, node: Entity, options?: DOMBridgeOptions): DOMBridgeHandle;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@vectojs/dom` — DOM visual projection backend for VectoJS (RFC2 P1).
3
+ *
4
+ * The scene graph keeps owning nodes, layout, visibility, lifecycle, and
5
+ * events; selected subtrees (explicit per-node `domPolicy === 'dom'` opt-in)
6
+ * materialize as live `HTMLElement`s positioned by their world matrix. Depends
7
+ * on `@vectojs/core`, never the reverse.
8
+ */
9
+ export { DOMProjection, writeField, DOM_VISUAL_Z_INDEX, DOM_ROOT_ATTR } from './DOMProjection';
10
+ export type { DOMProjectionStats, DOMKindSpec, DOMSyncFields } from './DOMProjection';
11
+ export { attachDOMBridge, getGestureOwner } from './eventBridge';
12
+ export type { DOMInteractionMode, DOMBridgeOptions, DOMBridgeHandle } from './eventBridge';
13
+ export { affineToMatrix3d } from './matrix';
14
+ export { DOMText, DOMButton, DOMInput, DOMContainer, DOMTransform } from './nodes';