@glissade/core 0.2.0 → 0.3.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 +30 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.js +68 -13
- package/package.json +1 -1
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