@wildo-ai/presets-components-3d 1.1.6
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 +34 -0
- package/dist/esm/frame/frame-3d-style.d.ts +156 -0
- package/dist/esm/frame/frame-3d-style.d.ts.map +1 -0
- package/dist/esm/frame/frame-3d-style.js +229 -0
- package/dist/esm/frame/frame-3d-style.js.map +1 -0
- package/dist/esm/frame/frame-appearance.d.ts +99 -0
- package/dist/esm/frame/frame-appearance.d.ts.map +1 -0
- package/dist/esm/frame/frame-appearance.js +207 -0
- package/dist/esm/frame/frame-appearance.js.map +1 -0
- package/dist/esm/frame/frame-canvas.d.ts +63 -0
- package/dist/esm/frame/frame-canvas.d.ts.map +1 -0
- package/dist/esm/frame/frame-canvas.js +134 -0
- package/dist/esm/frame/frame-canvas.js.map +1 -0
- package/dist/esm/frame/frame-canvas.lazy.d.ts +21 -0
- package/dist/esm/frame/frame-canvas.lazy.d.ts.map +1 -0
- package/dist/esm/frame/frame-canvas.lazy.js +31 -0
- package/dist/esm/frame/frame-canvas.lazy.js.map +1 -0
- package/dist/esm/frame/frame-color.d.ts +24 -0
- package/dist/esm/frame/frame-color.d.ts.map +1 -0
- package/dist/esm/frame/frame-color.js +52 -0
- package/dist/esm/frame/frame-color.js.map +1 -0
- package/dist/esm/frame/frame-driver.d.ts +60 -0
- package/dist/esm/frame/frame-driver.d.ts.map +1 -0
- package/dist/esm/frame/frame-driver.js +207 -0
- package/dist/esm/frame/frame-driver.js.map +1 -0
- package/dist/esm/frame/frame-renderer.d.ts +141 -0
- package/dist/esm/frame/frame-renderer.d.ts.map +1 -0
- package/dist/esm/frame/frame-renderer.js +635 -0
- package/dist/esm/frame/frame-renderer.js.map +1 -0
- package/dist/esm/frame/frame-shaders.d.ts +30 -0
- package/dist/esm/frame/frame-shaders.d.ts.map +1 -0
- package/dist/esm/frame/frame-shaders.js +97 -0
- package/dist/esm/frame/frame-shaders.js.map +1 -0
- package/dist/esm/frame/use-frame-appearance.d.ts +17 -0
- package/dist/esm/frame/use-frame-appearance.d.ts.map +1 -0
- package/dist/esm/frame/use-frame-appearance.js +38 -0
- package/dist/esm/frame/use-frame-appearance.js.map +1 -0
- package/dist/esm/index.d.ts +14 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/layout/header-first-shell-container.d.ts +22 -0
- package/dist/esm/layout/header-first-shell-container.d.ts.map +1 -0
- package/dist/esm/layout/header-first-shell-container.js +31 -0
- package/dist/esm/layout/header-first-shell-container.js.map +1 -0
- package/dist/esm/layout/layout-3d-definition.d.ts +54 -0
- package/dist/esm/layout/layout-3d-definition.d.ts.map +1 -0
- package/dist/esm/layout/layout-3d-definition.js +106 -0
- package/dist/esm/layout/layout-3d-definition.js.map +1 -0
- package/dist/esm/layout/layout-3d-transition-choreography.d.ts +30 -0
- package/dist/esm/layout/layout-3d-transition-choreography.d.ts.map +1 -0
- package/dist/esm/layout/layout-3d-transition-choreography.js +43 -0
- package/dist/esm/layout/layout-3d-transition-choreography.js.map +1 -0
- package/dist/esm/layout/layout-3d-vocabulary.d.ts +118 -0
- package/dist/esm/layout/layout-3d-vocabulary.d.ts.map +1 -0
- package/dist/esm/layout/layout-3d-vocabulary.js +104 -0
- package/dist/esm/layout/layout-3d-vocabulary.js.map +1 -0
- package/dist/esm/layout/shell-layout-placement.d.ts +26 -0
- package/dist/esm/layout/shell-layout-placement.d.ts.map +1 -0
- package/dist/esm/layout/shell-layout-placement.js +18 -0
- package/dist/esm/layout/shell-layout-placement.js.map +1 -0
- package/dist/esm/layout-3d-main-container.d.ts +73 -0
- package/dist/esm/layout-3d-main-container.d.ts.map +1 -0
- package/dist/esm/layout-3d-main-container.js +124 -0
- package/dist/esm/layout-3d-main-container.js.map +1 -0
- package/dist/esm/register-3d-layout.d.ts +40 -0
- package/dist/esm/register-3d-layout.d.ts.map +1 -0
- package/dist/esm/register-3d-layout.js +72 -0
- package/dist/esm/register-3d-layout.js.map +1 -0
- package/dist/esm/shell-region-measurement.d.ts +75 -0
- package/dist/esm/shell-region-measurement.d.ts.map +1 -0
- package/dist/esm/shell-region-measurement.js +75 -0
- package/dist/esm/shell-region-measurement.js.map +1 -0
- package/dist/esm/spatial/spatial-camera.d.ts +106 -0
- package/dist/esm/spatial/spatial-camera.d.ts.map +1 -0
- package/dist/esm/spatial/spatial-camera.js +151 -0
- package/dist/esm/spatial/spatial-camera.js.map +1 -0
- package/dist/esm/spatial/spatial-environment.d.ts +6 -0
- package/dist/esm/spatial/spatial-environment.d.ts.map +1 -0
- package/dist/esm/spatial/spatial-environment.js +166 -0
- package/dist/esm/spatial/spatial-environment.js.map +1 -0
- package/dist/esm/spatial/spatial-environment.lazy.d.ts +10 -0
- package/dist/esm/spatial/spatial-environment.lazy.d.ts.map +1 -0
- package/dist/esm/spatial/spatial-environment.lazy.js +13 -0
- package/dist/esm/spatial/spatial-environment.lazy.js.map +1 -0
- package/dist/esm/spatial/spatial-shell-container.d.ts +21 -0
- package/dist/esm/spatial/spatial-shell-container.d.ts.map +1 -0
- package/dist/esm/spatial/spatial-shell-container.js +231 -0
- package/dist/esm/spatial/spatial-shell-container.js.map +1 -0
- package/dist/esm/spatial/spatial-stage-store.d.ts +27 -0
- package/dist/esm/spatial/spatial-stage-store.d.ts.map +1 -0
- package/dist/esm/spatial/spatial-stage-store.js +33 -0
- package/dist/esm/spatial/spatial-stage-store.js.map +1 -0
- package/dist/esm/tokens/design-token-resolution.d.ts +110 -0
- package/dist/esm/tokens/design-token-resolution.d.ts.map +1 -0
- package/dist/esm/tokens/design-token-resolution.js +410 -0
- package/dist/esm/tokens/design-token-resolution.js.map +1 -0
- package/dist/esm/tokens/use-design-token-resolution.d.ts +22 -0
- package/dist/esm/tokens/use-design-token-resolution.d.ts.map +1 -0
- package/dist/esm/tokens/use-design-token-resolution.js +49 -0
- package/dist/esm/tokens/use-design-token-resolution.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frame's colour arithmetic (#1823). INTERNAL to the package: the root barrel does not export it. It is the
|
|
3
|
+
* model of what the shaders write, and the appearance derivation and its tests share it; it is not an API.
|
|
4
|
+
*/
|
|
5
|
+
export const BLACK = [0, 0, 0];
|
|
6
|
+
export const WHITE = [1, 1, 1];
|
|
7
|
+
/** The gradient is checked at these points, not only its ends: a lerp's luminance can dip below both ends. */
|
|
8
|
+
const GRADIENT_SAMPLES = [0, 0.25, 0.5, 0.75, 1];
|
|
9
|
+
const BOUND_STEPS = 40;
|
|
10
|
+
/** WCAG relative luminance of an sRGB-encoded colour. spec-cite: WCAG 2.2 — "relative luminance" definition — https://www.w3.org/TR/WCAG22/#dfn-relative-luminance */
|
|
11
|
+
export function relativeLuminance(color) {
|
|
12
|
+
const [r, g, b] = color.map(decodeSrgb);
|
|
13
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
14
|
+
}
|
|
15
|
+
/** WCAG contrast ratio between two sRGB-encoded colours. spec-cite: WCAG 2.2 — "contrast ratio" definition — https://www.w3.org/TR/WCAG22/#dfn-contrast-ratio */
|
|
16
|
+
export function contrastRatio(a, b) {
|
|
17
|
+
const la = relativeLuminance(a);
|
|
18
|
+
const lb = relativeLuminance(b);
|
|
19
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
20
|
+
}
|
|
21
|
+
/** `top` at `alpha` over an opaque `bottom`, blended in sRGB-encoded space as the framebuffer does. */
|
|
22
|
+
export function composite(top, alpha, bottom) {
|
|
23
|
+
return [0, 1, 2].map((i) => top[i] * alpha + bottom[i] * (1 - alpha));
|
|
24
|
+
}
|
|
25
|
+
export function mix(a, b, t) {
|
|
26
|
+
return [0, 1, 2].map((i) => a[i] + (b[i] - a[i]) * t);
|
|
27
|
+
}
|
|
28
|
+
/** The points of a gradient the bound checks. */
|
|
29
|
+
export function gradientPoints(from, to) {
|
|
30
|
+
return GRADIENT_SAMPLES.map((t) => mix(from, to, t));
|
|
31
|
+
}
|
|
32
|
+
/** Move `color` toward `pole` in small steps until `ok` holds; the pole itself if nothing short of it does. */
|
|
33
|
+
export function pushUntil(color, pole, ok) {
|
|
34
|
+
for (let step = 0; step <= BOUND_STEPS; step += 1) {
|
|
35
|
+
const candidate = mix(color, pole, step / BOUND_STEPS);
|
|
36
|
+
if (ok(candidate))
|
|
37
|
+
return candidate;
|
|
38
|
+
}
|
|
39
|
+
return pole;
|
|
40
|
+
}
|
|
41
|
+
export function decodeSrgb(channel) {
|
|
42
|
+
return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
|
|
43
|
+
}
|
|
44
|
+
export function encodeSrgb(channel) {
|
|
45
|
+
const c = Math.min(1, Math.max(0, channel));
|
|
46
|
+
return c <= 0.0031308 ? c * 12.92 : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
47
|
+
}
|
|
48
|
+
/** The colour as the 8-bit framebuffer stores it: each channel rounded to the nearest of 256 levels. */
|
|
49
|
+
export function quantize(color) {
|
|
50
|
+
return color.map((c) => Math.round(Math.min(1, Math.max(0, c)) * 255) / 255);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=frame-color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-color.js","sourceRoot":"","sources":["../../../../src/frame/frame-color.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The frame's colour arithmetic (#1823). INTERNAL to the package: the root barrel does not export it. It is the\n * model of what the shaders write, and the appearance derivation and its tests share it; it is not an API.\n */\n\n/** A colour as the frame draws it: sRGB-ENCODED channels in 0–1, exactly what lands in the framebuffer. */\nexport type FrameColor = readonly [number, number, number];\n\nexport const BLACK: FrameColor = [0, 0, 0];\nexport const WHITE: FrameColor = [1, 1, 1];\n\n/** The gradient is checked at these points, not only its ends: a lerp's luminance can dip below both ends. */\nconst GRADIENT_SAMPLES = [0, 0.25, 0.5, 0.75, 1];\nconst BOUND_STEPS = 40;\n\n/** WCAG relative luminance of an sRGB-encoded colour. spec-cite: WCAG 2.2 — \"relative luminance\" definition — https://www.w3.org/TR/WCAG22/#dfn-relative-luminance */\nexport function relativeLuminance(color: FrameColor): number {\n const [r, g, b] = color.map(decodeSrgb);\n return 0.2126 * r! + 0.7152 * g! + 0.0722 * b!;\n}\n\n/** WCAG contrast ratio between two sRGB-encoded colours. spec-cite: WCAG 2.2 — \"contrast ratio\" definition — https://www.w3.org/TR/WCAG22/#dfn-contrast-ratio */\nexport function contrastRatio(a: FrameColor, b: FrameColor): number {\n const la = relativeLuminance(a);\n const lb = relativeLuminance(b);\n return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);\n}\n\n/** `top` at `alpha` over an opaque `bottom`, blended in sRGB-encoded space as the framebuffer does. */\nexport function composite(top: FrameColor, alpha: number, bottom: FrameColor): FrameColor {\n return [0, 1, 2].map((i) => top[i]! * alpha + bottom[i]! * (1 - alpha)) as unknown as FrameColor;\n}\n\nexport function mix(a: FrameColor, b: FrameColor, t: number): FrameColor {\n return [0, 1, 2].map((i) => a[i]! + (b[i]! - a[i]!) * t) as unknown as FrameColor;\n}\n\n/** The points of a gradient the bound checks. */\nexport function gradientPoints(from: FrameColor, to: FrameColor): FrameColor[] {\n return GRADIENT_SAMPLES.map((t) => mix(from, to, t));\n}\n\n/** Move `color` toward `pole` in small steps until `ok` holds; the pole itself if nothing short of it does. */\nexport function pushUntil(color: FrameColor, pole: FrameColor, ok: (c: FrameColor) => boolean): FrameColor {\n for (let step = 0; step <= BOUND_STEPS; step += 1) {\n const candidate = mix(color, pole, step / BOUND_STEPS);\n if (ok(candidate)) return candidate;\n }\n return pole;\n}\n\nexport function decodeSrgb(channel: number): number {\n return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;\n}\n\nexport function encodeSrgb(channel: number): number {\n const c = Math.min(1, Math.max(0, channel));\n return c <= 0.0031308 ? c * 12.92 : 1.055 * c ** (1 / 2.4) - 0.055;\n}\n\n/** The colour as the 8-bit framebuffer stores it: each channel rounded to the nearest of 256 levels. */\nexport function quantize(color: FrameColor): FrameColor {\n return color.map((c) => Math.round(Math.min(1, Math.max(0, c)) * 255) / 255) as unknown as FrameColor;\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { FrameAppearance } from './frame-appearance';
|
|
2
|
+
import { FrameTransitionTrigger, type FrameRenderer, type FrameViewport } from './frame-renderer';
|
|
3
|
+
/** What the driver needs from its host. Injected, so the scheduling is tested with fake clocks and observers. */
|
|
4
|
+
export interface FrameDriverHost {
|
|
5
|
+
renderer: Pick<FrameRenderer, 'setAppearance' | 'setLayout' | 'setHovered' | 'setZones' | 'rimmedZone' | 'pulse' | 'hasPendingMotion' | 'tick'>;
|
|
6
|
+
/** Renders the scene as the renderer last left it. */
|
|
7
|
+
renderScene: () => void;
|
|
8
|
+
/** The canvas' size in CSS pixels, as the camera was fitted to it. */
|
|
9
|
+
viewport: () => FrameViewport;
|
|
10
|
+
/** The canvas element, for context-loss events. */
|
|
11
|
+
canvas: EventTarget;
|
|
12
|
+
document: Document;
|
|
13
|
+
requestFrame: (callback: () => void) => number;
|
|
14
|
+
cancelFrame: (handle: number) => void;
|
|
15
|
+
now: () => number;
|
|
16
|
+
createResizeObserver: (callback: () => void) => Pick<ResizeObserver, 'observe' | 'unobserve' | 'disconnect'>;
|
|
17
|
+
createMutationObserver: (callback: () => void) => Pick<MutationObserver, 'observe' | 'disconnect'>;
|
|
18
|
+
/** Whether an appearance change after the first cross-fades (the layout's transitions) or is shown at once. */
|
|
19
|
+
crossFadeAppearance: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* A transition of the frame started (#1826). The canvas counts them on its element, so a live probe can tell that a
|
|
22
|
+
* choreography ran — draws alone cannot, since a hover or a re-layout draws too.
|
|
23
|
+
*/
|
|
24
|
+
onTransitionStarted?: (trigger: FrameTransitionTrigger) => void;
|
|
25
|
+
}
|
|
26
|
+
/** The canvas-facing surface of a running driver. */
|
|
27
|
+
export interface FrameDriver {
|
|
28
|
+
/** A new appearance: shown at once the first time, cross-faded afterwards. */
|
|
29
|
+
setAppearance(appearance: FrameAppearance | null): void;
|
|
30
|
+
/** The canvas resized or its pixel ratio changed: the drawing buffer was cleared, so redraw regardless. */
|
|
31
|
+
redraw(): void;
|
|
32
|
+
/**
|
|
33
|
+
* The content's identity: the structural zones' top entries, as the navigation state reports them (#1826). The
|
|
34
|
+
* first value only records; every later CHANGE plays the layout's navigation choreography. An EMPTY signature (no
|
|
35
|
+
* zone holds anything yet: the route sync has not landed) is not a place and is never recorded, so the first
|
|
36
|
+
* landing after the canvas mounted is not played as a navigation.
|
|
37
|
+
*/
|
|
38
|
+
setNavigation(signature: string): void;
|
|
39
|
+
/** The navigation state's focused zone (a `data-content-area` value), or null. */
|
|
40
|
+
setFocusedZone(zone: string | null): void;
|
|
41
|
+
dispose(): void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The frame's scheduler (#1823): decides WHEN the renderer draws, and nothing about what it draws.
|
|
45
|
+
*
|
|
46
|
+
* - **At rest**, it draws synchronously inside the callback of whatever changed a region's box (ResizeObserver,
|
|
47
|
+
* a region mounting or leaving), so a slab never lags its region by a frame — and draws NOTHING when every box
|
|
48
|
+
* is where it was.
|
|
49
|
+
* - **While something moves** — a CSS transition in the shell, a cross-fade, a hover emphasis — it runs one loop
|
|
50
|
+
* per animation frame. The loop still renders only when a box moved or the renderer has motion pending. A
|
|
51
|
+
* `transitionstart` anywhere in the document (a button's hover colour) keeps the loop ALIVE, so a moving
|
|
52
|
+
* sidebar is followed frame by frame, but a frame where nothing in the scene changed costs no draw.
|
|
53
|
+
* - **A navigation or a zone focus** (#1826) is an input from the DOM tree (`setNavigation`, `setFocusedZone`); a
|
|
54
|
+
* **sidebar resize** is read off the measurement itself (its width changed while it stayed on screen). Each starts
|
|
55
|
+
* the renderer's choreography and the same loop, which stops the frame the choreography settles. The zone
|
|
56
|
+
* containers are observed like the regions, so a split resized by dragging its divider keeps the rim on the zone.
|
|
57
|
+
* - **After a context loss**, it draws nothing until the context is restored.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createFrameDriver(host: FrameDriverHost, initialAppearance: FrameAppearance | null): FrameDriver;
|
|
60
|
+
//# sourceMappingURL=frame-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-driver.d.ts","sourceRoot":"","sources":["../../../../src/frame/frame-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EAAqB,sBAAsB,EAAuB,KAAK,aAAa,EAAE,KAAK,aAAa,EAAqB,MAAM,kBAAkB,CAAC;AAG7J,iHAAiH;AACjH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,eAAe,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,kBAAkB,GAAG,MAAM,CAAC,CAAC;IAChJ,sDAAsD;IACtD,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,aAAa,CAAC;IAC9B,mDAAmD;IACnD,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,CAAC;IAC/C,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC;IAC7G,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,YAAY,CAAC,CAAC;IACnG,+GAA+G;IAC/G,mBAAmB,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACjE;AAED,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,aAAa,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI,CAAC;IACxD,2GAA2G;IAC3G,MAAM,IAAI,IAAI,CAAC;IACf;;;;;OAKG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,kFAAkF;IAClF,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,iBAAiB,EAAE,eAAe,GAAG,IAAI,GAAG,WAAW,CA2J/G"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { ShellRegion } from '@wildo-ai/presets-components-models';
|
|
2
|
+
import { FramePulseOutcome, FrameTransitionTrigger } from './frame-renderer.js';
|
|
3
|
+
import { findNavigationZoneElements, findShellRegionElements, measureNavigationZones, measureShellRegions } from '../shell-region-measurement.js';
|
|
4
|
+
/**
|
|
5
|
+
* The frame's scheduler (#1823): decides WHEN the renderer draws, and nothing about what it draws.
|
|
6
|
+
*
|
|
7
|
+
* - **At rest**, it draws synchronously inside the callback of whatever changed a region's box (ResizeObserver,
|
|
8
|
+
* a region mounting or leaving), so a slab never lags its region by a frame — and draws NOTHING when every box
|
|
9
|
+
* is where it was.
|
|
10
|
+
* - **While something moves** — a CSS transition in the shell, a cross-fade, a hover emphasis — it runs one loop
|
|
11
|
+
* per animation frame. The loop still renders only when a box moved or the renderer has motion pending. A
|
|
12
|
+
* `transitionstart` anywhere in the document (a button's hover colour) keeps the loop ALIVE, so a moving
|
|
13
|
+
* sidebar is followed frame by frame, but a frame where nothing in the scene changed costs no draw.
|
|
14
|
+
* - **A navigation or a zone focus** (#1826) is an input from the DOM tree (`setNavigation`, `setFocusedZone`); a
|
|
15
|
+
* **sidebar resize** is read off the measurement itself (its width changed while it stayed on screen). Each starts
|
|
16
|
+
* the renderer's choreography and the same loop, which stops the frame the choreography settles. The zone
|
|
17
|
+
* containers are observed like the regions, so a split resized by dragging its divider keeps the rim on the zone.
|
|
18
|
+
* - **After a context loss**, it draws nothing until the context is restored.
|
|
19
|
+
*/
|
|
20
|
+
export function createFrameDriver(host, initialAppearance) {
|
|
21
|
+
const { renderer, document } = host;
|
|
22
|
+
let contextLost = false;
|
|
23
|
+
let hadAppearance = false;
|
|
24
|
+
let appearance = null;
|
|
25
|
+
const observed = new Set();
|
|
26
|
+
const observedZones = new Set();
|
|
27
|
+
const resizeObserver = host.createResizeObserver(() => draw(false));
|
|
28
|
+
let navigationSignature = null;
|
|
29
|
+
let focusedZone = null;
|
|
30
|
+
/** The sidebar's width at the last measurement, or null when it was not on screen. */
|
|
31
|
+
let sidebarWidth = null;
|
|
32
|
+
const started = (trigger) => host.onTransitionStarted?.(trigger);
|
|
33
|
+
const observeOnly = (present, into) => {
|
|
34
|
+
for (const element of present)
|
|
35
|
+
if (!into.has(element)) {
|
|
36
|
+
resizeObserver.observe(element);
|
|
37
|
+
into.add(element);
|
|
38
|
+
}
|
|
39
|
+
for (const element of into)
|
|
40
|
+
if (!present.has(element)) {
|
|
41
|
+
resizeObserver.unobserve(element);
|
|
42
|
+
into.delete(element);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const measure = () => {
|
|
46
|
+
// Every marked region and zone is OBSERVED, whatever its box: one collapsed to nothing must redraw the moment it reappears.
|
|
47
|
+
observeOnly(new Set(findShellRegionElements(document)), observed);
|
|
48
|
+
observeOnly(new Set(findNavigationZoneElements(document)), observedZones);
|
|
49
|
+
const boxes = measureShellRegions(document).map(({ element, region, left, top, width, height, glass }) => ({ element, region, left, top, width, height, glass }));
|
|
50
|
+
const zones = measureNavigationZones(document);
|
|
51
|
+
const focused = zones.find((zone) => zone.zone === focusedZone)?.element ?? null;
|
|
52
|
+
const zoneBoxes = zones.map(({ element, left, top, width, height }) => ({ element, left, top, width, height }));
|
|
53
|
+
const moved = renderer.setLayout(boxes, host.viewport());
|
|
54
|
+
// A sidebar resize: its width changed while it stayed on screen. A sidebar that appears (labels ready, an
|
|
55
|
+
// organization switch) or leaves is not one. Every frame of its animation extends the same pulse.
|
|
56
|
+
const sidebar = boxes.find((box) => box.region === ShellRegion.SIDEBAR);
|
|
57
|
+
const width = sidebar ? sidebar.width : null;
|
|
58
|
+
const resizing = width !== null && sidebarWidth !== null && width !== sidebarWidth;
|
|
59
|
+
sidebarWidth = width;
|
|
60
|
+
// Reported when a pulse STARTS, never per frame: a ResizeObserver measure between two animation frames sees an
|
|
61
|
+
// unchanged width, so any "was it resizing last time" flag would re-arm and count every frame of the animation.
|
|
62
|
+
if (resizing && renderer.pulse(FrameTransitionTrigger.SIDEBAR_RESIZE) === FramePulseOutcome.STARTED)
|
|
63
|
+
started(FrameTransitionTrigger.SIDEBAR_RESIZE);
|
|
64
|
+
const rimmedBefore = renderer.rimmedZone();
|
|
65
|
+
const zonesChanged = renderer.setZones(zoneBoxes, focused);
|
|
66
|
+
const rimmedAfter = renderer.rimmedZone();
|
|
67
|
+
if (rimmedAfter !== null && rimmedAfter !== rimmedBefore)
|
|
68
|
+
started(FrameTransitionTrigger.ZONE_FOCUS);
|
|
69
|
+
return zonesChanged || moved || resizing;
|
|
70
|
+
};
|
|
71
|
+
const renderFrame = () => {
|
|
72
|
+
renderer.tick(host.now());
|
|
73
|
+
host.renderScene();
|
|
74
|
+
};
|
|
75
|
+
// A running CSS transition, per (property, element); the deadline stops following one whose end never arrives.
|
|
76
|
+
const runningTransitions = new Map();
|
|
77
|
+
let loopHandle = 0;
|
|
78
|
+
const kick = () => {
|
|
79
|
+
if (!loopHandle)
|
|
80
|
+
loopHandle = host.requestFrame(loop);
|
|
81
|
+
};
|
|
82
|
+
const loop = () => {
|
|
83
|
+
loopHandle = 0;
|
|
84
|
+
if (contextLost)
|
|
85
|
+
return;
|
|
86
|
+
const now = host.now();
|
|
87
|
+
for (const [key, deadline] of runningTransitions)
|
|
88
|
+
if (now > deadline)
|
|
89
|
+
runningTransitions.delete(key);
|
|
90
|
+
const moved = measure();
|
|
91
|
+
if (moved || renderer.hasPendingMotion())
|
|
92
|
+
renderFrame();
|
|
93
|
+
if (renderer.hasPendingMotion() || runningTransitions.size > 0)
|
|
94
|
+
kick();
|
|
95
|
+
};
|
|
96
|
+
function draw(force) {
|
|
97
|
+
if (contextLost)
|
|
98
|
+
return;
|
|
99
|
+
const moved = measure();
|
|
100
|
+
if (!moved && !force && !renderer.hasPendingMotion())
|
|
101
|
+
return;
|
|
102
|
+
renderFrame();
|
|
103
|
+
if (renderer.hasPendingMotion())
|
|
104
|
+
kick();
|
|
105
|
+
}
|
|
106
|
+
// Regions render nothing until their labels are ready and remount on an organization switch: re-query, at most
|
|
107
|
+
// once per animation frame, whenever the DOM gains or loses nodes.
|
|
108
|
+
let mutationHandle = 0;
|
|
109
|
+
const mutationObserver = host.createMutationObserver(() => {
|
|
110
|
+
if (mutationHandle)
|
|
111
|
+
return;
|
|
112
|
+
mutationHandle = host.requestFrame(() => { mutationHandle = 0; draw(false); });
|
|
113
|
+
});
|
|
114
|
+
mutationObserver.observe(document.body, { childList: true, subtree: true });
|
|
115
|
+
const transitionKey = (event) => `${event.propertyName}#${transitionTargetId(event.target)}`;
|
|
116
|
+
const onTransitionStart = (event) => {
|
|
117
|
+
const ceilingMs = (appearance?.transitionCeilingSeconds ?? 1.5) * 1000;
|
|
118
|
+
runningTransitions.set(transitionKey(event), host.now() + ceilingMs);
|
|
119
|
+
kick();
|
|
120
|
+
};
|
|
121
|
+
const onTransitionEnd = (event) => {
|
|
122
|
+
runningTransitions.delete(transitionKey(event));
|
|
123
|
+
draw(false);
|
|
124
|
+
};
|
|
125
|
+
document.addEventListener('transitionstart', onTransitionStart, true);
|
|
126
|
+
document.addEventListener('transitionend', onTransitionEnd, true);
|
|
127
|
+
document.addEventListener('transitioncancel', onTransitionEnd, true);
|
|
128
|
+
// Hover: read from the document (the canvas takes no pointer events), resolved to the region under the pointer.
|
|
129
|
+
const onPointerOver = (event) => {
|
|
130
|
+
const target = event.target instanceof Element ? event.target.closest('[data-component-area]') : null;
|
|
131
|
+
if (renderer.setHovered(target && observed.has(target) ? target : null))
|
|
132
|
+
kick();
|
|
133
|
+
};
|
|
134
|
+
const onPointerOut = (event) => {
|
|
135
|
+
if (event.relatedTarget !== null)
|
|
136
|
+
return;
|
|
137
|
+
if (renderer.setHovered(null))
|
|
138
|
+
kick();
|
|
139
|
+
};
|
|
140
|
+
document.addEventListener('pointerover', onPointerOver, { passive: true });
|
|
141
|
+
document.addEventListener('pointerout', onPointerOut, { passive: true });
|
|
142
|
+
const onContextLost = (event) => { event.preventDefault(); contextLost = true; };
|
|
143
|
+
const onContextRestored = () => { contextLost = false; draw(true); };
|
|
144
|
+
host.canvas.addEventListener('webglcontextlost', onContextLost);
|
|
145
|
+
host.canvas.addEventListener('webglcontextrestored', onContextRestored);
|
|
146
|
+
const driver = {
|
|
147
|
+
setAppearance(next) {
|
|
148
|
+
appearance = next ?? appearance;
|
|
149
|
+
renderer.setAppearance(next, { animate: hadAppearance && host.crossFadeAppearance });
|
|
150
|
+
hadAppearance = hadAppearance || next !== null;
|
|
151
|
+
draw(true);
|
|
152
|
+
},
|
|
153
|
+
redraw() {
|
|
154
|
+
draw(true);
|
|
155
|
+
},
|
|
156
|
+
setNavigation(signature) {
|
|
157
|
+
if (signature === '')
|
|
158
|
+
return;
|
|
159
|
+
const previous = navigationSignature;
|
|
160
|
+
navigationSignature = signature;
|
|
161
|
+
if (previous === null || previous === signature)
|
|
162
|
+
return;
|
|
163
|
+
const outcome = renderer.pulse(FrameTransitionTrigger.NAVIGATION);
|
|
164
|
+
if (outcome === FramePulseOutcome.STARTED)
|
|
165
|
+
started(FrameTransitionTrigger.NAVIGATION);
|
|
166
|
+
if (outcome !== FramePulseOutcome.NOT_PLAYED)
|
|
167
|
+
kick();
|
|
168
|
+
},
|
|
169
|
+
setFocusedZone(zone) {
|
|
170
|
+
if (zone === focusedZone)
|
|
171
|
+
return;
|
|
172
|
+
focusedZone = zone;
|
|
173
|
+
draw(false);
|
|
174
|
+
},
|
|
175
|
+
dispose() {
|
|
176
|
+
resizeObserver.disconnect();
|
|
177
|
+
mutationObserver.disconnect();
|
|
178
|
+
host.cancelFrame(mutationHandle);
|
|
179
|
+
host.cancelFrame(loopHandle);
|
|
180
|
+
document.removeEventListener('transitionstart', onTransitionStart, true);
|
|
181
|
+
document.removeEventListener('transitionend', onTransitionEnd, true);
|
|
182
|
+
document.removeEventListener('transitioncancel', onTransitionEnd, true);
|
|
183
|
+
document.removeEventListener('pointerover', onPointerOver);
|
|
184
|
+
document.removeEventListener('pointerout', onPointerOut);
|
|
185
|
+
host.canvas.removeEventListener('webglcontextlost', onContextLost);
|
|
186
|
+
host.canvas.removeEventListener('webglcontextrestored', onContextRestored);
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
// The current appearance, applied at once: a driver re-created by its host (a new renderer, a new camera) must
|
|
190
|
+
// not wait for the NEXT appearance change to show anything.
|
|
191
|
+
driver.setAppearance(initialAppearance);
|
|
192
|
+
return driver;
|
|
193
|
+
}
|
|
194
|
+
/** A stable identity per transitioning element, so two elements animating the same property are two entries. */
|
|
195
|
+
const transitionTargetIds = new WeakMap();
|
|
196
|
+
let nextTransitionTargetId = 0;
|
|
197
|
+
function transitionTargetId(target) {
|
|
198
|
+
if (!target)
|
|
199
|
+
return -1;
|
|
200
|
+
let id = transitionTargetIds.get(target);
|
|
201
|
+
if (id === undefined) {
|
|
202
|
+
id = nextTransitionTargetId++;
|
|
203
|
+
transitionTargetIds.set(target, id);
|
|
204
|
+
}
|
|
205
|
+
return id;
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=frame-driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-driver.js","sourceRoot":"","sources":["../../../../src/frame/frame-driver.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FrameAppearance } from './frame-appearance';\nimport { ShellRegion } from '@wildo-ai/presets-components-models';\n\nimport { FramePulseOutcome, FrameTransitionTrigger, type FrameRegionBox, type FrameRenderer, type FrameViewport, type FrameZoneBox } from './frame-renderer';\nimport { findNavigationZoneElements, findShellRegionElements, measureNavigationZones, measureShellRegions } from '../shell-region-measurement';\n\n/** What the driver needs from its host. Injected, so the scheduling is tested with fake clocks and observers. */\nexport interface FrameDriverHost {\n renderer: Pick<FrameRenderer, 'setAppearance' | 'setLayout' | 'setHovered' | 'setZones' | 'rimmedZone' | 'pulse' | 'hasPendingMotion' | 'tick'>;\n /** Renders the scene as the renderer last left it. */\n renderScene: () => void;\n /** The canvas' size in CSS pixels, as the camera was fitted to it. */\n viewport: () => FrameViewport;\n /** The canvas element, for context-loss events. */\n canvas: EventTarget;\n document: Document;\n requestFrame: (callback: () => void) => number;\n cancelFrame: (handle: number) => void;\n now: () => number;\n createResizeObserver: (callback: () => void) => Pick<ResizeObserver, 'observe' | 'unobserve' | 'disconnect'>;\n createMutationObserver: (callback: () => void) => Pick<MutationObserver, 'observe' | 'disconnect'>;\n /** Whether an appearance change after the first cross-fades (the layout's transitions) or is shown at once. */\n crossFadeAppearance: boolean;\n /**\n * A transition of the frame started (#1826). The canvas counts them on its element, so a live probe can tell that a\n * choreography ran — draws alone cannot, since a hover or a re-layout draws too.\n */\n onTransitionStarted?: (trigger: FrameTransitionTrigger) => void;\n}\n\n/** The canvas-facing surface of a running driver. */\nexport interface FrameDriver {\n /** A new appearance: shown at once the first time, cross-faded afterwards. */\n setAppearance(appearance: FrameAppearance | null): void;\n /** The canvas resized or its pixel ratio changed: the drawing buffer was cleared, so redraw regardless. */\n redraw(): void;\n /**\n * The content's identity: the structural zones' top entries, as the navigation state reports them (#1826). The\n * first value only records; every later CHANGE plays the layout's navigation choreography. An EMPTY signature (no\n * zone holds anything yet: the route sync has not landed) is not a place and is never recorded, so the first\n * landing after the canvas mounted is not played as a navigation.\n */\n setNavigation(signature: string): void;\n /** The navigation state's focused zone (a `data-content-area` value), or null. */\n setFocusedZone(zone: string | null): void;\n dispose(): void;\n}\n\n/**\n * The frame's scheduler (#1823): decides WHEN the renderer draws, and nothing about what it draws.\n *\n * - **At rest**, it draws synchronously inside the callback of whatever changed a region's box (ResizeObserver,\n * a region mounting or leaving), so a slab never lags its region by a frame — and draws NOTHING when every box\n * is where it was.\n * - **While something moves** — a CSS transition in the shell, a cross-fade, a hover emphasis — it runs one loop\n * per animation frame. The loop still renders only when a box moved or the renderer has motion pending. A\n * `transitionstart` anywhere in the document (a button's hover colour) keeps the loop ALIVE, so a moving\n * sidebar is followed frame by frame, but a frame where nothing in the scene changed costs no draw.\n * - **A navigation or a zone focus** (#1826) is an input from the DOM tree (`setNavigation`, `setFocusedZone`); a\n * **sidebar resize** is read off the measurement itself (its width changed while it stayed on screen). Each starts\n * the renderer's choreography and the same loop, which stops the frame the choreography settles. The zone\n * containers are observed like the regions, so a split resized by dragging its divider keeps the rim on the zone.\n * - **After a context loss**, it draws nothing until the context is restored.\n */\nexport function createFrameDriver(host: FrameDriverHost, initialAppearance: FrameAppearance | null): FrameDriver {\n const { renderer, document } = host;\n let contextLost = false;\n let hadAppearance = false;\n let appearance: FrameAppearance | null = null;\n const observed = new Set<HTMLElement>();\n const observedZones = new Set<HTMLElement>();\n const resizeObserver = host.createResizeObserver(() => draw(false));\n let navigationSignature: string | null = null;\n let focusedZone: string | null = null;\n /** The sidebar's width at the last measurement, or null when it was not on screen. */\n let sidebarWidth: number | null = null;\n const started = (trigger: FrameTransitionTrigger): void => host.onTransitionStarted?.(trigger);\n\n const observeOnly = (present: Set<HTMLElement>, into: Set<HTMLElement>): void => {\n for (const element of present) if (!into.has(element)) { resizeObserver.observe(element); into.add(element); }\n for (const element of into) if (!present.has(element)) { resizeObserver.unobserve(element); into.delete(element); }\n };\n\n const measure = (): boolean => {\n // Every marked region and zone is OBSERVED, whatever its box: one collapsed to nothing must redraw the moment it reappears.\n observeOnly(new Set(findShellRegionElements(document)), observed);\n observeOnly(new Set(findNavigationZoneElements(document)), observedZones);\n const boxes: FrameRegionBox[] = measureShellRegions(document).map(({ element, region, left, top, width, height, glass }) => ({ element, region, left, top, width, height, glass }));\n const zones = measureNavigationZones(document);\n const focused = zones.find((zone) => zone.zone === focusedZone)?.element ?? null;\n const zoneBoxes: FrameZoneBox[] = zones.map(({ element, left, top, width, height }) => ({ element, left, top, width, height }));\n const moved = renderer.setLayout(boxes, host.viewport());\n // A sidebar resize: its width changed while it stayed on screen. A sidebar that appears (labels ready, an\n // organization switch) or leaves is not one. Every frame of its animation extends the same pulse.\n const sidebar = boxes.find((box) => box.region === ShellRegion.SIDEBAR);\n const width = sidebar ? sidebar.width : null;\n const resizing = width !== null && sidebarWidth !== null && width !== sidebarWidth;\n sidebarWidth = width;\n // Reported when a pulse STARTS, never per frame: a ResizeObserver measure between two animation frames sees an\n // unchanged width, so any \"was it resizing last time\" flag would re-arm and count every frame of the animation.\n if (resizing && renderer.pulse(FrameTransitionTrigger.SIDEBAR_RESIZE) === FramePulseOutcome.STARTED) started(FrameTransitionTrigger.SIDEBAR_RESIZE);\n const rimmedBefore = renderer.rimmedZone();\n const zonesChanged = renderer.setZones(zoneBoxes, focused);\n const rimmedAfter = renderer.rimmedZone();\n if (rimmedAfter !== null && rimmedAfter !== rimmedBefore) started(FrameTransitionTrigger.ZONE_FOCUS);\n return zonesChanged || moved || resizing;\n };\n\n const renderFrame = (): void => {\n renderer.tick(host.now());\n host.renderScene();\n };\n\n // A running CSS transition, per (property, element); the deadline stops following one whose end never arrives.\n const runningTransitions = new Map<string, number>();\n let loopHandle = 0;\n const kick = (): void => {\n if (!loopHandle) loopHandle = host.requestFrame(loop);\n };\n const loop = (): void => {\n loopHandle = 0;\n if (contextLost) return;\n const now = host.now();\n for (const [key, deadline] of runningTransitions) if (now > deadline) runningTransitions.delete(key);\n const moved = measure();\n if (moved || renderer.hasPendingMotion()) renderFrame();\n if (renderer.hasPendingMotion() || runningTransitions.size > 0) kick();\n };\n\n function draw(force: boolean): void {\n if (contextLost) return;\n const moved = measure();\n if (!moved && !force && !renderer.hasPendingMotion()) return;\n renderFrame();\n if (renderer.hasPendingMotion()) kick();\n }\n\n // Regions render nothing until their labels are ready and remount on an organization switch: re-query, at most\n // once per animation frame, whenever the DOM gains or loses nodes.\n let mutationHandle = 0;\n const mutationObserver = host.createMutationObserver(() => {\n if (mutationHandle) return;\n mutationHandle = host.requestFrame(() => { mutationHandle = 0; draw(false); });\n });\n mutationObserver.observe(document.body, { childList: true, subtree: true });\n\n const transitionKey = (event: Event): string => `${(event as TransitionEvent).propertyName}#${transitionTargetId(event.target)}`;\n const onTransitionStart = (event: Event): void => {\n const ceilingMs = (appearance?.transitionCeilingSeconds ?? 1.5) * 1000;\n runningTransitions.set(transitionKey(event), host.now() + ceilingMs);\n kick();\n };\n const onTransitionEnd = (event: Event): void => {\n runningTransitions.delete(transitionKey(event));\n draw(false);\n };\n document.addEventListener('transitionstart', onTransitionStart, true);\n document.addEventListener('transitionend', onTransitionEnd, true);\n document.addEventListener('transitioncancel', onTransitionEnd, true);\n\n // Hover: read from the document (the canvas takes no pointer events), resolved to the region under the pointer.\n const onPointerOver = (event: Event): void => {\n const target = event.target instanceof Element ? event.target.closest<HTMLElement>('[data-component-area]') : null;\n if (renderer.setHovered(target && observed.has(target) ? target : null)) kick();\n };\n const onPointerOut = (event: Event): void => {\n if ((event as PointerEvent).relatedTarget !== null) return;\n if (renderer.setHovered(null)) kick();\n };\n document.addEventListener('pointerover', onPointerOver, { passive: true });\n document.addEventListener('pointerout', onPointerOut, { passive: true });\n\n const onContextLost = (event: Event): void => { event.preventDefault(); contextLost = true; };\n const onContextRestored = (): void => { contextLost = false; draw(true); };\n host.canvas.addEventListener('webglcontextlost', onContextLost);\n host.canvas.addEventListener('webglcontextrestored', onContextRestored);\n\n const driver: FrameDriver = {\n setAppearance(next) {\n appearance = next ?? appearance;\n renderer.setAppearance(next, { animate: hadAppearance && host.crossFadeAppearance });\n hadAppearance = hadAppearance || next !== null;\n draw(true);\n },\n redraw() {\n draw(true);\n },\n setNavigation(signature) {\n if (signature === '') return;\n const previous = navigationSignature;\n navigationSignature = signature;\n if (previous === null || previous === signature) return;\n const outcome = renderer.pulse(FrameTransitionTrigger.NAVIGATION);\n if (outcome === FramePulseOutcome.STARTED) started(FrameTransitionTrigger.NAVIGATION);\n if (outcome !== FramePulseOutcome.NOT_PLAYED) kick();\n },\n setFocusedZone(zone) {\n if (zone === focusedZone) return;\n focusedZone = zone;\n draw(false);\n },\n dispose() {\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n host.cancelFrame(mutationHandle);\n host.cancelFrame(loopHandle);\n document.removeEventListener('transitionstart', onTransitionStart, true);\n document.removeEventListener('transitionend', onTransitionEnd, true);\n document.removeEventListener('transitioncancel', onTransitionEnd, true);\n document.removeEventListener('pointerover', onPointerOver);\n document.removeEventListener('pointerout', onPointerOut);\n host.canvas.removeEventListener('webglcontextlost', onContextLost);\n host.canvas.removeEventListener('webglcontextrestored', onContextRestored);\n },\n };\n // The current appearance, applied at once: a driver re-created by its host (a new renderer, a new camera) must\n // not wait for the NEXT appearance change to show anything.\n driver.setAppearance(initialAppearance);\n return driver;\n}\n\n/** A stable identity per transitioning element, so two elements animating the same property are two entries. */\nconst transitionTargetIds = new WeakMap<object, number>();\nlet nextTransitionTargetId = 0;\nfunction transitionTargetId(target: EventTarget | null): number {\n if (!target) return -1;\n let id = transitionTargetIds.get(target);\n if (id === undefined) { id = nextTransitionTargetId++; transitionTargetIds.set(target, id); }\n return id;\n}\n"]}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Group } from 'three';
|
|
2
|
+
import { ShellRegion } from '@wildo-ai/presets-components-models';
|
|
3
|
+
import type { FrameAppearance, FrameEasing } from './frame-appearance';
|
|
4
|
+
import { type Layout3DTransitions } from '../layout/layout-3d-vocabulary';
|
|
5
|
+
/** One region the frame draws under, as measured off the DOM. */
|
|
6
|
+
export interface FrameRegionBox {
|
|
7
|
+
/** The region's element: the identity a slab (and its hover state) follows across redraws. */
|
|
8
|
+
element: object;
|
|
9
|
+
region: ShellRegion;
|
|
10
|
+
left: number;
|
|
11
|
+
top: number;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
/** The region already renders a glass material (`data-material-kind="glass"`): no slab under it (#1823, no double glass). */
|
|
15
|
+
glass: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** One navigation zone inside the content region, as measured off the DOM (#1826). */
|
|
18
|
+
export interface FrameZoneBox {
|
|
19
|
+
/** The zone container: the identity its rim emphasis follows across redraws. */
|
|
20
|
+
element: object;
|
|
21
|
+
left: number;
|
|
22
|
+
top: number;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}
|
|
26
|
+
/** The canvas' size in CSS pixels, which the orthographic camera is fitted to. */
|
|
27
|
+
export interface FrameViewport {
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* What starts a transition of the frame (#1826). The driver reports each start to its host, which is how a live
|
|
33
|
+
* probe tells that a choreography ran rather than inferring it from draws.
|
|
34
|
+
*/
|
|
35
|
+
export declare enum FrameTransitionTrigger {
|
|
36
|
+
/** A structural zone's top entry became another place. Plays the layout's `navigation` choreography. */
|
|
37
|
+
NAVIGATION = "navigation",
|
|
38
|
+
/** The sidebar's width changed while it stayed on screen. Plays the layout's `sidebarResize` choreography. */
|
|
39
|
+
SIDEBAR_RESIZE = "sidebar-resize",
|
|
40
|
+
/** The focused zone of a split changed, and the layout rims it. */
|
|
41
|
+
ZONE_FOCUS = "zone-focus"
|
|
42
|
+
}
|
|
43
|
+
/** What a {@link FrameRenderer.pulse} call did. */
|
|
44
|
+
export declare enum FramePulseOutcome {
|
|
45
|
+
/** Nothing plays: no choreography for the trigger, reduced motion, or no appearance yet. */
|
|
46
|
+
NOT_PLAYED = "not-played",
|
|
47
|
+
/** A new pulse started (from rest, or a new rise out of a settle): a transition the driver reports. */
|
|
48
|
+
STARTED = "started",
|
|
49
|
+
/** The trigger's pulse was already rising and keeps rising: nothing new to report. */
|
|
50
|
+
ALREADY_RISING = "already-rising"
|
|
51
|
+
}
|
|
52
|
+
/** The triggers that play a pulse-shaped {@link Layout3DFrameChoreography}. */
|
|
53
|
+
export type FramePulseTrigger = FrameTransitionTrigger.NAVIGATION | FrameTransitionTrigger.SIDEBAR_RESIZE;
|
|
54
|
+
/**
|
|
55
|
+
* The frame's scene, driven imperatively (#1823).
|
|
56
|
+
*
|
|
57
|
+
* It owns the meshes and their uniforms and nothing else: WHEN to draw is the canvas driver's decision. Every
|
|
58
|
+
* method is a plain state change plus {@link tick}, which applies the state for an instant and says whether
|
|
59
|
+
* anything is still animating — so the whole scene logic runs, and is tested, without a WebGL context.
|
|
60
|
+
*
|
|
61
|
+
* Hover emphasises the RIM only, and its progress is clamped to 0–1 even when a spring overshoots. The frost never
|
|
62
|
+
* changes on hover, so hover cannot take a colour outside the contrast bound the appearance derivation proved.
|
|
63
|
+
*/
|
|
64
|
+
export declare class FrameRenderer {
|
|
65
|
+
private readonly group;
|
|
66
|
+
private readonly geometry;
|
|
67
|
+
private readonly backgroundMesh;
|
|
68
|
+
private readonly slabs;
|
|
69
|
+
private boxes;
|
|
70
|
+
private viewport;
|
|
71
|
+
private layoutSignature;
|
|
72
|
+
/** What is on screen now: the last frame applied, mid-cross-fade included. A new appearance fades FROM this. */
|
|
73
|
+
private shown;
|
|
74
|
+
private tween;
|
|
75
|
+
private appearance;
|
|
76
|
+
private hovered;
|
|
77
|
+
private readonly hover;
|
|
78
|
+
private readonly choreographies;
|
|
79
|
+
private readonly rimsFocusedZone;
|
|
80
|
+
private readonly pulses;
|
|
81
|
+
/** The zone the rim is moving toward, or null. */
|
|
82
|
+
private rimmed;
|
|
83
|
+
private zones;
|
|
84
|
+
private zoneSignature;
|
|
85
|
+
private focusedZone;
|
|
86
|
+
/** Each zone's rim emphasis, 0 to 1, stepped exactly like a hover emphasis. */
|
|
87
|
+
private readonly zoneEmphasis;
|
|
88
|
+
private readonly zoneRims;
|
|
89
|
+
/**
|
|
90
|
+
* @param transitions What the layout plays on a navigation, a sidebar resize and a zone focus. Defaults to nothing:
|
|
91
|
+
* a renderer with no layout behind it (a test, a probe) draws the regions and moves only on appearance and hover.
|
|
92
|
+
*/
|
|
93
|
+
constructor(group: Group, transitions?: Pick<Layout3DTransitions, 'navigation' | 'sidebarResize' | 'zoneFocus'>);
|
|
94
|
+
/**
|
|
95
|
+
* The appearance to draw. With `animate`, the change cross-fades over the appearance's transition time —
|
|
96
|
+
* except when the FOREGROUND changed (a light/dark switch): text switches colour at once, and a backdrop
|
|
97
|
+
* halfway between two modes is bounded against neither foreground, so the frame switches at once too.
|
|
98
|
+
*/
|
|
99
|
+
setAppearance(appearance: FrameAppearance | null, options: {
|
|
100
|
+
animate: boolean;
|
|
101
|
+
}): void;
|
|
102
|
+
/** The measured regions. Returns false when nothing moved, so the caller can skip the frame entirely. */
|
|
103
|
+
setLayout(boxes: FrameRegionBox[], viewport: FrameViewport): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* The region element under the pointer, or null. Its rim moves toward the hover opacity — unless the
|
|
106
|
+
* appearance says the frame is static (reduced motion). Returns whether anything will change on screen.
|
|
107
|
+
*/
|
|
108
|
+
setHovered(element: object | null): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* A shell state changed (#1826): play the layout's choreography for that trigger. Nothing plays when the layout
|
|
111
|
+
* declares none for it, under reduced motion (the frame is static), or when the theme's motion duration is zero.
|
|
112
|
+
* Says whether a pulse STARTED, was already rising, or nothing plays.
|
|
113
|
+
*/
|
|
114
|
+
pulse(trigger: FramePulseTrigger): FramePulseOutcome;
|
|
115
|
+
/** The zone whose rim is shown or moving in, or null: what the driver compares to report a zone-focus transition. */
|
|
116
|
+
rimmedZone(): object | null;
|
|
117
|
+
/**
|
|
118
|
+
* The navigation zones inside the content region, and the focused one (the navigation state's `focusedZone`), or
|
|
119
|
+
* null. When the layout rims the focused zone and two or more zones are side by side, the focused one's rim moves
|
|
120
|
+
* toward full emphasis and every other zone's back to none. Returns whether anything will change on screen.
|
|
121
|
+
*/
|
|
122
|
+
setZones(zones: FrameZoneBox[], focused: object | null): boolean;
|
|
123
|
+
/** Whether a cross-fade, a hover or zone emphasis, or a pulse is still moving: the only reasons to draw when no region moved. */
|
|
124
|
+
hasPendingMotion(): boolean;
|
|
125
|
+
/** Apply the state for instant `now` (ms). Returns true while a cross-fade or a hover emphasis is still moving. */
|
|
126
|
+
tick(now: number): boolean;
|
|
127
|
+
dispose(): void;
|
|
128
|
+
private apply;
|
|
129
|
+
/**
|
|
130
|
+
* The focused zone's rim, drawn over the content slab: the content slab's rim colour and width, at most its HOVER
|
|
131
|
+
* opacity, and no fill. Each rim is inset from the content box by the content rim's own width, so it never lands
|
|
132
|
+
* on the content slab's rim band — where it is drawn, it is exactly the case the contrast bound was proved for.
|
|
133
|
+
*/
|
|
134
|
+
private applyZoneRims;
|
|
135
|
+
/** Point every zone's rim emphasis at its target: 1 for the focused zone of a split, 0 otherwise. Returns whether any target moved. */
|
|
136
|
+
private retargetZoneEmphasis;
|
|
137
|
+
private createSlab;
|
|
138
|
+
}
|
|
139
|
+
/** A cubic-Bézier easing evaluated at `x` in 0–1, as CSS `cubic-bezier()` defines it (x → y, solved by Newton then bisection). */
|
|
140
|
+
export declare function evaluateEasing([x1, y1, x2, y2]: FrameEasing, x: number): number;
|
|
141
|
+
//# sourceMappingURL=frame-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-renderer.d.ts","sourceRoot":"","sources":["../../../../src/frame/frame-renderer.ts"],"names":[],"mappings":"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"}
|