sketchmark 2.1.4 → 2.1.6
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/bin/editor-ui.cjs +11 -11
- package/dist/src/edit.js +86 -21
- package/dist/src/keyframes.js +12 -5
- package/dist/tests/run.js +33 -0
- package/package.json +1 -1
package/bin/editor-ui.cjs
CHANGED
|
@@ -686,7 +686,7 @@ function renderInspector() {
|
|
|
686
686
|
const element = findElement(selectedId);
|
|
687
687
|
const exportPanel = renderExportPanel();
|
|
688
688
|
if (!element) {
|
|
689
|
-
inspector.innerHTML =
|
|
689
|
+
inspector.innerHTML = "<div class='muted'>Select an element.</div>" + exportPanel;
|
|
690
690
|
bindPanelStates(inspector);
|
|
691
691
|
bindExportButtons();
|
|
692
692
|
return;
|
|
@@ -753,20 +753,20 @@ function renderInspector() {
|
|
|
753
753
|
paintRows +
|
|
754
754
|
paintHint;
|
|
755
755
|
const contentRows = pathRows + textRows + sourceRows;
|
|
756
|
-
const keyframeRows =
|
|
757
|
-
"<div class='row'><label>Time<input id='kfTime' type='number' step='0.05' value='" + currentTime.toFixed(2) + "'></label><div></div></div>" +
|
|
756
|
+
const keyframeRows =
|
|
757
|
+
"<div class='row'><label>Time<input id='kfTime' type='number' step='0.05' value='" + currentTime.toFixed(2) + "'></label><div></div></div>" +
|
|
758
758
|
"<p class='tiny'>Changing sidebar values updates keyframes at the current time.</p>" +
|
|
759
759
|
"<p class='tiny'>Interpolation curves are edited from timeline badges.</p>" +
|
|
760
|
-
"<p class='tiny'>Drag to move. Use the square to scale and the round handle to rotate.</p>";
|
|
760
|
+
"<p class='tiny'>Drag to move. Use the square to scale and the round handle to rotate.</p>";
|
|
761
761
|
inspector.innerHTML =
|
|
762
|
-
exportPanel +
|
|
763
762
|
panelDetails("inspector-selected", "Selected", selectedRows, { defaultOpen: true, meta: element.type }) +
|
|
764
|
-
panelDetails("inspector-transform", "Transform", transformRows, { defaultOpen: false }) +
|
|
765
|
-
panelDetails("inspector-appearance", "Appearance", appearanceRows, { defaultOpen: false }) +
|
|
766
|
-
(contentRows ? panelDetails("inspector-content", "Path / Content", contentRows, { defaultOpen: false }) : "") +
|
|
767
|
-
(supportsEffects ? panelDetails("inspector-effects", "Effects", effectsRows, { defaultOpen: false }) : "") +
|
|
768
|
-
(structuredPaintRows ? panelDetails("inspector-structured-paint", "Structured Paint", structuredPaintRows, { defaultOpen: false }) : "") +
|
|
769
|
-
panelDetails("inspector-keyframe", "Keyframe", keyframeRows, { defaultOpen: false })
|
|
763
|
+
panelDetails("inspector-transform", "Transform", transformRows, { defaultOpen: false }) +
|
|
764
|
+
panelDetails("inspector-appearance", "Appearance", appearanceRows, { defaultOpen: false }) +
|
|
765
|
+
(contentRows ? panelDetails("inspector-content", "Path / Content", contentRows, { defaultOpen: false }) : "") +
|
|
766
|
+
(supportsEffects ? panelDetails("inspector-effects", "Effects", effectsRows, { defaultOpen: false }) : "") +
|
|
767
|
+
(structuredPaintRows ? panelDetails("inspector-structured-paint", "Structured Paint", structuredPaintRows, { defaultOpen: false }) : "") +
|
|
768
|
+
panelDetails("inspector-keyframe", "Keyframe", keyframeRows, { defaultOpen: false }) +
|
|
769
|
+
exportPanel;
|
|
770
770
|
bindPanelStates(inspector);
|
|
771
771
|
bindExportButtons();
|
|
772
772
|
if (supportsPaint) {
|
package/dist/src/edit.js
CHANGED
|
@@ -142,13 +142,20 @@ function makeKeyframe(time, value, options) {
|
|
|
142
142
|
function mergeKeyframe(existing, next) {
|
|
143
143
|
if (Array.isArray(existing))
|
|
144
144
|
return next;
|
|
145
|
-
|
|
145
|
+
const merged = {
|
|
146
146
|
...existing,
|
|
147
|
-
...next
|
|
148
|
-
in: next.in ?? existing.in,
|
|
149
|
-
out: next.out ?? existing.out,
|
|
150
|
-
interpolation: next.interpolation ?? existing.interpolation
|
|
147
|
+
...next
|
|
151
148
|
};
|
|
149
|
+
mergeCurveValue(merged, "in", next.in ?? existing.in);
|
|
150
|
+
mergeCurveValue(merged, "out", next.out ?? existing.out);
|
|
151
|
+
mergeCurveValue(merged, "interpolation", next.interpolation ?? existing.interpolation);
|
|
152
|
+
return merged;
|
|
153
|
+
}
|
|
154
|
+
function mergeCurveValue(frame, key, value) {
|
|
155
|
+
if (value)
|
|
156
|
+
frame[key] = value;
|
|
157
|
+
else
|
|
158
|
+
delete frame[key];
|
|
152
159
|
}
|
|
153
160
|
function keyframeTime(frame) {
|
|
154
161
|
return Array.isArray(frame) ? frame[0] : frame.time;
|
|
@@ -171,30 +178,88 @@ function repairLegacyTimelineCurves(document) {
|
|
|
171
178
|
}
|
|
172
179
|
function repairTrackCurve(track) {
|
|
173
180
|
const record = track;
|
|
174
|
-
|
|
175
|
-
const curve = legacyCurve(record.curve);
|
|
176
|
-
if (curve)
|
|
177
|
-
record.curve = curve;
|
|
178
|
-
else
|
|
179
|
-
delete record.curve;
|
|
180
|
-
}
|
|
181
|
+
repairCurveField(record, "curve");
|
|
181
182
|
}
|
|
182
183
|
function repairKeyframeCurve(frame) {
|
|
183
184
|
if (Array.isArray(frame))
|
|
184
185
|
return;
|
|
185
186
|
const record = frame;
|
|
186
|
-
for (const key of ["in", "out", "interpolation"])
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
for (const key of ["in", "out", "interpolation"])
|
|
188
|
+
repairCurveField(record, key);
|
|
189
|
+
}
|
|
190
|
+
function repairCurveField(record, key) {
|
|
191
|
+
if (!(key in record))
|
|
192
|
+
return;
|
|
193
|
+
const curve = coerceTimelineCurve(record[key]);
|
|
194
|
+
if (curve)
|
|
195
|
+
record[key] = curve;
|
|
196
|
+
else
|
|
197
|
+
delete record[key];
|
|
198
|
+
}
|
|
199
|
+
function coerceTimelineCurve(value) {
|
|
200
|
+
if (value === undefined)
|
|
201
|
+
return undefined;
|
|
202
|
+
if (typeof value === "string")
|
|
203
|
+
return legacyCurve(value);
|
|
204
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
205
|
+
return undefined;
|
|
206
|
+
const record = value;
|
|
207
|
+
if (record.type === "hold")
|
|
208
|
+
return { type: "hold" };
|
|
209
|
+
if (record.type === "cubicBezier") {
|
|
210
|
+
const x1 = finiteNumber(record.x1);
|
|
211
|
+
const y1 = finiteNumber(record.y1);
|
|
212
|
+
const x2 = finiteNumber(record.x2);
|
|
213
|
+
const y2 = finiteNumber(record.y2);
|
|
214
|
+
if (x1 === undefined || y1 === undefined || x2 === undefined || y2 === undefined)
|
|
215
|
+
return undefined;
|
|
216
|
+
if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1)
|
|
217
|
+
return undefined;
|
|
218
|
+
return { type: "cubicBezier", x1, y1, x2, y2 };
|
|
219
|
+
}
|
|
220
|
+
if (record.type === "graph") {
|
|
221
|
+
const points = Array.isArray(record.points)
|
|
222
|
+
? record.points.map(coerceCurvePoint).filter((point) => Boolean(point))
|
|
223
|
+
: [];
|
|
224
|
+
if (points.length < 2)
|
|
225
|
+
return undefined;
|
|
226
|
+
let previousX = Number.NEGATIVE_INFINITY;
|
|
227
|
+
for (const point of points) {
|
|
228
|
+
if (point[0] < 0 || point[0] > 1 || point[0] <= previousX)
|
|
229
|
+
return undefined;
|
|
230
|
+
previousX = point[0];
|
|
231
|
+
}
|
|
232
|
+
if (points[0]?.[0] !== 0 || points[points.length - 1]?.[0] !== 1)
|
|
233
|
+
return undefined;
|
|
234
|
+
return { type: "graph", points };
|
|
194
235
|
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
function coerceCurvePoint(value) {
|
|
239
|
+
if (!Array.isArray(value) || value.length < 2)
|
|
240
|
+
return undefined;
|
|
241
|
+
const x = finiteNumber(value[0]);
|
|
242
|
+
const y = finiteNumber(value[1]);
|
|
243
|
+
return x === undefined || y === undefined ? undefined : [x, y];
|
|
244
|
+
}
|
|
245
|
+
function finiteNumber(value) {
|
|
246
|
+
const number = Number(value);
|
|
247
|
+
return Number.isFinite(number) ? number : undefined;
|
|
195
248
|
}
|
|
196
249
|
function legacyCurve(value) {
|
|
197
|
-
|
|
250
|
+
switch (value.trim()) {
|
|
251
|
+
case "ease":
|
|
252
|
+
case "easeInOut":
|
|
253
|
+
case "ease-inout":
|
|
254
|
+
case "easeInout":
|
|
255
|
+
return (0, keyframes_1.timelineCurvePreset)("ease-in-out");
|
|
256
|
+
case "easeIn":
|
|
257
|
+
return (0, keyframes_1.timelineCurvePreset)("ease-in");
|
|
258
|
+
case "easeOut":
|
|
259
|
+
return (0, keyframes_1.timelineCurvePreset)("ease-out");
|
|
260
|
+
default:
|
|
261
|
+
return (0, keyframes_1.timelineCurvePreset)(value);
|
|
262
|
+
}
|
|
198
263
|
}
|
|
199
264
|
function finiteOrZero(value) {
|
|
200
265
|
return (0, utils_1.isFiniteNumber)(value) ? value : 0;
|
package/dist/src/keyframes.js
CHANGED
|
@@ -151,13 +151,20 @@ function makeKeyframe(time, value, options = {}) {
|
|
|
151
151
|
function mergeKeyframe(existing, next) {
|
|
152
152
|
if (Array.isArray(existing))
|
|
153
153
|
return next;
|
|
154
|
-
|
|
154
|
+
const merged = {
|
|
155
155
|
...existing,
|
|
156
|
-
...next
|
|
157
|
-
in: next.in ?? existing.in,
|
|
158
|
-
out: next.out ?? existing.out,
|
|
159
|
-
interpolation: next.interpolation ?? existing.interpolation
|
|
156
|
+
...next
|
|
160
157
|
};
|
|
158
|
+
mergeCurveValue(merged, "in", next.in ?? existing.in);
|
|
159
|
+
mergeCurveValue(merged, "out", next.out ?? existing.out);
|
|
160
|
+
mergeCurveValue(merged, "interpolation", next.interpolation ?? existing.interpolation);
|
|
161
|
+
return merged;
|
|
162
|
+
}
|
|
163
|
+
function mergeCurveValue(frame, key, value) {
|
|
164
|
+
if (value)
|
|
165
|
+
frame[key] = value;
|
|
166
|
+
else
|
|
167
|
+
delete frame[key];
|
|
161
168
|
}
|
|
162
169
|
function keyframeTime(frame) {
|
|
163
170
|
return Array.isArray(frame) ? frame[0] : frame.time;
|
package/dist/tests/run.js
CHANGED
|
@@ -487,6 +487,39 @@ test("edits nested element properties and timeline keyframes", () => {
|
|
|
487
487
|
const cleaned = (0, src_1.removeTimelineKeyframe)(animated, "nested", "position", 1);
|
|
488
488
|
assert(!(0, src_1.listTimelineTracks)(cleaned, "nested").length, "timeline keyframe removal should prune empty tracks");
|
|
489
489
|
});
|
|
490
|
+
test("editor helpers repair malformed timeline curve fields before validating", () => {
|
|
491
|
+
const doc = {
|
|
492
|
+
version: 1,
|
|
493
|
+
canvas: { width: 320, height: 180, duration: 2 },
|
|
494
|
+
elements: [
|
|
495
|
+
{
|
|
496
|
+
id: "dot",
|
|
497
|
+
type: "point",
|
|
498
|
+
x: 0,
|
|
499
|
+
y: 0,
|
|
500
|
+
timeline: {
|
|
501
|
+
tracks: {
|
|
502
|
+
position: {
|
|
503
|
+
curve: "easeOut",
|
|
504
|
+
keyframes: [
|
|
505
|
+
{ time: 0, value: [0, 0], in: null, interpolation: ["linear"] },
|
|
506
|
+
{ time: 1, value: [100, 0] }
|
|
507
|
+
]
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
]
|
|
513
|
+
};
|
|
514
|
+
const repaired = (0, src_1.setTimelineKeyframe)(doc, "dot", "position", 0, [0, 0], { out: { type: "hold" } });
|
|
515
|
+
const result = (0, src_1.validateVisualDocument)(repaired);
|
|
516
|
+
assert(result.ok, `repaired edit document should validate: ${result.issues.map((item) => item.message).join("; ")}`);
|
|
517
|
+
const track = (repaired.elements?.[0]).timeline.tracks.position;
|
|
518
|
+
const first = track.keyframes[0];
|
|
519
|
+
assert(track.curve.type === "cubicBezier", "legacy camel-case track curve should be canonicalized");
|
|
520
|
+
assert(first.out.type === "hold", "new keyframe curve should be applied");
|
|
521
|
+
assert(!("in" in first) && !("interpolation" in first), "malformed keyframe curves should be removed");
|
|
522
|
+
});
|
|
490
523
|
test("edits and renders path position offsets", () => {
|
|
491
524
|
const doc = {
|
|
492
525
|
version: 1,
|