sketchmark 2.0.0 → 2.1.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.
Files changed (132) hide show
  1. package/ANIMATABLE_MATRIX.md +177 -0
  2. package/KERNEL_SPEC.md +412 -0
  3. package/PACKS.md +81 -0
  4. package/PRESETS.md +182 -0
  5. package/README.md +274 -188
  6. package/bin/editor-ui.cjs +2285 -0
  7. package/bin/preview-ui.cjs +74 -0
  8. package/bin/sketchmark.cjs +648 -2008
  9. package/dist/src/animatable.d.ts +21 -0
  10. package/dist/src/animatable.js +439 -0
  11. package/dist/src/builders/index.d.ts +1 -11
  12. package/dist/src/builders/index.js +1 -19
  13. package/dist/src/diagnostics.js +1 -64
  14. package/dist/src/edit.d.ts +27 -0
  15. package/dist/src/edit.js +162 -0
  16. package/dist/src/index.d.ts +4 -13
  17. package/dist/src/index.js +4 -13
  18. package/dist/src/keyframes.d.ts +48 -0
  19. package/dist/src/keyframes.js +182 -0
  20. package/dist/src/motion.d.ts +4 -0
  21. package/dist/src/motion.js +262 -0
  22. package/dist/src/normalize.js +120 -151
  23. package/dist/src/presets/characters.d.ts +15 -0
  24. package/dist/src/presets/characters.js +113 -0
  25. package/dist/src/presets/compose.d.ts +5 -0
  26. package/dist/src/presets/compose.js +80 -0
  27. package/dist/src/presets/effects.d.ts +40 -0
  28. package/dist/src/presets/effects.js +79 -0
  29. package/dist/src/presets/helpers.d.ts +33 -0
  30. package/dist/src/presets/helpers.js +165 -0
  31. package/dist/src/presets/index.d.ts +9 -0
  32. package/dist/src/presets/index.js +48 -0
  33. package/dist/src/presets/motions.d.ts +33 -0
  34. package/dist/src/presets/motions.js +75 -0
  35. package/dist/src/presets/scenes.d.ts +35 -0
  36. package/dist/src/presets/scenes.js +134 -0
  37. package/dist/src/presets/shapes.d.ts +71 -0
  38. package/dist/src/presets/shapes.js +96 -0
  39. package/dist/src/presets/transitions.d.ts +29 -0
  40. package/dist/src/presets/transitions.js +113 -0
  41. package/dist/src/presets/types.d.ts +34 -0
  42. package/dist/src/presets/types.js +2 -0
  43. package/dist/src/render/html.js +1 -4
  44. package/dist/src/render/svg.d.ts +2 -2
  45. package/dist/src/render/svg.js +86 -82
  46. package/dist/src/render/three-html.js +67 -113
  47. package/dist/src/scenes.js +1 -0
  48. package/dist/src/schema.js +218 -280
  49. package/dist/src/shapes/builtins.js +11 -47
  50. package/dist/src/shapes/common.js +12 -11
  51. package/dist/src/shapes/registry.d.ts +0 -1
  52. package/dist/src/shapes/registry.js +0 -4
  53. package/dist/src/shapes/types.d.ts +1 -3
  54. package/dist/src/types.d.ts +57 -288
  55. package/dist/src/utils.d.ts +2 -11
  56. package/dist/src/utils.js +13 -70
  57. package/dist/src/validate.js +321 -275
  58. package/dist/tests/run.js +576 -510
  59. package/examples/1730642890464.jpg +0 -0
  60. package/examples/app-screen.svg +1 -0
  61. package/examples/app-screen.visual.json +503 -0
  62. package/examples/dashboard-table.svg +1 -0
  63. package/examples/dashboard-table.visual.json +708 -0
  64. package/examples/dev-docs.svg +1 -0
  65. package/examples/dev-docs.visual.json +248 -0
  66. package/examples/explainer.mp4 +0 -0
  67. package/examples/explainer.visual.json +1713 -0
  68. package/examples/group-origin-effects-lab-check.svg +1 -0
  69. package/examples/group-origin-effects-lab.visual.json +1880 -0
  70. package/examples/image-clip-radius.visual.json +271 -0
  71. package/examples/make-app-screen.cjs +368 -0
  72. package/examples/make-dashboard-table.cjs +277 -0
  73. package/examples/make-dev-docs.cjs +233 -0
  74. package/examples/make-explainer.cjs +438 -0
  75. package/examples/make-group-origin-effects-lab.cjs +370 -0
  76. package/examples/make-image-clip-radius.cjs +169 -0
  77. package/examples/make-modal-dialog.cjs +355 -0
  78. package/examples/make-origin-effects-lab.cjs +311 -0
  79. package/examples/make-preset-character-motion.cjs +32 -0
  80. package/examples/make-presets-demo.cjs +30 -0
  81. package/examples/make-pricing.cjs +286 -0
  82. package/examples/make-product-demo.cjs +468 -0
  83. package/examples/make-product-hero.cjs +223 -0
  84. package/examples/make-release-notes.cjs +333 -0
  85. package/examples/make-settings-panel.cjs +435 -0
  86. package/examples/make-split-preview.cjs +248 -0
  87. package/examples/make-storyboard.cjs +215 -0
  88. package/examples/make-transcript.cjs +234 -0
  89. package/examples/make-typography-test.cjs +397 -0
  90. package/examples/make-ui-demo-explainer.cjs +1094 -0
  91. package/examples/make-ui-flow.cjs +762 -0
  92. package/examples/make-walkthrough.cjs +815 -0
  93. package/examples/modal-dialog.svg +1 -0
  94. package/examples/modal-dialog.visual.json +239 -0
  95. package/examples/origin-effects-lab-check.svg +1 -0
  96. package/examples/origin-effects-lab.visual.json +1412 -0
  97. package/examples/preset-character-motion.visual.json +949 -0
  98. package/examples/presets-demo.visual.json +787 -0
  99. package/examples/pricing.svg +1 -0
  100. package/examples/pricing.visual.json +652 -0
  101. package/examples/product-demo.mp4 +0 -0
  102. package/examples/product-demo.visual.json +866 -0
  103. package/examples/product-hero.svg +1 -0
  104. package/examples/product-hero.visual.json +242 -0
  105. package/examples/release-notes.svg +1 -0
  106. package/examples/release-notes.visual.json +467 -0
  107. package/examples/settings-panel.svg +1 -0
  108. package/examples/settings-panel.visual.json +501 -0
  109. package/examples/split-preview.svg +1 -0
  110. package/examples/split-preview.visual.json +124 -0
  111. package/examples/storyboard.svg +1 -0
  112. package/examples/storyboard.visual.json +312 -0
  113. package/examples/transcript.svg +1 -0
  114. package/examples/transcript.visual.json +407 -0
  115. package/examples/typography-indent-check.svg +1 -0
  116. package/examples/typography-lineheight-0.svg +1 -0
  117. package/examples/typography-lineheight-2.svg +1 -0
  118. package/examples/typography-test-check.svg +1 -0
  119. package/examples/typography-test.svg +1 -0
  120. package/examples/typography-test.visual.json +757 -0
  121. package/examples/ui-demo-explainer-billing.svg +1 -0
  122. package/examples/ui-demo-explainer-check.svg +1 -0
  123. package/examples/ui-demo-explainer-save.svg +1 -0
  124. package/examples/ui-demo-explainer-toggle.svg +1 -0
  125. package/examples/ui-demo-explainer.mp4 +0 -0
  126. package/examples/ui-demo-explainer.visual.json +2597 -0
  127. package/examples/ui-flow.mp4 +0 -0
  128. package/examples/ui-flow.visual.json +1211 -0
  129. package/examples/walkthrough.mp4 +0 -0
  130. package/examples/walkthrough.visual.json +1372 -0
  131. package/package.json +52 -52
  132. package/schema/visual.schema.json +1086 -930
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyMotionDrivers = applyMotionDrivers;
4
+ exports.evaluateDriver = evaluateDriver;
5
+ exports.evaluateExpression = evaluateExpression;
6
+ const path_sampling_1 = require("./path-sampling");
7
+ const shapes_1 = require("./shapes");
8
+ const utils_1 = require("./utils");
9
+ const utils_2 = require("./utils");
10
+ function applyMotionDrivers(document, time) {
11
+ const next = (0, utils_1.clone)(document);
12
+ const drivers = next.motion?.drivers ?? [];
13
+ if (!drivers.length)
14
+ return next;
15
+ const elements = next.elements ?? [];
16
+ for (const driver of drivers) {
17
+ const value = evaluateDriver(driver, elements, time);
18
+ if (value !== undefined)
19
+ applyDriverValue(elements, driver.target, value);
20
+ }
21
+ return next;
22
+ }
23
+ function evaluateDriver(driver, elements, time) {
24
+ if (driver.expr !== undefined)
25
+ return evaluateExpression(driver.expr, elements, time);
26
+ if (Array.isArray(driver.keyframes))
27
+ return interpolateKeyframes(driver.keyframes, time, driver.ease);
28
+ return undefined;
29
+ }
30
+ function evaluateExpression(input, elements, time) {
31
+ if (typeof input === "number" || typeof input === "string" || isMotionPoint(input))
32
+ return (0, utils_1.clone)(input);
33
+ if (!input || typeof input !== "object" || Array.isArray(input))
34
+ return 0;
35
+ switch (input.op) {
36
+ case "time":
37
+ return time;
38
+ case "prop":
39
+ return propertyValue(elements, input.target, input.property);
40
+ case "point": {
41
+ const [x, y] = input.args;
42
+ return [numberValue(evaluateExpression(x, elements, time)), numberValue(evaluateExpression(y, elements, time))];
43
+ }
44
+ case "add":
45
+ return input.args.reduce((total, item) => addValues(total, evaluateExpression(item, elements, time)), 0);
46
+ case "sub": {
47
+ const [first, ...rest] = input.args;
48
+ return rest.reduce((total, item) => subValues(total, evaluateExpression(item, elements, time)), evaluateExpression(first ?? 0, elements, time));
49
+ }
50
+ case "mul":
51
+ return input.args.reduce((total, item) => mulValues(total, evaluateExpression(item, elements, time)), 1);
52
+ case "div": {
53
+ const [first, ...rest] = input.args;
54
+ return rest.reduce((total, item) => divValues(total, evaluateExpression(item, elements, time)), evaluateExpression(first ?? 0, elements, time));
55
+ }
56
+ case "mod": {
57
+ const [left, right] = input.args;
58
+ const divisor = numberValue(evaluateExpression(right ?? 1, elements, time));
59
+ return divisor === 0 ? 0 : numberValue(evaluateExpression(left ?? 0, elements, time)) % divisor;
60
+ }
61
+ case "min":
62
+ return Math.min(...input.args.map((item) => numberValue(evaluateExpression(item, elements, time))));
63
+ case "max":
64
+ return Math.max(...input.args.map((item) => numberValue(evaluateExpression(item, elements, time))));
65
+ case "sin":
66
+ return Math.sin(numberValue(evaluateExpression(input.arg, elements, time)));
67
+ case "cos":
68
+ return Math.cos(numberValue(evaluateExpression(input.arg, elements, time)));
69
+ case "abs":
70
+ return absValue(evaluateExpression(input.arg, elements, time));
71
+ case "clamp":
72
+ return Math.max(numberValue(evaluateExpression(input.min, elements, time)), Math.min(numberValue(evaluateExpression(input.max, elements, time)), numberValue(evaluateExpression(input.value, elements, time))));
73
+ case "lerp":
74
+ return interpolateValue(evaluateExpression(input.from, elements, time), evaluateExpression(input.to, elements, time), numberValue(evaluateExpression(input.t, elements, time)));
75
+ case "bbox": {
76
+ const target = findElement(elements, input.target);
77
+ const box = target ? (0, utils_1.elementBox)(target) : undefined;
78
+ return box ? box[input.property] : 0;
79
+ }
80
+ case "anchor": {
81
+ const target = findElement(elements, input.target);
82
+ const box = target ? (0, utils_1.elementBox)(target) : undefined;
83
+ const point = box ? (0, utils_1.anchorPoint)(box, input.anchor) : [0, 0];
84
+ if (input.offset === undefined)
85
+ return point;
86
+ return point2Value(addValues(point, evaluateExpression(input.offset, elements, time)));
87
+ }
88
+ case "pointAt":
89
+ return pointOnTarget(elements, input.target, numberValue(evaluateExpression(input.progress, elements, time))) ?? [0, 0];
90
+ case "pathLength": {
91
+ const path = firstKernelPathForTarget(elements, input.target);
92
+ return path ? (0, path_sampling_1.pathLength)(path.d) : 0;
93
+ }
94
+ default:
95
+ return 0;
96
+ }
97
+ }
98
+ function interpolateKeyframes(keyframes, time, ease) {
99
+ const frames = keyframes
100
+ .filter((frame) => Array.isArray(frame) && (0, utils_1.isFiniteNumber)(frame[0]))
101
+ .slice()
102
+ .sort((left, right) => left[0] - right[0]);
103
+ if (!frames.length)
104
+ return undefined;
105
+ if (time <= frames[0][0])
106
+ return (0, utils_1.clone)(frames[0][1]);
107
+ for (let index = 1; index < frames.length; index += 1) {
108
+ const prev = frames[index - 1];
109
+ const next = frames[index];
110
+ if (time <= next[0]) {
111
+ const span = Math.max(0.000001, next[0] - prev[0]);
112
+ return interpolateValue(prev[1], next[1], (0, utils_2.easing)(ease, (time - prev[0]) / span));
113
+ }
114
+ }
115
+ return (0, utils_1.clone)(frames[frames.length - 1][1]);
116
+ }
117
+ function interpolateValue(from, to, t) {
118
+ const eased = Math.max(0, Math.min(1, t));
119
+ if (typeof from === "number" && typeof to === "number")
120
+ return from + (to - from) * eased;
121
+ if (isMotionPoint(from) && isMotionPoint(to))
122
+ return from.map((value, index) => value + (Number(to[index]) - value) * eased);
123
+ if (typeof from === "string" && typeof to === "string") {
124
+ const fromColor = parseHexColor(from);
125
+ const toColor = parseHexColor(to);
126
+ if (fromColor && toColor) {
127
+ return `#${hex(Math.round(fromColor[0] + (toColor[0] - fromColor[0]) * eased))}${hex(Math.round(fromColor[1] + (toColor[1] - fromColor[1]) * eased))}${hex(Math.round(fromColor[2] + (toColor[2] - fromColor[2]) * eased))}`;
128
+ }
129
+ }
130
+ return eased < 1 ? (0, utils_1.clone)(from) : (0, utils_1.clone)(to);
131
+ }
132
+ function applyDriverValue(elements, targetPath, value) {
133
+ const separator = targetPath.indexOf(".");
134
+ if (separator <= 0)
135
+ return;
136
+ const id = targetPath.slice(0, separator);
137
+ const property = targetPath.slice(separator + 1);
138
+ const target = findElement(elements, id);
139
+ if (!target)
140
+ return;
141
+ const record = target;
142
+ if (property === "position" && (0, utils_1.isPoint2)(value)) {
143
+ if ("cx" in record || target.type === "circle" || target.type === "ellipse" || target.type === "arc") {
144
+ record.cx = value[0];
145
+ record.cy = value[1];
146
+ }
147
+ else {
148
+ record.x = value[0];
149
+ record.y = value[1];
150
+ }
151
+ return;
152
+ }
153
+ record[property] = (0, utils_1.clone)(value);
154
+ }
155
+ function propertyValue(elements, id, property) {
156
+ const target = findElement(elements, id);
157
+ if (!target)
158
+ return 0;
159
+ const record = target;
160
+ if (property === "position") {
161
+ if ((0, utils_1.isFiniteNumber)(record.cx) && (0, utils_1.isFiniteNumber)(record.cy))
162
+ return [record.cx, record.cy];
163
+ if ((0, utils_1.isFiniteNumber)(record.x) && (0, utils_1.isFiniteNumber)(record.y))
164
+ return [record.x, record.y];
165
+ }
166
+ const value = record[property];
167
+ return typeof value === "number" || typeof value === "string" || isMotionPoint(value) ? (0, utils_1.clone)(value) : 0;
168
+ }
169
+ function pointOnTarget(elements, id, progress) {
170
+ const path = firstKernelPathForTarget(elements, id);
171
+ return path ? (0, path_sampling_1.pointOnPath)(path.d, progress) : undefined;
172
+ }
173
+ function firstKernelPathForTarget(elements, id) {
174
+ const target = findElement(elements, id);
175
+ if (!target)
176
+ return undefined;
177
+ const definition = (0, shapes_1.getInternalShapeDefinition)(target.type);
178
+ if (!definition?.pathQueryable)
179
+ return undefined;
180
+ const lowered = lowerFollowTarget(target);
181
+ return firstKernelPath(lowered);
182
+ }
183
+ function lowerFollowTarget(target) {
184
+ const lowerElements = (items) => items.flatMap((item) => (0, shapes_1.lowerAuthoringElement)(item, { lowerElements }));
185
+ return lowerElements([target]);
186
+ }
187
+ function firstKernelPath(elements) {
188
+ for (const element of elements) {
189
+ if (element.type === "path")
190
+ return element;
191
+ if ((element.type === "group" || element.type === "group3d") && Array.isArray(element.children)) {
192
+ const nested = firstKernelPath(element.children);
193
+ if (nested)
194
+ return nested;
195
+ }
196
+ }
197
+ return undefined;
198
+ }
199
+ function findElement(elements, id) {
200
+ return (0, utils_1.flattenElements)(elements).find((element) => element.id === id);
201
+ }
202
+ function numberValue(value) {
203
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
204
+ }
205
+ function addValues(left, right) {
206
+ if (isMotionPoint(left) || isMotionPoint(right))
207
+ return combinePointValues(left, right, (a, b) => a + b);
208
+ return numberValue(left) + numberValue(right);
209
+ }
210
+ function subValues(left, right) {
211
+ if (isMotionPoint(left) || isMotionPoint(right))
212
+ return combinePointValues(left, right, (a, b) => a - b);
213
+ return numberValue(left) - numberValue(right);
214
+ }
215
+ function mulValues(left, right) {
216
+ if (isMotionPoint(left) || isMotionPoint(right))
217
+ return combinePointValues(left, right, (a, b) => a * b);
218
+ return numberValue(left) * numberValue(right);
219
+ }
220
+ function divValues(left, right) {
221
+ if (isMotionPoint(left) || isMotionPoint(right))
222
+ return combinePointValues(left, right, (a, b) => a / safeDenominator(b));
223
+ return numberValue(left) / safeDenominator(numberValue(right));
224
+ }
225
+ function absValue(value) {
226
+ if (isMotionPoint(value))
227
+ return value.map((item) => Math.abs(item));
228
+ return Math.abs(numberValue(value));
229
+ }
230
+ function combinePointValues(left, right, fn) {
231
+ const length = (isMotionPoint(left) && left.length === 3) || (isMotionPoint(right) && right.length === 3) ? 3 : 2;
232
+ const values = Array.from({ length }, (_, index) => fn(componentValue(left, index), componentValue(right, index)));
233
+ return values;
234
+ }
235
+ function componentValue(value, index) {
236
+ if (isMotionPoint(value))
237
+ return (0, utils_1.isFiniteNumber)(value[index]) ? value[index] : 0;
238
+ return numberValue(value);
239
+ }
240
+ function point2Value(value) {
241
+ if (isMotionPoint(value))
242
+ return [value[0], value[1]];
243
+ const scalar = numberValue(value);
244
+ return [scalar, scalar];
245
+ }
246
+ function safeDenominator(value) {
247
+ if (Math.abs(value) >= 0.000001)
248
+ return value;
249
+ return value < 0 ? -0.000001 : 0.000001;
250
+ }
251
+ function isMotionPoint(value) {
252
+ return Array.isArray(value) && (value.length === 2 || value.length === 3) && value.every((item) => typeof item === "number" && Number.isFinite(item));
253
+ }
254
+ function parseHexColor(value) {
255
+ const match = /^#([0-9a-f]{6})$/i.exec(value.trim());
256
+ if (!match)
257
+ return undefined;
258
+ return [parseInt(match[1].slice(0, 2), 16), parseInt(match[1].slice(2, 4), 16), parseInt(match[1].slice(4, 6), 16)];
259
+ }
260
+ function hex(value) {
261
+ return value.toString(16).padStart(2, "0");
262
+ }
@@ -2,8 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizeVisualDocument = normalizeVisualDocument;
4
4
  exports.resolveVisualFrame = resolveVisualFrame;
5
- const path_sampling_1 = require("./path-sampling");
6
- const shapes_1 = require("./shapes");
5
+ const animatable_1 = require("./animatable");
7
6
  const utils_1 = require("./utils");
8
7
  const validate_1 = require("./validate");
9
8
  function normalizeVisualDocument(document, options = {}) {
@@ -14,178 +13,148 @@ function normalizeVisualDocument(document, options = {}) {
14
13
  throw new Error(first ? `${first.path}: ${first.message}` : "Invalid visual document.");
15
14
  }
16
15
  }
17
- const cloned = (0, utils_1.clone)(document);
18
- const elements = cloned.elements ?? [];
19
- const resolvedElements = resolveEndpointReferences(elements);
20
- return { ...cloned, elements: resolvedElements };
16
+ return { ...(0, utils_1.clone)(document), elements: (0, utils_1.clone)(document.elements ?? []) };
21
17
  }
22
18
  function resolveVisualFrame(document, time = 0) {
23
19
  const normalized = normalizeVisualDocument(document);
24
- const source = (0, utils_1.clone)(document.elements ?? normalized.elements);
25
- const animated = source.map((element) => resolveElementAnimation(element, time));
26
- const endpoints = resolveEndpointReferences(animated);
27
- const followed = endpoints.map((element) => resolveElementFollowers(element, endpoints, time));
28
- return { ...normalized, elements: followed };
29
- }
30
- function resolveEndpointReferences(elements) {
31
- const map = new Map();
32
- for (const element of (0, utils_1.flattenElements)(elements)) {
33
- if (element.id)
34
- map.set(element.id, element);
35
- }
36
- return elements.map((element) => resolveElementEndpoints(element, map));
37
- }
38
- function resolveElementEndpoints(element, map) {
39
- const next = (0, utils_1.clone)(element);
40
- if ((next.type === "line" || next.type === "arrow" || next.type === "curve") && next.from !== undefined && next.to !== undefined) {
41
- next.from = resolveEndpoint(next.from, map);
42
- next.to = resolveEndpoint(next.to, map);
43
- }
44
- if (next.type === "group" && Array.isArray(next.children)) {
45
- next.children = next.children.map((child) => resolveElementEndpoints(child, map));
46
- }
47
- return next;
48
- }
49
- function resolveEndpoint(endpoint, map) {
50
- if ((0, utils_1.isPoint2)(endpoint))
51
- return [endpoint[0], endpoint[1]];
52
- const { id, anchor } = (0, utils_1.parseReference)(String(endpoint));
53
- const target = map.get(id);
54
- const box = target ? (0, utils_1.elementBox)(target) : undefined;
55
- if (!box)
56
- return [0, 0];
57
- return (0, utils_1.anchorPoint)(box, anchor);
20
+ return {
21
+ ...normalized,
22
+ elements: resolveElements(normalized.elements, time)
23
+ };
58
24
  }
59
- function resolveElementAnimation(element, time) {
60
- const next = (0, utils_1.clone)(element);
61
- if (next.animate) {
62
- for (const [property, animation] of Object.entries(next.animate)) {
63
- const record = next;
64
- record[property] = resolveAnimation(animation, time, record[property]);
25
+ function resolveElements(elements, time) {
26
+ return elements.flatMap((element) => {
27
+ const timeline = element.timeline;
28
+ if (timeline?.start !== undefined && time < timeline.start)
29
+ return [];
30
+ if (timeline?.end !== undefined && time > timeline.end)
31
+ return [];
32
+ const next = (0, utils_1.clone)(element);
33
+ if (next.type === "group")
34
+ next.children = resolveElements(next.children, time);
35
+ const localTime = time - Number(timeline?.start ?? 0);
36
+ for (const [property, track] of Object.entries(timeline?.tracks ?? {})) {
37
+ const value = resolveTrack(track, localTime);
38
+ if (value !== undefined)
39
+ (0, animatable_1.applyPropertyValue)(next, property, value);
65
40
  }
66
- delete next.animate;
67
- }
68
- if (next.type === "group" && Array.isArray(next.children)) {
69
- next.children = next.children.map((child) => resolveElementAnimation(child, time));
70
- }
71
- return next;
72
- }
73
- function resolveElementFollowers(element, elements, time) {
74
- const next = (0, utils_1.clone)(element);
75
- if (next.type === "circle" && next.follow) {
76
- const point = pointOnFollowPath(elements, next.follow, progressValue(next.progress, time));
77
- if (point) {
78
- next.cx = point[0];
79
- next.cy = point[1];
41
+ delete next.timeline;
42
+ return [next];
43
+ });
44
+ }
45
+ function resolveTrack(track, time) {
46
+ const frames = track.keyframes
47
+ .map(normalizeKeyframe)
48
+ .filter((frame) => frame !== undefined)
49
+ .slice()
50
+ .sort((left, right) => left.time - right.time);
51
+ if (!frames.length)
52
+ return undefined;
53
+ if (time <= frames[0].time)
54
+ return (0, utils_1.clone)(frames[0].value);
55
+ for (let index = 1; index < frames.length; index += 1) {
56
+ const previous = frames[index - 1];
57
+ const next = frames[index];
58
+ if (time <= next.time) {
59
+ const span = Math.max(0.000001, next.time - previous.time);
60
+ const curve = previous.out ?? previous.interpolation ?? next.in ?? track.curve;
61
+ return interpolateValue(previous.value, next.value, curveProgress(curve, track.ease, (time - previous.time) / span));
80
62
  }
81
63
  }
82
- if (next.type === "group" && Array.isArray(next.children)) {
83
- next.children = next.children.map((child) => resolveElementFollowers(child, elements, time));
84
- }
85
- return next;
86
- }
87
- function progressValue(progress, time) {
88
- if (typeof progress === "number")
89
- return clamp(progress, 0, 1);
90
- if (progress && typeof progress === "object")
91
- return clamp(Number(resolveAnimation(progress, time, progress.from ?? 0)), 0, 1);
92
- return 0;
64
+ return (0, utils_1.clone)(frames[frames.length - 1].value);
93
65
  }
94
- function resolveAnimation(animation, time, fallback) {
95
- if (Array.isArray(animation.keyframes) && animation.keyframes.length) {
96
- const frames = animation.keyframes.slice().sort((left, right) => left[0] - right[0]);
97
- if (time <= frames[0][0])
98
- return frames[0][1];
99
- for (let index = 1; index < frames.length; index += 1) {
100
- const prev = frames[index - 1];
101
- const next = frames[index];
102
- if (time <= next[0]) {
103
- const span = Math.max(0.000001, next[0] - prev[0]);
104
- const t = (time - prev[0]) / span;
105
- return interpolateValue(prev[1], next[1], t);
106
- }
107
- }
108
- return frames[frames.length - 1][1];
66
+ function normalizeKeyframe(frame) {
67
+ if (Array.isArray(frame)) {
68
+ if (!(0, utils_1.isFiniteNumber)(frame[0]) || !(0, animatable_1.isTimelineValue)(frame[1]))
69
+ return undefined;
70
+ return { time: frame[0], value: (0, utils_1.clone)(frame[1]) };
109
71
  }
110
- const from = animation.from ?? fallback;
111
- const to = animation.to ?? from;
112
- const delay = animation.delay ?? 0;
113
- const duration = Math.max(0.000001, animation.duration ?? 0);
114
- const t = (0, utils_1.easing)(animation.ease, (time - delay) / duration);
115
- return interpolateValue(from ?? 0, to ?? from ?? 0, t);
116
- }
117
- function pointOnFollowPath(elements, id, progress) {
118
- const target = (0, utils_1.flattenElements)(elements).find((element) => element.id === id);
119
- if (!target)
72
+ if (!frame || typeof frame !== "object" || !(0, utils_1.isFiniteNumber)(frame.time) || !(0, animatable_1.isTimelineValue)(frame.value))
120
73
  return undefined;
121
- const definition = (0, shapes_1.getInternalShapeDefinition)(target.type);
122
- if (!definition?.followable)
123
- return undefined;
124
- const lowered = lowerFollowTarget(target);
125
- const path = firstKernelPath(lowered);
126
- return path ? (0, path_sampling_1.pointOnPath)(path.d, progress) : undefined;
127
- }
128
- function lowerFollowTarget(target) {
129
- const lowerElements = (items) => items.flatMap((item) => (0, shapes_1.lowerAuthoringElement)(item, { lowerElements }));
130
- return lowerElements([target]);
74
+ return {
75
+ time: frame.time,
76
+ value: (0, utils_1.clone)(frame.value),
77
+ ...(frame.in ? { in: (0, utils_1.clone)(frame.in) } : {}),
78
+ ...(frame.out ? { out: (0, utils_1.clone)(frame.out) } : {}),
79
+ ...(frame.interpolation ? { interpolation: (0, utils_1.clone)(frame.interpolation) } : {})
80
+ };
131
81
  }
132
- function firstKernelPath(elements) {
133
- for (const element of elements) {
134
- if (element.type === "path")
135
- return element;
136
- if ((element.type === "group" || element.type === "group3d") && Array.isArray(element.children)) {
137
- const nested = firstKernelPath(element.children);
138
- if (nested)
139
- return nested;
82
+ function curveProgress(curve, easeName, t) {
83
+ if (!curve)
84
+ return (0, utils_1.easing)(easeName, t);
85
+ const x = (0, utils_1.clamp)(t, 0, 1);
86
+ if (curve.type === "hold")
87
+ return x < 1 ? 0 : 1;
88
+ if (curve.type === "graph")
89
+ return graphProgress(curve.points, x);
90
+ if (curve.type === "cubicBezier")
91
+ return cubicBezierProgress(x, curve.x1, curve.y1, curve.x2, curve.y2);
92
+ return (0, utils_1.easing)(easeName, t);
93
+ }
94
+ function graphProgress(points, t) {
95
+ const sorted = points.slice().sort((left, right) => left[0] - right[0]);
96
+ if (!sorted.length)
97
+ return t;
98
+ if (t <= sorted[0][0])
99
+ return sorted[0][1];
100
+ for (let index = 1; index < sorted.length; index += 1) {
101
+ const previous = sorted[index - 1];
102
+ const next = sorted[index];
103
+ if (t <= next[0]) {
104
+ const span = Math.max(0.000001, next[0] - previous[0]);
105
+ const local = (t - previous[0]) / span;
106
+ return previous[1] + (next[1] - previous[1]) * local;
140
107
  }
141
108
  }
142
- return undefined;
109
+ return sorted[sorted.length - 1][1];
110
+ }
111
+ function cubicBezierProgress(t, x1, y1, x2, y2) {
112
+ let lo = 0;
113
+ let hi = 1;
114
+ let u = t;
115
+ for (let index = 0; index < 24; index += 1) {
116
+ u = (lo + hi) / 2;
117
+ const x = cubicBezier(0, x1, x2, 1, u);
118
+ if (x < t)
119
+ lo = u;
120
+ else
121
+ hi = u;
122
+ }
123
+ return cubicBezier(0, y1, y2, 1, u);
124
+ }
125
+ function cubicBezier(a, b, c, d, t) {
126
+ const mt = 1 - t;
127
+ return mt * mt * mt * a + 3 * mt * mt * t * b + 3 * mt * t * t * c + t * t * t * d;
143
128
  }
144
129
  function interpolateValue(from, to, t) {
145
- const eased = clamp(t, 0, 1);
146
130
  if (typeof from === "number" && typeof to === "number")
147
- return from + (to - from) * eased;
131
+ return from + (to - from) * t;
132
+ if ((0, utils_1.isPoint2)(from) && (0, utils_1.isPoint2)(to))
133
+ return [from[0] + (to[0] - from[0]) * t, from[1] + (to[1] - from[1]) * t];
134
+ if (isNumberArray(from) && isNumberArray(to) && from.length === to.length)
135
+ return from.map((value, index) => value + (to[index] - value) * t);
148
136
  if (typeof from === "string" && typeof to === "string") {
149
- const fromColor = parseHexColor(from);
150
- const toColor = parseHexColor(to);
151
- if (fromColor && toColor)
152
- return formatHexColor({
153
- r: Math.round(fromColor.r + (toColor.r - fromColor.r) * eased),
154
- g: Math.round(fromColor.g + (toColor.g - fromColor.g) * eased),
155
- b: Math.round(fromColor.b + (toColor.b - fromColor.b) * eased),
156
- a: fromColor.a + (toColor.a - fromColor.a) * eased
157
- });
137
+ const a = parseHexColor(from);
138
+ const b = parseHexColor(to);
139
+ if (a && b) {
140
+ return `#${hex(Math.round(a[0] + (b[0] - a[0]) * t))}${hex(Math.round(a[1] + (b[1] - a[1]) * t))}${hex(Math.round(a[2] + (b[2] - a[2]) * t))}`;
141
+ }
158
142
  }
159
- return eased < 1 ? from : to;
143
+ return t < 1 ? (0, utils_1.clone)(from) : (0, utils_1.clone)(to);
144
+ }
145
+ function isNumberArray(value) {
146
+ return Array.isArray(value) && value.every((item) => (0, utils_1.isFiniteNumber)(item));
160
147
  }
161
148
  function parseHexColor(value) {
162
- const input = value.trim();
163
- const short = /^#([0-9a-f]{3}|[0-9a-f]{4})$/i.exec(input);
149
+ const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(value.trim());
164
150
  if (short) {
165
- const chars = short[1];
166
- return {
167
- r: parseInt(chars[0] + chars[0], 16),
168
- g: parseInt(chars[1] + chars[1], 16),
169
- b: parseInt(chars[2] + chars[2], 16),
170
- a: chars.length === 4 ? parseInt(chars[3] + chars[3], 16) / 255 : 1
171
- };
151
+ return [parseInt(short[1] + short[1], 16), parseInt(short[2] + short[2], 16), parseInt(short[3] + short[3], 16)];
172
152
  }
173
- const long = /^#([0-9a-f]{6}|[0-9a-f]{8})$/i.exec(input);
153
+ const long = /^#([0-9a-f]{6})$/i.exec(value.trim());
174
154
  if (!long)
175
155
  return undefined;
176
- const hex = long[1];
177
- return {
178
- r: parseInt(hex.slice(0, 2), 16),
179
- g: parseInt(hex.slice(2, 4), 16),
180
- b: parseInt(hex.slice(4, 6), 16),
181
- a: hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1
182
- };
183
- }
184
- function formatHexColor(color) {
185
- const hex = (value) => clamp(Math.round(value), 0, 255).toString(16).padStart(2, "0");
186
- const alpha = clamp(Math.round(color.a * 255), 0, 255);
187
- return alpha >= 255 ? `#${hex(color.r)}${hex(color.g)}${hex(color.b)}` : `#${hex(color.r)}${hex(color.g)}${hex(color.b)}${hex(alpha)}`;
156
+ return [parseInt(long[1].slice(0, 2), 16), parseInt(long[1].slice(2, 4), 16), parseInt(long[1].slice(4, 6), 16)];
188
157
  }
189
- function clamp(value, min, max) {
190
- return Math.max(min, Math.min(max, value));
158
+ function hex(value) {
159
+ return value.toString(16).padStart(2, "0");
191
160
  }
@@ -0,0 +1,15 @@
1
+ import type { BasePresetOptions, PresetFragment } from "./types";
2
+ export interface CharacterOptions extends BasePresetOptions {
3
+ x?: number;
4
+ y?: number;
5
+ width?: number;
6
+ height?: number;
7
+ stroke?: string;
8
+ fill?: string;
9
+ }
10
+ export declare function stickPerson(options?: CharacterOptions): PresetFragment;
11
+ export declare function talkingHead(options?: CharacterOptions): PresetFragment;
12
+ export declare function simpleDog(options?: CharacterOptions): PresetFragment;
13
+ export declare function simpleSpider(options?: CharacterOptions): PresetFragment;
14
+ export declare function cursorHand(options?: CharacterOptions): PresetFragment;
15
+ export declare function simpleMascot(options?: CharacterOptions): PresetFragment;