@glissade/lottie 0.44.0 → 0.45.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/index.d.ts +126 -2
- package/dist/index.js +629 -3
- package/package.json +6 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _glissade_scene0 from "@glissade/scene";
|
|
2
|
-
import { Node } from "@glissade/scene";
|
|
2
|
+
import { Node, SceneModule } from "@glissade/scene";
|
|
3
3
|
import { PathContour, PathValue, Timeline, Vec2 } from "@glissade/core";
|
|
4
4
|
|
|
5
5
|
//#region src/spec.d.ts
|
|
@@ -64,12 +64,124 @@ interface CodegenOptions {
|
|
|
64
64
|
declare function generateSceneModule(result: LottieImportResult, opts?: CodegenOptions): string;
|
|
65
65
|
//#endregion
|
|
66
66
|
//#region src/types.d.ts
|
|
67
|
+
/**
|
|
68
|
+
* Loose typings for the subset of the Lottie/bodymovin JSON schema the S1
|
|
69
|
+
* importer reads. Old bodymovin exports (s/e keyframe pairs, top-level
|
|
70
|
+
* `closed` on sh, 0–255 colors) and modern exports (s-only keys, `c` inside
|
|
71
|
+
* the path object, 0–1 colors) are both accepted.
|
|
72
|
+
*/
|
|
73
|
+
/** Animatable property: static (`k` value) or keyframed (`k` array of LottieKeyframe). */
|
|
74
|
+
interface LottieProp {
|
|
75
|
+
a?: number;
|
|
76
|
+
k?: unknown;
|
|
77
|
+
/** Expression source (audited; stripped under allowDegraded). */
|
|
78
|
+
x?: unknown;
|
|
79
|
+
ix?: number | string;
|
|
80
|
+
}
|
|
81
|
+
/** Split position: `{ s: true, x, y }` — per-axis animatable scalars. */
|
|
82
|
+
interface LottieSplitPosition {
|
|
83
|
+
s: true;
|
|
84
|
+
x: LottieProp;
|
|
85
|
+
y: LottieProp;
|
|
86
|
+
}
|
|
67
87
|
interface LottieShapePathData {
|
|
68
88
|
v: number[][];
|
|
69
89
|
i: number[][];
|
|
70
90
|
o: number[][];
|
|
71
91
|
c?: boolean;
|
|
72
92
|
}
|
|
93
|
+
interface LottieShapeItem {
|
|
94
|
+
/** shape direction: 3 = reversed winding (el/rc). */
|
|
95
|
+
d?: number | {
|
|
96
|
+
k?: unknown;
|
|
97
|
+
};
|
|
98
|
+
ty: string;
|
|
99
|
+
nm?: string;
|
|
100
|
+
hd?: boolean;
|
|
101
|
+
/** gr */
|
|
102
|
+
it?: LottieShapeItem[];
|
|
103
|
+
/** sh */
|
|
104
|
+
ks?: LottieProp;
|
|
105
|
+
closed?: boolean;
|
|
106
|
+
/** el / rc / tr */
|
|
107
|
+
p?: LottieProp;
|
|
108
|
+
s?: LottieProp;
|
|
109
|
+
a?: LottieProp;
|
|
110
|
+
/** rc corner radius / fl-st opacity-adjacent fields */
|
|
111
|
+
r?: LottieProp | number;
|
|
112
|
+
/** fl / st */
|
|
113
|
+
c?: LottieProp;
|
|
114
|
+
o?: LottieProp;
|
|
115
|
+
w?: LottieProp;
|
|
116
|
+
/** mm */
|
|
117
|
+
mm?: number;
|
|
118
|
+
}
|
|
119
|
+
interface LottieTransform {
|
|
120
|
+
a?: LottieProp;
|
|
121
|
+
p?: LottieProp | LottieSplitPosition;
|
|
122
|
+
s?: LottieProp;
|
|
123
|
+
r?: LottieProp;
|
|
124
|
+
o?: LottieProp;
|
|
125
|
+
sk?: LottieProp;
|
|
126
|
+
sa?: LottieProp;
|
|
127
|
+
}
|
|
128
|
+
interface LottieLayer {
|
|
129
|
+
ty: number;
|
|
130
|
+
nm?: string;
|
|
131
|
+
/** Hidden: never rendered — skipped by audit and conversion alike. */
|
|
132
|
+
hd?: boolean;
|
|
133
|
+
ind?: number;
|
|
134
|
+
parent?: number;
|
|
135
|
+
ip: number;
|
|
136
|
+
op: number;
|
|
137
|
+
st?: number;
|
|
138
|
+
sr?: number;
|
|
139
|
+
ks?: LottieTransform;
|
|
140
|
+
ddd?: number;
|
|
141
|
+
ao?: number;
|
|
142
|
+
/** shape layer */
|
|
143
|
+
shapes?: LottieShapeItem[];
|
|
144
|
+
/** solid */
|
|
145
|
+
sw?: number;
|
|
146
|
+
sh?: number;
|
|
147
|
+
sc?: string;
|
|
148
|
+
/** image / precomp */
|
|
149
|
+
refId?: string;
|
|
150
|
+
/** rejections */
|
|
151
|
+
masksProperties?: unknown[];
|
|
152
|
+
hasMask?: boolean;
|
|
153
|
+
tt?: number;
|
|
154
|
+
td?: number;
|
|
155
|
+
tm?: unknown;
|
|
156
|
+
ef?: unknown[];
|
|
157
|
+
sy?: unknown[];
|
|
158
|
+
w?: number;
|
|
159
|
+
h?: number;
|
|
160
|
+
}
|
|
161
|
+
interface LottieAsset {
|
|
162
|
+
id: string;
|
|
163
|
+
/** image assets */
|
|
164
|
+
w?: number;
|
|
165
|
+
h?: number;
|
|
166
|
+
p?: string;
|
|
167
|
+
u?: string;
|
|
168
|
+
e?: number;
|
|
169
|
+
/** precomp assets */
|
|
170
|
+
layers?: LottieLayer[];
|
|
171
|
+
}
|
|
172
|
+
interface LottieDocument {
|
|
173
|
+
/** bodymovin schema version (`v`) — strict lottie-web/dotLottie validators require it. */
|
|
174
|
+
v?: string;
|
|
175
|
+
fr: number;
|
|
176
|
+
ip: number;
|
|
177
|
+
op: number;
|
|
178
|
+
w: number;
|
|
179
|
+
h: number;
|
|
180
|
+
nm?: string;
|
|
181
|
+
ddd?: number;
|
|
182
|
+
layers: LottieLayer[];
|
|
183
|
+
assets?: LottieAsset[];
|
|
184
|
+
}
|
|
73
185
|
//#endregion
|
|
74
186
|
//#region src/pathvalue.d.ts
|
|
75
187
|
/** sh data → one contour. `closedFallback` covers the old top-level `closed` flag. */
|
|
@@ -103,6 +215,18 @@ declare function lottieColor(value: unknown, bytes: boolean): string;
|
|
|
103
215
|
/** True when ANY component across the prop's values exceeds 1 (old byte exports). */
|
|
104
216
|
declare function colorPropIsBytes(values: unknown[]): boolean;
|
|
105
217
|
//#endregion
|
|
218
|
+
//#region src/export.d.ts
|
|
219
|
+
interface ExportOptions {
|
|
220
|
+
width: number;
|
|
221
|
+
height: number;
|
|
222
|
+
/** Frame rate; default the timeline's fps, else 60 (the golden FPS). */
|
|
223
|
+
fps?: number;
|
|
224
|
+
/** Sink for scope-out / degrade warnings; default `console.warn`. */
|
|
225
|
+
onWarn?: (message: string) => void;
|
|
226
|
+
}
|
|
227
|
+
/** Convert a SceneModule to a Lottie document. Pure over (scene, timeline). */
|
|
228
|
+
declare function exportLottie(mod: SceneModule, opts: ExportOptions): LottieDocument;
|
|
229
|
+
//#endregion
|
|
106
230
|
//#region src/index.d.ts
|
|
107
231
|
interface ImportOptions {
|
|
108
232
|
/** Downgrade degradable rejections (expressions, merge-paths modes ≠ 1) to warnings. */
|
|
@@ -110,4 +234,4 @@ interface ImportOptions {
|
|
|
110
234
|
}
|
|
111
235
|
declare function importLottie(json: unknown, opts?: ImportOptions): LottieImportResult;
|
|
112
236
|
//#endregion
|
|
113
|
-
export { type CodegenOptions, type GroupSpec, type ImageSpec, ImportOptions, KAPPA, LottieImportError, type LottieImportResult, type NodeSpec, type PathSpec, type RectSpec, buildNode, buildNodes, colorPropIsBytes, ellipseContour, generateSceneModule, importLottie, lottieColor, mergeContours, rectContour, reverseContour, shToContour };
|
|
237
|
+
export { type CodegenOptions, type ExportOptions, type GroupSpec, type ImageSpec, ImportOptions, KAPPA, type LottieDocument, LottieImportError, type LottieImportResult, type NodeSpec, type PathSpec, type RectSpec, buildNode, buildNodes, colorPropIsBytes, ellipseContour, exportLottie, generateSceneModule, importLottie, lottieColor, mergeContours, rectContour, reverseContour, shToContour };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Group, ImageNode, Path, Rect, createScene } from "@glissade/scene";
|
|
2
|
-
import { cubicBezier, formatColor, sampleTrack, track } from "@glissade/core";
|
|
1
|
+
import { Circle, Group, ImageNode, Path, Rect, createScene } from "@glissade/scene";
|
|
2
|
+
import { cubicBezier, formatColor, parseColor, sampleTrack, track } from "@glissade/core";
|
|
3
|
+
import "@glissade/core/expr";
|
|
3
4
|
//#region src/spec.ts
|
|
4
5
|
var LottieImportError = class extends Error {
|
|
5
6
|
problems;
|
|
@@ -1148,6 +1149,631 @@ ${children}
|
|
|
1148
1149
|
`;
|
|
1149
1150
|
}
|
|
1150
1151
|
//#endregion
|
|
1152
|
+
//#region src/emitGeometry.ts
|
|
1153
|
+
const pt = (p) => [p[0], p[1]];
|
|
1154
|
+
/** One PathContour → one Lottie sh path object (inverse of shToContour). */
|
|
1155
|
+
function contourToShData(c) {
|
|
1156
|
+
return {
|
|
1157
|
+
v: c.v.map(pt),
|
|
1158
|
+
i: c.in.map(pt),
|
|
1159
|
+
o: c.out.map(pt),
|
|
1160
|
+
c: c.closed
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* A PathValue (contour list) → the Lottie sh keyframe `s` payload: an array of
|
|
1165
|
+
* path objects (the importer's `toValue` maps `Array.isArray(v) ? v : [v]`, so
|
|
1166
|
+
* an array is the general, always-correct shape — including the single-contour
|
|
1167
|
+
* case).
|
|
1168
|
+
*/
|
|
1169
|
+
function pathValueToShData(pv) {
|
|
1170
|
+
return pv.map(contourToShData);
|
|
1171
|
+
}
|
|
1172
|
+
//#endregion
|
|
1173
|
+
//#region src/emitKeyframes.ts
|
|
1174
|
+
/** Seconds → Lottie frame index (offset 0, st 0), matching the importer's toSeconds inverse. */
|
|
1175
|
+
const toFrames = (tSec, fr) => Math.round(tSec * fr);
|
|
1176
|
+
/**
|
|
1177
|
+
* A cubicBezier / linear ease → the DEPARTING key's `o`/`i` handles. Linear
|
|
1178
|
+
* (undefined ease) is the bezier identity `o:{0,0} i:{1,1}` — which the importer
|
|
1179
|
+
* reads back as linear (departingEase returns undefined when x≈y on both
|
|
1180
|
+
* handles). Returns undefined for an ease that is NOT a plain bezier (a named
|
|
1181
|
+
* string or a spring) — the caller must sample that track instead.
|
|
1182
|
+
*/
|
|
1183
|
+
function easeHandles(ease) {
|
|
1184
|
+
if (ease === void 0) return {
|
|
1185
|
+
o: {
|
|
1186
|
+
x: 0,
|
|
1187
|
+
y: 0
|
|
1188
|
+
},
|
|
1189
|
+
i: {
|
|
1190
|
+
x: 1,
|
|
1191
|
+
y: 1
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
if (typeof ease === "object" && ease.kind === "cubicBezier") {
|
|
1195
|
+
const [x1, y1, x2, y2] = ease.pts;
|
|
1196
|
+
return {
|
|
1197
|
+
o: {
|
|
1198
|
+
x: x1,
|
|
1199
|
+
y: y1
|
|
1200
|
+
},
|
|
1201
|
+
i: {
|
|
1202
|
+
x: x2,
|
|
1203
|
+
y: y2
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* True when EVERY segment of the track is directly invertible to a Lottie
|
|
1210
|
+
* bezier: linear (no ease), cubicBezier, or hold. A named-string ease, a spring,
|
|
1211
|
+
* or an expr track is not — those are baked by sampling. (The ease lives on the
|
|
1212
|
+
* ARRIVING key, so segment j is described by `keys[j]`.)
|
|
1213
|
+
*/
|
|
1214
|
+
function isDirectlyInvertible(keys, expr) {
|
|
1215
|
+
if (expr !== void 0) return false;
|
|
1216
|
+
for (let j = 1; j < keys.length; j++) {
|
|
1217
|
+
const k = keys[j];
|
|
1218
|
+
if (k.interp === "hold") continue;
|
|
1219
|
+
if (k.ease === void 0) continue;
|
|
1220
|
+
if (typeof k.ease === "object" && k.ease.kind === "cubicBezier") continue;
|
|
1221
|
+
return false;
|
|
1222
|
+
}
|
|
1223
|
+
return true;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Emit Lottie keyframes with the ease shift: the ease/hold glissade stores on
|
|
1227
|
+
* ARRIVING key j+1 becomes the DEPARTING handles/hold of Lottie key j. `toS`
|
|
1228
|
+
* maps a glissade value to the Lottie `s` payload (a scalar `[v]`, a vec2
|
|
1229
|
+
* `[x,y]`, or an sh path-data array). Assumes {@link isDirectlyInvertible}.
|
|
1230
|
+
*/
|
|
1231
|
+
function emitKeys(keys, fr, toS) {
|
|
1232
|
+
const out = [];
|
|
1233
|
+
for (let j = 0; j < keys.length; j++) {
|
|
1234
|
+
const k = keys[j];
|
|
1235
|
+
const frame = {
|
|
1236
|
+
t: toFrames(k.t, fr),
|
|
1237
|
+
s: toS(k.value)
|
|
1238
|
+
};
|
|
1239
|
+
const next = keys[j + 1];
|
|
1240
|
+
if (next !== void 0) if (next.interp === "hold") frame.h = 1;
|
|
1241
|
+
else {
|
|
1242
|
+
const h = easeHandles(next.ease);
|
|
1243
|
+
if (h !== void 0) {
|
|
1244
|
+
frame.o = h.o;
|
|
1245
|
+
frame.i = h.i;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
out.push(frame);
|
|
1249
|
+
}
|
|
1250
|
+
return out;
|
|
1251
|
+
}
|
|
1252
|
+
//#endregion
|
|
1253
|
+
//#region src/sampleFallback.ts
|
|
1254
|
+
/**
|
|
1255
|
+
* Sample `tr` at every integer frame across its keyed span (or the whole
|
|
1256
|
+
* document `[ip, op]` for an expr track with no keys) and emit linear Lottie
|
|
1257
|
+
* keys, then DECIMATE redundant ones ({@link decimateLinearKeys}). `toS` maps a
|
|
1258
|
+
* sampled value to the Lottie `s` payload.
|
|
1259
|
+
*
|
|
1260
|
+
* Dense per-frame sampling is faithful but huge — a spring or named ease over a
|
|
1261
|
+
* long shot densifies to one key per frame on every channel (a real episode
|
|
1262
|
+
* measured ~148k keys / 139 MB). Since Lottie plays LINEAR between keys, most of
|
|
1263
|
+
* those samples lie on the straight run between two others and are redundant;
|
|
1264
|
+
* decimation drops them within a per-component tolerance, collapsing constant and
|
|
1265
|
+
* constant-velocity stretches to their endpoints while keeping the curvature.
|
|
1266
|
+
*/
|
|
1267
|
+
function sampleToLottieKeys(tr, fr, ip, op, toS) {
|
|
1268
|
+
const keys = tr.keys;
|
|
1269
|
+
let f0 = ip;
|
|
1270
|
+
let f1 = op;
|
|
1271
|
+
if (keys.length > 0) {
|
|
1272
|
+
f0 = toFrames(keys[0].t, fr);
|
|
1273
|
+
f1 = toFrames(keys[keys.length - 1].t, fr);
|
|
1274
|
+
}
|
|
1275
|
+
const out = [];
|
|
1276
|
+
for (let f = f0; f <= f1; f++) {
|
|
1277
|
+
const value = sampleTrack(tr, f / fr);
|
|
1278
|
+
const frame = {
|
|
1279
|
+
t: f,
|
|
1280
|
+
s: toS(value)
|
|
1281
|
+
};
|
|
1282
|
+
if (f < f1) {
|
|
1283
|
+
frame.o = {
|
|
1284
|
+
x: 0,
|
|
1285
|
+
y: 0
|
|
1286
|
+
};
|
|
1287
|
+
frame.i = {
|
|
1288
|
+
x: 1,
|
|
1289
|
+
y: 1
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
out.push(frame);
|
|
1293
|
+
}
|
|
1294
|
+
return decimateLinearKeys(out);
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Ramer–Douglas–Peucker over linear-interpolated keyframes: keep the endpoints
|
|
1298
|
+
* plus any interior key whose value is NOT reproduced (within `relEps` of each
|
|
1299
|
+
* component's range) by linear interpolation between the kept neighbors — those
|
|
1300
|
+
* are exactly the keys Lottie's linear playback can't recreate, so the rest are
|
|
1301
|
+
* safe to drop. Endpoints (first/last, hence the exact start/end value and time)
|
|
1302
|
+
* are always kept; existing linear handles on the survivors stay valid. Only
|
|
1303
|
+
* flat-numeric `s` payloads are decimated — path `sh` data (nested vertex arrays)
|
|
1304
|
+
* is left dense. `relEps` is a fraction of each channel's value range, so it is
|
|
1305
|
+
* scale-invariant across position (px), opacity (0–100), scale (×100), color (0–1).
|
|
1306
|
+
*/
|
|
1307
|
+
function decimateLinearKeys(keys, relEps = .002) {
|
|
1308
|
+
const n = keys.length;
|
|
1309
|
+
if (n <= 2) return keys;
|
|
1310
|
+
const isFlat = (s) => Array.isArray(s) && s.every((x) => typeof x === "number");
|
|
1311
|
+
if (!keys.every((k) => isFlat(k.s))) return keys;
|
|
1312
|
+
const sAt = (i) => keys[i].s;
|
|
1313
|
+
const dim = sAt(0).length;
|
|
1314
|
+
const min = new Array(dim).fill(Infinity);
|
|
1315
|
+
const max = new Array(dim).fill(-Infinity);
|
|
1316
|
+
for (let i = 0; i < n; i++) {
|
|
1317
|
+
const s = sAt(i);
|
|
1318
|
+
for (let c = 0; c < dim; c++) {
|
|
1319
|
+
const v = s[c];
|
|
1320
|
+
if (v < min[c]) min[c] = v;
|
|
1321
|
+
if (v > max[c]) max[c] = v;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
const invRange = new Array(dim);
|
|
1325
|
+
for (let c = 0; c < dim; c++) {
|
|
1326
|
+
const r = max[c] - min[c];
|
|
1327
|
+
invRange[c] = r > 1e-9 ? 1 / r : 0;
|
|
1328
|
+
}
|
|
1329
|
+
const keep = new Uint8Array(n);
|
|
1330
|
+
keep[0] = 1;
|
|
1331
|
+
keep[n - 1] = 1;
|
|
1332
|
+
const stack = [[0, n - 1]];
|
|
1333
|
+
while (stack.length > 0) {
|
|
1334
|
+
const [lo, hi] = stack.pop();
|
|
1335
|
+
if (hi - lo < 2) continue;
|
|
1336
|
+
const a = sAt(lo);
|
|
1337
|
+
const b = sAt(hi);
|
|
1338
|
+
const ta = keys[lo].t;
|
|
1339
|
+
const span = keys[hi].t - ta;
|
|
1340
|
+
let worst = -1;
|
|
1341
|
+
let worstDev = relEps;
|
|
1342
|
+
for (let i = lo + 1; i < hi; i++) {
|
|
1343
|
+
const s = sAt(i);
|
|
1344
|
+
const f = span > 0 ? (keys[i].t - ta) / span : 0;
|
|
1345
|
+
let dev = 0;
|
|
1346
|
+
for (let c = 0; c < dim; c++) {
|
|
1347
|
+
const chord = a[c] + (b[c] - a[c]) * f;
|
|
1348
|
+
const d = Math.abs(s[c] - chord) * invRange[c];
|
|
1349
|
+
if (d > dev) dev = d;
|
|
1350
|
+
}
|
|
1351
|
+
if (dev > worstDev) {
|
|
1352
|
+
worstDev = dev;
|
|
1353
|
+
worst = i;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (worst >= 0) {
|
|
1357
|
+
keep[worst] = 1;
|
|
1358
|
+
stack.push([lo, worst], [worst, hi]);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const out = [];
|
|
1362
|
+
for (let i = 0; i < n; i++) if (keep[i] === 1) out.push(keys[i]);
|
|
1363
|
+
return out;
|
|
1364
|
+
}
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region src/export.ts
|
|
1367
|
+
/**
|
|
1368
|
+
* @glissade/lottie EXPORT (Track → Lottie): a SceneModule → a `LottieDocument`,
|
|
1369
|
+
* the shipped importer (`convert.ts`) read backwards. Every mapping here is the
|
|
1370
|
+
* exact inverse of an import step, so a mappable scene round-trips through
|
|
1371
|
+
* `importLottie` faithfully (see test/roundtrip.test.ts).
|
|
1372
|
+
*
|
|
1373
|
+
* INPUT is the SceneModule (`{ createScene, timeline }`), NOT a flattened
|
|
1374
|
+
* DisplayList: Lottie is an ANIMATED, HIERARCHICAL format, so the node TREE +
|
|
1375
|
+
* the Timeline are the correct source. The walk emits one Lottie layer per node
|
|
1376
|
+
* (Group → a null/ty:3 transform parent; Rect/Circle/Path → a ty:4 shape layer),
|
|
1377
|
+
* parents children via `ind`/`parent`, and turns each `<id>/<prop>` track into an
|
|
1378
|
+
* animated Lottie channel (no track → a static `{a:0,k}` sampled at t=0).
|
|
1379
|
+
*
|
|
1380
|
+
* EASE FIDELITY: cubicBezier + hold + linear eases invert EXACTLY (emitKeyframes).
|
|
1381
|
+
* Named eases, springs, and expr formulas can't be one Lottie bezier — those are
|
|
1382
|
+
* baked to dense linear keys by sampling on the frame grid (sampleFallback), the
|
|
1383
|
+
* same discipline the importer uses for misaligned parametric geometry.
|
|
1384
|
+
*
|
|
1385
|
+
* SCOPE (MVP — mirror the importer's audit discipline: warn + drop, never
|
|
1386
|
+
* silent). IN: Group hierarchy, Rect/Circle/Path with a SOLID fill (+ optional
|
|
1387
|
+
* stroke), transform channels (position / position.x/.y split, opacity, scale,
|
|
1388
|
+
* rotation → identity degrees), animated `fill` color, animated `d` path
|
|
1389
|
+
* (constant topology). OUT (warned + dropped): Text, Image/Video, gradient/mesh
|
|
1390
|
+
* paint (solid only), non-center anchors, group opacity compositing (Lottie
|
|
1391
|
+
* parenting never inherits opacity). Animated primitive geometry (width/radius
|
|
1392
|
+
* tracks) is SAMPLED, not channel-mapped.
|
|
1393
|
+
*/
|
|
1394
|
+
const EMPTY_TRACKS = /* @__PURE__ */ new Map();
|
|
1395
|
+
/** Convert a SceneModule to a Lottie document. Pure over (scene, timeline). */
|
|
1396
|
+
function exportLottie(mod, opts) {
|
|
1397
|
+
const scene = mod.createScene();
|
|
1398
|
+
const fr = opts.fps ?? mod.timeline.fps ?? 60;
|
|
1399
|
+
const warn = opts.onWarn ?? ((m) => console.warn(`gs export: ${m}`));
|
|
1400
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
1401
|
+
for (const tr of mod.timeline.tracks) {
|
|
1402
|
+
const resolved = resolveTrackNode(scene.nodes, tr.target);
|
|
1403
|
+
if (resolved === void 0) {
|
|
1404
|
+
warn(`track '${tr.target}' targets no node in the scene — dropped`);
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
const [nodeId, prop] = resolved;
|
|
1408
|
+
let m = byNode.get(nodeId);
|
|
1409
|
+
if (m === void 0) {
|
|
1410
|
+
m = /* @__PURE__ */ new Map();
|
|
1411
|
+
byNode.set(nodeId, m);
|
|
1412
|
+
}
|
|
1413
|
+
m.set(prop, tr);
|
|
1414
|
+
}
|
|
1415
|
+
const op = computeOp(mod.timeline, fr);
|
|
1416
|
+
const ctx = {
|
|
1417
|
+
fr,
|
|
1418
|
+
ip: 0,
|
|
1419
|
+
op,
|
|
1420
|
+
warn,
|
|
1421
|
+
layers: [],
|
|
1422
|
+
ind: 0
|
|
1423
|
+
};
|
|
1424
|
+
walkChildren(ctx, scene.root.children, void 0, byNode);
|
|
1425
|
+
return {
|
|
1426
|
+
v: BODYMOVIN_VERSION,
|
|
1427
|
+
fr,
|
|
1428
|
+
ip: 0,
|
|
1429
|
+
op,
|
|
1430
|
+
w: opts.width,
|
|
1431
|
+
h: opts.height,
|
|
1432
|
+
nm: "glissade export",
|
|
1433
|
+
layers: ctx.layers
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* bodymovin schema version stamped on every export. Strict lottie-web / dotLottie
|
|
1438
|
+
* validators reject a document without a top-level `v`; 5.7.x is a widely-supported
|
|
1439
|
+
* modern version and the shape this exporter emits is a subset of it.
|
|
1440
|
+
*/
|
|
1441
|
+
const BODYMOVIN_VERSION = "5.7.0";
|
|
1442
|
+
/** Resolve a track target to `[nodeId, propPath]` by the longest registered-id prefix. */
|
|
1443
|
+
function resolveTrackNode(nodes, target) {
|
|
1444
|
+
for (let slash = target.lastIndexOf("/"); slash > 0; slash = target.lastIndexOf("/", slash - 1)) {
|
|
1445
|
+
const id = target.slice(0, slash);
|
|
1446
|
+
if (nodes.has(id)) return [id, target.slice(slash + 1)];
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
/** Document out-point: the timeline duration, else the max key time (≥ 1 frame). */
|
|
1450
|
+
function computeOp(tl, fr) {
|
|
1451
|
+
if (tl.duration !== void 0) return Math.max(1, Math.round(tl.duration * fr));
|
|
1452
|
+
let maxT = 0;
|
|
1453
|
+
for (const tr of tl.tracks) {
|
|
1454
|
+
const last = tr.keys[tr.keys.length - 1];
|
|
1455
|
+
if (last) maxT = Math.max(maxT, last.t);
|
|
1456
|
+
}
|
|
1457
|
+
return Math.max(1, Math.round(maxT * fr));
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Emit a sibling list in REVERSE array order so the array-LAST (top-painted)
|
|
1461
|
+
* node gets the SMALLER `ind` — the importer reconstructs paint order from
|
|
1462
|
+
* `zIndex = -ind`, so a descending ind per sibling group preserves it.
|
|
1463
|
+
*/
|
|
1464
|
+
function walkChildren(ctx, children, parentInd, byNode) {
|
|
1465
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
1466
|
+
const node = children[i];
|
|
1467
|
+
const kind = classify(node);
|
|
1468
|
+
if (kind === "drop") {
|
|
1469
|
+
ctx.warn(`${describe(node)} is not exportable (MVP: Group / Rect / Circle / Path) — dropped`);
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
const myInd = ++ctx.ind;
|
|
1473
|
+
const tracks = (node.id !== void 0 ? byNode.get(node.id) : void 0) ?? EMPTY_TRACKS;
|
|
1474
|
+
ctx.layers.push(kind === "group" ? buildNullLayer(ctx, node, myInd, parentInd, tracks) : buildShapeLayer(ctx, node, kind, myInd, parentInd, tracks));
|
|
1475
|
+
if (node instanceof Group) walkChildren(ctx, node.children, myInd, byNode);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
function classify(node) {
|
|
1479
|
+
if (node instanceof Rect) return "rect";
|
|
1480
|
+
if (node instanceof Circle) return "circle";
|
|
1481
|
+
if (node instanceof Path) return "path";
|
|
1482
|
+
if (node instanceof Group) return "group";
|
|
1483
|
+
return "drop";
|
|
1484
|
+
}
|
|
1485
|
+
const describe = (node) => `${node.describeType}${node.id !== void 0 ? ` '${node.id}'` : ""}`;
|
|
1486
|
+
function buildTransform(ctx, node, tracks) {
|
|
1487
|
+
if (node.hasAnchor && (node.anchor[0] !== .5 || node.anchor[1] !== .5)) ctx.warn(`${describe(node)}: a non-center anchor is not exported (MVP centers geometry) — placement may shift`);
|
|
1488
|
+
return {
|
|
1489
|
+
a: {
|
|
1490
|
+
a: 0,
|
|
1491
|
+
k: [0, 0]
|
|
1492
|
+
},
|
|
1493
|
+
p: positionProp(ctx, tracks, node.position()),
|
|
1494
|
+
s: vecProp(ctx, tracks, "scale", node.scale(), (v) => [v[0] * 100, v[1] * 100]),
|
|
1495
|
+
r: scalarProp(ctx, tracks, "rotation", node.rotation(), (v) => v),
|
|
1496
|
+
o: scalarProp(ctx, tracks, "opacity", node.opacity(), (v) => v * 100)
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
function scalarProp(ctx, tracks, prop, staticVal, map) {
|
|
1500
|
+
const tr = tracks.get(prop);
|
|
1501
|
+
if (tr) return {
|
|
1502
|
+
a: 1,
|
|
1503
|
+
k: scalarKeys(ctx, tr, map)
|
|
1504
|
+
};
|
|
1505
|
+
return {
|
|
1506
|
+
a: 0,
|
|
1507
|
+
k: map(staticVal)
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
function scalarKeys(ctx, tr, map) {
|
|
1511
|
+
const toS = (v) => [map(v)];
|
|
1512
|
+
return isDirectlyInvertible(tr.keys, tr.expr) ? emitKeys(tr.keys, ctx.fr, toS) : sampleToLottieKeys(tr, ctx.fr, ctx.ip, ctx.op, toS);
|
|
1513
|
+
}
|
|
1514
|
+
function vecProp(ctx, tracks, prop, staticVal, map) {
|
|
1515
|
+
const whole = tracks.get(prop);
|
|
1516
|
+
if (whole) return {
|
|
1517
|
+
a: 1,
|
|
1518
|
+
k: isDirectlyInvertible(whole.keys, whole.expr) ? emitKeys(whole.keys, ctx.fr, map) : sampleToLottieKeys(whole, ctx.fr, ctx.ip, ctx.op, map)
|
|
1519
|
+
};
|
|
1520
|
+
const xt = tracks.get(`${prop}.x`);
|
|
1521
|
+
const yt = tracks.get(`${prop}.y`);
|
|
1522
|
+
if (xt || yt) {
|
|
1523
|
+
ctx.warn(`per-axis '${prop}' animation is sampled at ${ctx.fr} fps into a combined channel`);
|
|
1524
|
+
return {
|
|
1525
|
+
a: 1,
|
|
1526
|
+
k: sampleComponentVec(ctx, xt, yt, staticVal, map)
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
return {
|
|
1530
|
+
a: 0,
|
|
1531
|
+
k: map(staticVal)
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
/** Position supports Lottie's native split form (`{s:true,x,y}`) for exactness. */
|
|
1535
|
+
function positionProp(ctx, tracks, staticPos) {
|
|
1536
|
+
const whole = tracks.get("position");
|
|
1537
|
+
if (whole) {
|
|
1538
|
+
const toS = (v) => [v[0], v[1]];
|
|
1539
|
+
return {
|
|
1540
|
+
a: 1,
|
|
1541
|
+
k: isDirectlyInvertible(whole.keys, whole.expr) ? emitKeys(whole.keys, ctx.fr, toS) : sampleToLottieKeys(whole, ctx.fr, ctx.ip, ctx.op, toS)
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
const xt = tracks.get("position.x");
|
|
1545
|
+
const yt = tracks.get("position.y");
|
|
1546
|
+
if (xt || yt) return {
|
|
1547
|
+
s: true,
|
|
1548
|
+
x: xt ? {
|
|
1549
|
+
a: 1,
|
|
1550
|
+
k: scalarKeys(ctx, xt, (v) => v)
|
|
1551
|
+
} : {
|
|
1552
|
+
a: 0,
|
|
1553
|
+
k: staticPos[0]
|
|
1554
|
+
},
|
|
1555
|
+
y: yt ? {
|
|
1556
|
+
a: 1,
|
|
1557
|
+
k: scalarKeys(ctx, yt, (v) => v)
|
|
1558
|
+
} : {
|
|
1559
|
+
a: 0,
|
|
1560
|
+
k: staticPos[1]
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
return {
|
|
1564
|
+
a: 0,
|
|
1565
|
+
k: [staticPos[0], staticPos[1]]
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
function sampleComponentVec(ctx, xt, yt, staticVal, map) {
|
|
1569
|
+
const [f0, f1] = frameSpan(ctx, [xt, yt]);
|
|
1570
|
+
const out = [];
|
|
1571
|
+
for (let f = f0; f <= f1; f++) {
|
|
1572
|
+
const t = f / ctx.fr;
|
|
1573
|
+
const x = xt ? sampleTrack(xt, t) : staticVal[0];
|
|
1574
|
+
const y = yt ? sampleTrack(yt, t) : staticVal[1];
|
|
1575
|
+
const frame = {
|
|
1576
|
+
t: f,
|
|
1577
|
+
s: map([x, y])
|
|
1578
|
+
};
|
|
1579
|
+
if (f < f1) {
|
|
1580
|
+
frame.o = {
|
|
1581
|
+
x: 0,
|
|
1582
|
+
y: 0
|
|
1583
|
+
};
|
|
1584
|
+
frame.i = {
|
|
1585
|
+
x: 1,
|
|
1586
|
+
y: 1
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
out.push(frame);
|
|
1590
|
+
}
|
|
1591
|
+
return out;
|
|
1592
|
+
}
|
|
1593
|
+
/** Union frame span of a set of tracks (their first→last key), else [ip, op]. */
|
|
1594
|
+
function frameSpan(ctx, tracks) {
|
|
1595
|
+
const bounds = [];
|
|
1596
|
+
for (const tr of tracks) if (tr && tr.keys.length > 0) bounds.push(toFrames(tr.keys[0].t, ctx.fr), toFrames(tr.keys[tr.keys.length - 1].t, ctx.fr));
|
|
1597
|
+
return bounds.length > 0 ? [Math.min(...bounds), Math.max(...bounds)] : [ctx.ip, ctx.op];
|
|
1598
|
+
}
|
|
1599
|
+
function buildNullLayer(ctx, node, ind, parentInd, tracks) {
|
|
1600
|
+
if (node.opacity() !== 1 || tracks.has("opacity")) ctx.warn(`${describe(node)}: group opacity is exported on the null layer, but Lottie parenting does not composite it over children`);
|
|
1601
|
+
return {
|
|
1602
|
+
ty: 3,
|
|
1603
|
+
nm: node.id ?? `group${ind}`,
|
|
1604
|
+
ind,
|
|
1605
|
+
ip: ctx.ip,
|
|
1606
|
+
op: ctx.op,
|
|
1607
|
+
ks: buildTransform(ctx, node, tracks),
|
|
1608
|
+
...parentInd !== void 0 ? { parent: parentInd } : {}
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
function buildShapeLayer(ctx, node, kind, ind, parentInd, tracks) {
|
|
1612
|
+
const shapes = [buildGeometry(ctx, node, kind, tracks)];
|
|
1613
|
+
const stroke = buildStroke(ctx, node, tracks);
|
|
1614
|
+
if (stroke) shapes.push(stroke);
|
|
1615
|
+
const fill = buildFill(ctx, node, tracks);
|
|
1616
|
+
if (fill) shapes.push(fill);
|
|
1617
|
+
return {
|
|
1618
|
+
ty: 4,
|
|
1619
|
+
nm: node.id ?? `shape${ind}`,
|
|
1620
|
+
ind,
|
|
1621
|
+
ip: ctx.ip,
|
|
1622
|
+
op: ctx.op,
|
|
1623
|
+
ks: buildTransform(ctx, node, tracks),
|
|
1624
|
+
shapes,
|
|
1625
|
+
...parentInd !== void 0 ? { parent: parentInd } : {}
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
function buildGeometry(ctx, node, kind, tracks) {
|
|
1629
|
+
if (kind === "path") return buildPathGeometry(ctx, node, tracks);
|
|
1630
|
+
const paramNames = kind === "rect" ? [
|
|
1631
|
+
"width",
|
|
1632
|
+
"height",
|
|
1633
|
+
"cornerRadius"
|
|
1634
|
+
] : ["radius"];
|
|
1635
|
+
const contourAt = (t) => {
|
|
1636
|
+
if (kind === "rect") {
|
|
1637
|
+
const r = node;
|
|
1638
|
+
return rectContour([0, 0], [paramAt(tracks, "width", r.width(), t), paramAt(tracks, "height", r.height(), t)], paramAt(tracks, "cornerRadius", r.cornerRadius(), t));
|
|
1639
|
+
}
|
|
1640
|
+
const rad = paramAt(tracks, "radius", node.radius(), t);
|
|
1641
|
+
return ellipseContour([0, 0], [rad * 2, rad * 2]);
|
|
1642
|
+
};
|
|
1643
|
+
if (!paramNames.some((p) => tracks.has(p))) return {
|
|
1644
|
+
ty: "sh",
|
|
1645
|
+
ks: {
|
|
1646
|
+
a: 0,
|
|
1647
|
+
k: [contourToShData(contourAt(0))]
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
ctx.warn(`${describe(node)}: animated primitive geometry is sampled at ${ctx.fr} fps (not channel-mapped)`);
|
|
1651
|
+
const [f0, f1] = frameSpan(ctx, paramNames.map((p) => tracks.get(p)));
|
|
1652
|
+
const keys = [];
|
|
1653
|
+
for (let f = f0; f <= f1; f++) {
|
|
1654
|
+
const frame = {
|
|
1655
|
+
t: f,
|
|
1656
|
+
s: [contourToShData(contourAt(f / ctx.fr))]
|
|
1657
|
+
};
|
|
1658
|
+
if (f < f1) {
|
|
1659
|
+
frame.o = {
|
|
1660
|
+
x: 0,
|
|
1661
|
+
y: 0
|
|
1662
|
+
};
|
|
1663
|
+
frame.i = {
|
|
1664
|
+
x: 1,
|
|
1665
|
+
y: 1
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
keys.push(frame);
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
ty: "sh",
|
|
1672
|
+
ks: {
|
|
1673
|
+
a: 1,
|
|
1674
|
+
k: keys
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function paramAt(tracks, prop, staticVal, t) {
|
|
1679
|
+
const tr = tracks.get(prop);
|
|
1680
|
+
return tr ? sampleTrack(tr, t) : staticVal;
|
|
1681
|
+
}
|
|
1682
|
+
function buildPathGeometry(ctx, node, tracks) {
|
|
1683
|
+
const tr = tracks.get("d");
|
|
1684
|
+
if (tr) return {
|
|
1685
|
+
ty: "sh",
|
|
1686
|
+
ks: {
|
|
1687
|
+
a: 1,
|
|
1688
|
+
k: isDirectlyInvertible(tr.keys, tr.expr) ? emitKeys(tr.keys, ctx.fr, pathValueToShData) : sampleToLottieKeys(tr, ctx.fr, ctx.ip, ctx.op, pathValueToShData)
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
return {
|
|
1692
|
+
ty: "sh",
|
|
1693
|
+
ks: {
|
|
1694
|
+
a: 0,
|
|
1695
|
+
k: pathValueToShData(node.data())
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
function colorToLottie(css) {
|
|
1700
|
+
const { r, g, b, a } = parseColor(css);
|
|
1701
|
+
const base = [
|
|
1702
|
+
r / 255,
|
|
1703
|
+
g / 255,
|
|
1704
|
+
b / 255
|
|
1705
|
+
];
|
|
1706
|
+
return a >= 1 ? base : [...base, a];
|
|
1707
|
+
}
|
|
1708
|
+
function colorKeys(ctx, tr) {
|
|
1709
|
+
return isDirectlyInvertible(tr.keys, tr.expr) ? emitKeys(tr.keys, ctx.fr, colorToLottie) : sampleToLottieKeys(tr, ctx.fr, ctx.ip, ctx.op, colorToLottie);
|
|
1710
|
+
}
|
|
1711
|
+
function buildFill(ctx, node, tracks) {
|
|
1712
|
+
const tr = tracks.get("fill");
|
|
1713
|
+
if (tr) {
|
|
1714
|
+
if (tr.type !== "color") {
|
|
1715
|
+
ctx.warn(`${describe(node)}: animated '${tr.type}' fill (gradient/mesh) is not exported — dropped`);
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
return {
|
|
1719
|
+
ty: "fl",
|
|
1720
|
+
c: {
|
|
1721
|
+
a: 1,
|
|
1722
|
+
k: colorKeys(ctx, tr)
|
|
1723
|
+
},
|
|
1724
|
+
o: {
|
|
1725
|
+
a: 0,
|
|
1726
|
+
k: 100
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
const fill = node.fill();
|
|
1731
|
+
if (typeof fill !== "string") {
|
|
1732
|
+
ctx.warn(`${describe(node)}: a gradient/mesh fill is not exported (MVP: solid color) — dropped`);
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
if (fill === "") return void 0;
|
|
1736
|
+
return {
|
|
1737
|
+
ty: "fl",
|
|
1738
|
+
c: {
|
|
1739
|
+
a: 0,
|
|
1740
|
+
k: colorToLottie(fill)
|
|
1741
|
+
},
|
|
1742
|
+
o: {
|
|
1743
|
+
a: 0,
|
|
1744
|
+
k: 100
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
function buildStroke(ctx, node, tracks) {
|
|
1749
|
+
const colorTr = tracks.get("stroke");
|
|
1750
|
+
const widthTr = tracks.get("strokeWidth");
|
|
1751
|
+
const staticStroke = node.stroke();
|
|
1752
|
+
const staticWidth = node.strokeWidth();
|
|
1753
|
+
if (!(colorTr !== void 0 || staticStroke !== "") || !(widthTr !== void 0 || staticWidth > 0)) return void 0;
|
|
1754
|
+
return {
|
|
1755
|
+
ty: "st",
|
|
1756
|
+
c: colorTr ? {
|
|
1757
|
+
a: 1,
|
|
1758
|
+
k: colorKeys(ctx, colorTr)
|
|
1759
|
+
} : {
|
|
1760
|
+
a: 0,
|
|
1761
|
+
k: colorToLottie(staticStroke)
|
|
1762
|
+
},
|
|
1763
|
+
o: {
|
|
1764
|
+
a: 0,
|
|
1765
|
+
k: 100
|
|
1766
|
+
},
|
|
1767
|
+
w: widthTr ? {
|
|
1768
|
+
a: 1,
|
|
1769
|
+
k: scalarKeys(ctx, widthTr, (v) => v)
|
|
1770
|
+
} : {
|
|
1771
|
+
a: 0,
|
|
1772
|
+
k: staticWidth
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
//#endregion
|
|
1151
1777
|
//#region src/index.ts
|
|
1152
1778
|
function assertDocument(json) {
|
|
1153
1779
|
const doc = json;
|
|
@@ -1172,4 +1798,4 @@ function importLottie(json, opts = {}) {
|
|
|
1172
1798
|
};
|
|
1173
1799
|
}
|
|
1174
1800
|
//#endregion
|
|
1175
|
-
export { KAPPA, LottieImportError, buildNode, buildNodes, colorPropIsBytes, ellipseContour, generateSceneModule, importLottie, lottieColor, mergeContours, rectContour, reverseContour, shToContour };
|
|
1801
|
+
export { KAPPA, LottieImportError, buildNode, buildNodes, colorPropIsBytes, ellipseContour, exportLottie, generateSceneModule, importLottie, lottieColor, mergeContours, rectContour, reverseContour, shToContour };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/lottie",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0-pre.1",
|
|
4
4
|
"description": "glissade Lottie import (S1 MVP): pure .json (Lottie/bodymovin) → node specs + a v1 Timeline. Fail-fast feature audit; no DOM/Node dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -18,8 +18,11 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@glissade/core": "0.
|
|
22
|
-
"@glissade/scene": "0.
|
|
21
|
+
"@glissade/core": "0.45.0-pre.1",
|
|
22
|
+
"@glissade/scene": "0.45.0-pre.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@glissade/backend-skia": "0.45.0-pre.1"
|
|
23
26
|
},
|
|
24
27
|
"repository": {
|
|
25
28
|
"type": "git",
|