@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,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OverlayRegistry — visual relationship lines, field lines, callouts, and debug layers, without
|
|
3
|
+
* corrupting semantic DOM. Overlays are *render layers*: they read from the relationship + measurement
|
|
4
|
+
* registries and produce geometry to draw. They do not own relationships and never mutate physics.
|
|
5
|
+
*/
|
|
6
|
+
import type { MeasurementRegistry } from './measurement.ts';
|
|
7
|
+
import type { CoordinateSpace } from './types.ts';
|
|
8
|
+
export type OverlayType = 'relationship' | 'field-line' | 'debug' | 'callout' | 'attention' | 'heatmap';
|
|
9
|
+
export type RenderTarget = 'css' | 'svg' | 'canvas' | 'dom';
|
|
10
|
+
export interface FieldOverlay {
|
|
11
|
+
id: string;
|
|
12
|
+
type: OverlayType;
|
|
13
|
+
/** the elements this overlay connects/annotates (e.g. [from, to] for a relationship line). */
|
|
14
|
+
sourceElements: Element[];
|
|
15
|
+
coordinateSpace: CoordinateSpace;
|
|
16
|
+
renderTarget: RenderTarget;
|
|
17
|
+
interactive: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** A resolved segment to draw, in the overlay's coordinate space. */
|
|
20
|
+
export interface OverlaySegment {
|
|
21
|
+
overlay: FieldOverlay;
|
|
22
|
+
from: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
|
26
|
+
to: {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare class OverlayRegistry {
|
|
32
|
+
private readonly overlays;
|
|
33
|
+
private seq;
|
|
34
|
+
/** Register an overlay (a render record — owns no relationship, mutates no physics). */
|
|
35
|
+
add(o: Omit<FieldOverlay, 'id' | 'coordinateSpace' | 'renderTarget' | 'interactive'> & Partial<FieldOverlay>): FieldOverlay;
|
|
36
|
+
remove(id: string): void;
|
|
37
|
+
/** Drop overlays referencing a detached source element — they can never resolve again, and their
|
|
38
|
+
* strong Element refs would otherwise pin removed subtrees alive. Run on a cadence by a frame loop. */
|
|
39
|
+
prune(): void;
|
|
40
|
+
all(): FieldOverlay[];
|
|
41
|
+
/**
|
|
42
|
+
* Resolve connection overlays (relationship/callout) into segments between the centres of their
|
|
43
|
+
* two source elements, using a measurement snapshot. Overlays whose endpoints aren't measured are
|
|
44
|
+
* skipped. Pure given the measurement registry — no drawing here.
|
|
45
|
+
*/
|
|
46
|
+
resolveSegments(measure: MeasurementRegistry): OverlaySegment[];
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=overlays.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlays.d.ts","sourceRoot":"","sources":["../src/overlays.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AACxG,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,8FAA8F;IAC9F,cAAc,EAAE,OAAO,EAAE,CAAC;IAC1B,eAAe,EAAE,eAAe,CAAC;IACjC,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,EAAE,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,GAAG,CAAK;IAEhB,wFAAwF;IACxF,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,iBAAiB,GAAG,cAAc,GAAG,aAAa,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY;IAY3H,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIxB;4GACwG;IACxG,KAAK,IAAI,IAAI;IAKb,GAAG,IAAI,YAAY,EAAE;IAIrB;;;;OAIG;IACH,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,cAAc,EAAE;CAWhE"}
|
package/dist/overlays.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export class OverlayRegistry {
|
|
2
|
+
overlays = new Map();
|
|
3
|
+
seq = 0;
|
|
4
|
+
/** Register an overlay (a render record — owns no relationship, mutates no physics). */
|
|
5
|
+
add(o) {
|
|
6
|
+
const full = {
|
|
7
|
+
coordinateSpace: 'field-root',
|
|
8
|
+
renderTarget: 'svg',
|
|
9
|
+
interactive: false,
|
|
10
|
+
...o,
|
|
11
|
+
id: o.id ?? `overlay-${this.seq++}`,
|
|
12
|
+
};
|
|
13
|
+
this.overlays.set(full.id, full);
|
|
14
|
+
return full;
|
|
15
|
+
}
|
|
16
|
+
remove(id) {
|
|
17
|
+
this.overlays.delete(id);
|
|
18
|
+
}
|
|
19
|
+
/** Drop overlays referencing a detached source element — they can never resolve again, and their
|
|
20
|
+
* strong Element refs would otherwise pin removed subtrees alive. Run on a cadence by a frame loop. */
|
|
21
|
+
prune() {
|
|
22
|
+
for (const [id, o] of this.overlays)
|
|
23
|
+
if (o.sourceElements.some((el) => el.isConnected === false))
|
|
24
|
+
this.overlays.delete(id);
|
|
25
|
+
}
|
|
26
|
+
all() {
|
|
27
|
+
return [...this.overlays.values()];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve connection overlays (relationship/callout) into segments between the centres of their
|
|
31
|
+
* two source elements, using a measurement snapshot. Overlays whose endpoints aren't measured are
|
|
32
|
+
* skipped. Pure given the measurement registry — no drawing here.
|
|
33
|
+
*/
|
|
34
|
+
resolveSegments(measure) {
|
|
35
|
+
const segs = [];
|
|
36
|
+
for (const o of this.overlays.values()) {
|
|
37
|
+
if (o.sourceElements.length < 2)
|
|
38
|
+
continue;
|
|
39
|
+
const a = measure.for(o.sourceElements[0]);
|
|
40
|
+
const b = measure.for(o.sourceElements[1]);
|
|
41
|
+
if (!a || !b)
|
|
42
|
+
continue;
|
|
43
|
+
segs.push({ overlay: o, from: { x: a.rect.cx, y: a.rect.cy }, to: { x: b.rect.cx, y: b.rect.cy } });
|
|
44
|
+
}
|
|
45
|
+
return segs;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=overlays.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlays.js","sourceRoot":"","sources":["../src/overlays.ts"],"names":[],"mappings":"AA4BA,MAAM,OAAO,eAAe;IACT,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;IACpD,GAAG,GAAG,CAAC,CAAC;IAEhB,wFAAwF;IACxF,GAAG,CAAC,CAAwG;QAC1G,MAAM,IAAI,GAAiB;YACzB,eAAe,EAAE,YAAY;YAC7B,YAAY,EAAE,KAAK;YACnB,WAAW,EAAE,KAAK;YAClB,GAAG,CAAC;YACJ,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,WAAW,IAAI,CAAC,GAAG,EAAE,EAAE;SACpC,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,CAAC,EAAU;QACf,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED;4GACwG;IACxG,KAAK;QACH,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ;YACjC,IAAI,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,KAAK,KAAK,CAAC;gBAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,GAAG;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,OAA4B;QAC1C,MAAM,IAAI,GAAqB,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YACvC,IAAI,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YAC1C,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAE,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAE,CAAC,CAAC;YAC5C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;gBAAE,SAAS;YACvB,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACtG,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
package/dist/perf.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldPerf — the frame-duration split of the performance-budget story: pure timing math
|
|
3
|
+
* lifted from the site's DataConsole prototype (which had been running these exact rules in
|
|
4
|
+
* production). Core's `inspectBudget`/`DEFAULT_BUDGET` judge a *configuration*; the
|
|
5
|
+
* QualityGovernor turns sustained overruns into a degradation *tier*; FieldPerf is the
|
|
6
|
+
* *measurement* — feed it rAF timestamps, read back fps / budget / percentiles / dropped.
|
|
7
|
+
*
|
|
8
|
+
* The lifted rules (byte-compatible with the DataConsole, so its conversion is
|
|
9
|
+
* behavior-identical):
|
|
10
|
+
* - deltas: consecutive `feed(ts)` differences, kept in a rolling window (default 180).
|
|
11
|
+
* - DISCONTINUITY: a gap > 500 ms (tab switch, system sleep) is ignored entirely — it
|
|
12
|
+
* enters neither the window, the budget seed, `frames`, nor `dropped`; timing simply
|
|
13
|
+
* resumes from the new timestamp (the QualityGovernor's "skip discontinuity frames"
|
|
14
|
+
* doctrine, given a concrete constant here).
|
|
15
|
+
* - percentile `pct(arr, p)`: nearest-rank-by-floor on the ascending sort —
|
|
16
|
+
* `sorted[Math.floor((p / 100) * (sorted.length - 1))]`; `null` when empty.
|
|
17
|
+
* - BUDGET DETECTION: the median (`pct(seed, 50)`) of the first `budgetSeed` (default 30)
|
|
18
|
+
* clean deltas — "clean" = past the discontinuity filter. Until the seed fills,
|
|
19
|
+
* `budgetMs` is `null` and nothing counts as dropped.
|
|
20
|
+
* - DROPPED: once the budget exists, a delta strictly greater than `budget × 1.5`
|
|
21
|
+
* increments `dropped` (cumulative, not windowed). The seed-completing delta is itself
|
|
22
|
+
* checked in the same feed — the DataConsole's exact ordering.
|
|
23
|
+
* - fps: `Math.round(1000 / medianMs)` over the current window; `null` while empty.
|
|
24
|
+
* - `frames`: total clean deltas ever counted (not capped by the window).
|
|
25
|
+
*
|
|
26
|
+
* Pure and host-driven by design: NO `requestAnimationFrame` of its own (callers feed their
|
|
27
|
+
* loop's timestamps) and NO PerformanceObserver — the LoAF / long-task split stays page-side
|
|
28
|
+
* in this slice (the DataConsole keeps its own observer; a platform LoAF lane is future work).
|
|
29
|
+
*/
|
|
30
|
+
export interface FieldPerfOptions {
|
|
31
|
+
/** rolling delta-window size (default 180 — the DataConsole's ~3 s at 60 Hz). Min 1. */
|
|
32
|
+
window?: number;
|
|
33
|
+
/** number of clean deltas used to detect the frame budget (default 30); the budget is their median. Min 1. */
|
|
34
|
+
budgetSeed?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface FieldPerfSnapshot {
|
|
37
|
+
/** `Math.round(1000 / medianMs)`; `null` until a delta exists. */
|
|
38
|
+
fps: number | null;
|
|
39
|
+
/** the detected frame budget (median of the seed deltas); `null` until the seed fills. */
|
|
40
|
+
budgetMs: number | null;
|
|
41
|
+
/** windowed median delta (`pct(deltas, 50)`); `null` while empty. */
|
|
42
|
+
medianMs: number | null;
|
|
43
|
+
/** windowed 95th-percentile delta; `null` while empty. */
|
|
44
|
+
p95Ms: number | null;
|
|
45
|
+
/** windowed 99th-percentile delta; `null` while empty. */
|
|
46
|
+
p99Ms: number | null;
|
|
47
|
+
/** cumulative deltas > budget × 1.5 since creation/reset (0 until the budget is detected). */
|
|
48
|
+
dropped: number;
|
|
49
|
+
/** total clean deltas counted since creation/reset (not capped by the window). */
|
|
50
|
+
frames: number;
|
|
51
|
+
}
|
|
52
|
+
export interface FieldPerf {
|
|
53
|
+
/** Feed one rAF timestamp (ms). Deltas are computed internally; gaps > 500 ms are ignored (discontinuity). */
|
|
54
|
+
feed(frameTs: number): void;
|
|
55
|
+
/** Read the current numbers (pure — no layout, no globals). */
|
|
56
|
+
snapshot(): FieldPerfSnapshot;
|
|
57
|
+
/** Forget everything: window, seed, budget, counters, and the last timestamp. */
|
|
58
|
+
reset(): void;
|
|
59
|
+
}
|
|
60
|
+
/** Create a frame-duration meter. Feed it rAF timestamps; read `snapshot()` whenever. */
|
|
61
|
+
export declare function createFieldPerf(opts?: FieldPerfOptions): FieldPerf;
|
|
62
|
+
//# sourceMappingURL=perf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf.d.ts","sourceRoot":"","sources":["../src/perf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8GAA8G;IAC9G,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,8GAA8G;IAC9G,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,IAAI,iBAAiB,CAAC;IAC9B,iFAAiF;IACjF,KAAK,IAAI,IAAI,CAAC;CACf;AAYD,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,IAAI,GAAE,gBAAqB,GAAG,SAAS,CAyDtE"}
|
package/dist/perf.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldPerf — the frame-duration split of the performance-budget story: pure timing math
|
|
3
|
+
* lifted from the site's DataConsole prototype (which had been running these exact rules in
|
|
4
|
+
* production). Core's `inspectBudget`/`DEFAULT_BUDGET` judge a *configuration*; the
|
|
5
|
+
* QualityGovernor turns sustained overruns into a degradation *tier*; FieldPerf is the
|
|
6
|
+
* *measurement* — feed it rAF timestamps, read back fps / budget / percentiles / dropped.
|
|
7
|
+
*
|
|
8
|
+
* The lifted rules (byte-compatible with the DataConsole, so its conversion is
|
|
9
|
+
* behavior-identical):
|
|
10
|
+
* - deltas: consecutive `feed(ts)` differences, kept in a rolling window (default 180).
|
|
11
|
+
* - DISCONTINUITY: a gap > 500 ms (tab switch, system sleep) is ignored entirely — it
|
|
12
|
+
* enters neither the window, the budget seed, `frames`, nor `dropped`; timing simply
|
|
13
|
+
* resumes from the new timestamp (the QualityGovernor's "skip discontinuity frames"
|
|
14
|
+
* doctrine, given a concrete constant here).
|
|
15
|
+
* - percentile `pct(arr, p)`: nearest-rank-by-floor on the ascending sort —
|
|
16
|
+
* `sorted[Math.floor((p / 100) * (sorted.length - 1))]`; `null` when empty.
|
|
17
|
+
* - BUDGET DETECTION: the median (`pct(seed, 50)`) of the first `budgetSeed` (default 30)
|
|
18
|
+
* clean deltas — "clean" = past the discontinuity filter. Until the seed fills,
|
|
19
|
+
* `budgetMs` is `null` and nothing counts as dropped.
|
|
20
|
+
* - DROPPED: once the budget exists, a delta strictly greater than `budget × 1.5`
|
|
21
|
+
* increments `dropped` (cumulative, not windowed). The seed-completing delta is itself
|
|
22
|
+
* checked in the same feed — the DataConsole's exact ordering.
|
|
23
|
+
* - fps: `Math.round(1000 / medianMs)` over the current window; `null` while empty.
|
|
24
|
+
* - `frames`: total clean deltas ever counted (not capped by the window).
|
|
25
|
+
*
|
|
26
|
+
* Pure and host-driven by design: NO `requestAnimationFrame` of its own (callers feed their
|
|
27
|
+
* loop's timestamps) and NO PerformanceObserver — the LoAF / long-task split stays page-side
|
|
28
|
+
* in this slice (the DataConsole keeps its own observer; a platform LoAF lane is future work).
|
|
29
|
+
*/
|
|
30
|
+
/** A gap above this (ms) is a discontinuity — tab switch / sleep — and is skipped, not measured. */
|
|
31
|
+
const DISCONTINUITY_MS = 500;
|
|
32
|
+
/** Nearest-rank-by-floor percentile on a copy (the DataConsole's `pct`). `null` when empty. */
|
|
33
|
+
function pct(arr, p) {
|
|
34
|
+
if (!arr.length)
|
|
35
|
+
return null;
|
|
36
|
+
const s = [...arr].sort((a, b) => a - b);
|
|
37
|
+
return s[Math.floor((p / 100) * (s.length - 1))];
|
|
38
|
+
}
|
|
39
|
+
/** Create a frame-duration meter. Feed it rAF timestamps; read `snapshot()` whenever. */
|
|
40
|
+
export function createFieldPerf(opts = {}) {
|
|
41
|
+
const windowSize = Math.max(1, Math.floor(opts.window ?? 180));
|
|
42
|
+
const seedSize = Math.max(1, Math.floor(opts.budgetSeed ?? 30));
|
|
43
|
+
let lastTs = null;
|
|
44
|
+
let deltas = [];
|
|
45
|
+
let seed = [];
|
|
46
|
+
let budget = null;
|
|
47
|
+
let dropped = 0;
|
|
48
|
+
let frames = 0;
|
|
49
|
+
const feed = (frameTs) => {
|
|
50
|
+
if (lastTs === null) {
|
|
51
|
+
lastTs = frameTs;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const d = frameTs - lastTs;
|
|
55
|
+
lastTs = frameTs;
|
|
56
|
+
if (d > DISCONTINUITY_MS)
|
|
57
|
+
return; // discontinuity: resume from the new timestamp, count nothing
|
|
58
|
+
deltas.push(d);
|
|
59
|
+
if (deltas.length > windowSize)
|
|
60
|
+
deltas.shift();
|
|
61
|
+
frames++;
|
|
62
|
+
// detect the budget from the first `seedSize` clean deltas (median)
|
|
63
|
+
if (budget === null && seed.length < seedSize) {
|
|
64
|
+
seed.push(d);
|
|
65
|
+
if (seed.length === seedSize)
|
|
66
|
+
budget = pct(seed, 50);
|
|
67
|
+
}
|
|
68
|
+
// the seed-completing delta is checked too — the DataConsole's exact ordering
|
|
69
|
+
if (budget !== null && d > budget * 1.5)
|
|
70
|
+
dropped++;
|
|
71
|
+
};
|
|
72
|
+
const snapshot = () => {
|
|
73
|
+
const medianMs = pct(deltas, 50);
|
|
74
|
+
return {
|
|
75
|
+
fps: medianMs ? Math.round(1000 / medianMs) : null,
|
|
76
|
+
budgetMs: budget,
|
|
77
|
+
medianMs,
|
|
78
|
+
p95Ms: pct(deltas, 95),
|
|
79
|
+
p99Ms: pct(deltas, 99),
|
|
80
|
+
dropped,
|
|
81
|
+
frames,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
const reset = () => {
|
|
85
|
+
lastTs = null;
|
|
86
|
+
deltas = [];
|
|
87
|
+
seed = [];
|
|
88
|
+
budget = null;
|
|
89
|
+
dropped = 0;
|
|
90
|
+
frames = 0;
|
|
91
|
+
};
|
|
92
|
+
return { feed, snapshot, reset };
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=perf.js.map
|
package/dist/perf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf.js","sourceRoot":"","sources":["../src/perf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createFieldPlatform — the coordinator that binds the platform registries to one shared
|
|
3
|
+
* FrameScheduler. The native participation surface Fundamental wishes the browser exposed.
|
|
4
|
+
*
|
|
5
|
+
* The scheduler owns loop discipline: every frame walks discover → read → compute → state → write →
|
|
6
|
+
* render in order (see schedule.ts). By default the platform wires the two phases the registries own
|
|
7
|
+
* outright — `read` (MeasurementRegistry) and `write` (FeedbackRegistry) — and installs the read
|
|
8
|
+
* guard so an off-phase measurement is caught. Callers add `discover`/`compute`/`state`/`render`
|
|
9
|
+
* handlers with `platform.on(phase, fn)`; relationships, visual bindings, and overlays plug into
|
|
10
|
+
* those. The shape is additive — adding handlers never reorders the core read→write spine.
|
|
11
|
+
*/
|
|
12
|
+
import { MeasurementRegistry } from './measurement.ts';
|
|
13
|
+
import { StateRegistry } from './state.ts';
|
|
14
|
+
import { FeedbackRegistry } from './feedback.ts';
|
|
15
|
+
import { RelationshipRegistry } from './relationships.ts';
|
|
16
|
+
import { VisualBindingRegistry } from './visual-bindings.ts';
|
|
17
|
+
import { OverlayRegistry } from './overlays.ts';
|
|
18
|
+
import { FrameScheduler } from './schedule.ts';
|
|
19
|
+
import type { Phase, PhaseHandler, FrameReport, SchedulerOptions } from './schedule.ts';
|
|
20
|
+
import type { Viewport } from './types.ts';
|
|
21
|
+
export interface FieldPlatform {
|
|
22
|
+
/** the platform root (a `<field-root>`, an article, or the document element). */
|
|
23
|
+
root: Element;
|
|
24
|
+
measure: MeasurementRegistry;
|
|
25
|
+
state: StateRegistry;
|
|
26
|
+
feedback: FeedbackRegistry;
|
|
27
|
+
relationships: RelationshipRegistry;
|
|
28
|
+
visuals: VisualBindingRegistry;
|
|
29
|
+
overlays: OverlayRegistry;
|
|
30
|
+
/** the shared frame scheduler driving the six-phase loop. */
|
|
31
|
+
scheduler: FrameScheduler;
|
|
32
|
+
/** register a phase handler (discover/compute/state/render are open for callers). Returns unsubscribe. */
|
|
33
|
+
on(phase: Phase, handler: PhaseHandler): () => void;
|
|
34
|
+
/** run one full six-phase frame; returns the per-frame report (phases run + any violations). */
|
|
35
|
+
tick(now?: number, viewport?: Viewport): FrameReport;
|
|
36
|
+
}
|
|
37
|
+
export interface PlatformOptions extends SchedulerOptions {
|
|
38
|
+
}
|
|
39
|
+
export declare function createFieldPlatform(root: Element, opts?: PlatformOptions): FieldPlatform;
|
|
40
|
+
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACxF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,mBAAmB,CAAC;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,aAAa,EAAE,oBAAoB,CAAC;IACpC,OAAO,EAAE,qBAAqB,CAAC;IAC/B,QAAQ,EAAE,eAAe,CAAC;IAC1B,6DAA6D;IAC7D,SAAS,EAAE,cAAc,CAAC;IAC1B,0GAA0G;IAC1G,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI,CAAC;IACpD,gGAAgG;IAChG,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;CACtD;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;CAAG;AAE5D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,GAAE,eAAoB,GAAG,aAAa,CA8C5F"}
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createFieldPlatform — the coordinator that binds the platform registries to one shared
|
|
3
|
+
* FrameScheduler. The native participation surface Fundamental wishes the browser exposed.
|
|
4
|
+
*
|
|
5
|
+
* The scheduler owns loop discipline: every frame walks discover → read → compute → state → write →
|
|
6
|
+
* render in order (see schedule.ts). By default the platform wires the two phases the registries own
|
|
7
|
+
* outright — `read` (MeasurementRegistry) and `write` (FeedbackRegistry) — and installs the read
|
|
8
|
+
* guard so an off-phase measurement is caught. Callers add `discover`/`compute`/`state`/`render`
|
|
9
|
+
* handlers with `platform.on(phase, fn)`; relationships, visual bindings, and overlays plug into
|
|
10
|
+
* those. The shape is additive — adding handlers never reorders the core read→write spine.
|
|
11
|
+
*/
|
|
12
|
+
import { MeasurementRegistry } from "./measurement.js";
|
|
13
|
+
import { StateRegistry } from "./state.js";
|
|
14
|
+
import { FeedbackRegistry } from "./feedback.js";
|
|
15
|
+
import { RelationshipRegistry } from "./relationships.js";
|
|
16
|
+
import { VisualBindingRegistry } from "./visual-bindings.js";
|
|
17
|
+
import { OverlayRegistry } from "./overlays.js";
|
|
18
|
+
import { FrameScheduler } from "./schedule.js";
|
|
19
|
+
export function createFieldPlatform(root, opts = {}) {
|
|
20
|
+
const measure = new MeasurementRegistry();
|
|
21
|
+
const state = new StateRegistry();
|
|
22
|
+
const feedback = new FeedbackRegistry();
|
|
23
|
+
const relationships = new RelationshipRegistry();
|
|
24
|
+
const visuals = new VisualBindingRegistry();
|
|
25
|
+
const overlays = new OverlayRegistry();
|
|
26
|
+
const scheduler = new FrameScheduler({ strict: opts.strict ?? false });
|
|
27
|
+
// read-phase discipline: measurement consults the scheduler before reading layout.
|
|
28
|
+
measure.setPhaseGuard(scheduler.readGuard());
|
|
29
|
+
// Body Matter Interaction, Bound Visual tier: source→visual state mirroring is the platform
|
|
30
|
+
// default — a scanned representation/measurement visual receives its source's feedback channels
|
|
31
|
+
// (--d / --load / the metrics) without the author wiring anything. Change-gated; see
|
|
32
|
+
// visual-bindings.ts MIRRORED_CHANNELS.
|
|
33
|
+
visuals.setMirroring(true);
|
|
34
|
+
// the two phases the registries own outright. Everything else is opt-in via platform.on(...).
|
|
35
|
+
scheduler.on('read', (ctx) => measure.measure(ctx.now, ctx.viewport));
|
|
36
|
+
scheduler.on('write', (ctx) => feedback.flush(state, ctx.now));
|
|
37
|
+
// staleness sweep: the StateRegistry and OverlayRegistry both hold strong Element refs with no
|
|
38
|
+
// natural prune moment — the feedback sink writes --lit/--d/--load onto every body every frame,
|
|
39
|
+
// and overlays pin their source elements — yet nothing deletes an entry when its element leaves
|
|
40
|
+
// the DOM. Run their prune on a low cadence (every 120th frame ≈ 2s) to release detached
|
|
41
|
+
// elements for GC. Iterating an empty registry is free, so the sweep is cheap when idle.
|
|
42
|
+
scheduler.on('write', (ctx) => {
|
|
43
|
+
if (ctx.frame % 120 === 0) {
|
|
44
|
+
state.prune();
|
|
45
|
+
overlays.prune();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
root,
|
|
50
|
+
measure,
|
|
51
|
+
state,
|
|
52
|
+
feedback,
|
|
53
|
+
relationships,
|
|
54
|
+
visuals,
|
|
55
|
+
overlays,
|
|
56
|
+
scheduler,
|
|
57
|
+
on: (phase, handler) => scheduler.on(phase, handler),
|
|
58
|
+
tick: (now = 0, viewport) => scheduler.runFrame(now, viewport),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAuB/C,MAAM,UAAU,mBAAmB,CAAC,IAAa,EAAE,OAAwB,EAAE;IAC3E,MAAM,OAAO,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,aAAa,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;IACxC,MAAM,aAAa,GAAG,IAAI,oBAAoB,EAAE,CAAC;IACjD,MAAM,OAAO,GAAG,IAAI,qBAAqB,EAAE,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,cAAc,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC,CAAC;IAEvE,mFAAmF;IACnF,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;IAE7C,4FAA4F;IAC5F,gGAAgG;IAChG,qFAAqF;IACrF,wCAAwC;IACxC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAE3B,8FAA8F;IAC9F,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtE,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAE/D,+FAA+F;IAC/F,gGAAgG;IAChG,gGAAgG;IAChG,yFAAyF;IACzF,yFAAyF;IACzF,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC5B,IAAI,GAAG,CAAC,KAAK,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC;YAC1B,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,IAAI;QACJ,OAAO;QACP,KAAK;QACL,QAAQ;QACR,aAAa;QACb,OAAO;QACP,QAAQ;QACR,SAAS;QACT,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC;QACpD,IAAI,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,QAAmB,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;KAC1E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RelationshipRegistry — the DOM is a tree, but interfaces are graphs. This normalizes the
|
|
3
|
+
* relationships HTML/ARIA already express (`a[href#id]`, `label[for]`, `aria-controls` /
|
|
4
|
+
* `-describedby` / `-labelledby` / `-flowto`, and `data-field-relation`/`-target`) into ONE typed
|
|
5
|
+
* relationship graph, then lets authors add expressive ones on top. Native semantics are respected
|
|
6
|
+
* first; Fundamental does not invent a parallel graph for links the platform already declares.
|
|
7
|
+
*
|
|
8
|
+
* Output maps to core's `RelationshipAgent` so the field engine treats relationships as agents.
|
|
9
|
+
*/
|
|
10
|
+
import type { RelationshipAgent } from '@fundamental-engine/core';
|
|
11
|
+
export type RelationshipSource = 'html' | 'aria' | 'data' | 'recipe' | 'runtime';
|
|
12
|
+
export type RelationshipDirection = 'from-to' | 'to-from' | 'bidirectional';
|
|
13
|
+
export interface FieldRelationship {
|
|
14
|
+
id: string;
|
|
15
|
+
from: Element;
|
|
16
|
+
to: Element;
|
|
17
|
+
type: string;
|
|
18
|
+
strength: number;
|
|
19
|
+
direction: RelationshipDirection;
|
|
20
|
+
confidence?: number;
|
|
21
|
+
source: RelationshipSource;
|
|
22
|
+
active: boolean;
|
|
23
|
+
memory: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A relationship an element DECLARES whose target id-ref does not resolve to an element. Tracked so
|
|
27
|
+
* resolution is real (a declared edge that points at nothing counts toward the total but not the
|
|
28
|
+
* resolved set) and so inspection can name the missing endpoint — rather than silently dropping it.
|
|
29
|
+
*/
|
|
30
|
+
export interface UnresolvedRelationship {
|
|
31
|
+
from: Element;
|
|
32
|
+
type: string;
|
|
33
|
+
/** the id-ref as authored (e.g. `#ghost` or `ghost`). */
|
|
34
|
+
target: string;
|
|
35
|
+
source: RelationshipSource;
|
|
36
|
+
}
|
|
37
|
+
/** Resolve an id-ref to an element. */
|
|
38
|
+
export type Resolver = (id: string) => Element | null;
|
|
39
|
+
export interface ScanResult {
|
|
40
|
+
resolved: FieldRelationship[];
|
|
41
|
+
unresolved: UnresolvedRelationship[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Scan the relationships a single element declares, partitioning them into RESOLVED edges (both
|
|
45
|
+
* endpoints known) and UNRESOLVED declarations (an id-ref that points at no element). Pure given a
|
|
46
|
+
* `resolve` for id-refs, so it is testable without a document. Native relationships first;
|
|
47
|
+
* `data-field-relation` last.
|
|
48
|
+
*/
|
|
49
|
+
export declare function scanRelationships(el: Element, resolve: Resolver): ScanResult;
|
|
50
|
+
/** The RESOLVED relationships an element declares (back-compat wrapper over {@link scanRelationships}). */
|
|
51
|
+
export declare function relationshipsFromElement(el: Element, resolve: Resolver): FieldRelationship[];
|
|
52
|
+
/** The DECLARED-but-UNRESOLVED relationships an element points at (targets that resolve to nothing). */
|
|
53
|
+
export declare function unresolvedRelationshipsFromElement(el: Element, resolve: Resolver): UnresolvedRelationship[];
|
|
54
|
+
export declare class RelationshipRegistry {
|
|
55
|
+
private readonly rels;
|
|
56
|
+
private readonly unresolvedRels;
|
|
57
|
+
/**
|
|
58
|
+
* Scan a root for native + declared relationships. Each call REPLACES the unresolved set (so
|
|
59
|
+
* previously-missing targets that have since mounted resolve on the next pass for free) and prunes
|
|
60
|
+
* resolved edges whose endpoints are no longer connected (elements removed from the DOM).
|
|
61
|
+
* Resolved edges accumulate across passes — only disconnected ones are dropped.
|
|
62
|
+
*/
|
|
63
|
+
discover(root: ParentNode, resolve?: Resolver): void;
|
|
64
|
+
/**
|
|
65
|
+
* Drop all resolved and unresolved edges that touch `element` (as either endpoint). Use when an
|
|
66
|
+
* element is removed and you want immediate reclamation ahead of the next discover() pass.
|
|
67
|
+
*/
|
|
68
|
+
unregister(element: Element): void;
|
|
69
|
+
/** Add an expressive relationship by hand (source defaults to `runtime`). */
|
|
70
|
+
add(r: Omit<FieldRelationship, 'id' | 'direction' | 'active' | 'memory'> & Partial<FieldRelationship>): FieldRelationship;
|
|
71
|
+
all(): FieldRelationship[];
|
|
72
|
+
get size(): number;
|
|
73
|
+
/** Declared relationships whose target id-ref resolved to no element. */
|
|
74
|
+
unresolvedAll(): UnresolvedRelationship[];
|
|
75
|
+
get unresolvedSize(): number;
|
|
76
|
+
/** Map the graph onto core `RelationshipAgent`s (endpoints keyed by element id). */
|
|
77
|
+
toAgents(): RelationshipAgent[];
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=relationships.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../src/relationships.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AACjF,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,eAAe,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,qBAAqB,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,uCAAuC;AACvC,MAAM,MAAM,QAAQ,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;AAuBtD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,UAAU,EAAE,sBAAsB,EAAE,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,GAAG,UAAU,CAmC5E;AAED,2GAA2G;AAC3G,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,GAAG,iBAAiB,EAAE,CAE5F;AAED,wGAAwG;AACxG,wBAAgB,kCAAkC,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,GAAG,sBAAsB,EAAE,CAE3G;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwC;IAC7D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6C;IAE5E;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,QAAQ,GAAG,IAAI;IAqBpD;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IASlC,6EAA6E;IAC7E,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB;IAYzH,GAAG,IAAI,iBAAiB,EAAE;IAI1B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,yEAAyE;IACzE,aAAa,IAAI,sBAAsB,EAAE;IAIzC,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,oFAAoF;IACpF,QAAQ,IAAI,iBAAiB,EAAE;CAYhC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Id-less elements need a STABLE, UNIQUE fallback id. A content hash (tagName) collapsed every
|
|
2
|
+
// id-less <a> onto one key, so edges keyed by `${idOf(from)}~type~${idOf(to)}` collided and
|
|
3
|
+
// silently overwrote each other. A WeakMap hands out `el-<seq>` on first sight: same element → same
|
|
4
|
+
// id, distinct elements → distinct ids, entries released when the element is collected.
|
|
5
|
+
const fallbackIds = new WeakMap();
|
|
6
|
+
let fallbackSeq = 0;
|
|
7
|
+
const idOf = (el) => {
|
|
8
|
+
if (el.id)
|
|
9
|
+
return el.id;
|
|
10
|
+
let id = fallbackIds.get(el);
|
|
11
|
+
if (id === undefined)
|
|
12
|
+
fallbackIds.set(el, (id = `el-${++fallbackSeq}`));
|
|
13
|
+
return id;
|
|
14
|
+
};
|
|
15
|
+
function ids(attr) {
|
|
16
|
+
return (attr ?? '').split(/\s+/).filter(Boolean);
|
|
17
|
+
}
|
|
18
|
+
function rel(from, to, type, source, strength) {
|
|
19
|
+
return { id: `${idOf(from)}~${type}~${idOf(to)}`, from, to, type, strength, direction: 'from-to', source, active: false, memory: 0 };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Scan the relationships a single element declares, partitioning them into RESOLVED edges (both
|
|
23
|
+
* endpoints known) and UNRESOLVED declarations (an id-ref that points at no element). Pure given a
|
|
24
|
+
* `resolve` for id-refs, so it is testable without a document. Native relationships first;
|
|
25
|
+
* `data-field-relation` last.
|
|
26
|
+
*/
|
|
27
|
+
export function scanRelationships(el, resolve) {
|
|
28
|
+
const resolved = [];
|
|
29
|
+
const unresolved = [];
|
|
30
|
+
const tag = el.tagName?.toUpperCase();
|
|
31
|
+
const get = (n) => el.getAttribute(n);
|
|
32
|
+
// one declared id-ref edge: resolved if the target exists, unresolved otherwise — never dropped.
|
|
33
|
+
const edge = (idref, type, source, strength) => {
|
|
34
|
+
const id = idref.startsWith('#') ? idref.slice(1) : idref;
|
|
35
|
+
const t = resolve(id);
|
|
36
|
+
if (t)
|
|
37
|
+
resolved.push(rel(el, t, type, source, strength));
|
|
38
|
+
else
|
|
39
|
+
unresolved.push({ from: el, type, target: idref, source });
|
|
40
|
+
};
|
|
41
|
+
const href = get('href');
|
|
42
|
+
if (tag === 'A' && href && href.startsWith('#'))
|
|
43
|
+
edge(href, 'references', 'html', 0.5);
|
|
44
|
+
if (tag === 'LABEL') {
|
|
45
|
+
const f = get('for');
|
|
46
|
+
if (f)
|
|
47
|
+
edge(f, 'labels', 'html', 0.8);
|
|
48
|
+
}
|
|
49
|
+
for (const [attr, type] of [
|
|
50
|
+
['aria-controls', 'controls'],
|
|
51
|
+
['aria-describedby', 'describes'],
|
|
52
|
+
['aria-labelledby', 'labelledby'],
|
|
53
|
+
['aria-flowto', 'flowto'],
|
|
54
|
+
]) {
|
|
55
|
+
for (const id of ids(get(attr)))
|
|
56
|
+
edge(id, type, 'aria', 0.6);
|
|
57
|
+
}
|
|
58
|
+
const declared = get('data-field-relation');
|
|
59
|
+
const target = get('data-field-target');
|
|
60
|
+
if (declared && target) {
|
|
61
|
+
const s = Number(get('data-field-strength'));
|
|
62
|
+
edge(target, declared, 'data', Number.isFinite(s) && s > 0 ? s : 0.7);
|
|
63
|
+
}
|
|
64
|
+
return { resolved, unresolved };
|
|
65
|
+
}
|
|
66
|
+
/** The RESOLVED relationships an element declares (back-compat wrapper over {@link scanRelationships}). */
|
|
67
|
+
export function relationshipsFromElement(el, resolve) {
|
|
68
|
+
return scanRelationships(el, resolve).resolved;
|
|
69
|
+
}
|
|
70
|
+
/** The DECLARED-but-UNRESOLVED relationships an element points at (targets that resolve to nothing). */
|
|
71
|
+
export function unresolvedRelationshipsFromElement(el, resolve) {
|
|
72
|
+
return scanRelationships(el, resolve).unresolved;
|
|
73
|
+
}
|
|
74
|
+
export class RelationshipRegistry {
|
|
75
|
+
rels = new Map();
|
|
76
|
+
unresolvedRels = new Map();
|
|
77
|
+
/**
|
|
78
|
+
* Scan a root for native + declared relationships. Each call REPLACES the unresolved set (so
|
|
79
|
+
* previously-missing targets that have since mounted resolve on the next pass for free) and prunes
|
|
80
|
+
* resolved edges whose endpoints are no longer connected (elements removed from the DOM).
|
|
81
|
+
* Resolved edges accumulate across passes — only disconnected ones are dropped.
|
|
82
|
+
*/
|
|
83
|
+
discover(root, resolve) {
|
|
84
|
+
const r = resolve ?? ((id) => (typeof document !== 'undefined' ? document.getElementById(id) : null));
|
|
85
|
+
// prune resolved edges whose from or to element left the DOM before rescanning
|
|
86
|
+
for (const [id, rl] of this.rels) {
|
|
87
|
+
if (!rl.from.isConnected || !rl.to.isConnected)
|
|
88
|
+
this.rels.delete(id);
|
|
89
|
+
}
|
|
90
|
+
// replace the unresolved set entirely — previously-missing targets that have since mounted will
|
|
91
|
+
// now resolve, and targets that are still absent will be re-recorded fresh
|
|
92
|
+
this.unresolvedRels.clear();
|
|
93
|
+
const sel = 'a[href^="#"], label[for], [aria-controls], [aria-describedby], [aria-labelledby], [aria-flowto], [data-field-relation]';
|
|
94
|
+
root.querySelectorAll(sel).forEach((el) => {
|
|
95
|
+
const { resolved, unresolved } = scanRelationships(el, r);
|
|
96
|
+
for (const rl of resolved)
|
|
97
|
+
this.rels.set(rl.id, rl);
|
|
98
|
+
for (const u of unresolved)
|
|
99
|
+
this.unresolvedRels.set(`${idOf(u.from)}~${u.type}~${u.target}`, u);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Drop all resolved and unresolved edges that touch `element` (as either endpoint). Use when an
|
|
104
|
+
* element is removed and you want immediate reclamation ahead of the next discover() pass.
|
|
105
|
+
*/
|
|
106
|
+
unregister(element) {
|
|
107
|
+
for (const [id, rl] of this.rels) {
|
|
108
|
+
if (rl.from === element || rl.to === element)
|
|
109
|
+
this.rels.delete(id);
|
|
110
|
+
}
|
|
111
|
+
for (const [key, u] of this.unresolvedRels) {
|
|
112
|
+
if (u.from === element)
|
|
113
|
+
this.unresolvedRels.delete(key);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Add an expressive relationship by hand (source defaults to `runtime`). */
|
|
117
|
+
add(r) {
|
|
118
|
+
const full = {
|
|
119
|
+
direction: 'from-to',
|
|
120
|
+
active: false,
|
|
121
|
+
memory: 0,
|
|
122
|
+
...r,
|
|
123
|
+
id: r.id ?? `${idOf(r.from)}~${r.type}~${idOf(r.to)}`,
|
|
124
|
+
};
|
|
125
|
+
this.rels.set(full.id, full);
|
|
126
|
+
return full;
|
|
127
|
+
}
|
|
128
|
+
all() {
|
|
129
|
+
return [...this.rels.values()];
|
|
130
|
+
}
|
|
131
|
+
get size() {
|
|
132
|
+
return this.rels.size;
|
|
133
|
+
}
|
|
134
|
+
/** Declared relationships whose target id-ref resolved to no element. */
|
|
135
|
+
unresolvedAll() {
|
|
136
|
+
return [...this.unresolvedRels.values()];
|
|
137
|
+
}
|
|
138
|
+
get unresolvedSize() {
|
|
139
|
+
return this.unresolvedRels.size;
|
|
140
|
+
}
|
|
141
|
+
/** Map the graph onto core `RelationshipAgent`s (endpoints keyed by element id). */
|
|
142
|
+
toAgents() {
|
|
143
|
+
return this.all().map((r) => ({
|
|
144
|
+
id: r.id,
|
|
145
|
+
from: idOf(r.from),
|
|
146
|
+
to: idOf(r.to),
|
|
147
|
+
type: r.type,
|
|
148
|
+
strength: r.strength,
|
|
149
|
+
tension: 0,
|
|
150
|
+
memory: r.memory,
|
|
151
|
+
active: r.active,
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=relationships.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relationships.js","sourceRoot":"","sources":["../src/relationships.ts"],"names":[],"mappings":"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"}
|