@sebastianwessel/isostate 0.4.0 → 0.5.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/animation/animation-engine.d.ts +1 -0
- package/dist/animation/animation-engine.d.ts.map +1 -1
- package/dist/animation/controller.d.ts +34 -2
- package/dist/animation/controller.d.ts.map +1 -1
- package/dist/browser/isostate.runtime.js +4 -4
- package/dist/browser/isostate.runtime.js.map +11 -10
- package/dist/chunks/{index-C3qcXbPI.js → index-CB6XEsbY.js} +257 -43
- package/dist/chunks/index-CB6XEsbY.js.map +1 -0
- package/dist/chunks/{rendering-engine-BUUV3Qq-.js → rendering-engine-nkqYx0qc.js} +215 -36
- package/dist/chunks/rendering-engine-nkqYx0qc.js.map +1 -0
- package/dist/chunks/{scene-parser-B9SPDLYs.js → scene-parser-F69XGE0l.js} +87 -32
- package/dist/chunks/scene-parser-F69XGE0l.js.map +1 -0
- package/dist/dsl/browser.js +1 -1
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/scene-parser.d.ts.map +1 -1
- package/dist/dsl/scene-validator.d.ts.map +1 -1
- package/dist/editor-support/index.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +470 -4
- package/dist/index.js.map +1 -1
- package/dist/rendering/animation-css.d.ts.map +1 -1
- package/dist/rendering/rendering-engine.d.ts +9 -0
- package/dist/rendering/rendering-engine.d.ts.map +1 -1
- package/dist/rendering/theme.d.ts +4 -0
- package/dist/rendering/theme.d.ts.map +1 -1
- package/dist/runtime/diagnostics-overlay.d.ts +29 -0
- package/dist/runtime/diagnostics-overlay.d.ts.map +1 -0
- package/dist/runtime/export.d.ts +27 -0
- package/dist/runtime/export.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/interactivity.d.ts +56 -0
- package/dist/runtime/interactivity.d.ts.map +1 -0
- package/dist/runtime/mount-scene.d.ts +11 -10
- package/dist/runtime/mount-scene.d.ts.map +1 -1
- package/dist/types/asset-registry.d.ts +1 -0
- package/dist/types/asset-registry.d.ts.map +1 -1
- package/dist/types/node.d.ts +14 -0
- package/dist/types/node.d.ts.map +1 -1
- package/dist/types/runtime-bundle.d.ts +35 -0
- package/dist/types/runtime-bundle.d.ts.map +1 -1
- package/dist/types/scene.d.ts +2 -0
- package/dist/types/scene.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/index-C3qcXbPI.js.map +0 -1
- package/dist/chunks/rendering-engine-BUUV3Qq-.js.map +0 -1
- package/dist/chunks/scene-parser-B9SPDLYs.js.map +0 -1
package/dist/dsl/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { c as compileScene, d as deriveProgresses, f as fromJs, a as fromJson, p as parseScene, r as resolveSceneSnapshots, t as toJs, b as toJson, v as validateScene } from '../chunks/scene-parser-
|
|
1
|
+
export { c as compileScene, d as deriveProgresses, f as fromJs, a as fromJson, p as parseScene, r as resolveSceneSnapshots, t as toJs, b as toJson, v as validateScene } from '../chunks/scene-parser-F69XGE0l.js';
|
|
2
2
|
import '../chunks/sha256-4JuurR-b.js';
|
|
3
3
|
import '../chunks/errors-DyqEkrm5.js';
|
|
4
4
|
import 'yaml';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-parser.d.ts","sourceRoot":"","sources":["../../src/dsl/scene-parser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAmBX,aAAa,EAIb,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"scene-parser.d.ts","sourceRoot":"","sources":["../../src/dsl/scene-parser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAmBX,aAAa,EAIb,MAAM,mBAAmB,CAAC;AAovB3B;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAkBrD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-validator.d.ts","sourceRoot":"","sources":["../../src/dsl/scene-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAeX,kBAAkB,EAClB,qBAAqB,EAErB,mBAAmB,EACnB,aAAa,EACb,SAAS,EAGT,gBAAgB,EAEhB,MAAM,mBAAmB,CAAC;AAwE3B,MAAM,WAAW,qBAAqB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,UAAU,EAAE,qBAAqB,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC5B;
|
|
1
|
+
{"version":3,"file":"scene-validator.d.ts","sourceRoot":"","sources":["../../src/dsl/scene-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAeX,kBAAkB,EAClB,qBAAqB,EAErB,mBAAmB,EACnB,aAAa,EACb,SAAS,EAGT,gBAAgB,EAEhB,MAAM,mBAAmB,CAAC;AAwE3B,MAAM,WAAW,qBAAqB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,UAAU,EAAE,qBAAqB,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC5B;AAqxDD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,gBAAgB,CAiBvE;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,qBAAqB,EAAE,CA0GtF;AAsBD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAK9D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { g as getResolvedProjectionLayout, p as projectToRaw, a as getResolvedViewBox, b as getCurrentElementBounds, c as getElementState, h as hideElementAfterExit, u as unhideElementOnReadd, d as getConnectorState, e as updateElementTransforms } from '../chunks/rendering-engine-nkqYx0qc.js';
|
|
2
2
|
import { R as RenderError } from '../chunks/errors-DyqEkrm5.js';
|
|
3
3
|
|
|
4
4
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -3,10 +3,13 @@ export type { CameraGridArea, CameraState, CameraZoomOptions, ControllerConfig,
|
|
|
3
3
|
export { AnimationController } from "./animation/controller.ts";
|
|
4
4
|
export { buildSceneDOM, type RenderConfig, } from "./rendering/rendering-engine.ts";
|
|
5
5
|
export { applyThemeToElement } from "./rendering/theme.ts";
|
|
6
|
+
export { attachDiagnosticsOverlay, type DiagnosticsOverlayHandle, type DiagnosticsOverlayOptions, } from "./runtime/diagnostics-overlay.ts";
|
|
7
|
+
export { exportScenePng, exportSceneSvg, type PngSnapshotOptions, type SnapshotOptions, } from "./runtime/export.ts";
|
|
8
|
+
export type { ElementPointerEvent, MountedSceneEvents } from "./runtime/index.ts";
|
|
6
9
|
export { type MountedScene, type MountSceneOptions, mountScene, type ResolvedRuntimeConfig, type RuntimeBundle, } from "./runtime/index.ts";
|
|
7
10
|
export { AssetRegistryImpl, composeTheme, createAssetRegistry, createDefaultRegistry, resolveTheme, } from "./types/asset-registry.ts";
|
|
8
11
|
export { AnimationError, ControllerError, ParseError, RenderError, ValidationErrorClass, } from "./types/errors.ts";
|
|
9
|
-
export type { AmbientAnimation, AssetCatalogEntry, AssetCategory, AssetDefinition, AssetRegistry, CameraEasing, CameraFocus, CameraTarget, CompiledAsset, CompiledFloor, CompiledLayer, CompiledLayout, ElementPatch, ElementPlacement, ElementRemoval, EntryAnimation, ExitAnimation, FloorConfig, GridConfig, LayerDefinition, LayoutBounds, LayoutFit, LifecycleStatus, ResolvedLayoutConfig, RuntimeElementState, RuntimeSceneStop, SceneDocument, SceneHeader, SceneStep, Theme, ValidationError, ValidationReport, ValidationWarning, } from "./types/index.ts";
|
|
12
|
+
export type { AmbientAnimation, AssetCatalogEntry, AssetCategory, AssetDefinition, AssetRegistry, CameraEasing, CameraFocus, CameraTarget, CompiledAsset, CompiledFloor, CompiledLayer, CompiledLayout, ConnectionPatch, ConnectionPlacement, ConnectionRemoval, ElementPatch, ElementPlacement, ElementRemoval, EntryAnimation, ExitAnimation, FloorConfig, GridConfig, LayerDefinition, LayoutBounds, LayoutFit, LifecycleStatus, PrimitiveContent, ResolvedLayoutConfig, RuntimeElementState, RuntimeSceneStop, SceneDocument, SceneHeader, SceneStep, TextContent, Theme, ValidationError, ValidationReport, ValidationWarning, } from "./types/index.ts";
|
|
10
13
|
export { guardEntryAnimation, guardExitAnimation, guardLifecycleStatus, } from "./types/node.ts";
|
|
11
14
|
export { calculateTransform, calculateVisualSize, DEFAULT_CELL_SIZE, type EasingFn, type EasingType, easeInCubic, easeInOutCubic, easeOutCubic, linear, projectToScreen, resolveEasing, } from "./utils/index.ts";
|
|
12
15
|
//# 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,OAAO,EACN,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,GACjB,MAAM,iCAAiC,CAAC;AACzC,YAAY,EACX,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,GAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACN,aAAa,EACb,KAAK,YAAY,GACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACN,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,qBAAqB,EACrB,YAAY,GACZ,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,cAAc,EACd,eAAe,EACf,UAAU,EACV,WAAW,EACX,oBAAoB,GACpB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACX,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,aAAa,EACb,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,aAAa,EACb,WAAW,EACX,UAAU,EACV,eAAe,EACf,YAAY,EACZ,SAAS,EACT,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,SAAS,EACT,KAAK,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACN,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,MAAM,EACN,eAAe,EACf,aAAa,GACb,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,GACjB,MAAM,iCAAiC,CAAC;AACzC,YAAY,EACX,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,GAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACN,aAAa,EACb,KAAK,YAAY,GACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EACN,wBAAwB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,GAC9B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,cAAc,EACd,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACpB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACN,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,qBAAqB,EACrB,YAAY,GACZ,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,cAAc,EACd,eAAe,EACf,UAAU,EACV,WAAW,EACX,oBAAoB,GACpB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACX,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,aAAa,EACb,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,aAAa,EACb,WAAW,EACX,UAAU,EACV,eAAe,EACf,YAAY,EACZ,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,SAAS,EACT,WAAW,EACX,KAAK,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACN,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,MAAM,EACN,eAAe,EACf,aAAa,GACb,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export { A as AnimationController, a as AnimationEngine, e as easeInCubic, b as easeInOutCubic, c as easeOutCubic, l as linear, m as mountScene, r as resolveEasing } from './chunks/index-
|
|
2
|
-
|
|
3
|
-
export { A as
|
|
1
|
+
export { A as AnimationController, a as AnimationEngine, e as easeInCubic, b as easeInOutCubic, c as easeOutCubic, l as linear, m as mountScene, r as resolveEasing } from './chunks/index-CB6XEsbY.js';
|
|
2
|
+
import { g as getResolvedProjectionLayout, a as getResolvedViewBox, f as projectToScreen, e as updateElementTransforms, c as getElementState, h as hideElementAfterExit, u as unhideElementOnReadd, d as getConnectorState } from './chunks/rendering-engine-nkqYx0qc.js';
|
|
3
|
+
export { A as AssetRegistryImpl, D as DEFAULT_CELL_SIZE, i as applyThemeToElement, j as buildSceneDOM, k as calculateTransform, l as calculateVisualSize, m as composeTheme, n as createAssetRegistry, o as createDefaultRegistry, r as resolveTheme } from './chunks/rendering-engine-nkqYx0qc.js';
|
|
4
|
+
import { R as RenderError } from './chunks/errors-DyqEkrm5.js';
|
|
5
|
+
export { A as AnimationError, C as ControllerError, P as ParseError, V as ValidationErrorClass } from './chunks/errors-DyqEkrm5.js';
|
|
4
6
|
import './chunks/sha256-4JuurR-b.js';
|
|
5
7
|
|
|
6
8
|
/** Runtime set of valid entry animation strings */
|
|
@@ -47,5 +49,469 @@ function guardLifecycleStatus(v) {
|
|
|
47
49
|
return LIFECYCLE_STATUSES.has(v) ? v : undefined;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
53
|
+
const DIAGNOSTICS_ATTR = "data-iso-diagnostics";
|
|
54
|
+
const GRID_STROKE = "var(--iso-diag-grid, rgba(37, 99, 235, 0.35))";
|
|
55
|
+
const TEXT_FILL = "var(--iso-diag-text, #1e3a8a)";
|
|
56
|
+
const ANCHOR_FILL = "var(--iso-diag-anchor, #dc2626)";
|
|
57
|
+
const ROUTE_FILL = "var(--iso-diag-route, #059669)";
|
|
58
|
+
const registryBySvg = new WeakMap();
|
|
59
|
+
/**
|
|
60
|
+
* Attach a development-time diagnostics overlay (grid, coordinates, element
|
|
61
|
+
* anchors, connector route points, and a scene/progress readout) to a mounted
|
|
62
|
+
* scene. See `specs/02-capabilities/diagnostics-overlay.md` for the full
|
|
63
|
+
* normative behavior.
|
|
64
|
+
*/
|
|
65
|
+
function attachDiagnosticsOverlay(mounted, options = {}) {
|
|
66
|
+
assertMounted(mounted);
|
|
67
|
+
const registry = registryBySvg.get(mounted.svg) ?? {};
|
|
68
|
+
registryBySvg.set(mounted.svg, registry);
|
|
69
|
+
// Replace any previously attached overlay: retire its token so its
|
|
70
|
+
// `update()`/`destroy()` become no-ops, then remove its DOM group.
|
|
71
|
+
registry.current?.handle.destroy();
|
|
72
|
+
const token = Symbol("diagnostics-overlay");
|
|
73
|
+
const group = document.createElementNS(SVG_NS, "g");
|
|
74
|
+
group.setAttribute(DIAGNOSTICS_ATTR, "");
|
|
75
|
+
mounted.svg.appendChild(group);
|
|
76
|
+
const render = () => renderOverlay(group, mounted, options);
|
|
77
|
+
render();
|
|
78
|
+
const unsubscribe = subscribeToLiveUpdates(mounted, render);
|
|
79
|
+
const isCurrent = () => registry.current?.token === token;
|
|
80
|
+
const handle = {
|
|
81
|
+
update() {
|
|
82
|
+
if (!isCurrent())
|
|
83
|
+
return;
|
|
84
|
+
render();
|
|
85
|
+
},
|
|
86
|
+
destroy() {
|
|
87
|
+
if (!isCurrent())
|
|
88
|
+
return;
|
|
89
|
+
unsubscribe();
|
|
90
|
+
group.parentNode?.removeChild(group);
|
|
91
|
+
registry.current = undefined;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
registry.current = { token, handle };
|
|
95
|
+
return handle;
|
|
96
|
+
}
|
|
97
|
+
function assertMounted(mounted) {
|
|
98
|
+
if (!mounted.svg.isConnected) {
|
|
99
|
+
throw new RenderError("MOUNT_DESTROYED", "Cannot attach a diagnostics overlay to a destroyed mount");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* When the mount has a controller, re-render on `progress-change` and
|
|
104
|
+
* `camera-change` so the overlay tracks live scroll/camera state. Without a
|
|
105
|
+
* controller, consumers call `update()` manually — returns a no-op teardown.
|
|
106
|
+
*/
|
|
107
|
+
function subscribeToLiveUpdates(mounted, render) {
|
|
108
|
+
const controller = mounted.controller;
|
|
109
|
+
if (!controller)
|
|
110
|
+
return () => undefined;
|
|
111
|
+
const onProgressChange = () => render();
|
|
112
|
+
const onCameraChange = () => render();
|
|
113
|
+
controller.on("progress-change", onProgressChange);
|
|
114
|
+
controller.on("camera-change", onCameraChange);
|
|
115
|
+
return () => {
|
|
116
|
+
controller.off("progress-change", onProgressChange);
|
|
117
|
+
controller.off("camera-change", onCameraChange);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// ── Rendering ────────────────────────────────────────────────────────────────
|
|
121
|
+
function renderOverlay(group, mounted, options) {
|
|
122
|
+
clearChildren(group);
|
|
123
|
+
const bundle = mounted.engine.bundle;
|
|
124
|
+
if (!bundle)
|
|
125
|
+
return;
|
|
126
|
+
const layout = getResolvedProjectionLayout(bundle);
|
|
127
|
+
const project = (x, y) => {
|
|
128
|
+
const screen = projectToScreen(x, y, layout.cellSize, layout.selectedBounds.minX, layout.selectedBounds.minY, layout.padding.x, layout.padding.y);
|
|
129
|
+
return { x: screen.screenX, y: screen.screenY };
|
|
130
|
+
};
|
|
131
|
+
const showGrid = options.grid ?? true;
|
|
132
|
+
const showCoordinates = options.coordinates ?? false;
|
|
133
|
+
const showAnchors = options.anchors ?? true;
|
|
134
|
+
const showRoutes = options.routes ?? true;
|
|
135
|
+
const showReadout = options.readout ?? true;
|
|
136
|
+
if (showGrid)
|
|
137
|
+
appendGrid(group, bundle, project);
|
|
138
|
+
if (showCoordinates)
|
|
139
|
+
appendCoordinates(group, bundle, project);
|
|
140
|
+
if (showAnchors)
|
|
141
|
+
appendAnchors(group, mounted, project);
|
|
142
|
+
if (showRoutes)
|
|
143
|
+
appendRoutes(group, mounted, project);
|
|
144
|
+
if (showReadout)
|
|
145
|
+
appendReadout(group, mounted, bundle);
|
|
146
|
+
}
|
|
147
|
+
function appendGrid(group, bundle, project) {
|
|
148
|
+
const [originX, originY] = bundle.floor.origin;
|
|
149
|
+
const [columns, rows] = bundle.floor.size;
|
|
150
|
+
for (let x = 0; x <= columns; x++) {
|
|
151
|
+
appendGridLine(group, project(originX + x, originY), project(originX + x, originY + rows));
|
|
152
|
+
}
|
|
153
|
+
for (let y = 0; y <= rows; y++) {
|
|
154
|
+
appendGridLine(group, project(originX, originY + y), project(originX + columns, originY + y));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function appendGridLine(group, start, end) {
|
|
158
|
+
const path = document.createElementNS(SVG_NS, "path");
|
|
159
|
+
path.setAttribute("d", `M ${start.x} ${start.y} L ${end.x} ${end.y}`);
|
|
160
|
+
path.setAttribute("stroke", GRID_STROKE);
|
|
161
|
+
path.setAttribute("stroke-width", "1");
|
|
162
|
+
path.setAttribute("fill", "none");
|
|
163
|
+
path.setAttribute("vector-effect", "non-scaling-stroke");
|
|
164
|
+
group.appendChild(path);
|
|
165
|
+
}
|
|
166
|
+
function appendCoordinates(group, bundle, project) {
|
|
167
|
+
const [originX, originY] = bundle.floor.origin;
|
|
168
|
+
const [columns, rows] = bundle.floor.size;
|
|
169
|
+
for (let x = 0; x <= columns; x++) {
|
|
170
|
+
for (let y = 0; y <= rows; y++) {
|
|
171
|
+
const gridX = originX + x;
|
|
172
|
+
const gridY = originY + y;
|
|
173
|
+
const point = project(gridX, gridY);
|
|
174
|
+
const text = document.createElementNS(SVG_NS, "text");
|
|
175
|
+
text.setAttribute("x", String(point.x));
|
|
176
|
+
text.setAttribute("y", String(point.y));
|
|
177
|
+
text.setAttribute("font-size", "8");
|
|
178
|
+
text.setAttribute("fill", TEXT_FILL);
|
|
179
|
+
text.textContent = `${gridX},${gridY}`;
|
|
180
|
+
group.appendChild(text);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function appendAnchors(group, mounted, project) {
|
|
185
|
+
for (const element of mounted.engine.getFrameUpdates()) {
|
|
186
|
+
if (element.lifecycle === "removed")
|
|
187
|
+
continue;
|
|
188
|
+
// The element's anchor is the node's local transform origin, which the
|
|
189
|
+
// renderer always places at `projectToScreen(pos + size, ...)` — see
|
|
190
|
+
// `applyElementTransform()` in rendering-engine.ts. No separate anchor
|
|
191
|
+
// ratio math is needed here to locate the point itself.
|
|
192
|
+
const point = project(element.pos[0] + element.size, element.pos[1] + element.size);
|
|
193
|
+
const circle = document.createElementNS(SVG_NS, "circle");
|
|
194
|
+
circle.setAttribute("cx", String(point.x));
|
|
195
|
+
circle.setAttribute("cy", String(point.y));
|
|
196
|
+
circle.setAttribute("r", "3");
|
|
197
|
+
circle.setAttribute("fill", ANCHOR_FILL);
|
|
198
|
+
group.appendChild(circle);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function appendRoutes(group, mounted, project) {
|
|
202
|
+
for (const connector of mounted.engine.getConnectorFrameUpdates()) {
|
|
203
|
+
if (connector.lifecycle === "removed")
|
|
204
|
+
continue;
|
|
205
|
+
for (const [x, y] of connector.route) {
|
|
206
|
+
const point = project(x, y);
|
|
207
|
+
const rect = document.createElementNS(SVG_NS, "rect");
|
|
208
|
+
rect.setAttribute("x", String(point.x - 2));
|
|
209
|
+
rect.setAttribute("y", String(point.y - 2));
|
|
210
|
+
rect.setAttribute("width", "4");
|
|
211
|
+
rect.setAttribute("height", "4");
|
|
212
|
+
rect.setAttribute("fill", ROUTE_FILL);
|
|
213
|
+
group.appendChild(rect);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function appendReadout(group, mounted, bundle) {
|
|
218
|
+
const viewBox = mounted.controller ? mounted.controller.getCameraState().viewBox : getResolvedViewBox(bundle);
|
|
219
|
+
const progress = mounted.controller ? mounted.controller.getProgress() : mounted.engine.getProgress();
|
|
220
|
+
// Resolved from the engine's own progress-to-stop mapping (not
|
|
221
|
+
// `controller.currentScene`, which only advances on explicit scene
|
|
222
|
+
// navigation and would go stale under scroll-driven progress changes).
|
|
223
|
+
const sceneId = mounted.controller ? mounted.engine.getCurrentState()?.id : undefined;
|
|
224
|
+
const text = document.createElementNS(SVG_NS, "text");
|
|
225
|
+
text.setAttribute("x", String(viewBox.minX + 4));
|
|
226
|
+
text.setAttribute("y", String(viewBox.minY + 10));
|
|
227
|
+
text.setAttribute("font-size", "10");
|
|
228
|
+
text.setAttribute("fill", TEXT_FILL);
|
|
229
|
+
text.textContent = sceneId
|
|
230
|
+
? `scene ${sceneId} · progress ${roundProgress(progress)}`
|
|
231
|
+
: `progress ${roundProgress(progress)}`;
|
|
232
|
+
group.appendChild(text);
|
|
233
|
+
}
|
|
234
|
+
function roundProgress(progress) {
|
|
235
|
+
return Math.round(progress * 1000) / 1000;
|
|
236
|
+
}
|
|
237
|
+
function clearChildren(node) {
|
|
238
|
+
while (node.firstChild)
|
|
239
|
+
node.removeChild(node.firstChild);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
243
|
+
const XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
|
|
244
|
+
const DIAGNOSTICS_OVERLAY_SELECTOR = "[data-iso-diagnostics]";
|
|
245
|
+
/**
|
|
246
|
+
* Serialize a mounted scene to a standalone SVG document string.
|
|
247
|
+
*
|
|
248
|
+
* The exported SVG carries the runtime stylesheet and current visual state
|
|
249
|
+
* as-is. Ambient/entry/exit CSS animations are not restarted or awaited by
|
|
250
|
+
* the export; the snapshot reflects the un-animated base state at the
|
|
251
|
+
* resolved progress.
|
|
252
|
+
*/
|
|
253
|
+
async function exportSceneSvg(mounted, options = {}) {
|
|
254
|
+
assertNotDestroyed(mounted);
|
|
255
|
+
assertValidSnapshotOptions(options);
|
|
256
|
+
return withRestoredProgress(mounted, options.progress, async () => {
|
|
257
|
+
const clone = prepareClone(mounted.svg, options);
|
|
258
|
+
if (options.inlineAssets ?? true) {
|
|
259
|
+
await inlineImageAssets(clone);
|
|
260
|
+
}
|
|
261
|
+
return serializeSvg(clone);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/** Rasterize a mounted scene to a PNG blob. */
|
|
265
|
+
async function exportScenePng(mounted, options = {}) {
|
|
266
|
+
assertNotDestroyed(mounted);
|
|
267
|
+
assertValidPngSnapshotOptions(options);
|
|
268
|
+
const scale = options.scale ?? 2;
|
|
269
|
+
return withRestoredProgress(mounted, options.progress, async () => {
|
|
270
|
+
const clone = prepareClone(mounted.svg, options);
|
|
271
|
+
// PNG export always inlines assets: external hrefs taint or fail canvas rasterization.
|
|
272
|
+
await inlineImageAssets(clone);
|
|
273
|
+
const svgString = serializeSvg(clone);
|
|
274
|
+
const viewBox = readViewBox(clone);
|
|
275
|
+
return rasterize(svgString, viewBox, scale);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
// ── Shared step 1-3: destroyed check, option validation, progress restore ──
|
|
279
|
+
function assertNotDestroyed(mounted) {
|
|
280
|
+
if (!mounted.svg.isConnected) {
|
|
281
|
+
throw new RenderError("EXPORT_TARGET_DESTROYED", "Cannot export a scene whose mount was destroyed");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function assertValidSnapshotOptions(options) {
|
|
285
|
+
if (options.progress !== undefined && !isValidProgress(options.progress)) {
|
|
286
|
+
throw new RenderError("EXPORT_INVALID_OPTIONS", "progress must be a finite number within [0, 1]", {
|
|
287
|
+
progress: options.progress,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function assertValidPngSnapshotOptions(options) {
|
|
292
|
+
assertValidSnapshotOptions(options);
|
|
293
|
+
if (options.scale !== undefined && !isValidScale(options.scale)) {
|
|
294
|
+
throw new RenderError("EXPORT_INVALID_OPTIONS", "scale must be a finite number greater than 0", {
|
|
295
|
+
scale: options.scale,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
if (options.inlineAssets === false) {
|
|
299
|
+
throw new RenderError("EXPORT_INVALID_OPTIONS", "exportScenePng() always inlines assets; inlineAssets: false is unsupported for PNG export");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function isValidProgress(progress) {
|
|
303
|
+
return Number.isFinite(progress) && progress >= 0 && progress <= 1;
|
|
304
|
+
}
|
|
305
|
+
function isValidScale(scale) {
|
|
306
|
+
return Number.isFinite(scale) && scale > 0;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Runs `run` at the requested `progress`, restoring the engine's prior
|
|
310
|
+
* progress afterwards on every code path (success or rejection). When
|
|
311
|
+
* `progress` is omitted, `run` executes at the scene's current progress and
|
|
312
|
+
* no restoration is needed.
|
|
313
|
+
*/
|
|
314
|
+
async function withRestoredProgress(mounted, progress, run) {
|
|
315
|
+
if (progress === undefined) {
|
|
316
|
+
return run();
|
|
317
|
+
}
|
|
318
|
+
const previousProgress = mounted.engine.getProgress();
|
|
319
|
+
try {
|
|
320
|
+
mounted.engine.setProgress(progress);
|
|
321
|
+
applyControllerFrame(mounted);
|
|
322
|
+
return await run();
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
mounted.engine.setProgress(previousProgress);
|
|
326
|
+
applyControllerFrame(mounted);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Applies the engine's current frame to the live SVG so DOM state (element
|
|
331
|
+
* transforms, generated content, connector routes, and lifecycle visibility)
|
|
332
|
+
* matches the engine's resolved progress. Mirrors the non-animated portion of
|
|
333
|
+
* `AnimationController`'s frame-apply path: transforms/content via
|
|
334
|
+
* `updateElementTransforms()`, plus visibility driven by the resolved
|
|
335
|
+
* lifecycle state. CSS entry/exit animation classes are intentionally not
|
|
336
|
+
* (re)triggered here, matching the spec's requirement that ambient/entry
|
|
337
|
+
* animations are irrelevant to export output.
|
|
338
|
+
*/
|
|
339
|
+
function applyControllerFrame(mounted) {
|
|
340
|
+
const svg = mounted.svg;
|
|
341
|
+
const elementUpdates = mounted.engine.getFrameUpdates().map((update) => ({
|
|
342
|
+
id: update.id,
|
|
343
|
+
asset: update.asset,
|
|
344
|
+
pos: update.pos,
|
|
345
|
+
size: update.size,
|
|
346
|
+
layer: update.layer,
|
|
347
|
+
presence: update.lifecycle,
|
|
348
|
+
enter: update.entry,
|
|
349
|
+
exit: update.exit,
|
|
350
|
+
ambient: update.ambient,
|
|
351
|
+
text: update.text,
|
|
352
|
+
primitive: update.primitive,
|
|
353
|
+
}));
|
|
354
|
+
const connectorUpdates = mounted.engine.getConnectorFrameUpdates().map((update) => ({
|
|
355
|
+
id: update.id,
|
|
356
|
+
route: update.route,
|
|
357
|
+
layer: update.layer,
|
|
358
|
+
presence: update.lifecycle,
|
|
359
|
+
style: update.style,
|
|
360
|
+
start: update.start,
|
|
361
|
+
end: update.end,
|
|
362
|
+
direction: update.direction,
|
|
363
|
+
enter: update.entry,
|
|
364
|
+
exit: update.exit,
|
|
365
|
+
ambient: update.ambient,
|
|
366
|
+
}));
|
|
367
|
+
updateElementTransforms(svg, elementUpdates, connectorUpdates);
|
|
368
|
+
applyElementVisibility(svg, elementUpdates);
|
|
369
|
+
applyConnectorVisibility(svg, connectorUpdates);
|
|
370
|
+
}
|
|
371
|
+
function applyElementVisibility(svg, elements) {
|
|
372
|
+
for (const element of elements) {
|
|
373
|
+
const state = getElementState(svg, element.id);
|
|
374
|
+
if (!state)
|
|
375
|
+
continue;
|
|
376
|
+
if (element.presence === "removed") {
|
|
377
|
+
state.isHidden = true;
|
|
378
|
+
hideElementAfterExit(state.node);
|
|
379
|
+
}
|
|
380
|
+
else if (state.isHidden) {
|
|
381
|
+
state.isHidden = false;
|
|
382
|
+
unhideElementOnReadd(state.node);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function applyConnectorVisibility(svg, connectors) {
|
|
387
|
+
for (const connector of connectors) {
|
|
388
|
+
const state = getConnectorState(svg, connector.id);
|
|
389
|
+
if (!state)
|
|
390
|
+
continue;
|
|
391
|
+
if (connector.presence === "removed") {
|
|
392
|
+
state.isHidden = true;
|
|
393
|
+
hideElementAfterExit(state.node);
|
|
394
|
+
}
|
|
395
|
+
else if (state.isHidden) {
|
|
396
|
+
state.isHidden = false;
|
|
397
|
+
unhideElementOnReadd(state.node);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// ── Step 4-5: clone and prepare ────────────────────────────────────────────
|
|
402
|
+
function prepareClone(svg, options) {
|
|
403
|
+
const clone = svg.cloneNode(true);
|
|
404
|
+
const viewBox = readViewBox(clone);
|
|
405
|
+
clone.setAttribute("width", String(viewBox.width));
|
|
406
|
+
clone.setAttribute("height", String(viewBox.height));
|
|
407
|
+
clone.setAttribute("xmlns", SVG_NAMESPACE);
|
|
408
|
+
clone.setAttribute("xmlns:xlink", XLINK_NAMESPACE);
|
|
409
|
+
if (options.background) {
|
|
410
|
+
const rect = document.createElementNS(SVG_NAMESPACE, "rect");
|
|
411
|
+
rect.setAttribute("x", String(viewBox.minX));
|
|
412
|
+
rect.setAttribute("y", String(viewBox.minY));
|
|
413
|
+
rect.setAttribute("width", String(viewBox.width));
|
|
414
|
+
rect.setAttribute("height", String(viewBox.height));
|
|
415
|
+
rect.setAttribute("fill", options.background);
|
|
416
|
+
insertAsFirstChild(clone, rect);
|
|
417
|
+
}
|
|
418
|
+
const diagnosticsOverlay = clone.querySelector(DIAGNOSTICS_OVERLAY_SELECTOR);
|
|
419
|
+
diagnosticsOverlay?.parentNode?.removeChild(diagnosticsOverlay);
|
|
420
|
+
return clone;
|
|
421
|
+
}
|
|
422
|
+
function insertAsFirstChild(parent, child) {
|
|
423
|
+
if (parent.firstChild) {
|
|
424
|
+
parent.insertBefore(child, parent.firstChild);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
parent.appendChild(child);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function readViewBox(svg) {
|
|
431
|
+
const raw = svg.getAttribute("viewBox");
|
|
432
|
+
const parts = (raw ?? "").trim().split(/\s+/).map(Number);
|
|
433
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isFinite(part))) {
|
|
434
|
+
return { minX: 0, minY: 0, width: 0, height: 0 };
|
|
435
|
+
}
|
|
436
|
+
const [minX, minY, width, height] = parts;
|
|
437
|
+
return { minX, minY, width, height };
|
|
438
|
+
}
|
|
439
|
+
// ── Step 6: asset inlining ──────────────────────────────────────────────────
|
|
440
|
+
async function inlineImageAssets(clone) {
|
|
441
|
+
const images = [...clone.querySelectorAll("image")];
|
|
442
|
+
await Promise.all(images.map((image) => inlineImageHref(image)));
|
|
443
|
+
}
|
|
444
|
+
async function inlineImageHref(image) {
|
|
445
|
+
const href = image.getAttribute("href") ?? image.getAttributeNS(XLINK_NAMESPACE, "href");
|
|
446
|
+
if (!href || href.startsWith("data:"))
|
|
447
|
+
return;
|
|
448
|
+
const dataUri = await fetchAsDataUri(href);
|
|
449
|
+
image.setAttribute("href", dataUri);
|
|
450
|
+
image.setAttributeNS(XLINK_NAMESPACE, "href", dataUri);
|
|
451
|
+
}
|
|
452
|
+
async function fetchAsDataUri(url) {
|
|
453
|
+
let response;
|
|
454
|
+
try {
|
|
455
|
+
response = await fetch(url);
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
throw new RenderError("EXPORT_ASSET_FETCH_FAILED", `Failed to fetch asset for inlining: ${url}`, { url });
|
|
459
|
+
}
|
|
460
|
+
if (!response.ok) {
|
|
461
|
+
throw new RenderError("EXPORT_ASSET_FETCH_FAILED", `Failed to fetch asset for inlining: ${url}`, { url });
|
|
462
|
+
}
|
|
463
|
+
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
464
|
+
const buffer = await response.arrayBuffer();
|
|
465
|
+
return `data:${contentType};base64,${arrayBufferToBase64(buffer)}`;
|
|
466
|
+
}
|
|
467
|
+
function arrayBufferToBase64(buffer) {
|
|
468
|
+
let binary = "";
|
|
469
|
+
const bytes = new Uint8Array(buffer);
|
|
470
|
+
for (const byte of bytes)
|
|
471
|
+
binary += String.fromCharCode(byte);
|
|
472
|
+
return btoa(binary);
|
|
473
|
+
}
|
|
474
|
+
// ── Step 7: SVG serialization ───────────────────────────────────────────────
|
|
475
|
+
function serializeSvg(clone) {
|
|
476
|
+
const serializer = new XMLSerializer();
|
|
477
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n${serializer.serializeToString(clone)}`;
|
|
478
|
+
}
|
|
479
|
+
// ── Step 8: PNG rasterization ────────────────────────────────────────────────
|
|
480
|
+
async function rasterize(svgString, viewBox, scale) {
|
|
481
|
+
const width = Math.ceil(viewBox.width * scale);
|
|
482
|
+
const height = Math.ceil(viewBox.height * scale);
|
|
483
|
+
const svgBlob = new Blob([svgString], { type: "image/svg+xml" });
|
|
484
|
+
const objectUrl = URL.createObjectURL(svgBlob);
|
|
485
|
+
try {
|
|
486
|
+
const image = await loadImage(objectUrl);
|
|
487
|
+
const canvas = document.createElement("canvas");
|
|
488
|
+
canvas.width = width;
|
|
489
|
+
canvas.height = height;
|
|
490
|
+
const context = canvas.getContext("2d");
|
|
491
|
+
if (!context) {
|
|
492
|
+
throw new RenderError("EXPORT_RASTERIZE_FAILED", "Canvas 2D context is unavailable");
|
|
493
|
+
}
|
|
494
|
+
context.drawImage(image, 0, 0, width, height);
|
|
495
|
+
const blob = await new Promise((resolve) => {
|
|
496
|
+
canvas.toBlob(resolve, "image/png");
|
|
497
|
+
});
|
|
498
|
+
if (!blob) {
|
|
499
|
+
throw new RenderError("EXPORT_RASTERIZE_FAILED", "Canvas failed to encode PNG output");
|
|
500
|
+
}
|
|
501
|
+
return blob;
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
URL.revokeObjectURL(objectUrl);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function loadImage(src) {
|
|
508
|
+
return new Promise((resolve, reject) => {
|
|
509
|
+
const image = new Image();
|
|
510
|
+
image.onload = () => resolve(image);
|
|
511
|
+
image.onerror = () => reject(new RenderError("EXPORT_RASTERIZE_FAILED", "Failed to load rasterization source image"));
|
|
512
|
+
image.src = src;
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export { RenderError, attachDiagnosticsOverlay, exportScenePng, exportSceneSvg, guardEntryAnimation, guardExitAnimation, guardLifecycleStatus, projectToScreen };
|
|
51
517
|
//# sourceMappingURL=index.js.map
|