@glissade/core 0.50.0 → 0.51.0-pre.1

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/track.d.ts CHANGED
@@ -168,6 +168,14 @@ interface ValueType<T> {
168
168
  /** Accepts easedT outside [0,1] (spring overshoot)? Otherwise clamped. */
169
169
  extrapolates: boolean;
170
170
  equals(a: T, b: T): boolean;
171
+ /**
172
+ * Optional fail-loud structural guard for a keyframe VALUE, called per key by
173
+ * `validateTrack`. Throws (with the offending field named) on a structurally
174
+ * invalid value that `lerp` would otherwise turn into a NaN / native-backend
175
+ * panic much later. ADDITIVE: only throws on genuinely invalid input, so every
176
+ * valid document is unaffected (goldens byte-identical).
177
+ */
178
+ validate?(value: T): void;
171
179
  /** Optional linear-space operators (offset decay + reserved additive blending, §B.6). */
172
180
  add?(a: T, b: T): T;
173
181
  sub?(a: T, b: T): T;
package/dist/track.js CHANGED
@@ -809,6 +809,22 @@ const paintType = {
809
809
  return paintSnap(t, a, b);
810
810
  },
811
811
  extrapolates: false,
812
+ validate: (v) => {
813
+ if (v === null || typeof v !== "object" || !("kind" in v)) throw new TypeError(`paint value must be an object with a 'kind', got ${JSON.stringify(v)}`);
814
+ switch (v.kind) {
815
+ case "color":
816
+ if (typeof v.color !== "string") throw new TypeError("paint kind 'color' requires a string `color`");
817
+ return;
818
+ case "linear":
819
+ case "radial":
820
+ if (!Array.isArray(v.stops) || v.stops.length === 0) throw new TypeError(`paint kind '${v.kind}' requires a non-empty \`stops\` array`);
821
+ return;
822
+ case "mesh":
823
+ if (!Array.isArray(v.points) || v.points.length === 0) throw new TypeError("paint kind 'mesh' requires a non-empty `points` array");
824
+ return;
825
+ default: throw new TypeError(`unknown paint kind '${v.kind}' (expected color | linear | radial | mesh)`);
826
+ }
827
+ },
812
828
  equals: (a, b) => {
813
829
  if (a === b) return true;
814
830
  if (a.kind !== b.kind) return false;
@@ -900,6 +916,11 @@ function validateTrack(track) {
900
916
  if (vt.defaultHandoff === "cut") {
901
917
  for (const k of track.keys) if (k.interp !== "hold") k.interp = "hold";
902
918
  }
919
+ if (vt.validate) for (const k of track.keys) try {
920
+ vt.validate(k.value);
921
+ } catch (e) {
922
+ throw new TrackValidationError(track.target, `invalid ${vt.id} keyframe at t=${k.t}: ${e instanceof Error ? e.message : String(e)}`);
923
+ }
903
924
  const repr = vt.repr ?? vt.id;
904
925
  if (repr === "number" || repr === "vec2") {
905
926
  for (const k of track.keys) if (!(repr === "number" ? typeof k.value === "number" && Number.isFinite(k.value) : Array.isArray(k.value) && k.value.length === 2 && k.value.every((n) => typeof n === "number" && Number.isFinite(n)))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/core",
3
- "version": "0.50.0",
3
+ "version": "0.51.0-pre.1",
4
4
  "description": "glissade core: signals, tracks, timeline document, evaluation, easing, springs, seeded RNG. Zero DOM/Node dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {