@fundamental-engine/dom 0.7.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/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/apply-recipe.d.ts +103 -0
- package/dist/apply-recipe.d.ts.map +1 -0
- package/dist/apply-recipe.js +271 -0
- package/dist/apply-recipe.js.map +1 -0
- package/dist/bind-data.d.ts +72 -0
- package/dist/bind-data.d.ts.map +1 -0
- package/dist/bind-data.js +164 -0
- package/dist/bind-data.js.map +1 -0
- package/dist/browser-host.d.ts +11 -0
- package/dist/browser-host.d.ts.map +1 -0
- package/dist/browser-host.js +41 -0
- package/dist/browser-host.js.map +1 -0
- package/dist/contours.d.ts +79 -0
- package/dist/contours.d.ts.map +1 -0
- package/dist/contours.js +88 -0
- package/dist/contours.js.map +1 -0
- package/dist/env.d.ts +39 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +47 -0
- package/dist/env.js.map +1 -0
- package/dist/export-dom.d.ts +7 -0
- package/dist/export-dom.d.ts.map +1 -0
- package/dist/export-dom.js +28 -0
- package/dist/export-dom.js.map +1 -0
- package/dist/feedback.d.ts +57 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +134 -0
- package/dist/feedback.js.map +1 -0
- package/dist/field-nav.d.ts +35 -0
- package/dist/field-nav.d.ts.map +1 -0
- package/dist/field-nav.js +82 -0
- package/dist/field-nav.js.map +1 -0
- package/dist/flip.d.ts +31 -0
- package/dist/flip.d.ts.map +1 -0
- package/dist/flip.js +65 -0
- package/dist/flip.js.map +1 -0
- package/dist/governor.d.ts +37 -0
- package/dist/governor.d.ts.map +1 -0
- package/dist/governor.js +72 -0
- package/dist/governor.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/lint.d.ts +78 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +153 -0
- package/dist/lint.js.map +1 -0
- package/dist/measurement.d.ts +44 -0
- package/dist/measurement.d.ts.map +1 -0
- package/dist/measurement.js +95 -0
- package/dist/measurement.js.map +1 -0
- package/dist/metrics.d.ts +70 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +119 -0
- package/dist/metrics.js.map +1 -0
- package/dist/overlays.d.ts +48 -0
- package/dist/overlays.d.ts.map +1 -0
- package/dist/overlays.js +48 -0
- package/dist/overlays.js.map +1 -0
- package/dist/perf.d.ts +62 -0
- package/dist/perf.d.ts.map +1 -0
- package/dist/perf.js +94 -0
- package/dist/perf.js.map +1 -0
- package/dist/platform.d.ts +40 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +61 -0
- package/dist/platform.js.map +1 -0
- package/dist/relationships.d.ts +79 -0
- package/dist/relationships.d.ts.map +1 -0
- package/dist/relationships.js +155 -0
- package/dist/relationships.js.map +1 -0
- package/dist/schedule.d.ts +84 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +91 -0
- package/dist/schedule.js.map +1 -0
- package/dist/state.d.ts +36 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +113 -0
- package/dist/state.js.map +1 -0
- package/dist/text-bodies.d.ts +71 -0
- package/dist/text-bodies.d.ts.map +1 -0
- package/dist/text-bodies.js +159 -0
- package/dist/text-bodies.js.map +1 -0
- package/dist/thread-overlay.d.ts +63 -0
- package/dist/thread-overlay.d.ts.map +1 -0
- package/dist/thread-overlay.js +110 -0
- package/dist/thread-overlay.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/visual-bindings.d.ts +95 -0
- package/dist/visual-bindings.d.ts.map +1 -0
- package/dist/visual-bindings.js +211 -0
- package/dist/visual-bindings.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FrameScheduler — one shared loop with explicit, ordered phases so the registries never fight each
|
|
3
|
+
* other. Without it, MeasurementRegistry (reads) and FeedbackRegistry (writes) can interleave and
|
|
4
|
+
* thrash layout. With it, every frame walks the same six phases in the same order:
|
|
5
|
+
*
|
|
6
|
+
* discover → read → compute → state → write → render
|
|
7
|
+
*
|
|
8
|
+
* 1. discover register/unregister bodies, relationships, visual bindings (structure changes)
|
|
9
|
+
* 2. read measure the DOM once — snapshot geometry, visibility (the ONLY place layout is read)
|
|
10
|
+
* 3. compute run field/force/agent logic against the immutable snapshot (no DOM)
|
|
11
|
+
* 4. state fold results into StateRegistry + thresholds (internal truth, no DOM writes)
|
|
12
|
+
* 5. write flush state → CSS vars, data attrs, ElementInternals, thresholded events
|
|
13
|
+
* 6. render draw overlays, field lines, heatmaps from the registries (read-only)
|
|
14
|
+
*
|
|
15
|
+
* The scheduler also enforces the discipline: a read requested during the write phase is a
|
|
16
|
+
* violation. By default violations are recorded (so platform lint can report them); `strict: true`
|
|
17
|
+
* throws instead — useful in tests and dev. The scheduler holds no DOM and is fully testable.
|
|
18
|
+
*/
|
|
19
|
+
import type { Viewport } from './types.ts';
|
|
20
|
+
export declare const PHASES: readonly ["discover", "read", "compute", "state", "write", "render"];
|
|
21
|
+
export type Phase = (typeof PHASES)[number];
|
|
22
|
+
/** Phases in which reading layout is legal. Reading anywhere else thrashes against pending writes. */
|
|
23
|
+
export declare const READ_PHASES: readonly Phase[];
|
|
24
|
+
export interface FrameContext {
|
|
25
|
+
/** the frame time (ms). Injectable so frames are deterministic in tests. */
|
|
26
|
+
now: number;
|
|
27
|
+
/** viewport box for visibility math (omitted → registries fall back to `window`). */
|
|
28
|
+
viewport?: Viewport;
|
|
29
|
+
/** the phase currently running. */
|
|
30
|
+
phase: Phase;
|
|
31
|
+
/** monotonically increasing frame counter (starts at 0). */
|
|
32
|
+
frame: number;
|
|
33
|
+
}
|
|
34
|
+
export type PhaseHandler = (ctx: FrameContext) => void;
|
|
35
|
+
export interface PhaseViolation {
|
|
36
|
+
/** the phase that was active when the illegal operation happened. */
|
|
37
|
+
phase: Phase;
|
|
38
|
+
/** the operation that was attempted (e.g. `measure`). */
|
|
39
|
+
op: string;
|
|
40
|
+
/** the phases in which the operation would have been legal. */
|
|
41
|
+
allowed: readonly Phase[];
|
|
42
|
+
frame: number;
|
|
43
|
+
message: string;
|
|
44
|
+
}
|
|
45
|
+
export interface FrameReport {
|
|
46
|
+
frame: number;
|
|
47
|
+
now: number;
|
|
48
|
+
/** the phases that ran, in order. */
|
|
49
|
+
ran: Phase[];
|
|
50
|
+
/** violations recorded during this frame (empty when clean). */
|
|
51
|
+
violations: PhaseViolation[];
|
|
52
|
+
}
|
|
53
|
+
export interface SchedulerOptions {
|
|
54
|
+
/** throw on a phase violation instead of recording it (default false). */
|
|
55
|
+
strict?: boolean;
|
|
56
|
+
}
|
|
57
|
+
export declare class FrameScheduler {
|
|
58
|
+
private readonly handlers;
|
|
59
|
+
private readonly strict;
|
|
60
|
+
private readonly recorded;
|
|
61
|
+
private current;
|
|
62
|
+
private count;
|
|
63
|
+
constructor(opts?: SchedulerOptions);
|
|
64
|
+
/** Register a handler for a phase. Returns an unsubscribe function. */
|
|
65
|
+
on(phase: Phase, handler: PhaseHandler): () => void;
|
|
66
|
+
/** The phase currently executing, or null when no frame is running (e.g. direct unit calls). */
|
|
67
|
+
get phase(): Phase | null;
|
|
68
|
+
/** How many frames have run. */
|
|
69
|
+
get frame(): number;
|
|
70
|
+
/**
|
|
71
|
+
* Assert that the current phase is one in which `op` is legal. Outside a frame (`phase === null`)
|
|
72
|
+
* anything is allowed — direct calls in tests/setup are not frame-disciplined. Inside a frame, an
|
|
73
|
+
* out-of-phase call is recorded (or thrown in strict mode).
|
|
74
|
+
*/
|
|
75
|
+
assertPhase(allowed: readonly Phase[], op: string): void;
|
|
76
|
+
/** A read-phase guard suitable for handing to MeasurementRegistry.setPhaseGuard. */
|
|
77
|
+
readGuard(): (op: string) => void;
|
|
78
|
+
/** Violations recorded so far (across frames, until cleared). */
|
|
79
|
+
violations(): readonly PhaseViolation[];
|
|
80
|
+
clearViolations(): void;
|
|
81
|
+
/** Run one frame: every phase, in order, with its handlers. Returns a per-frame report. */
|
|
82
|
+
runFrame(now?: number, viewport?: Viewport): FrameReport;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=schedule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,MAAM,sEAAuE,CAAC;AAC3F,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5C,sGAAsG;AACtG,eAAO,MAAM,WAAW,EAAE,SAAS,KAAK,EAAyB,CAAC;AAElE,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,mCAAmC;IACnC,KAAK,EAAE,KAAK,CAAC;IACb,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,KAAK,EAAE,KAAK,CAAC;IACb,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,OAAO,EAAE,SAAS,KAAK,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,GAAG,EAAE,KAAK,EAAE,CAAC;IACb,gEAAgE;IAChE,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;IAClE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,KAAK,CAAK;gBAEN,IAAI,GAAE,gBAAqB;IAKvC,uEAAuE;IACvE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI;IASnD,gGAAgG;IAChG,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,CAExB;IAED,gCAAgC;IAChC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAexD,oFAAoF;IACpF,SAAS,IAAI,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI;IAIjC,iEAAiE;IACjE,UAAU,IAAI,SAAS,cAAc,EAAE;IAIvC,eAAe,IAAI,IAAI;IAIvB,2FAA2F;IAC3F,QAAQ,CAAC,GAAG,SAAI,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,WAAW;CAqBpD"}
|
package/dist/schedule.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export const PHASES = ['discover', 'read', 'compute', 'state', 'write', 'render'];
|
|
2
|
+
/** Phases in which reading layout is legal. Reading anywhere else thrashes against pending writes. */
|
|
3
|
+
export const READ_PHASES = ['discover', 'read'];
|
|
4
|
+
export class FrameScheduler {
|
|
5
|
+
handlers = new Map();
|
|
6
|
+
strict;
|
|
7
|
+
recorded = [];
|
|
8
|
+
current = null;
|
|
9
|
+
count = 0;
|
|
10
|
+
constructor(opts = {}) {
|
|
11
|
+
this.strict = opts.strict ?? false;
|
|
12
|
+
for (const p of PHASES)
|
|
13
|
+
this.handlers.set(p, []);
|
|
14
|
+
}
|
|
15
|
+
/** Register a handler for a phase. Returns an unsubscribe function. */
|
|
16
|
+
on(phase, handler) {
|
|
17
|
+
const list = this.handlers.get(phase);
|
|
18
|
+
list.push(handler);
|
|
19
|
+
return () => {
|
|
20
|
+
const i = list.indexOf(handler);
|
|
21
|
+
if (i >= 0)
|
|
22
|
+
list.splice(i, 1);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** The phase currently executing, or null when no frame is running (e.g. direct unit calls). */
|
|
26
|
+
get phase() {
|
|
27
|
+
return this.current;
|
|
28
|
+
}
|
|
29
|
+
/** How many frames have run. */
|
|
30
|
+
get frame() {
|
|
31
|
+
return this.count;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Assert that the current phase is one in which `op` is legal. Outside a frame (`phase === null`)
|
|
35
|
+
* anything is allowed — direct calls in tests/setup are not frame-disciplined. Inside a frame, an
|
|
36
|
+
* out-of-phase call is recorded (or thrown in strict mode).
|
|
37
|
+
*/
|
|
38
|
+
assertPhase(allowed, op) {
|
|
39
|
+
const phase = this.current;
|
|
40
|
+
if (phase === null)
|
|
41
|
+
return; // unmanaged call — not part of a scheduled frame
|
|
42
|
+
if (allowed.includes(phase))
|
|
43
|
+
return;
|
|
44
|
+
const v = {
|
|
45
|
+
phase,
|
|
46
|
+
op,
|
|
47
|
+
allowed,
|
|
48
|
+
frame: this.count,
|
|
49
|
+
message: `"${op}" ran in the ${phase} phase; allowed only in: ${allowed.join(', ')}`,
|
|
50
|
+
};
|
|
51
|
+
if (this.strict)
|
|
52
|
+
throw new Error(`[Fundamental/platform] ${v.message}`);
|
|
53
|
+
this.recorded.push(v);
|
|
54
|
+
}
|
|
55
|
+
/** A read-phase guard suitable for handing to MeasurementRegistry.setPhaseGuard. */
|
|
56
|
+
readGuard() {
|
|
57
|
+
return (op) => this.assertPhase(READ_PHASES, op);
|
|
58
|
+
}
|
|
59
|
+
/** Violations recorded so far (across frames, until cleared). */
|
|
60
|
+
violations() {
|
|
61
|
+
return this.recorded;
|
|
62
|
+
}
|
|
63
|
+
clearViolations() {
|
|
64
|
+
this.recorded.length = 0;
|
|
65
|
+
}
|
|
66
|
+
/** Run one frame: every phase, in order, with its handlers. Returns a per-frame report. */
|
|
67
|
+
runFrame(now = 0, viewport) {
|
|
68
|
+
const startViolations = this.recorded.length;
|
|
69
|
+
const ran = [];
|
|
70
|
+
for (const phase of PHASES) {
|
|
71
|
+
const list = this.handlers.get(phase);
|
|
72
|
+
if (list.length === 0)
|
|
73
|
+
continue;
|
|
74
|
+
this.current = phase;
|
|
75
|
+
ran.push(phase);
|
|
76
|
+
const ctx = { now, viewport, phase, frame: this.count };
|
|
77
|
+
for (const h of list)
|
|
78
|
+
h(ctx);
|
|
79
|
+
}
|
|
80
|
+
this.current = null;
|
|
81
|
+
const report = {
|
|
82
|
+
frame: this.count,
|
|
83
|
+
now,
|
|
84
|
+
ran,
|
|
85
|
+
violations: this.recorded.slice(startViolations),
|
|
86
|
+
};
|
|
87
|
+
this.count++;
|
|
88
|
+
return report;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=schedule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.js","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"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"}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StateRegistry — typed, observable element state that is NOT accessibility state. The native
|
|
3
|
+
* primitive Fundamental wishes existed: a place to hold numeric/boolean/vector2 channels (density,
|
|
4
|
+
* attention, lit, pull) per element, separate from ARIA, that CSS and JS can both consume. This
|
|
5
|
+
* registry only *holds* state; the FeedbackRegistry writes it to the DOM (read-phase vs write-phase).
|
|
6
|
+
*/
|
|
7
|
+
import type { FieldStateValue } from './types.ts';
|
|
8
|
+
type Raw = number | boolean | string | {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
};
|
|
12
|
+
export type StateListener = (value: FieldStateValue, key: string, element: Element) => void;
|
|
13
|
+
export declare class StateRegistry {
|
|
14
|
+
private readonly store;
|
|
15
|
+
private readonly listeners;
|
|
16
|
+
/** Set a typed state value on an element, notifying observers if it changed. */
|
|
17
|
+
set(element: Element, key: string, value: Raw): void;
|
|
18
|
+
/** A numeric convenience reader (0 for absent / non-numeric). */
|
|
19
|
+
number(element: Element, key: string): number;
|
|
20
|
+
get(element: Element, key: string): FieldStateValue | undefined;
|
|
21
|
+
has(element: Element, key: string): boolean;
|
|
22
|
+
/** All state on an element (empty map if none). */
|
|
23
|
+
values(element: Element): ReadonlyMap<string, FieldStateValue>;
|
|
24
|
+
/** Every element that currently holds state (for lint / iteration). */
|
|
25
|
+
elements(): Element[];
|
|
26
|
+
delete(element: Element, key: string): void;
|
|
27
|
+
/** Drop every state entry whose element left the DOM (the per-frame prune other registries do
|
|
28
|
+
* at their natural moments — callers with a frame loop can run this on a cadence). */
|
|
29
|
+
prune(): void;
|
|
30
|
+
clear(element: Element): void;
|
|
31
|
+
/** Observe one key on one element. Returns an unsubscribe function. */
|
|
32
|
+
observe(element: Element, key: string, fn: StateListener): () => void;
|
|
33
|
+
private notify;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,KAAK,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAShE,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;AAE5F,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoD;IAC1E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuD;IAEjF,gFAAgF;IAChF,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAYpD,iEAAiE;IACjE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;IAK7C,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAI/D,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAI3C,mDAAmD;IACnD,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC;IAI9D,uEAAuE;IACvE,QAAQ,IAAI,OAAO,EAAE;IAIrB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAiB3C;2FACuF;IACvF,KAAK,IAAI,IAAI;IAKb,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAK7B,uEAAuE;IACvE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,GAAG,MAAM,IAAI;IAerE,OAAO,CAAC,MAAM;CAIf"}
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
function normalize(v) {
|
|
2
|
+
if (typeof v === 'number')
|
|
3
|
+
return { type: 'number', value: v };
|
|
4
|
+
if (typeof v === 'boolean')
|
|
5
|
+
return { type: 'boolean', value: v };
|
|
6
|
+
if (typeof v === 'string')
|
|
7
|
+
return { type: 'string', value: v };
|
|
8
|
+
return { type: 'vector2', x: v.x, y: v.y };
|
|
9
|
+
}
|
|
10
|
+
export class StateRegistry {
|
|
11
|
+
store = new Map();
|
|
12
|
+
listeners = new Map();
|
|
13
|
+
/** Set a typed state value on an element, notifying observers if it changed. */
|
|
14
|
+
set(element, key, value) {
|
|
15
|
+
let m = this.store.get(element);
|
|
16
|
+
if (!m) {
|
|
17
|
+
m = new Map();
|
|
18
|
+
this.store.set(element, m);
|
|
19
|
+
}
|
|
20
|
+
const next = normalize(value);
|
|
21
|
+
const prev = m.get(key);
|
|
22
|
+
m.set(key, next);
|
|
23
|
+
if (!prev || !sameValue(prev, next))
|
|
24
|
+
this.notify(element, key, next);
|
|
25
|
+
}
|
|
26
|
+
/** A numeric convenience reader (0 for absent / non-numeric). */
|
|
27
|
+
number(element, key) {
|
|
28
|
+
const v = this.store.get(element)?.get(key);
|
|
29
|
+
return v && v.type === 'number' ? v.value : 0;
|
|
30
|
+
}
|
|
31
|
+
get(element, key) {
|
|
32
|
+
return this.store.get(element)?.get(key);
|
|
33
|
+
}
|
|
34
|
+
has(element, key) {
|
|
35
|
+
return this.store.get(element)?.has(key) ?? false;
|
|
36
|
+
}
|
|
37
|
+
/** All state on an element (empty map if none). */
|
|
38
|
+
values(element) {
|
|
39
|
+
return this.store.get(element) ?? new Map();
|
|
40
|
+
}
|
|
41
|
+
/** Every element that currently holds state (for lint / iteration). */
|
|
42
|
+
elements() {
|
|
43
|
+
return [...this.store.keys()];
|
|
44
|
+
}
|
|
45
|
+
delete(element, key) {
|
|
46
|
+
const m = this.store.get(element);
|
|
47
|
+
if (m) {
|
|
48
|
+
m.delete(key);
|
|
49
|
+
if (m.size === 0)
|
|
50
|
+
this.store.delete(element); // last key gone → no empty husk
|
|
51
|
+
}
|
|
52
|
+
// listener hygiene: an unsubscribe leaves an empty Set, and a per-key delete should not
|
|
53
|
+
// strand the listener scaffolding for that key — prune emptied maps so a long-lived
|
|
54
|
+
// registry never accumulates husks for keys that stopped existing.
|
|
55
|
+
const byKey = this.listeners.get(element);
|
|
56
|
+
if (byKey) {
|
|
57
|
+
const set = byKey.get(key);
|
|
58
|
+
if (set && set.size === 0)
|
|
59
|
+
byKey.delete(key);
|
|
60
|
+
if (byKey.size === 0)
|
|
61
|
+
this.listeners.delete(element);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Drop every state entry whose element left the DOM (the per-frame prune other registries do
|
|
65
|
+
* at their natural moments — callers with a frame loop can run this on a cadence). */
|
|
66
|
+
prune() {
|
|
67
|
+
for (const el of this.store.keys())
|
|
68
|
+
if (el.isConnected === false)
|
|
69
|
+
this.store.delete(el);
|
|
70
|
+
for (const el of this.listeners.keys())
|
|
71
|
+
if (el.isConnected === false)
|
|
72
|
+
this.listeners.delete(el);
|
|
73
|
+
}
|
|
74
|
+
clear(element) {
|
|
75
|
+
this.store.delete(element);
|
|
76
|
+
this.listeners.delete(element);
|
|
77
|
+
}
|
|
78
|
+
/** Observe one key on one element. Returns an unsubscribe function. */
|
|
79
|
+
observe(element, key, fn) {
|
|
80
|
+
let byKey = this.listeners.get(element);
|
|
81
|
+
if (!byKey) {
|
|
82
|
+
byKey = new Map();
|
|
83
|
+
this.listeners.set(element, byKey);
|
|
84
|
+
}
|
|
85
|
+
let set = byKey.get(key);
|
|
86
|
+
if (!set) {
|
|
87
|
+
set = new Set();
|
|
88
|
+
byKey.set(key, set);
|
|
89
|
+
}
|
|
90
|
+
set.add(fn);
|
|
91
|
+
return () => set.delete(fn);
|
|
92
|
+
}
|
|
93
|
+
notify(element, key, value) {
|
|
94
|
+
const fns = this.listeners.get(element)?.get(key);
|
|
95
|
+
if (fns)
|
|
96
|
+
for (const fn of fns)
|
|
97
|
+
fn(value, key, element);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function sameValue(a, b) {
|
|
101
|
+
if (a.type !== b.type)
|
|
102
|
+
return false;
|
|
103
|
+
if (a.type === 'vector2' && b.type === 'vector2')
|
|
104
|
+
return a.x === b.x && a.y === b.y;
|
|
105
|
+
if (a.type === 'number' && b.type === 'number')
|
|
106
|
+
return a.value === b.value;
|
|
107
|
+
if (a.type === 'boolean' && b.type === 'boolean')
|
|
108
|
+
return a.value === b.value;
|
|
109
|
+
if (a.type === 'string' && b.type === 'string')
|
|
110
|
+
return a.value === b.value;
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* textBodies — sample a text element's *rendered* geometry into boundary bodies the field flows
|
|
3
|
+
* around and along (#257, first slice).
|
|
4
|
+
*
|
|
5
|
+
* The invisible-fields law holds: the field writes INTO type (words glow via `--d`), but matter
|
|
6
|
+
* never FORMS words. This helper goes the other direction — it turns text into a BOUNDARY the
|
|
7
|
+
* field acts against (`wall` bounces matter off the box, `shear` drags it along the line), never
|
|
8
|
+
* a morph target. The homepage's hand-rolled "prominent text is a body" curation is the `box`
|
|
9
|
+
* granularity of exactly this; `line` and `word` are the first real steps past the box
|
|
10
|
+
* approximation.
|
|
11
|
+
*
|
|
12
|
+
* Geometry source — honest about what the platform can measure today: `document.createRange()`
|
|
13
|
+
* over the element's text nodes + `Range.getClientRects()`, which yields the rendered line boxes
|
|
14
|
+
* (`line`) or per-word fragment boxes (`word`). This is BOX geometry, not glyph contours — true
|
|
15
|
+
* glyph-outline sampling (font path data / canvas rasterization) is the planned next slice of
|
|
16
|
+
* #257 and will slot in as a finer granularity behind the same API.
|
|
17
|
+
*
|
|
18
|
+
* Why spans: engine bodies are *elements* (`data-body` is the body contract; the scanner measures
|
|
19
|
+
* `getBoundingClientRect` per element). Emitting one absolutely-positioned, `aria-hidden`,
|
|
20
|
+
* `pointer-events:none` span per box gives the field per-line/per-word geometry through the
|
|
21
|
+
* existing contract — no new engine surface. Each span also declares the visual-binding pair
|
|
22
|
+
* (`data-field-visual-for` → the source element, role `representation`), so a
|
|
23
|
+
* `platform.visuals.scan()` binds and lints them like any authored representation.
|
|
24
|
+
*
|
|
25
|
+
* Lifecycle contract:
|
|
26
|
+
* - `annotate()` is idempotent — re-calling disposes the previous span set first, then re-measures.
|
|
27
|
+
* - The engine picks the spans up on its next scan: call `field.rescan()` (or re-run the platform
|
|
28
|
+
* scan) after `annotate()` and after disposing.
|
|
29
|
+
* - Resize/reflow honesty: the spans are a static snapshot in page coordinates. Callers re-call
|
|
30
|
+
* `annotate()` (and rescan) when the text reflows — wiring a ResizeObserver is deliberately NOT
|
|
31
|
+
* this slice's job and is noted as the follow-up.
|
|
32
|
+
* - Reduced motion is irrelevant here: this is geometry only; it animates nothing.
|
|
33
|
+
* - SSR-safe: no module-top DOM access; everything reaches the DOM through the passed element's
|
|
34
|
+
* `ownerDocument`.
|
|
35
|
+
*/
|
|
36
|
+
export type TextBodiesGranularity = 'box' | 'line' | 'word';
|
|
37
|
+
export interface TextBodiesOptions {
|
|
38
|
+
/**
|
|
39
|
+
* How finely to sample the rendered text:
|
|
40
|
+
* - `box` — the element's own bounding box (the homepage's current hand-rolled behavior, via API);
|
|
41
|
+
* - `line` — one box per rendered line box (`Range.selectNodeContents` + `getClientRects`);
|
|
42
|
+
* - `word` (default) — one box per word fragment, via a Range over each whitespace-delimited run.
|
|
43
|
+
*/
|
|
44
|
+
granularity?: TextBodiesGranularity;
|
|
45
|
+
/** The boundary force token the spans carry: `wall` (bounce off) or `shear` (flow along). Default `shear`. */
|
|
46
|
+
body?: 'wall' | 'shear';
|
|
47
|
+
/** `data-strength` written on each span. Default 1. */
|
|
48
|
+
strength?: number;
|
|
49
|
+
}
|
|
50
|
+
export interface TextBodiesHandle {
|
|
51
|
+
/** The boxes measured at creation time (viewport coordinates, zero-size fragments dropped). */
|
|
52
|
+
boxes: DOMRect[];
|
|
53
|
+
/**
|
|
54
|
+
* Create the boundary spans (re-measuring fresh boxes) and return a disposer that removes them.
|
|
55
|
+
* Idempotent: calling again first disposes the previous set.
|
|
56
|
+
*/
|
|
57
|
+
annotate(): () => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Sample `el`'s rendered text geometry into field boundary bodies.
|
|
61
|
+
*
|
|
62
|
+
* Returns the measured `boxes` and an `annotate()` that emits one boundary span per box —
|
|
63
|
+
* `data-body` wall/shear + `data-strength`, `data-range` hugged to the box (max dimension, so a
|
|
64
|
+
* word's influence stays near the word), `aria-hidden`, `pointer-events:none`, and the
|
|
65
|
+
* `data-field-visual-for`/`data-field-visual-role="representation"` declaration pointing back at
|
|
66
|
+
* `el`. Spans live in one absolutely-positioned container appended to `document.body` (page
|
|
67
|
+
* coordinates), so the source's own layout is never disturbed. The engine's scanner registers
|
|
68
|
+
* them on the next `rescan()`; the disposer removes the whole set.
|
|
69
|
+
*/
|
|
70
|
+
export declare function textBodies(el: HTMLElement, opts?: TextBodiesOptions): TextBodiesHandle;
|
|
71
|
+
//# sourceMappingURL=text-bodies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-bodies.d.ts","sourceRoot":"","sources":["../src/text-bodies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,8GAA8G;IAC9G,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB;;;OAGG;IACH,QAAQ,IAAI,MAAM,IAAI,CAAC;CACxB;AA2DD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,GAAE,iBAAsB,GAAG,gBAAgB,CA0D1F"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* textBodies — sample a text element's *rendered* geometry into boundary bodies the field flows
|
|
3
|
+
* around and along (#257, first slice).
|
|
4
|
+
*
|
|
5
|
+
* The invisible-fields law holds: the field writes INTO type (words glow via `--d`), but matter
|
|
6
|
+
* never FORMS words. This helper goes the other direction — it turns text into a BOUNDARY the
|
|
7
|
+
* field acts against (`wall` bounces matter off the box, `shear` drags it along the line), never
|
|
8
|
+
* a morph target. The homepage's hand-rolled "prominent text is a body" curation is the `box`
|
|
9
|
+
* granularity of exactly this; `line` and `word` are the first real steps past the box
|
|
10
|
+
* approximation.
|
|
11
|
+
*
|
|
12
|
+
* Geometry source — honest about what the platform can measure today: `document.createRange()`
|
|
13
|
+
* over the element's text nodes + `Range.getClientRects()`, which yields the rendered line boxes
|
|
14
|
+
* (`line`) or per-word fragment boxes (`word`). This is BOX geometry, not glyph contours — true
|
|
15
|
+
* glyph-outline sampling (font path data / canvas rasterization) is the planned next slice of
|
|
16
|
+
* #257 and will slot in as a finer granularity behind the same API.
|
|
17
|
+
*
|
|
18
|
+
* Why spans: engine bodies are *elements* (`data-body` is the body contract; the scanner measures
|
|
19
|
+
* `getBoundingClientRect` per element). Emitting one absolutely-positioned, `aria-hidden`,
|
|
20
|
+
* `pointer-events:none` span per box gives the field per-line/per-word geometry through the
|
|
21
|
+
* existing contract — no new engine surface. Each span also declares the visual-binding pair
|
|
22
|
+
* (`data-field-visual-for` → the source element, role `representation`), so a
|
|
23
|
+
* `platform.visuals.scan()` binds and lints them like any authored representation.
|
|
24
|
+
*
|
|
25
|
+
* Lifecycle contract:
|
|
26
|
+
* - `annotate()` is idempotent — re-calling disposes the previous span set first, then re-measures.
|
|
27
|
+
* - The engine picks the spans up on its next scan: call `field.rescan()` (or re-run the platform
|
|
28
|
+
* scan) after `annotate()` and after disposing.
|
|
29
|
+
* - Resize/reflow honesty: the spans are a static snapshot in page coordinates. Callers re-call
|
|
30
|
+
* `annotate()` (and rescan) when the text reflows — wiring a ResizeObserver is deliberately NOT
|
|
31
|
+
* this slice's job and is noted as the follow-up.
|
|
32
|
+
* - Reduced motion is irrelevant here: this is geometry only; it animates nothing.
|
|
33
|
+
* - SSR-safe: no module-top DOM access; everything reaches the DOM through the passed element's
|
|
34
|
+
* `ownerDocument`.
|
|
35
|
+
*/
|
|
36
|
+
/** module-scope id sequence for sources that lack an id (no DOM touched at module top). */
|
|
37
|
+
let idSeq = 0;
|
|
38
|
+
const TEXT_NODE = 3;
|
|
39
|
+
const ELEMENT_NODE = 1;
|
|
40
|
+
/** Depth-first text nodes under `el` (own implementation — keeps the fake-DOM test surface tiny). */
|
|
41
|
+
function textNodesUnder(el) {
|
|
42
|
+
const out = [];
|
|
43
|
+
const walk = (n) => {
|
|
44
|
+
for (const child of Array.from(n.childNodes)) {
|
|
45
|
+
if (child.nodeType === TEXT_NODE)
|
|
46
|
+
out.push(child);
|
|
47
|
+
else if (child.nodeType === ELEMENT_NODE)
|
|
48
|
+
walk(child);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
walk(el);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
const usable = (r) => r.width > 0 && r.height > 0;
|
|
55
|
+
/** Measure the boxes for one granularity (viewport coordinates, straight off the live layout). */
|
|
56
|
+
function measureBoxes(el, granularity) {
|
|
57
|
+
if (granularity === 'box') {
|
|
58
|
+
const r = el.getBoundingClientRect();
|
|
59
|
+
return usable(r) ? [r] : [];
|
|
60
|
+
}
|
|
61
|
+
const doc = el.ownerDocument;
|
|
62
|
+
const boxes = [];
|
|
63
|
+
if (granularity === 'line') {
|
|
64
|
+
const range = doc.createRange();
|
|
65
|
+
range.selectNodeContents(el);
|
|
66
|
+
for (const r of Array.from(range.getClientRects()))
|
|
67
|
+
if (usable(r))
|
|
68
|
+
boxes.push(r);
|
|
69
|
+
return boxes;
|
|
70
|
+
}
|
|
71
|
+
// word: one Range per whitespace-delimited run in each text node. A word that fragments
|
|
72
|
+
// (wraps, spans styling boundaries) contributes one box per rendered fragment.
|
|
73
|
+
for (const node of textNodesUnder(el)) {
|
|
74
|
+
const text = node.textContent ?? '';
|
|
75
|
+
const re = /\S+/g;
|
|
76
|
+
let m;
|
|
77
|
+
while ((m = re.exec(text)) !== null) {
|
|
78
|
+
const range = doc.createRange();
|
|
79
|
+
range.setStart(node, m.index);
|
|
80
|
+
range.setEnd(node, m.index + m[0].length);
|
|
81
|
+
for (const r of Array.from(range.getClientRects()))
|
|
82
|
+
if (usable(r))
|
|
83
|
+
boxes.push(r);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return boxes;
|
|
87
|
+
}
|
|
88
|
+
/** The visual-binding ref for the source: its id, assigned if missing (the resolver takes bare ids). */
|
|
89
|
+
function ensureRef(el) {
|
|
90
|
+
if (!el.id)
|
|
91
|
+
el.id = `field-text-source-${++idSeq}`;
|
|
92
|
+
return el.id;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Sample `el`'s rendered text geometry into field boundary bodies.
|
|
96
|
+
*
|
|
97
|
+
* Returns the measured `boxes` and an `annotate()` that emits one boundary span per box —
|
|
98
|
+
* `data-body` wall/shear + `data-strength`, `data-range` hugged to the box (max dimension, so a
|
|
99
|
+
* word's influence stays near the word), `aria-hidden`, `pointer-events:none`, and the
|
|
100
|
+
* `data-field-visual-for`/`data-field-visual-role="representation"` declaration pointing back at
|
|
101
|
+
* `el`. Spans live in one absolutely-positioned container appended to `document.body` (page
|
|
102
|
+
* coordinates), so the source's own layout is never disturbed. The engine's scanner registers
|
|
103
|
+
* them on the next `rescan()`; the disposer removes the whole set.
|
|
104
|
+
*/
|
|
105
|
+
export function textBodies(el, opts = {}) {
|
|
106
|
+
const granularity = opts.granularity ?? 'word';
|
|
107
|
+
const body = opts.body ?? 'shear';
|
|
108
|
+
const strength = opts.strength ?? 1;
|
|
109
|
+
let disposeCurrent = null;
|
|
110
|
+
const annotate = () => {
|
|
111
|
+
disposeCurrent?.(); // idempotent: one live span set per handle
|
|
112
|
+
const doc = el.ownerDocument;
|
|
113
|
+
const view = doc.defaultView;
|
|
114
|
+
const sx = view?.scrollX ?? 0;
|
|
115
|
+
const sy = view?.scrollY ?? 0;
|
|
116
|
+
const ref = ensureRef(el);
|
|
117
|
+
const container = doc.createElement('div');
|
|
118
|
+
container.setAttribute('data-text-bodies', '');
|
|
119
|
+
container.setAttribute('aria-hidden', 'true');
|
|
120
|
+
const cs = container.style;
|
|
121
|
+
cs.setProperty('position', 'absolute');
|
|
122
|
+
cs.setProperty('left', '0');
|
|
123
|
+
cs.setProperty('top', '0');
|
|
124
|
+
cs.setProperty('width', '0');
|
|
125
|
+
cs.setProperty('height', '0');
|
|
126
|
+
cs.setProperty('overflow', 'visible');
|
|
127
|
+
cs.setProperty('pointer-events', 'none');
|
|
128
|
+
for (const box of measureBoxes(el, granularity)) {
|
|
129
|
+
const span = doc.createElement('span');
|
|
130
|
+
span.setAttribute('data-body', body);
|
|
131
|
+
span.setAttribute('data-strength', String(strength));
|
|
132
|
+
// influence hugs the box: wall collides with the box itself; shear's gradient reaches
|
|
133
|
+
// one box-max-dimension out, so a word steers matter near the word, not across the page.
|
|
134
|
+
span.setAttribute('data-range', String(Math.ceil(Math.max(box.width, box.height))));
|
|
135
|
+
span.setAttribute('data-field-visual-for', ref);
|
|
136
|
+
span.setAttribute('data-field-visual-role', 'representation');
|
|
137
|
+
span.setAttribute('aria-hidden', 'true');
|
|
138
|
+
const ss = span.style;
|
|
139
|
+
ss.setProperty('position', 'absolute');
|
|
140
|
+
ss.setProperty('display', 'block');
|
|
141
|
+
ss.setProperty('left', `${box.left + sx}px`);
|
|
142
|
+
ss.setProperty('top', `${box.top + sy}px`);
|
|
143
|
+
ss.setProperty('width', `${box.width}px`);
|
|
144
|
+
ss.setProperty('height', `${box.height}px`);
|
|
145
|
+
ss.setProperty('pointer-events', 'none');
|
|
146
|
+
container.appendChild(span);
|
|
147
|
+
}
|
|
148
|
+
doc.body.appendChild(container);
|
|
149
|
+
const dispose = () => {
|
|
150
|
+
container.parentNode?.removeChild(container);
|
|
151
|
+
if (disposeCurrent === dispose)
|
|
152
|
+
disposeCurrent = null;
|
|
153
|
+
};
|
|
154
|
+
disposeCurrent = dispose;
|
|
155
|
+
return dispose;
|
|
156
|
+
};
|
|
157
|
+
return { boxes: measureBoxes(el, granularity), annotate };
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=text-bodies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-bodies.js","sourceRoot":"","sources":["../src/text-bodies.ts"],"names":[],"mappings":"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"}
|