@glissade/core 0.2.0 → 0.4.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 ADDED
@@ -0,0 +1,30 @@
1
+ # @glissade/core
2
+
3
+ The engine-agnostic heart: pull-based signals, the serializable keyframe **Timeline document**, the fluent builder that compiles to it, easing + closed-form springs, OKLab color, `bake()` for stateful simulation under seeking, and the v2 analytic layer (ease derivatives, `velocityAt`, `spring.retarget`). Zero DOM or Node dependencies; ≤ 11 kB gz.
4
+
5
+ ```sh
6
+ npm i @glissade/core
7
+ ```
8
+
9
+ ```ts
10
+ import { timeline, spring } from '@glissade/core';
11
+
12
+ const doc = timeline((tl) => {
13
+ tl.to('dot/opacity', 1, { duration: 0.5 })
14
+ .to('dot/position.x', 520, { ease: spring({ stiffness: 170, damping: 14 }) })
15
+ .label('arrived')
16
+ .to('dot/fill', '#7c4dff', { duration: 0.6, at: 'arrived' });
17
+ });
18
+ // `doc` is plain JSON: nothing executes at play time, so any t samples in O(log keys)
19
+ ```
20
+
21
+ One contract underneath everything: evaluation is a **pure function of time** — same inputs, same output, in any order.
22
+
23
+ ## Part of glissade
24
+
25
+ *(glide & slide)* — programmatic motion graphics for TypeScript: realtime-first in any web page, deterministic headless video export from the same code, a visual studio over the same document. No generator functions.
26
+
27
+ - [Repository & full README](https://github.com/tyevco/glissade)
28
+ - [Getting started](https://github.com/tyevco/glissade/blob/main/docs/getting-started.md) · [Concepts](https://github.com/tyevco/glissade/blob/main/docs/concepts.md) · [Interactivity](https://github.com/tyevco/glissade/blob/main/docs/interactivity.md)
29
+
30
+ Apache-2.0.
package/dist/index.d.ts CHANGED
@@ -49,6 +49,15 @@ declare function untracked<T>(fn: () => T): T;
49
49
  * (spring overshoot); non-extrapolating types clamp.
50
50
  */
51
51
  type Vec2 = readonly [number, number];
52
+ /** One bezier contour in Lottie's vertex form: anchor points + RELATIVE in/out tangents. */
53
+ interface PathContour {
54
+ closed: boolean;
55
+ v: Vec2[];
56
+ in: Vec2[];
57
+ out: Vec2[];
58
+ }
59
+ /** The 'path' document value (§2.2): plain JSON, serializes with no new hooks. */
60
+ type PathValue = PathContour[];
52
61
  /** Transition handoff policies (v2 addendum §A.4/§B.1); 'crossfade' reserved. */
53
62
  type HandoffKind = 'cut' | 'decay' | 'spring' | 'blend-from-frozen';
54
63
  interface ValueType<T> {
@@ -76,6 +85,15 @@ declare const vec2Type: ValueType<Vec2>;
76
85
  declare const colorType: ValueType<string>;
77
86
  declare const stringType: ValueType<string>;
78
87
  declare const booleanType: ValueType<boolean>;
88
+ /**
89
+ * Path morphing (§2.2): pairwise lerp of anchors and tangents — exactly how
90
+ * lottie-web morphs, so imported animations are pixel-faithful. Mismatched
91
+ * topology snaps (hold a, then b at t ≥ 1) with a one-time dev warning; the
92
+ * de Casteljau normalization fallback for arbitrary native morphs is tracked
93
+ * future work. Lerp-only: offsets are not well-defined under mismatched
94
+ * topology, so no add/sub/scale — handoffs blend from the frozen value.
95
+ */
96
+ declare const pathType: ValueType<PathValue>;
79
97
  declare class ValueTypeInferenceError extends Error {
80
98
  constructor(value: unknown);
81
99
  }
@@ -521,4 +539,4 @@ declare function normalizeEditedKeys(keys: Key[]): Key[];
521
539
  */
522
540
  declare function mergeSidecar(code: Timeline, sidecar: SidecarDoc | null | undefined): Timeline;
523
541
  //#endregion
524
- export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindTarget, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type CompiledTimeline, type CurveSampler, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type HandoffKind, type Json, type Key, type KeyOpts, type Marker, type OkLab, type Playhead, type Position, PositionError, type ReadonlySignal, type RetargetSpring, type Rgba, type Rng, type SidecarDoc, SidecarVersionError, type Signal, type SignalOptions, type SpringConfig, type SpringEase, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Signal, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
542
+ export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindTarget, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type CompiledTimeline, type CurveSampler, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type HandoffKind, type Json, type Key, type KeyOpts, type Marker, type OkLab, type PathContour, type PathValue, type Playhead, type Position, PositionError, type ReadonlySignal, type RetargetSpring, type Rgba, type Rng, type SidecarDoc, SidecarVersionError, type Signal, type SignalOptions, type SpringConfig, type SpringEase, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Signal, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
package/dist/index.js CHANGED
@@ -287,6 +287,18 @@ function lerpColor(from, to, t) {
287
287
  }));
288
288
  }
289
289
  //#endregion
290
+ //#region src/devWarning.ts
291
+ let devWarn = (msg) => {
292
+ globalThis.console?.warn(`[glissade] ${msg}`);
293
+ };
294
+ function setDevWarning(fn) {
295
+ devWarn = fn;
296
+ }
297
+ /** Internal: emit through the configurable channel. */
298
+ function emitDevWarning(message) {
299
+ devWarn(message);
300
+ }
301
+ //#endregion
290
302
  //#region src/valueTypes.ts
291
303
  /**
292
304
  * Value-type registry with pluggable per-type interpolation (DESIGN.md §2.2).
@@ -348,17 +360,71 @@ function discrete(id) {
348
360
  }
349
361
  const stringType = discrete("string");
350
362
  const booleanType = discrete("boolean");
363
+ const lerpV = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
364
+ /** Topology must match for a morph (contour count, closed flags, vertex counts). */
365
+ function pathTopologyMatches(a, b) {
366
+ if (a.length !== b.length) return false;
367
+ for (let i = 0; i < a.length; i++) {
368
+ const ca = a[i];
369
+ const cb = b[i];
370
+ if (ca.closed !== cb.closed || ca.v.length !== cb.v.length) return false;
371
+ }
372
+ return true;
373
+ }
374
+ let warnedPathTopology = false;
375
+ /**
376
+ * Path morphing (§2.2): pairwise lerp of anchors and tangents — exactly how
377
+ * lottie-web morphs, so imported animations are pixel-faithful. Mismatched
378
+ * topology snaps (hold a, then b at t ≥ 1) with a one-time dev warning; the
379
+ * de Casteljau normalization fallback for arbitrary native morphs is tracked
380
+ * future work. Lerp-only: offsets are not well-defined under mismatched
381
+ * topology, so no add/sub/scale — handoffs blend from the frozen value.
382
+ */
383
+ const pathType = {
384
+ id: "path",
385
+ lerp: (a, b, t) => {
386
+ if (!pathTopologyMatches(a, b)) {
387
+ if (!warnedPathTopology) {
388
+ warnedPathTopology = true;
389
+ emitDevWarning("path lerp with mismatched topology (contour/vertex counts or closed flags differ): snapping instead of morphing — supply matched vertex counts (§2.2)");
390
+ }
391
+ return t >= 1 ? b : a;
392
+ }
393
+ return a.map((ca, i) => {
394
+ const cb = b[i];
395
+ return {
396
+ closed: ca.closed,
397
+ v: ca.v.map((p, j) => lerpV(p, cb.v[j], t)),
398
+ in: ca.in.map((p, j) => lerpV(p, cb.in[j], t)),
399
+ out: ca.out.map((p, j) => lerpV(p, cb.out[j], t))
400
+ };
401
+ });
402
+ },
403
+ extrapolates: false,
404
+ equals: (a, b) => {
405
+ if (a === b) return true;
406
+ if (!pathTopologyMatches(a, b)) return false;
407
+ const eq = (x, y) => x[0] === y[0] && x[1] === y[1];
408
+ return a.every((ca, i) => {
409
+ const cb = b[i];
410
+ return ca.v.every((p, j) => eq(p, cb.v[j])) && ca.in.every((p, j) => eq(p, cb.in[j])) && ca.out.every((p, j) => eq(p, cb.out[j]));
411
+ });
412
+ },
413
+ defaultHandoff: "blend-from-frozen"
414
+ };
351
415
  var ValueTypeInferenceError = class extends Error {
352
416
  constructor(value) {
353
417
  super(`cannot infer a value type for ${JSON.stringify(value)}; register a custom type`);
354
418
  this.name = "ValueTypeInferenceError";
355
419
  }
356
420
  };
421
+ const isContour = (c) => typeof c === "object" && c !== null && typeof c.closed === "boolean" && Array.isArray(c.v) && Array.isArray(c.in) && Array.isArray(c.out);
357
422
  /** Infer a registered type id from a sample value (builder + bake authoring surfaces). */
358
423
  function inferValueType(value) {
359
424
  if (typeof value === "number") return "number";
360
425
  if (typeof value === "boolean") return "boolean";
361
426
  if (Array.isArray(value) && value.length === 2 && value.every((v) => typeof v === "number")) return "vec2";
427
+ if (Array.isArray(value) && value.length > 0 && value.every(isContour)) return "path";
362
428
  if (typeof value === "string") try {
363
429
  parseColor(value);
364
430
  return "color";
@@ -372,6 +438,7 @@ registerValueType(vec2Type);
372
438
  registerValueType(colorType);
373
439
  registerValueType(stringType);
374
440
  registerValueType(booleanType);
441
+ registerValueType(pathType);
375
442
  //#endregion
376
443
  //#region src/vec2Signal.ts
377
444
  /**
@@ -754,18 +821,6 @@ function springEasingDerivative(cfg) {
754
821
  return (p) => p >= 1 ? 0 : rawDerivative(cfg, p * d) * scale;
755
822
  }
756
823
  //#endregion
757
- //#region src/devWarning.ts
758
- let devWarn = (msg) => {
759
- globalThis.console?.warn(`[glissade] ${msg}`);
760
- };
761
- function setDevWarning(fn) {
762
- devWarn = fn;
763
- }
764
- /** Internal: emit through the configurable channel. */
765
- function emitDevWarning(message) {
766
- devWarn(message);
767
- }
768
- //#endregion
769
824
  //#region src/track.ts
770
825
  /**
771
826
  * Track & keyframe model (DESIGN.md §2.2) and sampling (§2.4): binary search
@@ -1554,4 +1609,4 @@ function mergeSidecar(code, sidecar) {
1554
1609
  return merged;
1555
1610
  }
1556
1611
  //#endregion
1557
- export { BakeError, CircularDependencyError, ColorParseError, DEFAULT_EASE, PositionError, SidecarVersionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
1612
+ export { BakeError, CircularDependencyError, ColorParseError, DEFAULT_EASE, PositionError, SidecarVersionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
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
  "type": "module",