@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.
- package/dist/DOMProjection.d.ts +112 -0
- package/dist/eventBridge.d.ts +57 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +589 -0
- package/dist/index.mjs +551 -0
- package/dist/matrix.d.ts +16 -0
- package/dist/nodes.d.ts +62 -0
- package/package.json +53 -0
|
@@ -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;
|
package/dist/index.d.ts
ADDED
|
@@ -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';
|