@sebastianwessel/isostate 0.1.0 → 0.2.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/README.md +62 -0
- package/dist/animation/controller.d.ts +25 -1
- package/dist/animation/controller.d.ts.map +1 -1
- package/dist/browser/isostate.runtime.js +5 -2448
- package/dist/browser/isostate.runtime.js.map +21 -1
- package/dist/browser-runtime.d.ts +5 -0
- package/dist/browser-runtime.d.ts.map +1 -0
- package/dist/chunks/{index-CDQt8CfR.js → index-DoyWxJDm.js} +321 -20
- package/dist/chunks/index-DoyWxJDm.js.map +1 -0
- package/dist/dsl/index.js +111 -2
- package/dist/dsl/index.js.map +1 -1
- package/dist/dsl/scene-parser.d.ts.map +1 -1
- package/dist/dsl/scene-validator.d.ts +2 -1
- package/dist/dsl/scene-validator.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/rendering/rendering-engine.d.ts +15 -1
- package/dist/rendering/rendering-engine.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/mount-scene.d.ts +9 -0
- package/dist/runtime/mount-scene.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/node.d.ts +39 -0
- package/dist/types/node.d.ts.map +1 -1
- package/dist/types/scene.d.ts +3 -1
- package/dist/types/scene.d.ts.map +1 -1
- package/package.json +13 -1
- package/dist/chunks/index-CDQt8CfR.js.map +0 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { mountScene } from "./runtime/mount-scene.ts";
|
|
2
|
+
import { projectToScreen as projectGridToScreen } from "./utils/projection.ts";
|
|
3
|
+
export { mountScene };
|
|
4
|
+
export declare function projectToScreen(x: number, y: number, cellSize: number, offsetX?: number, offsetY?: number, paddingX?: number, paddingY?: number): ReturnType<typeof projectGridToScreen>;
|
|
5
|
+
//# sourceMappingURL=browser-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-runtime.d.ts","sourceRoot":"","sources":["../src/browser-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,eAAe,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE/E,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,wBAAgB,eAAe,CAC9B,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GACf,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAExC"}
|
|
@@ -258,7 +258,7 @@ function interpolateConnector(id, prevStop, nextStop, t) {
|
|
|
258
258
|
route: interpolateRoute(prev.route, next.route, t),
|
|
259
259
|
layer: t < 1 ? prev.layer : next.layer,
|
|
260
260
|
lifecycle,
|
|
261
|
-
style: cloneConnectorStyle(next.style),
|
|
261
|
+
style: cloneConnectorStyle(t < 1 ? prev.style : next.style),
|
|
262
262
|
start: next.start,
|
|
263
263
|
end: next.end,
|
|
264
264
|
direction: next.direction,
|
|
@@ -908,6 +908,7 @@ function buildSceneDOM(container, bundle, config) {
|
|
|
908
908
|
presence: "removed",
|
|
909
909
|
};
|
|
910
910
|
const instance = createElementInstance(initial, layout, assetResolver);
|
|
911
|
+
instance.anchor = assetAnchorForBounds(bundle, initial);
|
|
911
912
|
instance.node.classList.add(`iso-layer-${def.layer}`);
|
|
912
913
|
instance.node.setAttribute("data-layer", def.layer);
|
|
913
914
|
if (initial.presence === "removed") {
|
|
@@ -936,6 +937,7 @@ function updateElementTransforms(svg, elements, connectors = []) {
|
|
|
936
937
|
const state = map.get(def.id);
|
|
937
938
|
if (!state)
|
|
938
939
|
continue;
|
|
940
|
+
state.current = def;
|
|
939
941
|
updateGeneratedElementContent(state.node, def, layout);
|
|
940
942
|
applyElementTransform(state.node, def, layout);
|
|
941
943
|
applyAmbientClasses(state, def.ambient ?? []);
|
|
@@ -962,6 +964,32 @@ function getConnectorState(svg, id) {
|
|
|
962
964
|
function getResolvedViewBox(bundle) {
|
|
963
965
|
return resolveSceneLayout(bundle).viewBox;
|
|
964
966
|
}
|
|
967
|
+
function getCurrentElementBounds(svg, id) {
|
|
968
|
+
const layout = svg._layout;
|
|
969
|
+
const state = svg._elementMap?.get(id);
|
|
970
|
+
if (!layout || !state || state.current.presence === "removed")
|
|
971
|
+
return undefined;
|
|
972
|
+
return boundsToViewBox(calculateElementBounds(state.current, layout, state.anchor));
|
|
973
|
+
}
|
|
974
|
+
function getGridAreaBounds(bundle, area) {
|
|
975
|
+
const layout = resolveSceneLayout(bundle);
|
|
976
|
+
const [x, y] = area.at;
|
|
977
|
+
const [width, height] = area.size;
|
|
978
|
+
let bounds = emptyBounds();
|
|
979
|
+
for (const point of [
|
|
980
|
+
[x, y],
|
|
981
|
+
[x + width, y],
|
|
982
|
+
[x, y + height],
|
|
983
|
+
[x + width, y + height],
|
|
984
|
+
]) {
|
|
985
|
+
const screen = projectToScreen(point[0], point[1], layout.cellSize, layout.selectedBounds.minX, layout.selectedBounds.minY, layout.padding.x, layout.padding.y);
|
|
986
|
+
bounds = includePoint(bounds, screen.screenX, screen.screenY);
|
|
987
|
+
}
|
|
988
|
+
return boundsToViewBox(bounds);
|
|
989
|
+
}
|
|
990
|
+
function applySceneViewBox(svg, viewBox) {
|
|
991
|
+
svg.setAttribute("viewBox", `${viewBox.minX} ${viewBox.minY} ${viewBox.width} ${viewBox.height}`);
|
|
992
|
+
}
|
|
965
993
|
// ── Lifecycle helpers ─────────────────────────────────────────────────────
|
|
966
994
|
/** Hide an element after its exit animation completes. */
|
|
967
995
|
function hideElementAfterExit(node) {
|
|
@@ -1003,15 +1031,7 @@ function calculateContentBounds(bundle, cellSize) {
|
|
|
1003
1031
|
for (const element of stop.elements ?? []) {
|
|
1004
1032
|
if (element.presence === "removed")
|
|
1005
1033
|
continue;
|
|
1006
|
-
|
|
1007
|
-
const visualSize = calculateVisualSize(element.size, cellSize);
|
|
1008
|
-
const [anchorX, anchorY] = assetAnchorForBounds(bundle, element);
|
|
1009
|
-
bounds = includeBounds(bounds, {
|
|
1010
|
-
minX: rawX - visualSize * anchorX,
|
|
1011
|
-
minY: rawY - visualSize * anchorY,
|
|
1012
|
-
maxX: rawX + visualSize * (1 - anchorX),
|
|
1013
|
-
maxY: rawY + visualSize * (1 - anchorY),
|
|
1014
|
-
});
|
|
1034
|
+
bounds = includeBounds(bounds, calculateRawElementBounds(bundle, element, cellSize));
|
|
1015
1035
|
}
|
|
1016
1036
|
for (const connector of stop.connectors ?? []) {
|
|
1017
1037
|
if (connector.presence === "removed")
|
|
@@ -1021,6 +1041,27 @@ function calculateContentBounds(bundle, cellSize) {
|
|
|
1021
1041
|
}
|
|
1022
1042
|
return normalizeBounds(bounds, cellSize);
|
|
1023
1043
|
}
|
|
1044
|
+
function calculateRawElementBounds(bundle, element, cellSize) {
|
|
1045
|
+
const { rawX, rawY } = projectToRaw(element.pos[0] + element.size, element.pos[1] + element.size, cellSize);
|
|
1046
|
+
const visualSize = calculateVisualSize(element.size, cellSize);
|
|
1047
|
+
const [anchorX, anchorY] = assetAnchorForBounds(bundle, element);
|
|
1048
|
+
return {
|
|
1049
|
+
minX: rawX - visualSize * anchorX,
|
|
1050
|
+
minY: rawY - visualSize * anchorY,
|
|
1051
|
+
maxX: rawX + visualSize * (1 - anchorX),
|
|
1052
|
+
maxY: rawY + visualSize * (1 - anchorY),
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function calculateElementBounds(element, layout, anchor) {
|
|
1056
|
+
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);
|
|
1057
|
+
const visualSize = calculateVisualSize(element.size, layout.cellSize);
|
|
1058
|
+
return {
|
|
1059
|
+
minX: screen.screenX - visualSize * anchor[0],
|
|
1060
|
+
minY: screen.screenY - visualSize * anchor[1],
|
|
1061
|
+
maxX: screen.screenX + visualSize * (1 - anchor[0]),
|
|
1062
|
+
maxY: screen.screenY + visualSize * (1 - anchor[1]),
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1024
1065
|
function assetAnchorForBounds(bundle, element) {
|
|
1025
1066
|
return bundle.assets?.[element.asset]?.anchor ?? [0.5, 1];
|
|
1026
1067
|
}
|
|
@@ -1076,6 +1117,18 @@ function normalizeBounds(bounds, cellSize) {
|
|
|
1076
1117
|
return bounds;
|
|
1077
1118
|
return { minX: 0, minY: 0, maxX: cellSize, maxY: cellSize };
|
|
1078
1119
|
}
|
|
1120
|
+
function boundsToViewBox(bounds) {
|
|
1121
|
+
const centerX = (bounds.minX + bounds.maxX) / 2;
|
|
1122
|
+
const centerY = (bounds.minY + bounds.maxY) / 2;
|
|
1123
|
+
const width = Math.max(1, bounds.maxX - bounds.minX);
|
|
1124
|
+
const height = Math.max(1, bounds.maxY - bounds.minY);
|
|
1125
|
+
return {
|
|
1126
|
+
minX: roundDimension(centerX - width / 2),
|
|
1127
|
+
minY: roundDimension(centerY - height / 2),
|
|
1128
|
+
width: roundDimension(width),
|
|
1129
|
+
height: roundDimension(height),
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1079
1132
|
function includePoint(bounds, x, y) {
|
|
1080
1133
|
return {
|
|
1081
1134
|
minX: Math.min(bounds.minX, x),
|
|
@@ -1242,9 +1295,9 @@ function createElementInstance(def, layout, resolveAsset) {
|
|
|
1242
1295
|
node.addEventListener("animationend", () => {
|
|
1243
1296
|
node.style.animation = "";
|
|
1244
1297
|
}, { once: true });
|
|
1245
|
-
return { node, isHidden: false, entryKey: entryAnim, ambient: new Set() };
|
|
1298
|
+
return { node, current: def, anchor: [0.5, 1], isHidden: false, entryKey: entryAnim, ambient: new Set() };
|
|
1246
1299
|
}
|
|
1247
|
-
return { node, isHidden: false, ambient: new Set() };
|
|
1300
|
+
return { node, current: def, anchor: [0.5, 1], isHidden: false, ambient: new Set() };
|
|
1248
1301
|
}
|
|
1249
1302
|
function isTextAsset(assetId) {
|
|
1250
1303
|
return assetId === BUILT_IN_TEXT_ASSET_ID;
|
|
@@ -1502,8 +1555,7 @@ function applyConnectorAmbientClasses(state, ambient) {
|
|
|
1502
1555
|
state.shaft.classList.remove(`iso-ambient-${name}`);
|
|
1503
1556
|
}
|
|
1504
1557
|
for (const name of next) {
|
|
1505
|
-
|
|
1506
|
-
state.shaft.classList.add(`iso-ambient-${name}`);
|
|
1558
|
+
state.shaft.classList.add(`iso-ambient-${name}`);
|
|
1507
1559
|
}
|
|
1508
1560
|
state.ambient = next;
|
|
1509
1561
|
}
|
|
@@ -1587,6 +1639,8 @@ class AnimationController {
|
|
|
1587
1639
|
_pendingProgress = null;
|
|
1588
1640
|
_destroyed = false;
|
|
1589
1641
|
_listeners = new Map();
|
|
1642
|
+
_cameraAnim = null;
|
|
1643
|
+
_cameraState = null;
|
|
1590
1644
|
// Scroll tracking state
|
|
1591
1645
|
_minScroll = 0;
|
|
1592
1646
|
_maxScroll = 0;
|
|
@@ -1638,6 +1692,11 @@ class AnimationController {
|
|
|
1638
1692
|
this._paused = false;
|
|
1639
1693
|
this._config = { ...DEFAULT_CONFIG, ...config };
|
|
1640
1694
|
this._sceneElement = runtime.sceneElement ?? config.sceneElement ?? null;
|
|
1695
|
+
this._cameraState = {
|
|
1696
|
+
viewBox: getResolvedViewBox(bundle),
|
|
1697
|
+
target: { type: "reset" },
|
|
1698
|
+
isZoomed: false,
|
|
1699
|
+
};
|
|
1641
1700
|
this._engine.init(bundle);
|
|
1642
1701
|
this._bindScroll();
|
|
1643
1702
|
}
|
|
@@ -1654,6 +1713,7 @@ class AnimationController {
|
|
|
1654
1713
|
return;
|
|
1655
1714
|
}
|
|
1656
1715
|
this._progress = clamped;
|
|
1716
|
+
this._cancelCameraAnimation();
|
|
1657
1717
|
if (this._paused)
|
|
1658
1718
|
return;
|
|
1659
1719
|
this._scheduleProgressForward(clamped);
|
|
@@ -1721,6 +1781,47 @@ class AnimationController {
|
|
|
1721
1781
|
this._assertNotDestroyed();
|
|
1722
1782
|
return this._paused;
|
|
1723
1783
|
}
|
|
1784
|
+
zoomToElement(id, options = {}) {
|
|
1785
|
+
this._assertNotDestroyed();
|
|
1786
|
+
const svg = this._getSceneSvg();
|
|
1787
|
+
if (!this._bundle || !svg) {
|
|
1788
|
+
throw new ControllerError("CAMERA_NOT_INITIALIZED", "zoomToElement() requires an initialized scene SVG");
|
|
1789
|
+
}
|
|
1790
|
+
const elementBounds = getCurrentElementBounds(svg, id);
|
|
1791
|
+
if (!elementBounds) {
|
|
1792
|
+
throw new ControllerError("CAMERA_TARGET_NOT_FOUND", `Camera target element "${id}" was not found`, {
|
|
1793
|
+
elementId: id,
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
this._applyCameraDestination(expandViewBox(elementBounds, this._resolveCameraPadding(options)), { type: "element", id }, options);
|
|
1797
|
+
}
|
|
1798
|
+
zoomToArea(area, options = {}) {
|
|
1799
|
+
this._assertNotDestroyed();
|
|
1800
|
+
if (!this._bundle || !this._getSceneSvg()) {
|
|
1801
|
+
throw new ControllerError("CAMERA_NOT_INITIALIZED", "zoomToArea() requires an initialized scene SVG");
|
|
1802
|
+
}
|
|
1803
|
+
this._assertValidCameraArea(area);
|
|
1804
|
+
const bounds = getGridAreaBounds(this._bundle, area);
|
|
1805
|
+
this._applyCameraDestination(expandViewBox(bounds, this._resolveCameraPadding(options)), {
|
|
1806
|
+
type: "area",
|
|
1807
|
+
at: [...area.at],
|
|
1808
|
+
size: [...area.size],
|
|
1809
|
+
}, options);
|
|
1810
|
+
}
|
|
1811
|
+
resetZoom(options = {}) {
|
|
1812
|
+
this._assertNotDestroyed();
|
|
1813
|
+
if (!this._bundle || !this._getSceneSvg()) {
|
|
1814
|
+
throw new ControllerError("CAMERA_NOT_INITIALIZED", "resetZoom() requires an initialized scene SVG");
|
|
1815
|
+
}
|
|
1816
|
+
this._applyCameraDestination(getResolvedViewBox(this._bundle), { type: "reset" }, options);
|
|
1817
|
+
}
|
|
1818
|
+
getCameraState() {
|
|
1819
|
+
this._assertNotDestroyed();
|
|
1820
|
+
if (!this._cameraState) {
|
|
1821
|
+
throw new ControllerError("CAMERA_NOT_INITIALIZED", "Camera state is not initialized");
|
|
1822
|
+
}
|
|
1823
|
+
return copyCameraState(this._cameraState);
|
|
1824
|
+
}
|
|
1724
1825
|
/**
|
|
1725
1826
|
* Destroy controller and clean up all listeners and resources.
|
|
1726
1827
|
*/
|
|
@@ -1729,12 +1830,14 @@ class AnimationController {
|
|
|
1729
1830
|
this._unbindScroll();
|
|
1730
1831
|
this._cancelFrame();
|
|
1731
1832
|
this._cancelTransition();
|
|
1833
|
+
this._cancelCameraAnimation();
|
|
1732
1834
|
if (this._ownsEngine)
|
|
1733
1835
|
this._engine.destroy();
|
|
1734
1836
|
this._listeners.clear();
|
|
1735
1837
|
this._bundle = null;
|
|
1736
1838
|
this._container = null;
|
|
1737
1839
|
this._pendingProgress = null;
|
|
1840
|
+
this._cameraState = null;
|
|
1738
1841
|
this._destroyed = true;
|
|
1739
1842
|
}
|
|
1740
1843
|
// ── Event system ───────────────────────────────────────────────────────
|
|
@@ -1824,6 +1927,121 @@ class AnimationController {
|
|
|
1824
1927
|
updateElementTransforms(svg, updates, connectors);
|
|
1825
1928
|
this._applyLifecycleChanges(updates);
|
|
1826
1929
|
this._applyConnectorLifecycleChanges(connectors);
|
|
1930
|
+
this._applyCameraForProgress(this._progress);
|
|
1931
|
+
}
|
|
1932
|
+
_applyCameraForProgress(progress) {
|
|
1933
|
+
const svg = this._getSceneSvg();
|
|
1934
|
+
if (!this._bundle || !svg)
|
|
1935
|
+
return;
|
|
1936
|
+
const sceneStops = this.scenes;
|
|
1937
|
+
if (sceneStops.length === 0)
|
|
1938
|
+
return;
|
|
1939
|
+
const { previous, next } = surroundingStops(sceneStops, progress);
|
|
1940
|
+
const previousCamera = this._effectiveCameraAt(previous.index);
|
|
1941
|
+
const nextCamera = this._effectiveCameraAt(next.index);
|
|
1942
|
+
const previousViewBox = this._resolveCameraViewBox(previousCamera);
|
|
1943
|
+
const nextViewBox = this._resolveCameraViewBox(nextCamera);
|
|
1944
|
+
const range = next.scene.progress - previous.scene.progress;
|
|
1945
|
+
const rawT = range <= 0 ? 0 : (progress - previous.scene.progress) / range;
|
|
1946
|
+
const t = resolveCameraEasing(nextCamera.easing)(Math.max(0, Math.min(1, rawT)));
|
|
1947
|
+
const viewBox = lerpViewBox(previousViewBox, nextViewBox, t);
|
|
1948
|
+
const target = t < 1 ? previousCamera.target : nextCamera.target;
|
|
1949
|
+
this._setCameraViewBox(svg, viewBox, target);
|
|
1950
|
+
}
|
|
1951
|
+
_effectiveCameraAt(index) {
|
|
1952
|
+
for (let i = index; i >= 0; i--) {
|
|
1953
|
+
const camera = this.scenes[i]?.camera;
|
|
1954
|
+
if (camera) {
|
|
1955
|
+
return {
|
|
1956
|
+
target: camera.target,
|
|
1957
|
+
padding: camera.padding,
|
|
1958
|
+
duration: camera.duration,
|
|
1959
|
+
easing: camera.easing,
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
return { target: { type: "reset" } };
|
|
1964
|
+
}
|
|
1965
|
+
_resolveCameraViewBox(camera) {
|
|
1966
|
+
if (!this._bundle)
|
|
1967
|
+
throw new ControllerError("CAMERA_NOT_INITIALIZED", "Camera requires an initialized bundle");
|
|
1968
|
+
if (camera.target.type === "reset")
|
|
1969
|
+
return getResolvedViewBox(this._bundle);
|
|
1970
|
+
if (camera.target.type === "area") {
|
|
1971
|
+
return expandViewBox(getGridAreaBounds(this._bundle, { at: camera.target.at, size: camera.target.size }), camera.padding ?? 32);
|
|
1972
|
+
}
|
|
1973
|
+
const svg = this._getSceneSvg();
|
|
1974
|
+
if (!svg)
|
|
1975
|
+
throw new ControllerError("CAMERA_NOT_INITIALIZED", "Camera requires an initialized scene SVG");
|
|
1976
|
+
const bounds = getCurrentElementBounds(svg, camera.target.id);
|
|
1977
|
+
if (!bounds) {
|
|
1978
|
+
throw new ControllerError("CAMERA_TARGET_NOT_FOUND", `Camera target element "${camera.target.id}" was not found`, {
|
|
1979
|
+
elementId: camera.target.id,
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
return expandViewBox(bounds, camera.padding ?? 32);
|
|
1983
|
+
}
|
|
1984
|
+
_applyCameraDestination(viewBox, target, options) {
|
|
1985
|
+
const svg = this._getSceneSvg();
|
|
1986
|
+
if (!svg)
|
|
1987
|
+
throw new ControllerError("CAMERA_NOT_INITIALIZED", "Camera requires an initialized scene SVG");
|
|
1988
|
+
this._cancelCameraAnimation();
|
|
1989
|
+
const duration = this._resolveCameraDuration(options);
|
|
1990
|
+
if (duration === 0) {
|
|
1991
|
+
this._setCameraViewBox(svg, viewBox, target);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
const from = currentSvgViewBox(svg) ?? this._cameraState?.viewBox ?? viewBox;
|
|
1995
|
+
const easing = resolveCameraEasing(options.easing ?? this._config.transitionEasing);
|
|
1996
|
+
const start = performance.now();
|
|
1997
|
+
const step = (now) => {
|
|
1998
|
+
const t = Math.min((now - start) / duration, 1);
|
|
1999
|
+
this._setCameraViewBox(svg, lerpViewBox(from, viewBox, easing(t)), target);
|
|
2000
|
+
if (t < 1) {
|
|
2001
|
+
this._cameraAnim = requestAnimationFrame(step);
|
|
2002
|
+
}
|
|
2003
|
+
else {
|
|
2004
|
+
this._cameraAnim = null;
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
this._cameraAnim = requestAnimationFrame(step);
|
|
2008
|
+
}
|
|
2009
|
+
_setCameraViewBox(svg, viewBox, target) {
|
|
2010
|
+
applySceneViewBox(svg, viewBox);
|
|
2011
|
+
const full = this._bundle ? getResolvedViewBox(this._bundle) : viewBox;
|
|
2012
|
+
this._cameraState = {
|
|
2013
|
+
viewBox,
|
|
2014
|
+
target,
|
|
2015
|
+
isZoomed: !sameViewBox(viewBox, full),
|
|
2016
|
+
};
|
|
2017
|
+
this._emit("camera-change", this.getCameraState());
|
|
2018
|
+
}
|
|
2019
|
+
_getSceneSvg() {
|
|
2020
|
+
return (this._sceneElement ?? this._container?.querySelector("svg") ?? null);
|
|
2021
|
+
}
|
|
2022
|
+
_resolveCameraPadding(options) {
|
|
2023
|
+
const padding = options.padding ?? 32;
|
|
2024
|
+
if (!Number.isFinite(padding) || padding < 0 || padding > 2048) {
|
|
2025
|
+
throw new ControllerError("INVALID_CAMERA_OPTIONS", "Camera padding must be between 0 and 2048");
|
|
2026
|
+
}
|
|
2027
|
+
return padding;
|
|
2028
|
+
}
|
|
2029
|
+
_resolveCameraDuration(options) {
|
|
2030
|
+
const duration = options.duration ?? this._config.transitionDuration;
|
|
2031
|
+
if (!Number.isInteger(duration) || duration < 0 || duration > 10000) {
|
|
2032
|
+
throw new ControllerError("INVALID_CAMERA_OPTIONS", "Camera duration must be an integer from 0 to 10000");
|
|
2033
|
+
}
|
|
2034
|
+
return duration;
|
|
2035
|
+
}
|
|
2036
|
+
_assertValidCameraArea(area) {
|
|
2037
|
+
if (!Array.isArray(area.at) ||
|
|
2038
|
+
area.at.length !== 2 ||
|
|
2039
|
+
!area.at.every((value) => Number.isFinite(value) && value >= 0) ||
|
|
2040
|
+
!Array.isArray(area.size) ||
|
|
2041
|
+
area.size.length !== 2 ||
|
|
2042
|
+
!area.size.every((value) => Number.isFinite(value) && value > 0)) {
|
|
2043
|
+
throw new ControllerError("INVALID_CAMERA_OPTIONS", "Camera area must use non-negative at and positive size");
|
|
2044
|
+
}
|
|
1827
2045
|
}
|
|
1828
2046
|
_applyLifecycleChanges(elements) {
|
|
1829
2047
|
for (const elDef of elements) {
|
|
@@ -1910,8 +2128,11 @@ class AnimationController {
|
|
|
1910
2128
|
if (entryAnim === "none")
|
|
1911
2129
|
return;
|
|
1912
2130
|
animateElement(state.node, `iso-anim-${entryAnim}`, "enter");
|
|
2131
|
+
const expectedAnimation = state.node.style.animation;
|
|
1913
2132
|
state.node.addEventListener("animationend", () => {
|
|
1914
|
-
state.node.style.animation
|
|
2133
|
+
if (state.node.style.animation === expectedAnimation) {
|
|
2134
|
+
state.node.style.animation = "";
|
|
2135
|
+
}
|
|
1915
2136
|
}, { once: true });
|
|
1916
2137
|
}
|
|
1917
2138
|
_applyExitAnimation(elDef, state) {
|
|
@@ -1921,8 +2142,11 @@ class AnimationController {
|
|
|
1921
2142
|
return;
|
|
1922
2143
|
}
|
|
1923
2144
|
animateElement(state.node, `iso-anim-${exitAnim}`, "exit");
|
|
2145
|
+
const expectedAnimation = state.node.style.animation;
|
|
1924
2146
|
state.node.addEventListener("animationend", () => {
|
|
1925
|
-
|
|
2147
|
+
if (state.node.style.animation === expectedAnimation) {
|
|
2148
|
+
hideElementAfterExit(state.node);
|
|
2149
|
+
}
|
|
1926
2150
|
}, { once: true });
|
|
1927
2151
|
}
|
|
1928
2152
|
_applyConnectorEntryAnimation(connectorDef, state) {
|
|
@@ -1930,8 +2154,11 @@ class AnimationController {
|
|
|
1930
2154
|
if (entryAnim === "none")
|
|
1931
2155
|
return;
|
|
1932
2156
|
animateElement(state.node, `iso-anim-${entryAnim}`, "enter");
|
|
2157
|
+
const expectedAnimation = state.node.style.animation;
|
|
1933
2158
|
state.node.addEventListener("animationend", () => {
|
|
1934
|
-
state.node.style.animation
|
|
2159
|
+
if (state.node.style.animation === expectedAnimation) {
|
|
2160
|
+
state.node.style.animation = "";
|
|
2161
|
+
}
|
|
1935
2162
|
}, { once: true });
|
|
1936
2163
|
}
|
|
1937
2164
|
_applyConnectorExitAnimation(connectorDef, state) {
|
|
@@ -1941,8 +2168,11 @@ class AnimationController {
|
|
|
1941
2168
|
return;
|
|
1942
2169
|
}
|
|
1943
2170
|
animateElement(state.node, `iso-anim-${exitAnim}`, "exit");
|
|
2171
|
+
const expectedAnimation = state.node.style.animation;
|
|
1944
2172
|
state.node.addEventListener("animationend", () => {
|
|
1945
|
-
|
|
2173
|
+
if (state.node.style.animation === expectedAnimation) {
|
|
2174
|
+
hideElementAfterExit(state.node);
|
|
2175
|
+
}
|
|
1946
2176
|
}, { once: true });
|
|
1947
2177
|
}
|
|
1948
2178
|
// ── Scene transitions ──────────────────────────────────────────────────
|
|
@@ -1991,6 +2221,12 @@ class AnimationController {
|
|
|
1991
2221
|
this._transitionAnim = null;
|
|
1992
2222
|
}
|
|
1993
2223
|
}
|
|
2224
|
+
_cancelCameraAnimation() {
|
|
2225
|
+
if (this._cameraAnim !== null) {
|
|
2226
|
+
cancelAnimationFrame(this._cameraAnim);
|
|
2227
|
+
this._cameraAnim = null;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
1994
2230
|
// ── Scroll binding ─────────────────────────────────────────────────────
|
|
1995
2231
|
_bindScroll() {
|
|
1996
2232
|
const container = this._config.container;
|
|
@@ -2175,9 +2411,70 @@ function oppositeEntryAnimation(exit) {
|
|
|
2175
2411
|
return "none";
|
|
2176
2412
|
}
|
|
2177
2413
|
}
|
|
2414
|
+
function surroundingStops(scenes, progress) {
|
|
2415
|
+
let previousIndex = 0;
|
|
2416
|
+
let nextIndex = scenes.length - 1;
|
|
2417
|
+
for (let i = 0; i < scenes.length; i++) {
|
|
2418
|
+
if (scenes[i].progress <= progress)
|
|
2419
|
+
previousIndex = i;
|
|
2420
|
+
if (scenes[i].progress >= progress) {
|
|
2421
|
+
nextIndex = i;
|
|
2422
|
+
break;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
return {
|
|
2426
|
+
previous: { scene: scenes[previousIndex], index: previousIndex },
|
|
2427
|
+
next: { scene: scenes[nextIndex], index: nextIndex },
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
function expandViewBox(viewBox, padding) {
|
|
2431
|
+
return {
|
|
2432
|
+
minX: roundCameraNumber(viewBox.minX - padding),
|
|
2433
|
+
minY: roundCameraNumber(viewBox.minY - padding),
|
|
2434
|
+
width: roundCameraNumber(Math.max(1, viewBox.width + padding * 2)),
|
|
2435
|
+
height: roundCameraNumber(Math.max(1, viewBox.height + padding * 2)),
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
function lerpViewBox(from, to, t) {
|
|
2439
|
+
return {
|
|
2440
|
+
minX: roundCameraNumber(from.minX + (to.minX - from.minX) * t),
|
|
2441
|
+
minY: roundCameraNumber(from.minY + (to.minY - from.minY) * t),
|
|
2442
|
+
width: roundCameraNumber(from.width + (to.width - from.width) * t),
|
|
2443
|
+
height: roundCameraNumber(from.height + (to.height - from.height) * t),
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
function sameViewBox(a, b) {
|
|
2447
|
+
return a.minX === b.minX && a.minY === b.minY && a.width === b.width && a.height === b.height;
|
|
2448
|
+
}
|
|
2449
|
+
function currentSvgViewBox(svg) {
|
|
2450
|
+
const raw = svg.getAttribute("viewBox");
|
|
2451
|
+
if (!raw)
|
|
2452
|
+
return undefined;
|
|
2453
|
+
const parts = raw.trim().split(/\s+/).map(Number);
|
|
2454
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isFinite(part)))
|
|
2455
|
+
return undefined;
|
|
2456
|
+
return { minX: parts[0], minY: parts[1], width: parts[2], height: parts[3] };
|
|
2457
|
+
}
|
|
2458
|
+
function resolveCameraEasing(easing) {
|
|
2459
|
+
return resolveEasing(easing === "ease-in-out" ? "easeInOutCubic" : easing === "ease-out" ? "easeOutCubic" : "linear");
|
|
2460
|
+
}
|
|
2461
|
+
function copyCameraState(state) {
|
|
2462
|
+
return {
|
|
2463
|
+
viewBox: { ...state.viewBox },
|
|
2464
|
+
target: state.target?.type === "area"
|
|
2465
|
+
? { type: "area", at: [...state.target.at], size: [...state.target.size] }
|
|
2466
|
+
: state.target
|
|
2467
|
+
? { ...state.target }
|
|
2468
|
+
: undefined,
|
|
2469
|
+
isZoomed: state.isZoomed,
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
function roundCameraNumber(value) {
|
|
2473
|
+
return Math.round(value * 1000) / 1000;
|
|
2474
|
+
}
|
|
2178
2475
|
|
|
2179
2476
|
const RUNTIME_BUNDLE_FORMAT = "isostate-runtime-bundle";
|
|
2180
|
-
const RUNTIME_VERSION = "0.
|
|
2477
|
+
const RUNTIME_VERSION = "0.2.0";
|
|
2181
2478
|
const HEX_DIGEST_PATTERN = /^[a-f0-9]{64}$/;
|
|
2182
2479
|
/** Mount a compiled runtime bundle into an HTML element. */
|
|
2183
2480
|
function mountScene(target, bundle, options = {}) {
|
|
@@ -2267,6 +2564,10 @@ function getResolvedConfig(bundle, options = {}) {
|
|
|
2267
2564
|
align: [...bundle.layout.align],
|
|
2268
2565
|
},
|
|
2269
2566
|
viewBox: getResolvedViewBox(bundle),
|
|
2567
|
+
camera: {
|
|
2568
|
+
viewBox: getResolvedViewBox(bundle),
|
|
2569
|
+
isZoomed: false,
|
|
2570
|
+
},
|
|
2270
2571
|
theme: bundle.theme,
|
|
2271
2572
|
themeVars: getResolvedThemeVars(bundle, options.themeVars),
|
|
2272
2573
|
scenes: bundle.scenes.map((scene) => ({
|
|
@@ -2377,4 +2678,4 @@ function rotr(value, bits) {
|
|
|
2377
2678
|
}
|
|
2378
2679
|
|
|
2379
2680
|
export { AnimationController as A, DEFAULT_CELL_SIZE as D, AnimationEngine as a, AssetRegistryImpl as b, applyThemeToElement as c, buildSceneDOM as d, calculateTransform as e, calculateVisualSize as f, composeTheme as g, createAssetRegistry as h, createDefaultRegistry as i, easeInCubic as j, easeInOutCubic as k, easeOutCubic as l, linear as m, mountScene as n, resolveTheme as o, projectToScreen as p, resolveEasing as r };
|
|
2380
|
-
//# sourceMappingURL=index-
|
|
2681
|
+
//# sourceMappingURL=index-DoyWxJDm.js.map
|