@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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { A as AnimationError, C as ControllerError, R as RenderError } from './errors-DyqEkrm5.js';
|
|
2
|
+
import { a as getResolvedViewBox, b as getCurrentElementBounds, c as getElementState, q as getGridAreaBounds, e as updateElementTransforms, g as getResolvedProjectionLayout, f as projectToScreen, l as calculateVisualSize, s as applySceneViewBox, u as unhideElementOnReadd, h as hideElementAfterExit, d as getConnectorState, t as animateElement, j as buildSceneDOM, r as resolveTheme } from './rendering-engine-nkqYx0qc.js';
|
|
3
3
|
import { s as sha256 } from './sha256-4JuurR-b.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -55,6 +55,9 @@ class AnimationEngine {
|
|
|
55
55
|
}
|
|
56
56
|
/** Set current scroll progress (0-1) and compute frame update. */
|
|
57
57
|
setProgress(progress) {
|
|
58
|
+
if (!Number.isFinite(progress)) {
|
|
59
|
+
throw new AnimationError("INVALID_PROGRESS", "setProgress() requires a finite progress value");
|
|
60
|
+
}
|
|
58
61
|
const clamped = Math.max(0, Math.min(1, progress));
|
|
59
62
|
if (this._paused)
|
|
60
63
|
return;
|
|
@@ -234,7 +237,7 @@ function interpolateElement(id, prevStop, nextStop, t) {
|
|
|
234
237
|
pos: interpolatePos(prev.pos, next.pos, t),
|
|
235
238
|
size: prev.size + (next.size - prev.size) * t,
|
|
236
239
|
lifecycle,
|
|
237
|
-
ambient: cloneAmbient(next.ambient),
|
|
240
|
+
ambient: cloneAmbient(t < 1 ? prev.ambient : next.ambient),
|
|
238
241
|
layer: t < 1 ? prev.layer : next.layer,
|
|
239
242
|
entry: next.enter ?? prev.enter,
|
|
240
243
|
exit: next.exit ?? prev.exit,
|
|
@@ -261,10 +264,10 @@ function interpolateConnector(id, prevStop, nextStop, t) {
|
|
|
261
264
|
layer: t < 1 ? prev.layer : next.layer,
|
|
262
265
|
lifecycle,
|
|
263
266
|
style: cloneConnectorStyle(t < 1 ? prev.style : next.style),
|
|
264
|
-
start: next.start,
|
|
265
|
-
end: next.end,
|
|
266
|
-
direction: next.direction,
|
|
267
|
-
ambient: cloneAmbient(next.ambient),
|
|
267
|
+
start: t < 1 ? prev.start : next.start,
|
|
268
|
+
end: t < 1 ? prev.end : next.end,
|
|
269
|
+
direction: t < 1 ? prev.direction : next.direction,
|
|
270
|
+
ambient: cloneAmbient(t < 1 ? prev.ambient : next.ambient),
|
|
268
271
|
entry: next.enter ?? prev.enter,
|
|
269
272
|
exit: next.exit ?? prev.exit,
|
|
270
273
|
};
|
|
@@ -570,8 +573,8 @@ class AnimationController {
|
|
|
570
573
|
// Scroll tracking state
|
|
571
574
|
_minScroll = 0;
|
|
572
575
|
_maxScroll = 0;
|
|
573
|
-
|
|
574
|
-
|
|
576
|
+
_lastTouchY = 0;
|
|
577
|
+
_lastTouchX = 0;
|
|
575
578
|
_isDragging = false;
|
|
576
579
|
// Transition animation state
|
|
577
580
|
_transitionAnim = null;
|
|
@@ -715,6 +718,11 @@ class AnimationController {
|
|
|
715
718
|
}
|
|
716
719
|
const elementBounds = getCurrentElementBounds(svg, id);
|
|
717
720
|
if (!elementBounds) {
|
|
721
|
+
if (getElementState(svg, id)) {
|
|
722
|
+
throw new ControllerError("CAMERA_TARGET_NOT_VISIBLE", `Camera target element "${id}" is currently removed`, {
|
|
723
|
+
elementId: id,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
718
726
|
throw new ControllerError("CAMERA_TARGET_NOT_FOUND", `Camera target element "${id}" was not found`, {
|
|
719
727
|
elementId: id,
|
|
720
728
|
});
|
|
@@ -865,8 +873,8 @@ class AnimationController {
|
|
|
865
873
|
const { previous, next } = surroundingStops(sceneStops, progress);
|
|
866
874
|
const previousCamera = this._effectiveCameraAt(previous.index);
|
|
867
875
|
const nextCamera = this._effectiveCameraAt(next.index);
|
|
868
|
-
const previousViewBox = this._resolveCameraViewBox(previousCamera);
|
|
869
|
-
const nextViewBox = this._resolveCameraViewBox(nextCamera);
|
|
876
|
+
const previousViewBox = this._resolveCameraViewBox(previousCamera, { forInterpolation: true });
|
|
877
|
+
const nextViewBox = this._resolveCameraViewBox(nextCamera, { forInterpolation: true });
|
|
870
878
|
const range = next.scene.progress - previous.scene.progress;
|
|
871
879
|
const rawT = range <= 0 ? 0 : (progress - previous.scene.progress) / range;
|
|
872
880
|
const t = resolveCameraEasing(nextCamera.easing)(Math.max(0, Math.min(1, rawT)));
|
|
@@ -888,7 +896,7 @@ class AnimationController {
|
|
|
888
896
|
}
|
|
889
897
|
return { target: { type: "reset" } };
|
|
890
898
|
}
|
|
891
|
-
_resolveCameraViewBox(camera) {
|
|
899
|
+
_resolveCameraViewBox(camera, options = {}) {
|
|
892
900
|
if (!this._bundle)
|
|
893
901
|
throw new ControllerError("CAMERA_NOT_INITIALIZED", "Camera requires an initialized bundle");
|
|
894
902
|
if (camera.target.type === "reset")
|
|
@@ -899,7 +907,9 @@ class AnimationController {
|
|
|
899
907
|
const svg = this._getSceneSvg();
|
|
900
908
|
if (!svg)
|
|
901
909
|
throw new ControllerError("CAMERA_NOT_INITIALIZED", "Camera requires an initialized scene SVG");
|
|
902
|
-
const bounds =
|
|
910
|
+
const bounds = options.forInterpolation
|
|
911
|
+
? this._resolveElementBoundsForInterpolation(svg, camera.target.id)
|
|
912
|
+
: getCurrentElementBounds(svg, camera.target.id);
|
|
903
913
|
if (!bounds) {
|
|
904
914
|
throw new ControllerError("CAMERA_TARGET_NOT_FOUND", `Camera target element "${camera.target.id}" was not found`, {
|
|
905
915
|
elementId: camera.target.id,
|
|
@@ -907,6 +917,37 @@ class AnimationController {
|
|
|
907
917
|
}
|
|
908
918
|
return expandViewBox(bounds, camera.padding ?? 32);
|
|
909
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Resolve element bounds for scroll/setProgress camera interpolation.
|
|
922
|
+
*
|
|
923
|
+
* Unlike `getCurrentElementBounds()`, this tolerates an element whose
|
|
924
|
+
* resolved presence is transiently "removed" because progress has not yet
|
|
925
|
+
* reached the stop that introduces it (the engine still carries valid
|
|
926
|
+
* authored geometry for it via nearest-geometry fallback). It falls back to
|
|
927
|
+
* that geometry instead of reporting the target as missing so a scene that
|
|
928
|
+
* both adds an element and focuses the camera on it in the same stop can be
|
|
929
|
+
* interpolated into smoothly. A genuinely unknown element id still resolves
|
|
930
|
+
* to `undefined` so callers can throw `CAMERA_TARGET_NOT_FOUND`.
|
|
931
|
+
*/
|
|
932
|
+
_resolveElementBoundsForInterpolation(svg, id) {
|
|
933
|
+
const direct = getCurrentElementBounds(svg, id);
|
|
934
|
+
if (direct)
|
|
935
|
+
return direct;
|
|
936
|
+
const state = getElementState(svg, id);
|
|
937
|
+
if (!state || !this._bundle)
|
|
938
|
+
return undefined;
|
|
939
|
+
const layout = getResolvedProjectionLayout(this._bundle);
|
|
940
|
+
const element = state.current;
|
|
941
|
+
const screen = projectToScreen(element.pos[0] + element.size, element.pos[1] + element.size, layout.cellSize, layout.selectedBounds.minX, layout.selectedBounds.minY, layout.padding.x, layout.padding.y);
|
|
942
|
+
const visualSize = calculateVisualSize(element.size, layout.cellSize);
|
|
943
|
+
const [anchorX, anchorY] = state.anchor;
|
|
944
|
+
return {
|
|
945
|
+
minX: roundCameraNumber(screen.screenX - visualSize * anchorX),
|
|
946
|
+
minY: roundCameraNumber(screen.screenY - visualSize * anchorY),
|
|
947
|
+
width: roundCameraNumber(Math.max(1, visualSize)),
|
|
948
|
+
height: roundCameraNumber(Math.max(1, visualSize)),
|
|
949
|
+
};
|
|
950
|
+
}
|
|
910
951
|
_applyCameraDestination(viewBox, target, options) {
|
|
911
952
|
const svg = this._getSceneSvg();
|
|
912
953
|
if (!svg)
|
|
@@ -1104,19 +1145,20 @@ class AnimationController {
|
|
|
1104
1145
|
// ── Scene transitions ──────────────────────────────────────────────────
|
|
1105
1146
|
_transitionToScene(index) {
|
|
1106
1147
|
this._cancelTransition();
|
|
1107
|
-
if (index === this._sceneIndex)
|
|
1108
|
-
return;
|
|
1109
|
-
this._sceneIndex = index;
|
|
1110
|
-
this._emit("scene-change", index);
|
|
1111
1148
|
const stop = this.scenes[index];
|
|
1112
1149
|
const from = this._progress;
|
|
1113
1150
|
const to = stop.progress;
|
|
1151
|
+
if (index === this._sceneIndex && from === to)
|
|
1152
|
+
return;
|
|
1153
|
+
this._sceneIndex = index;
|
|
1154
|
+
this._emit("scene-change", index);
|
|
1114
1155
|
const duration = this._config.transitionDuration;
|
|
1115
1156
|
if (duration > 0 && from !== to) {
|
|
1116
1157
|
this._animateProgress(from, to, duration);
|
|
1117
1158
|
return;
|
|
1118
1159
|
}
|
|
1119
1160
|
this._progress = to;
|
|
1161
|
+
this._cancelCameraAnimation();
|
|
1120
1162
|
this._scheduleProgressForward(to);
|
|
1121
1163
|
}
|
|
1122
1164
|
_animateProgress(from, to, duration) {
|
|
@@ -1155,6 +1197,9 @@ class AnimationController {
|
|
|
1155
1197
|
}
|
|
1156
1198
|
// ── Scroll binding ─────────────────────────────────────────────────────
|
|
1157
1199
|
_bindScroll() {
|
|
1200
|
+
if (this._config.keyboardControls) {
|
|
1201
|
+
document.addEventListener("keydown", this._onKeyDown);
|
|
1202
|
+
}
|
|
1158
1203
|
const container = this._config.container;
|
|
1159
1204
|
if (!container)
|
|
1160
1205
|
return;
|
|
@@ -1162,9 +1207,6 @@ class AnimationController {
|
|
|
1162
1207
|
this._calculateScrollBounds();
|
|
1163
1208
|
container.addEventListener("scroll", this._onScroll, { passive: true });
|
|
1164
1209
|
window.addEventListener("resize", this._onResize, { passive: true });
|
|
1165
|
-
if (this._config.keyboardControls) {
|
|
1166
|
-
document.addEventListener("keydown", this._onKeyDown);
|
|
1167
|
-
}
|
|
1168
1210
|
if (this._config.touchControls) {
|
|
1169
1211
|
container.addEventListener("touchstart", this._onTouchStart, {
|
|
1170
1212
|
passive: true,
|
|
@@ -1176,12 +1218,12 @@ class AnimationController {
|
|
|
1176
1218
|
}
|
|
1177
1219
|
}
|
|
1178
1220
|
_unbindScroll() {
|
|
1221
|
+
document.removeEventListener("keydown", this._onKeyDown);
|
|
1179
1222
|
const container = this._config.container;
|
|
1180
1223
|
if (!container)
|
|
1181
1224
|
return;
|
|
1182
1225
|
container.removeEventListener("scroll", this._onScroll);
|
|
1183
1226
|
window.removeEventListener("resize", this._onResize);
|
|
1184
|
-
document.removeEventListener("keydown", this._onKeyDown);
|
|
1185
1227
|
container.removeEventListener("touchstart", this._onTouchStart);
|
|
1186
1228
|
container.removeEventListener("touchmove", this._onTouchMove);
|
|
1187
1229
|
container.removeEventListener("touchend", this._onTouchEnd);
|
|
@@ -1210,10 +1252,11 @@ class AnimationController {
|
|
|
1210
1252
|
if (range <= 0)
|
|
1211
1253
|
return;
|
|
1212
1254
|
const rawProgress = (currentScroll - this._minScroll) / range;
|
|
1213
|
-
const clampedProgress = Math.max(this._config.minProgress ?? 0, Math.min(this._config.maxProgress ?? 1, rawProgress));
|
|
1214
1255
|
const sensitivity = this._config.scrollSensitivity ?? 1;
|
|
1256
|
+
const scaledProgress = rawProgress * sensitivity;
|
|
1257
|
+
const clampedProgress = Math.max(this._config.minProgress ?? 0, Math.min(this._config.maxProgress ?? 1, scaledProgress));
|
|
1215
1258
|
if (clampedProgress !== this._progress) {
|
|
1216
|
-
this.setProgress(clampedProgress
|
|
1259
|
+
this.setProgress(clampedProgress);
|
|
1217
1260
|
}
|
|
1218
1261
|
};
|
|
1219
1262
|
_onResize = () => {
|
|
@@ -1239,10 +1282,10 @@ class AnimationController {
|
|
|
1239
1282
|
this._isDragging = true;
|
|
1240
1283
|
const touch = e.touches[0];
|
|
1241
1284
|
if (this._config.scrollDirection === "horizontal") {
|
|
1242
|
-
this.
|
|
1285
|
+
this._lastTouchX = touch.clientX;
|
|
1243
1286
|
}
|
|
1244
1287
|
else {
|
|
1245
|
-
this.
|
|
1288
|
+
this._lastTouchY = touch.clientY;
|
|
1246
1289
|
}
|
|
1247
1290
|
};
|
|
1248
1291
|
_onTouchMove = (e) => {
|
|
@@ -1251,9 +1294,14 @@ class AnimationController {
|
|
|
1251
1294
|
if (!this._isDragging)
|
|
1252
1295
|
return;
|
|
1253
1296
|
const touch = e.touches[0];
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
|
|
1297
|
+
const isHorizontal = this._config.scrollDirection === "horizontal";
|
|
1298
|
+
const delta = isHorizontal ? this._lastTouchX - touch.clientX : this._lastTouchY - touch.clientY;
|
|
1299
|
+
if (isHorizontal) {
|
|
1300
|
+
this._lastTouchX = touch.clientX;
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
this._lastTouchY = touch.clientY;
|
|
1304
|
+
}
|
|
1257
1305
|
const sensitivity = this._config.scrollSensitivity ?? 1.0;
|
|
1258
1306
|
const progressDelta = (delta / 300) * sensitivity;
|
|
1259
1307
|
const newProgress = Math.max(0, Math.min(1, this._progress + progressDelta));
|
|
@@ -1266,10 +1314,7 @@ class AnimationController {
|
|
|
1266
1314
|
};
|
|
1267
1315
|
// ── Pause state ────────────────────────────────────────────────────────
|
|
1268
1316
|
_applyPauseState(pause) {
|
|
1269
|
-
const
|
|
1270
|
-
if (!container)
|
|
1271
|
-
return;
|
|
1272
|
-
const svg = container.querySelector("svg");
|
|
1317
|
+
const svg = this._getSceneSvg();
|
|
1273
1318
|
if (!svg)
|
|
1274
1319
|
return;
|
|
1275
1320
|
const playState = pause ? "paused" : "running";
|
|
@@ -1399,8 +1444,136 @@ function roundCameraNumber(value) {
|
|
|
1399
1444
|
return Math.round(value * 1000) / 1000;
|
|
1400
1445
|
}
|
|
1401
1446
|
|
|
1447
|
+
const CSS_CLASS_INTERACTIVE_ROOT = "iso-interactive";
|
|
1448
|
+
const CSS_CLASS_HOVER = "iso-hover";
|
|
1449
|
+
/**
|
|
1450
|
+
* Owns delegated pointer listeners and event subscriptions for one mounted
|
|
1451
|
+
* scene. Created unconditionally by `mountScene()`; the three DOM listeners
|
|
1452
|
+
* are only attached when `interactive: true`.
|
|
1453
|
+
*/
|
|
1454
|
+
class SceneInteractivity {
|
|
1455
|
+
svg;
|
|
1456
|
+
listeners = new Map();
|
|
1457
|
+
destroyed = false;
|
|
1458
|
+
attached = false;
|
|
1459
|
+
onClick = (event) => this.handleClick(event);
|
|
1460
|
+
onPointerOver = (event) => this.handlePointerOver(event);
|
|
1461
|
+
onPointerOut = (event) => this.handlePointerOut(event);
|
|
1462
|
+
constructor(svg, interactive) {
|
|
1463
|
+
this.svg = svg;
|
|
1464
|
+
if (interactive) {
|
|
1465
|
+
svg.classList.add(CSS_CLASS_INTERACTIVE_ROOT);
|
|
1466
|
+
svg.addEventListener("click", this.onClick);
|
|
1467
|
+
svg.addEventListener("pointerover", this.onPointerOver);
|
|
1468
|
+
svg.addEventListener("pointerout", this.onPointerOut);
|
|
1469
|
+
this.attached = true;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
/** Subscribe to interactivity events. Returns an unsubscribe function. */
|
|
1473
|
+
on(event, listener) {
|
|
1474
|
+
if (this.destroyed) {
|
|
1475
|
+
throw new RenderError("MOUNT_DESTROYED", "Cannot call on() after destroy()");
|
|
1476
|
+
}
|
|
1477
|
+
const set = this.listeners.get(event) ?? new Set();
|
|
1478
|
+
set.add(listener);
|
|
1479
|
+
this.listeners.set(event, set);
|
|
1480
|
+
return () => {
|
|
1481
|
+
set.delete(listener);
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
/** Remove the three delegated listeners and all subscriptions. Safe to call more than once. */
|
|
1485
|
+
destroy() {
|
|
1486
|
+
if (this.destroyed)
|
|
1487
|
+
return;
|
|
1488
|
+
this.destroyed = true;
|
|
1489
|
+
if (this.attached) {
|
|
1490
|
+
this.svg.removeEventListener("click", this.onClick);
|
|
1491
|
+
this.svg.removeEventListener("pointerover", this.onPointerOver);
|
|
1492
|
+
this.svg.removeEventListener("pointerout", this.onPointerOut);
|
|
1493
|
+
this.attached = false;
|
|
1494
|
+
}
|
|
1495
|
+
this.listeners.clear();
|
|
1496
|
+
}
|
|
1497
|
+
handleClick(event) {
|
|
1498
|
+
const id = this.resolveVisibleElementId(event.target);
|
|
1499
|
+
if (!id)
|
|
1500
|
+
return;
|
|
1501
|
+
this.emit("element-click", { id, originalEvent: event });
|
|
1502
|
+
}
|
|
1503
|
+
handlePointerOver(event) {
|
|
1504
|
+
const enteredId = this.resolveVisibleElementId(event.target);
|
|
1505
|
+
if (!enteredId)
|
|
1506
|
+
return;
|
|
1507
|
+
const previousId = this.resolveVisibleElementId(event.relatedTarget);
|
|
1508
|
+
if (enteredId === previousId)
|
|
1509
|
+
return;
|
|
1510
|
+
this.toggleHoverClass(enteredId, true);
|
|
1511
|
+
this.emit("element-enter", { id: enteredId, originalEvent: event });
|
|
1512
|
+
}
|
|
1513
|
+
handlePointerOut(event) {
|
|
1514
|
+
const leftId = this.resolveVisibleElementId(event.target);
|
|
1515
|
+
if (!leftId)
|
|
1516
|
+
return;
|
|
1517
|
+
const nextId = this.resolveVisibleElementId(event.relatedTarget);
|
|
1518
|
+
if (leftId === nextId)
|
|
1519
|
+
return;
|
|
1520
|
+
this.toggleHoverClass(leftId, false);
|
|
1521
|
+
this.emit("element-leave", { id: leftId, originalEvent: event });
|
|
1522
|
+
}
|
|
1523
|
+
toggleHoverClass(id, hovered) {
|
|
1524
|
+
const state = getElementState(this.svg, id);
|
|
1525
|
+
if (!state)
|
|
1526
|
+
return;
|
|
1527
|
+
state.node.classList.toggle(CSS_CLASS_HOVER, hovered);
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Resolve the scene element id for a pointer event target: walk up
|
|
1531
|
+
* `parentNode` from the event target until the root SVG, looking for the
|
|
1532
|
+
* first ancestor `<g>` that is a member of the engine's element state map
|
|
1533
|
+
* (connector groups also carry `data-id` but are never members of this
|
|
1534
|
+
* map, and floor/defs/diagnostics nodes carry no `data-id` at all). An
|
|
1535
|
+
* element whose current presence is `removed` never resolves.
|
|
1536
|
+
*/
|
|
1537
|
+
resolveVisibleElementId(target) {
|
|
1538
|
+
let node = target;
|
|
1539
|
+
while (node && node !== this.svg) {
|
|
1540
|
+
const id = node.getAttribute?.("data-id");
|
|
1541
|
+
if (id) {
|
|
1542
|
+
const state = getElementState(this.svg, id);
|
|
1543
|
+
if (state && state.current.presence !== "removed")
|
|
1544
|
+
return id;
|
|
1545
|
+
return undefined;
|
|
1546
|
+
}
|
|
1547
|
+
node = node.parentNode;
|
|
1548
|
+
}
|
|
1549
|
+
return undefined;
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Invoke every listener for `event`. Exceptions are not caught: consistent
|
|
1553
|
+
* with `AnimationController`'s event system, a throwing listener is
|
|
1554
|
+
* rethrown asynchronously (via `queueMicrotask`) rather than swallowed, so
|
|
1555
|
+
* it surfaces as an unhandled error without breaking delivery to sibling
|
|
1556
|
+
* listeners in the same `Set`.
|
|
1557
|
+
*/
|
|
1558
|
+
emit(event, payload) {
|
|
1559
|
+
const set = this.listeners.get(event);
|
|
1560
|
+
if (!set)
|
|
1561
|
+
return;
|
|
1562
|
+
for (const listener of set) {
|
|
1563
|
+
try {
|
|
1564
|
+
listener(payload);
|
|
1565
|
+
}
|
|
1566
|
+
catch (error) {
|
|
1567
|
+
queueMicrotask(() => {
|
|
1568
|
+
throw error;
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1402
1575
|
const RUNTIME_BUNDLE_FORMAT = "isostate-runtime-bundle";
|
|
1403
|
-
const RUNTIME_VERSION = "0.
|
|
1576
|
+
const RUNTIME_VERSION = "0.5.0";
|
|
1404
1577
|
const HEX_DIGEST_PATTERN = /^[a-f0-9]{64}$/;
|
|
1405
1578
|
/** Mount a compiled runtime bundle into an HTML element. */
|
|
1406
1579
|
function mountScene(target, bundle, options = {}) {
|
|
@@ -1424,17 +1597,20 @@ function mountScene(target, bundle, options = {}) {
|
|
|
1424
1597
|
sceneElement: svg,
|
|
1425
1598
|
});
|
|
1426
1599
|
}
|
|
1600
|
+
const interactivity = new SceneInteractivity(svg, options.interactive === true);
|
|
1427
1601
|
let destroyed = false;
|
|
1428
1602
|
return {
|
|
1429
1603
|
svg,
|
|
1430
1604
|
engine,
|
|
1431
1605
|
controller,
|
|
1432
|
-
getResolvedConfig: () => getResolvedConfig(bundle, options),
|
|
1606
|
+
getResolvedConfig: () => getResolvedConfig(bundle, options, controller),
|
|
1607
|
+
on: (event, listener) => interactivity.on(event, listener),
|
|
1433
1608
|
destroy: () => {
|
|
1434
1609
|
if (destroyed)
|
|
1435
1610
|
return;
|
|
1436
1611
|
destroyed = true;
|
|
1437
|
-
|
|
1612
|
+
interactivity.destroy();
|
|
1613
|
+
destroyControllerSafely(controller);
|
|
1438
1614
|
engine.destroy();
|
|
1439
1615
|
if (svg.parentNode === target) {
|
|
1440
1616
|
target.removeChild(svg);
|
|
@@ -1445,6 +1621,24 @@ function mountScene(target, bundle, options = {}) {
|
|
|
1445
1621
|
},
|
|
1446
1622
|
};
|
|
1447
1623
|
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Destroy the controller, tolerating a controller that was already destroyed
|
|
1626
|
+
* independently (e.g. by application code calling `controller.destroy()`
|
|
1627
|
+
* before `mountScene().destroy()`). Keeps `mountScene().destroy()` idempotent
|
|
1628
|
+
* and defensive so engine cleanup and SVG removal always run.
|
|
1629
|
+
*/
|
|
1630
|
+
function destroyControllerSafely(controller) {
|
|
1631
|
+
if (!controller)
|
|
1632
|
+
return;
|
|
1633
|
+
try {
|
|
1634
|
+
controller.destroy();
|
|
1635
|
+
}
|
|
1636
|
+
catch (error) {
|
|
1637
|
+
if (error instanceof ControllerError && error.code === "CONTROLLER_DESTROYED")
|
|
1638
|
+
return;
|
|
1639
|
+
throw error;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1448
1642
|
function assertMountTarget(target) {
|
|
1449
1643
|
if (!target || typeof target.appendChild !== "function" || typeof target.removeChild !== "function") {
|
|
1450
1644
|
throw new RenderError("INVALID_MOUNT_TARGET", "mountScene() requires a DOM HTMLElement target");
|
|
@@ -1480,20 +1674,17 @@ function validateRuntimeBundle(bundle) {
|
|
|
1480
1674
|
});
|
|
1481
1675
|
}
|
|
1482
1676
|
}
|
|
1483
|
-
function getResolvedConfig(bundle, options = {}) {
|
|
1677
|
+
function getResolvedConfig(bundle, options = {}, controller) {
|
|
1484
1678
|
return {
|
|
1485
1679
|
grid: { cellSize: bundle.grid.cellSize },
|
|
1486
|
-
floor: { ...bundle.floor },
|
|
1680
|
+
floor: { ...bundle.floor, size: [...bundle.floor.size], origin: [...bundle.floor.origin] },
|
|
1487
1681
|
layout: {
|
|
1488
1682
|
...bundle.layout,
|
|
1489
1683
|
padding: { ...bundle.layout.padding },
|
|
1490
1684
|
align: [...bundle.layout.align],
|
|
1491
1685
|
},
|
|
1492
1686
|
viewBox: getResolvedViewBox(bundle),
|
|
1493
|
-
camera:
|
|
1494
|
-
viewBox: getResolvedViewBox(bundle),
|
|
1495
|
-
isZoomed: false,
|
|
1496
|
-
},
|
|
1687
|
+
camera: resolveCameraState(bundle, controller),
|
|
1497
1688
|
theme: bundle.theme,
|
|
1498
1689
|
themeVars: getResolvedThemeVars(bundle, options.themeVars),
|
|
1499
1690
|
scenes: bundle.scenes.map((scene) => ({
|
|
@@ -1502,9 +1693,32 @@ function getResolvedConfig(bundle, options = {}) {
|
|
|
1502
1693
|
})),
|
|
1503
1694
|
layerOrder: bundle.layers
|
|
1504
1695
|
.map((layer) => ({ name: layer.name, order: layer.order }))
|
|
1505
|
-
.sort((a, b) => a.order - b.order || a.name
|
|
1696
|
+
.sort((a, b) => a.order - b.order || compareCodePointOrder(a.name, b.name)),
|
|
1506
1697
|
};
|
|
1507
1698
|
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Live camera state from the controller when one is attached and alive;
|
|
1701
|
+
* otherwise the static bundle-derived default.
|
|
1702
|
+
*/
|
|
1703
|
+
function resolveCameraState(bundle, controller) {
|
|
1704
|
+
if (controller) {
|
|
1705
|
+
try {
|
|
1706
|
+
return controller.getCameraState();
|
|
1707
|
+
}
|
|
1708
|
+
catch (error) {
|
|
1709
|
+
if (!(error instanceof ControllerError))
|
|
1710
|
+
throw error;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
return {
|
|
1714
|
+
viewBox: getResolvedViewBox(bundle),
|
|
1715
|
+
isZoomed: false,
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
/** Locale-independent string comparator so tie-break order is byte-deterministic across hosts. */
|
|
1719
|
+
function compareCodePointOrder(a, b) {
|
|
1720
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1721
|
+
}
|
|
1508
1722
|
function getResolvedThemeVars(bundle, overrides = {}) {
|
|
1509
1723
|
return {
|
|
1510
1724
|
...(resolveTheme(bundle.theme) ?? {}),
|
|
@@ -1539,4 +1753,4 @@ function isPlainObject(value) {
|
|
|
1539
1753
|
}
|
|
1540
1754
|
|
|
1541
1755
|
export { AnimationController as A, AnimationEngine as a, easeInOutCubic as b, easeOutCubic as c, easeInCubic as e, linear as l, mountScene as m, resolveEasing as r };
|
|
1542
|
-
//# sourceMappingURL=index-
|
|
1756
|
+
//# sourceMappingURL=index-CB6XEsbY.js.map
|