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 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 = exportPanel + "<div class='muted'>Select an element.</div>";
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
- return {
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
- if (typeof record.curve === "string") {
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
- if (typeof record[key] !== "string")
188
- continue;
189
- const curve = legacyCurve(record[key]);
190
- if (curve)
191
- record[key] = curve;
192
- else
193
- delete record[key];
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
- return (0, keyframes_1.timelineCurvePreset)(value);
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;
@@ -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
- return {
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sketchmark",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "Render kernel for Sketchmark visual documents.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",