@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/dist/dsl/index.js CHANGED
@@ -45,6 +45,7 @@ const VALID_CONNECTOR_SIDES = new Set(["auto", "top", "right", "bottom", "left",
45
45
  const VALID_CONNECTOR_ROUTING_MODES = new Set(["straight", "orthogonal", "manual"]);
46
46
  const VALID_CONNECTOR_ROUTING_PREFERENCES = new Set(["direct", "fewest-bends", "shortest"]);
47
47
  const VALID_CONNECTOR_LANES = new Set(["none", "center-dashed"]);
48
+ const VALID_CAMERA_EASINGS = new Set(["linear", "ease-in-out", "ease-out"]);
48
49
  const VALID_TEXT_ALIGN = new Set(["start", "middle", "end"]);
49
50
  const VALID_TEXT_WEIGHT = new Set(["normal", "bold"]);
50
51
  const VALID_LINE_CAPS = new Set(["butt", "round", "square"]);
@@ -557,6 +558,7 @@ function validateSceneObjectDeltas(document, errors) {
557
558
  }
558
559
  connectors.set(connection.id, normalizeConnectionPlacement(document, connection));
559
560
  }
561
+ validateCamera(first.camera, first.id, elements, connectors, errors);
560
562
  for (const scene of document.scenes.slice(1)) {
561
563
  const updateIds = new Set();
562
564
  for (const update of scene.update?.elements ?? []) {
@@ -648,6 +650,7 @@ function validateSceneObjectDeltas(document, errors) {
648
650
  }));
649
651
  }
650
652
  }
653
+ validateCamera(scene.camera, scene.id, elementsForConnections, connectors, errors);
651
654
  for (const update of scene.update?.elements ?? []) {
652
655
  const existing = elements.get(update.id);
653
656
  if (existing) {
@@ -688,6 +691,55 @@ function collectDocumentElementIds(document) {
688
691
  }
689
692
  return ids;
690
693
  }
694
+ function validateCamera(camera, sceneId, elements, connectors, errors) {
695
+ if (!camera)
696
+ return;
697
+ const target = camera.target;
698
+ const hasElement = "element" in target && target.element !== undefined;
699
+ const hasArea = "area" in target && target.area !== undefined;
700
+ const hasReset = "reset" in target && target.reset !== undefined;
701
+ const targetCount = Number(hasElement) + Number(hasArea) + Number(hasReset);
702
+ if (targetCount !== 1) {
703
+ errors.push(issue("INVALID_CAMERA_TARGET", "Camera target must use exactly one target kind", { sceneId }));
704
+ }
705
+ if (hasElement) {
706
+ if (!isValidIdentifier(target.element)) {
707
+ errors.push(issue("INVALID_CAMERA_TARGET", "Camera element target must be kebab-case", { sceneId }));
708
+ }
709
+ else if (connectors.has(target.element)) {
710
+ errors.push(issue("INVALID_CAMERA_TARGET", "Camera element target cannot reference a connector", { sceneId }));
711
+ }
712
+ else if (!elements.has(target.element)) {
713
+ errors.push(issue("CAMERA_TARGET_NOT_FOUND", `Camera target "${target.element}" was not found`, {
714
+ sceneId,
715
+ elementId: target.element,
716
+ }));
717
+ }
718
+ }
719
+ if (hasArea) {
720
+ if (!isValidPosition(target.area.at) || !isValidPositiveTuple(target.area.size)) {
721
+ errors.push(issue("INVALID_CAMERA_OPTIONS", "Camera area must use non-negative at and positive size", { sceneId }));
722
+ }
723
+ }
724
+ if (hasReset && target.reset !== true) {
725
+ errors.push(issue("INVALID_CAMERA_TARGET", "Camera reset target must be true", { sceneId }));
726
+ }
727
+ if (camera.padding !== undefined) {
728
+ if (hasReset) {
729
+ errors.push(issue("INVALID_CAMERA_OPTIONS", "Camera reset must not define padding", { sceneId }));
730
+ }
731
+ if (!Number.isFinite(camera.padding) || camera.padding < 0 || camera.padding > 2048) {
732
+ errors.push(issue("INVALID_CAMERA_OPTIONS", "Camera padding is invalid", { sceneId }));
733
+ }
734
+ }
735
+ if (camera.duration !== undefined &&
736
+ (!Number.isInteger(camera.duration) || camera.duration < 0 || camera.duration > 10000)) {
737
+ errors.push(issue("INVALID_CAMERA_OPTIONS", "Camera duration is invalid", { sceneId }));
738
+ }
739
+ if (camera.easing !== undefined && !VALID_CAMERA_EASINGS.has(camera.easing)) {
740
+ errors.push(issue("INVALID_CAMERA_OPTIONS", "Camera easing is invalid", { sceneId }));
741
+ }
742
+ }
691
743
  function validateEndpointRemovalRule(scene, connectors, errors) {
692
744
  const removedElements = new Set((scene.remove?.elements ?? []).map((removal) => removal.id));
693
745
  if (removedElements.size === 0)
@@ -1089,6 +1141,7 @@ function resolveSceneSnapshots(document) {
1089
1141
  progress: progress[index],
1090
1142
  elements: snapshotElements,
1091
1143
  connectors: snapshotConnectors,
1144
+ ...(scene.camera ? { camera: normalizeCamera(scene.camera) } : {}),
1092
1145
  });
1093
1146
  for (const removal of scene.remove?.connections ?? []) {
1094
1147
  currentConnectors.delete(removal.id);
@@ -1099,6 +1152,26 @@ function resolveSceneSnapshots(document) {
1099
1152
  }
1100
1153
  return snapshots;
1101
1154
  }
1155
+ function normalizeCamera(camera) {
1156
+ const normalized = "element" in camera.target && camera.target.element !== undefined
1157
+ ? {
1158
+ target: { type: "element", id: camera.target.element },
1159
+ padding: camera.padding ?? 32,
1160
+ }
1161
+ : "area" in camera.target && camera.target.area !== undefined
1162
+ ? {
1163
+ target: { type: "area", at: camera.target.area.at, size: camera.target.area.size },
1164
+ padding: camera.padding ?? 32,
1165
+ }
1166
+ : {
1167
+ target: { type: "reset" },
1168
+ };
1169
+ if (camera.duration !== undefined)
1170
+ normalized.duration = camera.duration;
1171
+ if (camera.easing !== undefined)
1172
+ normalized.easing = camera.easing;
1173
+ return normalized;
1174
+ }
1102
1175
  function deriveProgresses(scenes) {
1103
1176
  if (scenes.length <= 1) {
1104
1177
  return scenes.map(() => 0);
@@ -1413,7 +1486,7 @@ function isGridAxisRoute(route) {
1413
1486
  return true;
1414
1487
  }
1415
1488
 
1416
- const DEFAULT_VERSION = "0.1.0";
1489
+ const DEFAULT_VERSION = "0.2.0";
1417
1490
  const RUNTIME_BUNDLE_FORMAT = "isostate-runtime-bundle";
1418
1491
  const BUILT_IN_TEXT_ASSET_ID = "text";
1419
1492
  const BUILT_IN_PRIMITIVE_ASSET_IDS = new Set(["rectangle", "circle", "polygon", "line"]);
@@ -2116,10 +2189,43 @@ function parseConnectionRemoval(raw, context) {
2116
2189
  }
2117
2190
  return parsed;
2118
2191
  }
2192
+ function parseCamera(raw, context) {
2193
+ const camera = requireObject(raw, context);
2194
+ assertKnownFields(camera, new Set(["target", "padding", "duration", "easing"]), context);
2195
+ const target = requireObject(camera.target, `${context}.target`);
2196
+ assertKnownFields(target, new Set(["element", "area", "reset"]), `${context}.target`);
2197
+ const parsed = { target: {} };
2198
+ if (target.element !== undefined) {
2199
+ parsed.target = { element: requireIdentifier(target.element, `${context}.target.element`) };
2200
+ }
2201
+ if (target.area !== undefined) {
2202
+ const area = requireObject(target.area, `${context}.target.area`);
2203
+ assertKnownFields(area, new Set(["at", "size"]), `${context}.target.area`);
2204
+ parsed.target = {
2205
+ area: {
2206
+ at: parseTuple2(area.at, `${context}.target.area.at`),
2207
+ size: parseTuple2(area.size, `${context}.target.area.size`),
2208
+ },
2209
+ };
2210
+ }
2211
+ if (target.reset !== undefined) {
2212
+ parsed.target = { reset: requireBoolean(target.reset, `${context}.target.reset`) };
2213
+ }
2214
+ if (camera.padding !== undefined) {
2215
+ parsed.padding = requireNumber(camera.padding, `${context}.padding`);
2216
+ }
2217
+ if (camera.duration !== undefined) {
2218
+ parsed.duration = requireNumber(camera.duration, `${context}.duration`);
2219
+ }
2220
+ if (camera.easing !== undefined) {
2221
+ parsed.easing = requireString(camera.easing, `${context}.easing`);
2222
+ }
2223
+ return parsed;
2224
+ }
2119
2225
  function parseScenes(raw) {
2120
2226
  return requireArray(raw, "scenes").map((item, index) => {
2121
2227
  const scene = requireObject(item, `scenes[${index}]`);
2122
- assertKnownFields(scene, new Set(["id", "elements", "connections", "add", "update", "remove"]), `scenes[${index}]`);
2228
+ assertKnownFields(scene, new Set(["id", "elements", "connections", "add", "update", "remove", "camera"]), `scenes[${index}]`);
2123
2229
  const parsed = {
2124
2230
  id: requireIdentifier(scene.id, `scenes[${index}].id`),
2125
2231
  };
@@ -2162,6 +2268,9 @@ function parseScenes(raw) {
2162
2268
  parsed.remove.connections = requireArray(remove.connections, `scenes[${index}].remove.connections`).map((removal, removalIndex) => parseConnectionRemoval(removal, `scenes[${index}].remove.connections[${removalIndex}]`));
2163
2269
  }
2164
2270
  }
2271
+ if (scene.camera !== undefined) {
2272
+ parsed.camera = parseCamera(scene.camera, `scenes[${index}].camera`);
2273
+ }
2165
2274
  return parsed;
2166
2275
  });
2167
2276
  }