@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.
Files changed (49) hide show
  1. package/dist/animation/animation-engine.d.ts +1 -0
  2. package/dist/animation/animation-engine.d.ts.map +1 -1
  3. package/dist/animation/controller.d.ts +34 -2
  4. package/dist/animation/controller.d.ts.map +1 -1
  5. package/dist/browser/isostate.runtime.js +4 -4
  6. package/dist/browser/isostate.runtime.js.map +11 -10
  7. package/dist/chunks/{index-C3qcXbPI.js → index-CB6XEsbY.js} +257 -43
  8. package/dist/chunks/index-CB6XEsbY.js.map +1 -0
  9. package/dist/chunks/{rendering-engine-BUUV3Qq-.js → rendering-engine-nkqYx0qc.js} +215 -36
  10. package/dist/chunks/rendering-engine-nkqYx0qc.js.map +1 -0
  11. package/dist/chunks/{scene-parser-B9SPDLYs.js → scene-parser-F69XGE0l.js} +87 -32
  12. package/dist/chunks/scene-parser-F69XGE0l.js.map +1 -0
  13. package/dist/dsl/browser.js +1 -1
  14. package/dist/dsl/index.js +1 -1
  15. package/dist/dsl/scene-parser.d.ts.map +1 -1
  16. package/dist/dsl/scene-validator.d.ts.map +1 -1
  17. package/dist/editor-support/index.js +1 -1
  18. package/dist/index.d.ts +4 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +470 -4
  21. package/dist/index.js.map +1 -1
  22. package/dist/rendering/animation-css.d.ts.map +1 -1
  23. package/dist/rendering/rendering-engine.d.ts +9 -0
  24. package/dist/rendering/rendering-engine.d.ts.map +1 -1
  25. package/dist/rendering/theme.d.ts +4 -0
  26. package/dist/rendering/theme.d.ts.map +1 -1
  27. package/dist/runtime/diagnostics-overlay.d.ts +29 -0
  28. package/dist/runtime/diagnostics-overlay.d.ts.map +1 -0
  29. package/dist/runtime/export.d.ts +27 -0
  30. package/dist/runtime/export.d.ts.map +1 -0
  31. package/dist/runtime/index.d.ts +1 -0
  32. package/dist/runtime/index.d.ts.map +1 -1
  33. package/dist/runtime/index.js +2 -2
  34. package/dist/runtime/interactivity.d.ts +56 -0
  35. package/dist/runtime/interactivity.d.ts.map +1 -0
  36. package/dist/runtime/mount-scene.d.ts +11 -10
  37. package/dist/runtime/mount-scene.d.ts.map +1 -1
  38. package/dist/types/asset-registry.d.ts +1 -0
  39. package/dist/types/asset-registry.d.ts.map +1 -1
  40. package/dist/types/node.d.ts +14 -0
  41. package/dist/types/node.d.ts.map +1 -1
  42. package/dist/types/runtime-bundle.d.ts +35 -0
  43. package/dist/types/runtime-bundle.d.ts.map +1 -1
  44. package/dist/types/scene.d.ts +2 -0
  45. package/dist/types/scene.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/chunks/index-C3qcXbPI.js.map +0 -1
  48. package/dist/chunks/rendering-engine-BUUV3Qq-.js.map +0 -1
  49. 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-B9SPDLYs.js';
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;AAmvB3B;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAkBrD"}
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;AAitDD,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
+ {"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 { n as getResolvedProjectionLayout, q as projectToRaw, o as getResolvedViewBox, k as getCurrentElementBounds, l as getElementState, p as hideElementAfterExit, u as unhideElementOnReadd, j as getConnectorState, t as updateElementTransforms } from '../chunks/rendering-engine-BUUV3Qq-.js';
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
@@ -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-C3qcXbPI.js';
2
- export { A as AssetRegistryImpl, D as DEFAULT_CELL_SIZE, c as applyThemeToElement, d as buildSceneDOM, e as calculateTransform, f as calculateVisualSize, g as composeTheme, h as createAssetRegistry, i as createDefaultRegistry, r as projectToScreen, s as resolveTheme } from './chunks/rendering-engine-BUUV3Qq-.js';
3
- export { A as AnimationError, C as ControllerError, P as ParseError, R as RenderError, V as ValidationErrorClass } from './chunks/errors-DyqEkrm5.js';
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
- export { guardEntryAnimation, guardExitAnimation, guardLifecycleStatus };
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