@fundamental-engine/dom 0.7.0 → 0.9.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/browser-host.d.ts.map +1 -1
- package/dist/browser-host.js +4 -0
- package/dist/browser-host.js.map +1 -1
- package/dist/container-host.d.ts +12 -0
- package/dist/container-host.d.ts.map +1 -0
- package/dist/container-host.js +60 -0
- package/dist/container-host.js.map +1 -0
- package/dist/feedback.d.ts +21 -0
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +18 -0
- package/dist/feedback.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/lint.d.ts +55 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +185 -1
- package/dist/lint.js.map +1 -1
- package/dist/perf.d.ts +31 -4
- package/dist/perf.d.ts.map +1 -1
- package/dist/perf.js +57 -4
- package/dist/perf.js.map +1 -1
- package/dist/register-properties.d.ts +25 -0
- package/dist/register-properties.d.ts.map +1 -0
- package/dist/register-properties.js +48 -0
- package/dist/register-properties.js.map +1 -0
- package/dist/text-bodies.d.ts +16 -5
- package/dist/text-bodies.d.ts.map +1 -1
- package/dist/text-bodies.js +37 -6
- package/dist/text-bodies.js.map +1 -1
- package/dist/worker/offthread-bridge.d.ts +17 -0
- package/dist/worker/offthread-bridge.d.ts.map +1 -0
- package/dist/worker/offthread-bridge.js +47 -0
- package/dist/worker/offthread-bridge.js.map +1 -0
- package/dist/worker/render-worker.d.ts +9 -0
- package/dist/worker/render-worker.d.ts.map +1 -0
- package/dist/worker/render-worker.js +43 -0
- package/dist/worker/render-worker.js.map +1 -0
- package/dist/x-ray.d.ts +14 -0
- package/dist/x-ray.d.ts.map +1 -0
- package/dist/x-ray.js +66 -0
- package/dist/x-ray.js.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-host.d.ts","sourceRoot":"","sources":["../src/browser-host.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"browser-host.d.ts","sourceRoot":"","sources":["../src/browser-host.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAM1D,yDAAyD;AACzD,wBAAgB,WAAW,IAAI,SAAS,CAqCvC"}
|
package/dist/browser-host.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { prefersReducedMotion, pageHidden } from "./env.js";
|
|
2
|
+
import { registerFieldProperties } from "./register-properties.js";
|
|
2
3
|
const INPUT_EVENTS = ['pointerdown', 'wheel', 'keydown', 'touchstart'];
|
|
3
4
|
/** Build a FieldHost backed by `window` / `document`. */
|
|
4
5
|
export function browserHost() {
|
|
6
|
+
// Register the field-density channels as typed, compositor-interpolable CSS properties once at
|
|
7
|
+
// boot so consumers can transition/animate var(--field-density)/var(--d). No-op if unsupported.
|
|
8
|
+
registerFieldProperties();
|
|
5
9
|
return {
|
|
6
10
|
root: document,
|
|
7
11
|
viewport: () => ({ width: window.innerWidth, height: window.innerHeight, dpr: window.devicePixelRatio || 1 }),
|
package/dist/browser-host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-host.js","sourceRoot":"","sources":["../src/browser-host.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"browser-host.js","sourceRoot":"","sources":["../src/browser-host.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,MAAM,YAAY,GAAG,CAAC,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,CAAU,CAAC;AAEhF,yDAAyD;AACzD,MAAM,UAAU,WAAW;IACzB,+FAA+F;IAC/F,gGAAgG;IAChG,uBAAuB,EAAE,CAAC;IAC1B,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,GAAG,EAAE,MAAM,CAAC,gBAAgB,IAAI,CAAC,EAAE,CAAC;QAC7G,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC;QAClC,YAAY,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,YAAY;QACzD,aAAa,EAAE,GAAG,EAAE,CAAC,oBAAoB,EAAE;QAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE;QAC1B,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,qBAAqB,CAAC,EAAE,CAAC;QACtC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC3C,YAAY,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC;QACpD,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE;YACf,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACzD,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE;YACf,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACzD,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE;YACnB,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;YAClD,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE;YACd,KAAK,MAAM,EAAE,IAAI,YAAY;gBAAE,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAClF,OAAO,GAAG,EAAE;gBACV,KAAK,MAAM,EAAE,IAAI,YAAY;oBAAE,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACpE,CAAC,CAAC;QACJ,CAAC;QACD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE;YACxB,QAAQ,CAAC,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACpC,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* containerHost — a {@link FieldHost} scoped to a single element instead of the window (#540). The
|
|
3
|
+
* field, its bodies, and its canvas all live in the container's local coordinate space: `viewport()`
|
|
4
|
+
* returns the element's size + its `left,top` as the field-space origin, `root` scans only inside the
|
|
5
|
+
* element, and scroll/resize/input are observed on the element. This is the supported way to render a
|
|
6
|
+
* CONTAINED, card-sized field — pass `bounds: el` to `createField`/`FieldField`, or wire it directly:
|
|
7
|
+
* `createField(canvas, { host: containerHost(card) })`. The first concrete `FieldSurface` (#539).
|
|
8
|
+
*/
|
|
9
|
+
import type { FieldHost } from '@fundamental-engine/core';
|
|
10
|
+
/** Build a FieldHost scoped to `el` — a contained field in the element's local coordinate space. */
|
|
11
|
+
export declare function containerHost(el: HTMLElement): FieldHost;
|
|
12
|
+
//# sourceMappingURL=container-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-host.d.ts","sourceRoot":"","sources":["../src/container-host.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAK1D,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,EAAE,EAAE,WAAW,GAAG,SAAS,CAqDxD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { prefersReducedMotion, pageHidden } from "./env.js";
|
|
2
|
+
const INPUT_EVENTS = ['pointerdown', 'wheel', 'keydown', 'touchstart'];
|
|
3
|
+
/** Build a FieldHost scoped to `el` — a contained field in the element's local coordinate space. */
|
|
4
|
+
export function containerHost(el) {
|
|
5
|
+
return {
|
|
6
|
+
root: el,
|
|
7
|
+
viewport: () => {
|
|
8
|
+
const r = el.getBoundingClientRect();
|
|
9
|
+
// originX/Y = the container's viewport position → bodies/threads/moves are measured
|
|
10
|
+
// container-local (their window rects minus this origin); the canvas is drawn in the same space.
|
|
11
|
+
return { width: r.width, height: r.height, dpr: window.devicePixelRatio || 1, originX: r.left, originY: r.top };
|
|
12
|
+
},
|
|
13
|
+
scrollY: () => el.scrollTop,
|
|
14
|
+
scrollHeight: () => el.scrollHeight,
|
|
15
|
+
reducedMotion: () => prefersReducedMotion(),
|
|
16
|
+
hidden: () => pageHidden(),
|
|
17
|
+
raf: (cb) => requestAnimationFrame(cb),
|
|
18
|
+
cancelRaf: (id) => cancelAnimationFrame(id),
|
|
19
|
+
createCanvas: () => document.createElement('canvas'),
|
|
20
|
+
onResize: (cb) => {
|
|
21
|
+
// a container can resize without the window doing so → ResizeObserver; window resize can still
|
|
22
|
+
// move it (re-layout), so keep that too.
|
|
23
|
+
const ro = new ResizeObserver(cb);
|
|
24
|
+
ro.observe(el);
|
|
25
|
+
window.addEventListener('resize', cb, { passive: true });
|
|
26
|
+
return () => {
|
|
27
|
+
ro.disconnect();
|
|
28
|
+
window.removeEventListener('resize', cb);
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
onScroll: (cb) => {
|
|
32
|
+
// content scrolling INSIDE the container, plus page scroll (which moves the container in the
|
|
33
|
+
// viewport → its origin shifts). The engine re-reads the origin each frame for a contained field.
|
|
34
|
+
el.addEventListener('scroll', cb, { passive: true });
|
|
35
|
+
window.addEventListener('scroll', cb, { passive: true });
|
|
36
|
+
return () => {
|
|
37
|
+
el.removeEventListener('scroll', cb);
|
|
38
|
+
window.removeEventListener('scroll', cb);
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
onVisibility: (cb) => {
|
|
42
|
+
document.addEventListener('visibilitychange', cb);
|
|
43
|
+
return () => document.removeEventListener('visibilitychange', cb);
|
|
44
|
+
},
|
|
45
|
+
onInput: (cb) => {
|
|
46
|
+
// interaction within the container drives its field (not the whole window).
|
|
47
|
+
for (const ev of INPUT_EVENTS)
|
|
48
|
+
el.addEventListener(ev, cb, { passive: true });
|
|
49
|
+
return () => {
|
|
50
|
+
for (const ev of INPUT_EVENTS)
|
|
51
|
+
el.removeEventListener(ev, cb);
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
onBodyEvent: (type, cb) => {
|
|
55
|
+
el.addEventListener(type, cb);
|
|
56
|
+
return () => el.removeEventListener(type, cb);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=container-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-host.js","sourceRoot":"","sources":["../src/container-host.ts"],"names":[],"mappings":"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"}
|
package/dist/feedback.d.ts
CHANGED
|
@@ -10,10 +10,19 @@ export interface ThresholdOptions {
|
|
|
10
10
|
/** event dispatched on the exit edge (e.g. `field:dim` to pair with `field:lit`). */
|
|
11
11
|
exitEvent?: string;
|
|
12
12
|
}
|
|
13
|
+
/** Per-binding runtime activity: how many flushes the body has lived through, and whether any
|
|
14
|
+
* bound channel has ever received a non-zero value. The "silent contract gap" detector reads this. */
|
|
15
|
+
export interface FeedbackActivity {
|
|
16
|
+
/** flushes seen while this binding was registered (and the element connected). */
|
|
17
|
+
flushes: number;
|
|
18
|
+
/** has any bound channel ever flushed a non-zero value onto this element? */
|
|
19
|
+
written: boolean;
|
|
20
|
+
}
|
|
13
21
|
export declare class FeedbackRegistry {
|
|
14
22
|
private readonly bindings;
|
|
15
23
|
private readonly direct;
|
|
16
24
|
private readonly thresholds;
|
|
25
|
+
private readonly activity;
|
|
17
26
|
private _cssWritesLastFrame;
|
|
18
27
|
/** Declare which state keys map to which CSS vars on an element (e.g. `{ density: '--field-density' }`). */
|
|
19
28
|
bind(element: Element, map: VarBinding): void;
|
|
@@ -30,6 +39,18 @@ export declare class FeedbackRegistry {
|
|
|
30
39
|
element: Element;
|
|
31
40
|
vars: string[];
|
|
32
41
|
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Per-element runtime feedback activity accumulated across `flush()` calls: how many flushes the
|
|
44
|
+
* binding has lived through and whether any bound channel ever flushed a non-zero value. The
|
|
45
|
+
* "silent contract gap" detector (`lintFeedbackNeverWritten`) reads this to flag a body that has
|
|
46
|
+
* been bound for many frames but never actually received a value — it reacts to nothing. Inspection
|
|
47
|
+
* only; never mutated outside `flush()`.
|
|
48
|
+
*/
|
|
49
|
+
feedbackActivity(): Array<{
|
|
50
|
+
element: Element;
|
|
51
|
+
flushes: number;
|
|
52
|
+
written: boolean;
|
|
53
|
+
}>;
|
|
33
54
|
/**
|
|
34
55
|
* Actual `style.setProperty` calls made during the last `flush()`. Use this (not
|
|
35
56
|
* `boundVars().length`) to measure real per-frame DOM write cost: off-screen elements with
|
package/dist/feedback.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feedback.d.ts","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,8DAA8D;AAC9D,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AA6BzC,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8C;IACrE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwB;IACnD,OAAO,CAAC,mBAAmB,CAAK;IAEhC,4GAA4G;IAC5G,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI;IAI7C;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAK7C,4FAA4F;IAC5F,SAAS,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAIxD;;;;OAIG;IACH,kBAAkB,IAAI,MAAM;IAI5B,kEAAkE;IAClE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI;IAMlE,iGAAiG;IACjG,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAU5E;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"feedback.d.ts","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,8DAA8D;AAC9D,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AA6BzC,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;uGACuG;AACvG,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8C;IACrE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwB;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IACjE,OAAO,CAAC,mBAAmB,CAAK;IAEhC,4GAA4G;IAC5G,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI;IAI7C;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAK7C,4FAA4F;IAC5F,SAAS,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAIxD;;;;;;OAMG;IACH,gBAAgB,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAIlF;;;;OAIG;IACH,kBAAkB,IAAI,MAAM;IAI5B,kEAAkE;IAClE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI;IAMlE,iGAAiG;IACjG,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAU5E;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAUlC;;;;;OAKG;IACH,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,SAAI,GAAG,IAAI;CA8B3C"}
|
package/dist/feedback.js
CHANGED
|
@@ -29,6 +29,7 @@ export class FeedbackRegistry {
|
|
|
29
29
|
bindings = new Map();
|
|
30
30
|
direct = new Map();
|
|
31
31
|
thresholds = [];
|
|
32
|
+
activity = new Map();
|
|
32
33
|
_cssWritesLastFrame = 0;
|
|
33
34
|
/** Declare which state keys map to which CSS vars on an element (e.g. `{ density: '--field-density' }`). */
|
|
34
35
|
bind(element, map) {
|
|
@@ -50,6 +51,16 @@ export class FeedbackRegistry {
|
|
|
50
51
|
boundVars() {
|
|
51
52
|
return [...this.bindings].map(([element, map]) => ({ element, vars: Object.values(map) }));
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Per-element runtime feedback activity accumulated across `flush()` calls: how many flushes the
|
|
56
|
+
* binding has lived through and whether any bound channel ever flushed a non-zero value. The
|
|
57
|
+
* "silent contract gap" detector (`lintFeedbackNeverWritten`) reads this to flag a body that has
|
|
58
|
+
* been bound for many frames but never actually received a value — it reacts to nothing. Inspection
|
|
59
|
+
* only; never mutated outside `flush()`.
|
|
60
|
+
*/
|
|
61
|
+
feedbackActivity() {
|
|
62
|
+
return [...this.activity].map(([element, a]) => ({ element, flushes: a.flushes, written: a.written }));
|
|
63
|
+
}
|
|
53
64
|
/**
|
|
54
65
|
* Actual `style.setProperty` calls made during the last `flush()`. Use this (not
|
|
55
66
|
* `boundVars().length`) to measure real per-frame DOM write cost: off-screen elements with
|
|
@@ -82,6 +93,7 @@ export class FeedbackRegistry {
|
|
|
82
93
|
unregister(element) {
|
|
83
94
|
this.bindings.delete(element);
|
|
84
95
|
this.direct.delete(element);
|
|
96
|
+
this.activity.delete(element);
|
|
85
97
|
// splice in reverse so the index arithmetic stays correct as we remove entries
|
|
86
98
|
for (let i = this.thresholds.length - 1; i >= 0; i--) {
|
|
87
99
|
if (this.thresholds[i].element === element)
|
|
@@ -101,14 +113,20 @@ export class FeedbackRegistry {
|
|
|
101
113
|
// layout-wise but still costs a Map lookup + style.setProperty call every frame.
|
|
102
114
|
if (!el.isConnected) {
|
|
103
115
|
this.bindings.delete(el);
|
|
116
|
+
this.activity.delete(el);
|
|
104
117
|
continue;
|
|
105
118
|
}
|
|
119
|
+
const act = this.activity.get(el) ?? { flushes: 0, written: false };
|
|
120
|
+
act.flushes++;
|
|
106
121
|
for (const key of Object.keys(map)) {
|
|
107
122
|
const v = state.get(el, key);
|
|
108
123
|
if (!v)
|
|
109
124
|
continue;
|
|
125
|
+
if (v.type === 'number' ? v.value !== 0 : Boolean(v.value))
|
|
126
|
+
act.written = true;
|
|
110
127
|
this._cssWritesLastFrame += writeVar(el, map[key], v.type === 'number' ? v.value.toFixed(3) : String(v.value ?? ''));
|
|
111
128
|
}
|
|
129
|
+
this.activity.set(el, act);
|
|
112
130
|
}
|
|
113
131
|
for (const [el, vars] of this.direct) {
|
|
114
132
|
for (const name of Object.keys(vars))
|
package/dist/feedback.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feedback.js","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAcvD,8EAA8E;AAC9E,SAAS,QAAQ,CAAC,OAAgB,EAAE,IAAY,EAAE,KAAa;IAC7D,MAAM,KAAK,GAAI,OAAuB,CAAC,KAAK,CAAC;IAC7C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,WAAW,KAAK,UAAU;QAAE,OAAO,CAAC,CAAC;IAChE,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,SAAS,CAAC,OAAgB,EAAE,IAAY;IAC/C,MAAM,KAAK,GAAI,OAAuB,CAAC,KAAK,CAAC;IAC7C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,cAAc,KAAK,UAAU;QAAE,OAAO;IACjE,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,IAAI,CAAC,OAAgB,EAAE,IAAY,EAAE,MAAe;IAC3D,IAAI,OAAO,OAAO,CAAC,aAAa,KAAK,UAAU;QAAE,OAAO;IACxD,OAAO,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAC1F,CAAC;
|
|
1
|
+
{"version":3,"file":"feedback.js","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export * from './apply-recipe.ts';
|
|
|
27
27
|
export * from './field-nav.ts';
|
|
28
28
|
export * from './bind-data.ts';
|
|
29
29
|
export * from './browser-host.ts';
|
|
30
|
+
export * from './register-properties.ts';
|
|
31
|
+
export * from './container-host.ts';
|
|
30
32
|
export * from './export-dom.ts';
|
|
31
33
|
export * from './governor.ts';
|
|
32
34
|
export * from './flip.ts';
|
|
@@ -34,6 +36,8 @@ export * from './text-bodies.ts';
|
|
|
34
36
|
export * from './contours.ts';
|
|
35
37
|
export * from './thread-overlay.ts';
|
|
36
38
|
export * from './perf.ts';
|
|
39
|
+
export { attachOffthreadRender, type OffthreadBridgeResult } from './worker/offthread-bridge.ts';
|
|
40
|
+
export * from './x-ray.ts';
|
|
37
41
|
/** Start the core engine on a canvas with the default browser host — `createField` + `browserHost()`. */
|
|
38
42
|
export declare function createBrowserField(canvas: HTMLCanvasElement, opts?: Omit<FieldOptions, 'host'>): FieldHandle;
|
|
39
43
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAe,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG5F,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAe,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG5F,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACjG,cAAc,YAAY,CAAC;AAE3B,yGAAyG;AACzG,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,EAAE,IAAI,GAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAM,GAAG,WAAW,CAEhH"}
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,8 @@ export * from "./apply-recipe.js";
|
|
|
28
28
|
export * from "./field-nav.js";
|
|
29
29
|
export * from "./bind-data.js";
|
|
30
30
|
export * from "./browser-host.js";
|
|
31
|
+
export * from "./register-properties.js";
|
|
32
|
+
export * from "./container-host.js";
|
|
31
33
|
export * from "./export-dom.js";
|
|
32
34
|
export * from "./governor.js";
|
|
33
35
|
export * from "./flip.js";
|
|
@@ -35,6 +37,8 @@ export * from "./text-bodies.js";
|
|
|
35
37
|
export * from "./contours.js";
|
|
36
38
|
export * from "./thread-overlay.js";
|
|
37
39
|
export * from "./perf.js";
|
|
40
|
+
export { attachOffthreadRender } from "./worker/offthread-bridge.js";
|
|
41
|
+
export * from "./x-ray.js";
|
|
38
42
|
/** Start the core engine on a canvas with the default browser host — `createField` + `browserHost()`. */
|
|
39
43
|
export function createBrowserField(canvas, opts = {}) {
|
|
40
44
|
return createField(canvas, { ...opts, host: browserHost() });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAuC,MAAM,0BAA0B,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAuC,MAAM,0BAA0B,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAA8B,MAAM,8BAA8B,CAAC;AACjG,cAAc,YAAY,CAAC;AAE3B,yGAAyG;AACzG,MAAM,UAAU,kBAAkB,CAAC,MAAyB,EAAE,OAAmC,EAAE;IACjG,OAAO,WAAW,CAAC,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;AAC/D,CAAC"}
|
package/dist/lint.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { RelationshipRegistry } from './relationships.ts';
|
|
|
15
15
|
import type { VisualBindingRegistry } from './visual-bindings.ts';
|
|
16
16
|
import type { FrameScheduler } from './schedule.ts';
|
|
17
17
|
import type { Resolver } from './relationships.ts';
|
|
18
|
-
export type LintCode = 'relation-target-missing' | 'visual-orphan' | 'visual-not-hidden' | 'state-unregistered' | 'overlay-without-links' | 'feedback-non-css-var' | 'measurement-off-phase' | 'sink-without-feedback' | 'feedback-vars-unwritten' | 'feedback-lane-inert';
|
|
18
|
+
export type LintCode = 'relation-target-missing' | 'visual-orphan' | 'visual-not-hidden' | 'state-unregistered' | 'overlay-without-links' | 'feedback-non-css-var' | 'measurement-off-phase' | 'sink-without-feedback' | 'feedback-vars-unwritten' | 'feedback-writes-unread' | 'feedback-reads-unwritten' | 'feedback-never-written' | 'feedback-lane-inert' | 'compositing-fill-trap';
|
|
19
19
|
export interface PlatformLintWarning {
|
|
20
20
|
code: LintCode;
|
|
21
21
|
severity: 'warning' | 'error';
|
|
@@ -38,6 +38,28 @@ export declare function lintSinkFeedback(root: ParentNode): PlatformLintWarning[
|
|
|
38
38
|
* styles itself from variables that will never be set for it. Pure.
|
|
39
39
|
*/
|
|
40
40
|
export declare function lintFeedbackVarReads(root: ParentNode): PlatformLintWarning[];
|
|
41
|
+
/**
|
|
42
|
+
* The producer side of the feedback contract: a `data-feedback` body gets `--d`/`--load`/`--field-*`
|
|
43
|
+
* written onto it every frame, but if **no** style rule reads those vars the body changes invisibly —
|
|
44
|
+
* the recurring "charged but reads nothing" bug (`.btn`, `.hero-mass`). This is the inverse of
|
|
45
|
+
* `lintFeedbackVarReads` (reads-without-writes). **Dev-only, heuristic, browser-coupled:** it walks the
|
|
46
|
+
* document's accessible stylesheets for rules that read a feedback var (the *consumers*) and warns for
|
|
47
|
+
* any `data-feedback` body that neither reads one inline nor matches such a rule. It no-ops where
|
|
48
|
+
* stylesheets aren't reachable (SSR / tests / cross-origin sheets), so it can only under-report there,
|
|
49
|
+
* never false-positive. Pseudo-classes/elements are stripped from consumer selectors before matching,
|
|
50
|
+
* keeping the heuristic lenient (it would rather miss a warning than fire a wrong one).
|
|
51
|
+
*/
|
|
52
|
+
export declare function lintFeedbackWritesUnread(root: ParentNode): PlatformLintWarning[];
|
|
53
|
+
/**
|
|
54
|
+
* The consumer side at the STYLESHEET level — the stylesheet companion to `lintFeedbackVarReads`
|
|
55
|
+
* (which only sees *inline* styles) and the mirror of `lintFeedbackWritesUnread`: a CSS rule reads a
|
|
56
|
+
* feedback var (`var(--field-*)`/`var(--load)`/`var(--d)`) and matches a `[data-body]` element that has
|
|
57
|
+
* **no** `data-feedback` — a field body styled from a channel it never opted into, so the style sits at
|
|
58
|
+
* its `var(…, fallback)` forever. Scoped to `[data-body]` (not every matched element) to stay high-signal
|
|
59
|
+
* and avoid flagging intentional generic fallbacks. **Dev-only, heuristic, browser-coupled:** no-ops
|
|
60
|
+
* where stylesheets are unreachable (SSR / tests / cross-origin), so it can only under-report.
|
|
61
|
+
*/
|
|
62
|
+
export declare function lintFeedbackReadsUnwritten(root: ParentNode): PlatformLintWarning[];
|
|
41
63
|
/** An element styled from field state it was never registered to measure is feedback with no source. Pure. */
|
|
42
64
|
export declare function lintStateRegistration(state: StateRegistry, measure: MeasurementRegistry): PlatformLintWarning[];
|
|
43
65
|
/** A relationship overlay with no relationships behind it draws a graph that does not exist. Pure. */
|
|
@@ -51,6 +73,18 @@ export declare function lintOverlayLinks(overlays: OverlayRegistry, relationship
|
|
|
51
73
|
* never reports (`lintSinkFeedback`). Pure.
|
|
52
74
|
*/
|
|
53
75
|
export declare function lintInertFeedback(feedback: FeedbackRegistry): PlatformLintWarning[];
|
|
76
|
+
/** Default flush count after which a still-empty feedback body is considered "never written". */
|
|
77
|
+
export declare const FEEDBACK_NEVER_WRITTEN_FRAMES = 120;
|
|
78
|
+
/**
|
|
79
|
+
* The RUNTIME half of the silent contract gap, the companion to `lintFeedbackWritesUnread` (which is
|
|
80
|
+
* static/CSS-based): a `data-feedback` body that has lived through many `flush()` calls but never once
|
|
81
|
+
* received a non-zero value. The CSS consumer may be wired correctly, yet the body still reacts to
|
|
82
|
+
* nothing — its density (or any bound channel) stays at rest forever, usually because it was never
|
|
83
|
+
* measured, sits outside the field, or no force ever reaches it. Reads `FeedbackRegistry.feedbackActivity()`
|
|
84
|
+
* (accumulated in `flush()`); a body under the threshold is still warming up and is left alone, so this
|
|
85
|
+
* only fires once the field has had a fair chance to write. Pure (reads the registry, never mutates).
|
|
86
|
+
*/
|
|
87
|
+
export declare function lintFeedbackNeverWritten(feedback: FeedbackRegistry, afterFrames?: number): PlatformLintWarning[];
|
|
54
88
|
/** Feedback must write CSS custom properties, not ARIA/attributes — state is not accessibility state. Pure. */
|
|
55
89
|
export declare function lintFeedbackVars(feedback: FeedbackRegistry): PlatformLintWarning[];
|
|
56
90
|
/** Off-phase reads recorded by the scheduler (e.g. a measurement during the write phase). */
|
|
@@ -67,12 +101,32 @@ export interface PlatformLike {
|
|
|
67
101
|
overlays: OverlayRegistry;
|
|
68
102
|
scheduler: FrameScheduler;
|
|
69
103
|
}
|
|
104
|
+
/** A pluggable lint rule. Pure: reads only, never mutates the platform or DOM. */
|
|
105
|
+
export interface LintRule {
|
|
106
|
+
readonly id: string;
|
|
107
|
+
run(root: ParentNode, warnings: PlatformLintWarning[]): void;
|
|
108
|
+
}
|
|
109
|
+
/** Register a custom lint rule globally. Returns an unregister function.
|
|
110
|
+
* The rule runs alongside built-ins on every lintPlatform() call.
|
|
111
|
+
*/
|
|
112
|
+
export declare function registerLintRule(rule: LintRule): () => void;
|
|
70
113
|
export interface LintOptions {
|
|
71
114
|
/** the subtree to scan for relation targets (defaults to the platform root). */
|
|
72
115
|
root?: ParentNode;
|
|
73
116
|
/** id resolver for relation targets (defaults to document.getElementById). */
|
|
74
117
|
resolve?: Resolver;
|
|
118
|
+
/** inline rules to run alongside built-ins for this call only. */
|
|
119
|
+
rules?: LintRule[];
|
|
75
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* The DPR2 / mix-blend fill trap (#405, #532) — the hardest-won perf lesson, made a guard. A
|
|
123
|
+
* full-viewport `mix-blend-mode` canvas re-composites the WHOLE screen every frame the layer below
|
|
124
|
+
* animates, **even when it's empty**. So it must stay `display:none` until it actively draws. This flags
|
|
125
|
+
* a mounted full-viewport mix-blend canvas whose backing store is unsized (`0×0` → not drawing) yet
|
|
126
|
+
* isn't `display:none` — paying the full-screen re-blend for nothing. Inline-style only (pure, no
|
|
127
|
+
* `getComputedStyle`), matching the rest of lint.
|
|
128
|
+
*/
|
|
129
|
+
export declare function lintCompositingPerf(root: ParentNode): PlatformLintWarning[];
|
|
76
130
|
/** Run every platform lint rule over a FieldPlatform and return the aggregated warnings. */
|
|
77
131
|
export declare function lintPlatform(platform: PlatformLike, opts?: LintOptions): PlatformLintWarning[];
|
|
78
132
|
//# sourceMappingURL=lint.d.ts.map
|
package/dist/lint.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnD,MAAM,MAAM,QAAQ,GAChB,yBAAyB,GACzB,eAAe,GACf,mBAAmB,GACnB,oBAAoB,GACpB,uBAAuB,GACvB,sBAAsB,GACtB,uBAAuB,GACvB,uBAAuB,GACvB,yBAAyB,GACzB,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"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"}
|
package/dist/lint.js
CHANGED
|
@@ -61,6 +61,115 @@ export function lintFeedbackVarReads(root) {
|
|
|
61
61
|
});
|
|
62
62
|
return out;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* The producer side of the feedback contract: a `data-feedback` body gets `--d`/`--load`/`--field-*`
|
|
66
|
+
* written onto it every frame, but if **no** style rule reads those vars the body changes invisibly —
|
|
67
|
+
* the recurring "charged but reads nothing" bug (`.btn`, `.hero-mass`). This is the inverse of
|
|
68
|
+
* `lintFeedbackVarReads` (reads-without-writes). **Dev-only, heuristic, browser-coupled:** it walks the
|
|
69
|
+
* document's accessible stylesheets for rules that read a feedback var (the *consumers*) and warns for
|
|
70
|
+
* any `data-feedback` body that neither reads one inline nor matches such a rule. It no-ops where
|
|
71
|
+
* stylesheets aren't reachable (SSR / tests / cross-origin sheets), so it can only under-report there,
|
|
72
|
+
* never false-positive. Pseudo-classes/elements are stripped from consumer selectors before matching,
|
|
73
|
+
* keeping the heuristic lenient (it would rather miss a warning than fire a wrong one).
|
|
74
|
+
*/
|
|
75
|
+
export function lintFeedbackWritesUnread(root) {
|
|
76
|
+
const out = [];
|
|
77
|
+
if (typeof document === 'undefined')
|
|
78
|
+
return out;
|
|
79
|
+
// collect the selectors of rules that read a feedback var — the consumer side of the contract.
|
|
80
|
+
const consumerSelectors = [];
|
|
81
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
82
|
+
let rules;
|
|
83
|
+
try {
|
|
84
|
+
rules = sheet.cssRules;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
continue;
|
|
88
|
+
} // cross-origin sheet → unreadable, skip
|
|
89
|
+
for (const rule of Array.from(rules ?? [])) {
|
|
90
|
+
const r = rule;
|
|
91
|
+
if (r.selectorText && r.cssText && FEEDBACK_VAR_READS.some((v) => r.cssText.includes(v)))
|
|
92
|
+
consumerSelectors.push(r.selectorText);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
root.querySelectorAll('[data-body][data-feedback]').forEach((el) => {
|
|
96
|
+
const style = el.getAttribute('style') ?? '';
|
|
97
|
+
if (FEEDBACK_VAR_READS.some((v) => style.includes(v)))
|
|
98
|
+
return; // reads one inline — consumed
|
|
99
|
+
const consumed = consumerSelectors.some((sel) => sel.split(',').some((part) => {
|
|
100
|
+
const clean = part.trim().replace(/::?[\w-]+(\([^)]*\))?/g, ''); // drop :hover / ::before etc.
|
|
101
|
+
if (!clean)
|
|
102
|
+
return false;
|
|
103
|
+
try {
|
|
104
|
+
return el.matches(clean);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}));
|
|
110
|
+
if (!consumed)
|
|
111
|
+
out.push({
|
|
112
|
+
code: 'feedback-writes-unread',
|
|
113
|
+
severity: 'warning',
|
|
114
|
+
element: el,
|
|
115
|
+
message: 'data-feedback body is written --d/--load/--field-* every frame but no style rule reads them — it changes invisibly (style against var(--field-density), var(--d)…)',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The consumer side at the STYLESHEET level — the stylesheet companion to `lintFeedbackVarReads`
|
|
122
|
+
* (which only sees *inline* styles) and the mirror of `lintFeedbackWritesUnread`: a CSS rule reads a
|
|
123
|
+
* feedback var (`var(--field-*)`/`var(--load)`/`var(--d)`) and matches a `[data-body]` element that has
|
|
124
|
+
* **no** `data-feedback` — a field body styled from a channel it never opted into, so the style sits at
|
|
125
|
+
* its `var(…, fallback)` forever. Scoped to `[data-body]` (not every matched element) to stay high-signal
|
|
126
|
+
* and avoid flagging intentional generic fallbacks. **Dev-only, heuristic, browser-coupled:** no-ops
|
|
127
|
+
* where stylesheets are unreachable (SSR / tests / cross-origin), so it can only under-report.
|
|
128
|
+
*/
|
|
129
|
+
export function lintFeedbackReadsUnwritten(root) {
|
|
130
|
+
const out = [];
|
|
131
|
+
if (typeof document === 'undefined')
|
|
132
|
+
return out;
|
|
133
|
+
const seen = new Set();
|
|
134
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
135
|
+
let rules;
|
|
136
|
+
try {
|
|
137
|
+
rules = sheet.cssRules;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
continue;
|
|
141
|
+
} // cross-origin sheet → unreadable, skip
|
|
142
|
+
for (const rule of Array.from(rules ?? [])) {
|
|
143
|
+
const r = rule;
|
|
144
|
+
if (!r.selectorText || !r.cssText || !FEEDBACK_VAR_READS.some((v) => r.cssText.includes(v)))
|
|
145
|
+
continue;
|
|
146
|
+
for (const part of r.selectorText.split(',')) {
|
|
147
|
+
const clean = part.trim().replace(/::?[\w-]+(\([^)]*\))?/g, ''); // drop :hover / ::before etc.
|
|
148
|
+
if (!clean)
|
|
149
|
+
continue;
|
|
150
|
+
let matched;
|
|
151
|
+
try {
|
|
152
|
+
matched = root.querySelectorAll(clean);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
for (const el of Array.from(matched)) {
|
|
158
|
+
if (seen.has(el) || !el.hasAttribute('data-body') || el.hasAttribute('data-feedback'))
|
|
159
|
+
continue;
|
|
160
|
+
seen.add(el);
|
|
161
|
+
out.push({
|
|
162
|
+
code: 'feedback-reads-unwritten',
|
|
163
|
+
severity: 'warning',
|
|
164
|
+
element: el,
|
|
165
|
+
message: `a style rule (${r.selectorText}) styles this body from a feedback var, but it has no data-feedback — that channel is never written for it, so the style stays at its fallback`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
64
173
|
/** An element styled from field state it was never registered to measure is feedback with no source. Pure. */
|
|
65
174
|
export function lintStateRegistration(state, measure) {
|
|
66
175
|
const out = [];
|
|
@@ -106,6 +215,31 @@ export function lintInertFeedback(feedback) {
|
|
|
106
215
|
}
|
|
107
216
|
return out;
|
|
108
217
|
}
|
|
218
|
+
/** Default flush count after which a still-empty feedback body is considered "never written". */
|
|
219
|
+
export const FEEDBACK_NEVER_WRITTEN_FRAMES = 120;
|
|
220
|
+
/**
|
|
221
|
+
* The RUNTIME half of the silent contract gap, the companion to `lintFeedbackWritesUnread` (which is
|
|
222
|
+
* static/CSS-based): a `data-feedback` body that has lived through many `flush()` calls but never once
|
|
223
|
+
* received a non-zero value. The CSS consumer may be wired correctly, yet the body still reacts to
|
|
224
|
+
* nothing — its density (or any bound channel) stays at rest forever, usually because it was never
|
|
225
|
+
* measured, sits outside the field, or no force ever reaches it. Reads `FeedbackRegistry.feedbackActivity()`
|
|
226
|
+
* (accumulated in `flush()`); a body under the threshold is still warming up and is left alone, so this
|
|
227
|
+
* only fires once the field has had a fair chance to write. Pure (reads the registry, never mutates).
|
|
228
|
+
*/
|
|
229
|
+
export function lintFeedbackNeverWritten(feedback, afterFrames = FEEDBACK_NEVER_WRITTEN_FRAMES) {
|
|
230
|
+
const out = [];
|
|
231
|
+
for (const { element, flushes, written } of feedback.feedbackActivity()) {
|
|
232
|
+
if (written || flushes < afterFrames)
|
|
233
|
+
continue;
|
|
234
|
+
out.push({
|
|
235
|
+
code: 'feedback-never-written',
|
|
236
|
+
severity: 'warning',
|
|
237
|
+
element,
|
|
238
|
+
message: `data-feedback body has been bound for ${flushes} frames but never received a non-zero value — the reciprocal loop is inert (it is likely unmeasured, outside the field, or no force reaches it)`,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
109
243
|
/** Feedback must write CSS custom properties, not ARIA/attributes — state is not accessibility state. Pure. */
|
|
110
244
|
export function lintFeedbackVars(feedback) {
|
|
111
245
|
const out = [];
|
|
@@ -134,20 +268,70 @@ export function lintVisuals(visuals) {
|
|
|
134
268
|
element: w.visual,
|
|
135
269
|
}));
|
|
136
270
|
}
|
|
271
|
+
const _customRules = new Map();
|
|
272
|
+
/** Register a custom lint rule globally. Returns an unregister function.
|
|
273
|
+
* The rule runs alongside built-ins on every lintPlatform() call.
|
|
274
|
+
*/
|
|
275
|
+
export function registerLintRule(rule) {
|
|
276
|
+
_customRules.set(rule.id, rule);
|
|
277
|
+
return () => { _customRules.delete(rule.id); };
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The DPR2 / mix-blend fill trap (#405, #532) — the hardest-won perf lesson, made a guard. A
|
|
281
|
+
* full-viewport `mix-blend-mode` canvas re-composites the WHOLE screen every frame the layer below
|
|
282
|
+
* animates, **even when it's empty**. So it must stay `display:none` until it actively draws. This flags
|
|
283
|
+
* a mounted full-viewport mix-blend canvas whose backing store is unsized (`0×0` → not drawing) yet
|
|
284
|
+
* isn't `display:none` — paying the full-screen re-blend for nothing. Inline-style only (pure, no
|
|
285
|
+
* `getComputedStyle`), matching the rest of lint.
|
|
286
|
+
*/
|
|
287
|
+
export function lintCompositingPerf(root) {
|
|
288
|
+
const out = [];
|
|
289
|
+
root.querySelectorAll('canvas').forEach((el) => {
|
|
290
|
+
const c = el;
|
|
291
|
+
const s = c.style;
|
|
292
|
+
const blend = s.mixBlendMode;
|
|
293
|
+
if (!blend || blend === 'normal')
|
|
294
|
+
return; // only mix-blend canvases pay the per-frame re-blend
|
|
295
|
+
const fullViewport = s.position === 'fixed' &&
|
|
296
|
+
(s.inset === '0' || s.inset === '0px' || (s.width === '100%' && s.height === '100%'));
|
|
297
|
+
if (!fullViewport)
|
|
298
|
+
return;
|
|
299
|
+
if (s.display === 'none')
|
|
300
|
+
return; // correctly gated out of the compositing tree — no cost
|
|
301
|
+
if (c.width !== 0 && c.height !== 0)
|
|
302
|
+
return; // has a sized backing store → assume it's actively drawing
|
|
303
|
+
out.push({
|
|
304
|
+
code: 'compositing-fill-trap',
|
|
305
|
+
severity: 'warning',
|
|
306
|
+
element: c,
|
|
307
|
+
message: `a full-viewport mix-blend-mode="${blend}" canvas is mounted with an unsized backing store (0×0) but not display:none — it re-composites the whole screen every frame for nothing (#405). Set display:none until it actively draws.`,
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
137
312
|
/** Run every platform lint rule over a FieldPlatform and return the aggregated warnings. */
|
|
138
313
|
export function lintPlatform(platform, opts = {}) {
|
|
139
314
|
const root = opts.root ?? platform.root;
|
|
140
315
|
const resolve = opts.resolve ?? ((id) => (typeof document !== 'undefined' ? document.getElementById(id) : null));
|
|
141
|
-
|
|
316
|
+
const warnings = [
|
|
142
317
|
...lintRelationTargets(root, resolve),
|
|
318
|
+
...lintCompositingPerf(root),
|
|
143
319
|
...lintSinkFeedback(root),
|
|
144
320
|
...lintFeedbackVarReads(root),
|
|
321
|
+
...lintFeedbackWritesUnread(root),
|
|
322
|
+
...lintFeedbackReadsUnwritten(root),
|
|
145
323
|
...lintStateRegistration(platform.state, platform.measure),
|
|
146
324
|
...lintOverlayLinks(platform.overlays, platform.relationships),
|
|
147
325
|
...lintFeedbackVars(platform.feedback),
|
|
148
326
|
...lintInertFeedback(platform.feedback),
|
|
327
|
+
...lintFeedbackNeverWritten(platform.feedback),
|
|
149
328
|
...lintVisuals(platform.visuals),
|
|
150
329
|
...lintSchedulerViolations(platform.scheduler),
|
|
151
330
|
];
|
|
331
|
+
// pluggable rules — inline (opts.rules) + globally registered
|
|
332
|
+
const extraRules = [...(opts?.rules ?? []), ..._customRules.values()];
|
|
333
|
+
for (const rule of extraRules)
|
|
334
|
+
rule.run(root, warnings);
|
|
335
|
+
return warnings;
|
|
152
336
|
}
|
|
153
337
|
//# sourceMappingURL=lint.js.map
|