@glissade/core 0.13.0-pre.0 → 0.13.0-pre.2
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/clips.d.ts +1 -1
- package/dist/clips.js +23 -13
- package/dist/font-ingest.js +13 -2
- package/dist/targetRef.js +25 -6
- package/package.json +2 -1
package/dist/clips.d.ts
CHANGED
|
@@ -88,7 +88,7 @@ interface DurationOpts {
|
|
|
88
88
|
/** Arriving ease of the motion. */
|
|
89
89
|
ease?: EaseSpec;
|
|
90
90
|
}
|
|
91
|
-
/** Entrance: opacity 0→1 and scale 0.8→1 (a "pop" in). */
|
|
91
|
+
/** Entrance: opacity 0→1 and scale [0.8,0.8]→[1,1] (a "pop" in). */
|
|
92
92
|
declare function popIn(opts?: DurationOpts): Clip;
|
|
93
93
|
type SlideEdge = 'left' | 'right' | 'top' | 'bottom';
|
|
94
94
|
/**
|
package/dist/clips.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as resolveTweenTarget, p as track, s as key, t as TARGET_PATH, x as inferValueType } from "./targetRef.js";
|
|
2
2
|
//#region src/clip.ts
|
|
3
3
|
/**
|
|
4
4
|
* Motion clips (DESIGN.md §2 build-time sugar): `clip()` captures a relative-time
|
|
@@ -168,7 +168,7 @@ function clipList(c, targets, startSec, opts) {
|
|
|
168
168
|
* Loop clips (`pulse`, `driftLoop`) author first-key-value == last-key-value, so
|
|
169
169
|
* tiling them under `clipList` (or repeating the clip) reads seamlessly.
|
|
170
170
|
*/
|
|
171
|
-
/** Entrance: opacity 0→1 and scale 0.8→1 (a "pop" in). */
|
|
171
|
+
/** Entrance: opacity 0→1 and scale [0.8,0.8]→[1,1] (a "pop" in). */
|
|
172
172
|
function popIn(opts) {
|
|
173
173
|
const d = opts?.duration ?? .3;
|
|
174
174
|
const ease = opts?.ease ?? "easeOutCubic";
|
|
@@ -179,7 +179,7 @@ function popIn(opts) {
|
|
|
179
179
|
},
|
|
180
180
|
scale: {
|
|
181
181
|
path: "scale",
|
|
182
|
-
keys: [key(0, .8), key(d, 1, ease)]
|
|
182
|
+
keys: [key(0, [.8, .8]), key(d, [1, 1], ease)]
|
|
183
183
|
}
|
|
184
184
|
} });
|
|
185
185
|
}
|
|
@@ -212,9 +212,9 @@ function pulse(opts) {
|
|
|
212
212
|
return clip({ channels: { scale: {
|
|
213
213
|
path: "scale",
|
|
214
214
|
keys: [
|
|
215
|
-
key(0, 1),
|
|
216
|
-
key(d / 2, peak, ease),
|
|
217
|
-
key(d, 1, ease)
|
|
215
|
+
key(0, [1, 1]),
|
|
216
|
+
key(d / 2, [peak, peak], ease),
|
|
217
|
+
key(d, [1, 1], ease)
|
|
218
218
|
]
|
|
219
219
|
} } });
|
|
220
220
|
}
|
|
@@ -271,11 +271,16 @@ function assertFiniteBox(label, b) {
|
|
|
271
271
|
* `'<nodeId>/<prop>'` string the clip map form resolves (which re-runs the
|
|
272
272
|
* structural/anonymous-id rejection via resolveTweenTarget). A string target is
|
|
273
273
|
* a bare node id; a property-signal carrier exposes its node via TARGET_PATH.
|
|
274
|
+
*
|
|
275
|
+
* The node id may itself carry slashes (an each() clone like 'card/3'); DO NOT
|
|
276
|
+
* re-split it on the FIRST slash — APPEND the prop and trust the caller. The
|
|
277
|
+
* scene's longest-registered-prefix resolver disambiguates node id vs prop path
|
|
278
|
+
* at bind time, so 'card/3' targets the clone, not the wrapping 'card' Group.
|
|
274
279
|
*/
|
|
275
280
|
function nodeTarget(target, prop) {
|
|
276
281
|
const id = typeof target === "string" ? target : target[TARGET_PATH];
|
|
277
282
|
if (typeof id !== "string" || id.length === 0) return `${String(id)}/${prop}`;
|
|
278
|
-
return `${
|
|
283
|
+
return `${typeof target === "string" ? id : id.slice(0, Math.max(0, id.lastIndexOf("/")))}/${prop}`;
|
|
279
284
|
}
|
|
280
285
|
/**
|
|
281
286
|
* Compile a shared-element box-FLIP morph into hand-authored-equivalent tracks.
|
|
@@ -426,17 +431,16 @@ function partitionOpacity(tracks, opacityTarget) {
|
|
|
426
431
|
*/
|
|
427
432
|
function presence(nodeId, opts) {
|
|
428
433
|
const { show, hide } = opts;
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
const nodeIdStr =
|
|
432
|
-
const opacityTarget = `${nodeIdStr}/opacity`;
|
|
434
|
+
const opacityTarget = resolveTweenTarget(typeof nodeId === "string" ? `${nodeId}/opacity` : nodeId);
|
|
435
|
+
const lastSlash = opacityTarget.lastIndexOf("/");
|
|
436
|
+
const nodeIdStr = lastSlash < 0 ? opacityTarget : opacityTarget.slice(0, lastSlash);
|
|
433
437
|
const enter = opts.enter ?? defaultEnter();
|
|
434
438
|
const exit = opts.exit ?? defaultExit();
|
|
435
439
|
const enterRes = enter.apply(nodeIdStr, show, opts.enterOpts);
|
|
436
440
|
const enterEnd = show + effectiveDuration(enter, opts.enterOpts);
|
|
437
441
|
const exitDur = effectiveDuration(exit, opts.exitOpts);
|
|
438
442
|
const exitStart = hide - exitDur;
|
|
439
|
-
if (exitStart
|
|
443
|
+
if (exitStart <= show) throw new PresenceError(`presence('${nodeIdStr}') exit needs ${exitDur.toFixed(3)}s but only ${(hide - show).toFixed(3)}s between show (${show}) and hide (${hide}) — widen the window or shorten/speed up the exit`);
|
|
440
444
|
const exitRes = exit.apply(nodeIdStr, exitStart, opts.exitOpts);
|
|
441
445
|
const { opacityKeys: enterOpacity, rest: enterRest } = partitionOpacity(enterRes.tracks, opacityTarget);
|
|
442
446
|
const { opacityKeys: exitOpacity, rest: exitRest } = partitionOpacity(exitRes.tracks, opacityTarget);
|
|
@@ -446,7 +450,13 @@ function presence(nodeId, opts) {
|
|
|
446
450
|
key(hide, 0, { interp: "hold" })
|
|
447
451
|
];
|
|
448
452
|
const overlay = [];
|
|
449
|
-
if (enterOpacity.length > 0)
|
|
453
|
+
if (enterOpacity.length > 0) enterOpacity.forEach((k, i) => {
|
|
454
|
+
if (i === 0 && k.value !== 0 && k.interp !== "hold") overlay.push({
|
|
455
|
+
...k,
|
|
456
|
+
interp: "hold"
|
|
457
|
+
});
|
|
458
|
+
else overlay.push(k);
|
|
459
|
+
});
|
|
450
460
|
else overlay.push(key(show, 0), key(enterEnd, 1));
|
|
451
461
|
if (exitOpacity.length > 0) for (const k of exitOpacity) overlay.push(k);
|
|
452
462
|
else overlay.push(key(exitStart, 1), key(hide, 0));
|
package/dist/font-ingest.js
CHANGED
|
@@ -104,6 +104,17 @@ async function loadSubsetFont() {
|
|
|
104
104
|
});
|
|
105
105
|
return subsetFontPromise;
|
|
106
106
|
}
|
|
107
|
+
let fontverterPromise;
|
|
108
|
+
async function loadFontverter() {
|
|
109
|
+
fontverterPromise ??= import("fontverter").then((m) => {
|
|
110
|
+
const convert = (m.default ?? m).convert;
|
|
111
|
+
if (typeof convert !== "function") throw new FontIngestError(`'fontverter' loaded but exposes no convert() — incompatible version`);
|
|
112
|
+
return convert;
|
|
113
|
+
}, (err) => {
|
|
114
|
+
throw new FontIngestError(`font ingestion needs the optional 'fontverter' dependency for woff/woff2 decode — install it. (${String(err?.message ?? err)})`);
|
|
115
|
+
});
|
|
116
|
+
return fontverterPromise;
|
|
117
|
+
}
|
|
107
118
|
/**
|
|
108
119
|
* Build the "retain every code point" string from a font's own cmap — used to
|
|
109
120
|
* run hb-subset purely as a woff2 DECODER / variable-axis INSTANCER without
|
|
@@ -132,10 +143,10 @@ async function ingestFont(init) {
|
|
|
132
143
|
let bytes;
|
|
133
144
|
if (!needsDecode && !needsInstance) bytes = input;
|
|
134
145
|
else {
|
|
135
|
-
const subsetFont = await loadSubsetFont();
|
|
136
146
|
let decoded = input;
|
|
137
|
-
if (needsDecode) decoded = await
|
|
147
|
+
if (needsDecode) decoded = await (await loadFontverter())(input, "truetype");
|
|
138
148
|
if (needsInstance) {
|
|
149
|
+
const subsetFont = await loadSubsetFont();
|
|
139
150
|
const coverage = parseCmap(decoded);
|
|
140
151
|
decoded = await subsetFont(decoded, retainAllText(coverage), {
|
|
141
152
|
targetFormat: "sfnt",
|
package/dist/targetRef.js
CHANGED
|
@@ -603,12 +603,31 @@ const pathType = {
|
|
|
603
603
|
const lerpN = (a, b, t) => a + (b - a) * t;
|
|
604
604
|
const lerpPt = (a, b, t) => [lerpN(a[0], b[0], t), lerpN(a[1], b[1], t)];
|
|
605
605
|
/** Same hue at alpha 0 — a transparent stand-in so an appearing/disappearing
|
|
606
|
-
* `bg` (mesh baseline) fades through alpha instead of popping at the boundary.
|
|
606
|
+
* `bg` (mesh baseline) fades through alpha instead of popping at the boundary.
|
|
607
|
+
* `parseColor` THROWS on a non-hex/non-canonical CSS color (hsl/named/oklch);
|
|
608
|
+
* since this runs inside `lerp` during evaluate() it must NEVER throw — fall
|
|
609
|
+
* back to holding the PRESENT color so the bg snaps in/out instead of crashing
|
|
610
|
+
* the whole frame. */
|
|
607
611
|
function transparentOf(color) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
+
try {
|
|
613
|
+
return formatColor({
|
|
614
|
+
...parseColor(color),
|
|
615
|
+
a: 0
|
|
616
|
+
});
|
|
617
|
+
} catch {
|
|
618
|
+
return color;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/** `lerpColor` that NEVER throws inside `lerp` (evaluate()): on an unparseable
|
|
622
|
+
* color (hsl/named/oklch — `parseColor` only knows hex/rgb) it SNAPS (holds `a`,
|
|
623
|
+
* then `b` at t≥1) instead of crashing the frame. Byte-identical to `lerpColor`
|
|
624
|
+
* for parseable colors, so it does not move any golden. */
|
|
625
|
+
function safeLerpColor(a, b, t) {
|
|
626
|
+
try {
|
|
627
|
+
return lerpColor(a, b, t);
|
|
628
|
+
} catch {
|
|
629
|
+
return t >= 1 ? b : a;
|
|
630
|
+
}
|
|
612
631
|
}
|
|
613
632
|
/** Lift a solid color to a uniform gradient matching `shape` (every stop = color),
|
|
614
633
|
* so a color↔gradient tween fades smoothly instead of snapping. */
|
|
@@ -665,7 +684,7 @@ const paintType = {
|
|
|
665
684
|
color: lerpColor(pa.color, pb.color, t)
|
|
666
685
|
};
|
|
667
686
|
});
|
|
668
|
-
const bg = a.bg !== void 0 || b.bg !== void 0 ? { bg:
|
|
687
|
+
const bg = a.bg !== void 0 || b.bg !== void 0 ? { bg: safeLerpColor(a.bg ?? transparentOf(b.bg), b.bg ?? transparentOf(a.bg), t) } : {};
|
|
669
688
|
return {
|
|
670
689
|
kind: "mesh",
|
|
671
690
|
points,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/core",
|
|
3
|
-
"version": "0.13.0-pre.
|
|
3
|
+
"version": "0.13.0-pre.2",
|
|
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": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"directory": "packages/core"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
|
38
|
+
"fontverter": "^2.0.0",
|
|
38
39
|
"subset-font": "^2.5.0"
|
|
39
40
|
},
|
|
40
41
|
"scripts": {
|