sketchmark 2.1.6 → 2.1.8

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/src/edit.js CHANGED
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.listElementReferences = listElementReferences;
4
4
  exports.findElementById = findElementById;
5
+ exports.insertElementPreset = insertElementPreset;
6
+ exports.reorderElement = reorderElement;
7
+ exports.deleteElement = deleteElement;
5
8
  exports.setElementProperty = setElementProperty;
6
9
  exports.setTimelineKeyframe = setTimelineKeyframe;
7
10
  exports.removeTimelineKeyframe = removeTimelineKeyframe;
@@ -28,6 +31,67 @@ function findElementById(document, id) {
28
31
  });
29
32
  return found;
30
33
  }
34
+ function insertElementPreset(document, preset, options = {}) {
35
+ const next = (0, utils_1.clone)(document);
36
+ repairLegacyTimelineCurves(next);
37
+ next.elements ?? (next.elements = []);
38
+ const parentId = typeof options.parentId === "string" && options.parentId ? options.parentId : "";
39
+ const parent = parentId ? requireElement(next, parentId) : undefined;
40
+ if (parent && parent.type !== "group")
41
+ throw new Error(`Element '${parentId}' is not a group.`);
42
+ const id = uniqueElementId(next, options.id || presetBaseId(preset));
43
+ const element = createPresetElement(preset, id, insertionPoint(next, parent));
44
+ const target = parent ? parent.children : next.elements;
45
+ insertAt(target, element, options.index);
46
+ assertValid(next);
47
+ return { document: next, element: (0, utils_1.clone)(element), ...(parentId ? { parentId } : {}) };
48
+ }
49
+ function reorderElement(document, id, options = {}) {
50
+ if (!id)
51
+ throw new Error("id must be a non-empty string.");
52
+ const next = (0, utils_1.clone)(document);
53
+ repairLegacyTimelineCurves(next);
54
+ next.elements ?? (next.elements = []);
55
+ const slot = findElementSlot(next.elements, id);
56
+ if (!slot)
57
+ throw new Error(`Unknown element '${id}'.`);
58
+ const previousIndex = slot.index;
59
+ const nextIndex = layerTargetIndex(slot.index, slot.elements.length, options);
60
+ if (nextIndex !== slot.index) {
61
+ const [element] = slot.elements.splice(slot.index, 1);
62
+ if (element)
63
+ slot.elements.splice(nextIndex, 0, element);
64
+ }
65
+ assertValid(next);
66
+ return {
67
+ document: next,
68
+ id,
69
+ previousIndex,
70
+ index: nextIndex,
71
+ ...(slot.parentId ? { parentId: slot.parentId } : {})
72
+ };
73
+ }
74
+ function deleteElement(document, id) {
75
+ if (!id)
76
+ throw new Error("id must be a non-empty string.");
77
+ const next = (0, utils_1.clone)(document);
78
+ repairLegacyTimelineCurves(next);
79
+ next.elements ?? (next.elements = []);
80
+ const slot = findElementSlot(next.elements, id);
81
+ if (!slot)
82
+ throw new Error(`Unknown element '${id}'.`);
83
+ const [element] = slot.elements.splice(slot.index, 1);
84
+ if (!element)
85
+ throw new Error(`Unknown element '${id}'.`);
86
+ assertValid(next);
87
+ return {
88
+ document: next,
89
+ element: (0, utils_1.clone)(element),
90
+ id,
91
+ index: slot.index,
92
+ ...(slot.parentId ? { parentId: slot.parentId } : {})
93
+ };
94
+ }
31
95
  function setElementProperty(document, id, property, value) {
32
96
  const next = (0, utils_1.clone)(document);
33
97
  repairLegacyTimelineCurves(next);
@@ -127,6 +191,158 @@ function requireElement(document, id) {
127
191
  throw new Error(`Unknown element '${id}'.`);
128
192
  return element;
129
193
  }
194
+ function findElementSlot(elements, id, parentId = "") {
195
+ for (let index = 0; index < elements.length; index += 1) {
196
+ const element = elements[index];
197
+ if (!element)
198
+ continue;
199
+ if (element.id === id)
200
+ return { elements, index, parentId };
201
+ if (element.type === "group") {
202
+ const found = findElementSlot(element.children, id, element.id || parentId);
203
+ if (found)
204
+ return found;
205
+ }
206
+ }
207
+ }
208
+ function layerTargetIndex(index, length, options) {
209
+ if (length <= 1)
210
+ return index;
211
+ if (options.toIndex !== undefined) {
212
+ const nextIndex = Math.round(Number(options.toIndex));
213
+ if (!Number.isFinite(nextIndex))
214
+ throw new Error("toIndex must be a finite number.");
215
+ return Math.max(0, Math.min(length - 1, nextIndex));
216
+ }
217
+ switch (options.direction || "forward") {
218
+ case "backward":
219
+ return Math.max(0, index - 1);
220
+ case "forward":
221
+ return Math.min(length - 1, index + 1);
222
+ case "back":
223
+ return 0;
224
+ case "front":
225
+ return length - 1;
226
+ default:
227
+ throw new Error(`Unknown reorder direction '${options.direction}'.`);
228
+ }
229
+ }
230
+ function createPresetElement(preset, id, point) {
231
+ const x = Math.round(point[0]);
232
+ const y = Math.round(point[1]);
233
+ switch (preset) {
234
+ case "text":
235
+ return {
236
+ id,
237
+ type: "text",
238
+ x,
239
+ y,
240
+ text: "Text",
241
+ align: "center",
242
+ valign: "middle",
243
+ fontSize: 36,
244
+ weight: 700,
245
+ fill: "#111827"
246
+ };
247
+ case "rectangle":
248
+ return {
249
+ id,
250
+ type: "path",
251
+ x,
252
+ y,
253
+ d: "M -80 -50 H 80 V 50 H -80 Z",
254
+ fill: "#dbeafe",
255
+ stroke: "#2563eb",
256
+ strokeWidth: 3
257
+ };
258
+ case "circle":
259
+ return {
260
+ id,
261
+ type: "path",
262
+ x,
263
+ y,
264
+ d: "M 0 -55 A 55 55 0 1 1 0 55 A 55 55 0 1 1 0 -55 Z",
265
+ fill: "#dcfce7",
266
+ stroke: "#16a34a",
267
+ strokeWidth: 3
268
+ };
269
+ case "line":
270
+ return {
271
+ id,
272
+ type: "path",
273
+ x,
274
+ y,
275
+ d: "M -90 0 H 90",
276
+ fill: "none",
277
+ stroke: "#111827",
278
+ strokeWidth: 5,
279
+ strokeCap: "round"
280
+ };
281
+ case "path":
282
+ return {
283
+ id,
284
+ type: "path",
285
+ x,
286
+ y,
287
+ d: "M -80 40 C -40 -50 40 -50 80 40",
288
+ fill: "none",
289
+ stroke: "#7c3aed",
290
+ strokeWidth: 5,
291
+ strokeCap: "round"
292
+ };
293
+ case "point":
294
+ return { id, type: "point", x, y };
295
+ case "group":
296
+ return {
297
+ id,
298
+ type: "group",
299
+ x: Math.round(x - 100),
300
+ y: Math.round(y - 80),
301
+ width: 200,
302
+ height: 160,
303
+ children: []
304
+ };
305
+ default:
306
+ throw new Error(`Unknown element preset '${preset}'.`);
307
+ }
308
+ }
309
+ function insertionPoint(document, parent) {
310
+ if (parent && parent.type === "group") {
311
+ return [Math.max(0, Number(parent.width ?? 0)) / 2, Math.max(0, Number(parent.height ?? 0)) / 2];
312
+ }
313
+ return [Math.max(1, Number(document.canvas?.width ?? 1)) / 2, Math.max(1, Number(document.canvas?.height ?? 1)) / 2];
314
+ }
315
+ function insertAt(elements, element, index) {
316
+ if (Number.isInteger(index) && Number(index) >= 0 && Number(index) <= elements.length)
317
+ elements.splice(Number(index), 0, element);
318
+ else
319
+ elements.push(element);
320
+ }
321
+ function uniqueElementId(document, base) {
322
+ const ids = new Set();
323
+ visitElements(document.elements ?? [], (element) => {
324
+ if (element.id)
325
+ ids.add(element.id);
326
+ });
327
+ const normalized = presetBaseId(base);
328
+ if (!ids.has(normalized))
329
+ return normalized;
330
+ for (let index = 2; index < 10000; index += 1) {
331
+ const candidate = `${normalized}_${index}`;
332
+ if (!ids.has(candidate))
333
+ return candidate;
334
+ }
335
+ throw new Error(`Could not generate a unique id for '${normalized}'.`);
336
+ }
337
+ function presetBaseId(value) {
338
+ const text = String(value || "element")
339
+ .trim()
340
+ .toLowerCase()
341
+ .replace(/[^a-z0-9_.-]+/g, "_")
342
+ .replace(/^[^a-z_]+/i, "")
343
+ .replace(/^_+|_+$/g, "");
344
+ return text || "element";
345
+ }
130
346
  function applyProperty(element, property, value) {
131
347
  (0, animatable_1.applyPropertyValue)(element, property, value);
132
348
  }
@@ -5,6 +5,7 @@ export * from "./diagnostics";
5
5
  export * from "./schema";
6
6
  export * from "./keyframes";
7
7
  export * from "./edit";
8
+ export { deleteElement, insertElementPreset, reorderElement } from "./edit";
8
9
  export * from "./animatable";
9
10
  export * from "./render/svg";
10
11
  export * from "./render/html";
package/dist/src/index.js CHANGED
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.reorderElement = exports.insertElementPreset = exports.deleteElement = void 0;
17
18
  __exportStar(require("./types"), exports);
18
19
  __exportStar(require("./validate"), exports);
19
20
  __exportStar(require("./normalize"), exports);
@@ -21,6 +22,10 @@ __exportStar(require("./diagnostics"), exports);
21
22
  __exportStar(require("./schema"), exports);
22
23
  __exportStar(require("./keyframes"), exports);
23
24
  __exportStar(require("./edit"), exports);
25
+ var edit_1 = require("./edit");
26
+ Object.defineProperty(exports, "deleteElement", { enumerable: true, get: function () { return edit_1.deleteElement; } });
27
+ Object.defineProperty(exports, "insertElementPreset", { enumerable: true, get: function () { return edit_1.insertElementPreset; } });
28
+ Object.defineProperty(exports, "reorderElement", { enumerable: true, get: function () { return edit_1.reorderElement; } });
24
29
  __exportStar(require("./animatable"), exports);
25
30
  __exportStar(require("./render/svg"), exports);
26
31
  __exportStar(require("./render/html"), exports);
package/dist/tests/run.js CHANGED
@@ -462,6 +462,58 @@ test("compiles per-property curves and timing offsets", () => {
462
462
  assert(keyframes[1].time === 1.5, `global and property offsets should shift the target keyframe, got ${keyframes[1].time}`);
463
463
  assert(near((0, src_1.resolveVisualFrame)(animated, 1).elements[0].x, 0), "hold curve should keep the point at the base value before the target");
464
464
  });
465
+ test("inserts element presets at root and inside groups", () => {
466
+ const doc = {
467
+ version: 1,
468
+ canvas: { width: 320, height: 180, duration: 2 },
469
+ elements: [{ id: "group", type: "group", x: 20, y: 30, width: 120, height: 80, children: [] }]
470
+ };
471
+ const rootInsert = (0, src_1.insertElementPreset)(doc, "rectangle");
472
+ assert(rootInsert.element.id === "rectangle", "root preset should use the preset name as id");
473
+ assert((0, src_1.validateVisualDocument)(rootInsert.document).ok, "root preset insert should validate");
474
+ assert((0, src_1.findElementById)(rootInsert.document, "rectangle").type === "path", "rectangle preset should be a path");
475
+ const nestedInsert = (0, src_1.insertElementPreset)(rootInsert.document, "text", { parentId: "group" });
476
+ assert(nestedInsert.parentId === "group", "nested insert should report parent id");
477
+ assert((0, src_1.findElementById)(nestedInsert.document, "group").children.some((item) => item.id === "text"), "nested preset should be added to group children");
478
+ const duplicate = (0, src_1.insertElementPreset)(nestedInsert.document, "text", { parentId: "group" });
479
+ assert(duplicate.element.id === "text_2", "duplicate preset ids should be made unique");
480
+ });
481
+ test("reorders and deletes elements inside their sibling layer", () => {
482
+ const doc = {
483
+ version: 1,
484
+ canvas: { width: 320, height: 180, duration: 2 },
485
+ elements: [
486
+ { id: "a", type: "point", x: 0, y: 0 },
487
+ { id: "b", type: "point", x: 10, y: 0 },
488
+ {
489
+ id: "group",
490
+ type: "group",
491
+ x: 20,
492
+ y: 30,
493
+ width: 120,
494
+ height: 80,
495
+ children: [
496
+ { id: "child1", type: "point", x: 0, y: 0 },
497
+ { id: "child2", type: "point", x: 10, y: 0 }
498
+ ]
499
+ }
500
+ ]
501
+ };
502
+ const front = (0, src_1.reorderElement)(doc, "a", { direction: "front" });
503
+ assert(front.previousIndex === 0 && front.index === 2, "root reorder should report old and new indexes");
504
+ assert((front.document.elements ?? []).map((item) => item.id).join(",") === "b,group,a", "front should move the element to the end of root order");
505
+ const backward = (0, src_1.reorderElement)(front.document, "a", { direction: "backward" });
506
+ assert((backward.document.elements ?? []).map((item) => item.id).join(",") === "b,a,group", "backward should move one layer down");
507
+ const nested = (0, src_1.reorderElement)(backward.document, "child1", { direction: "front" });
508
+ assert(nested.parentId === "group", "nested reorder should report parent id");
509
+ assert((0, src_1.findElementById)(nested.document, "group").children.map((item) => item.id).join(",") === "child2,child1", "nested reorder should only affect group children");
510
+ const deleted = (0, src_1.deleteElement)(nested.document, "child2");
511
+ assert(deleted.parentId === "group", "nested delete should report parent id");
512
+ assert(!(0, src_1.findElementById)(deleted.document, "child2"), "deleted child should be removed");
513
+ assert((0, src_1.findElementById)(deleted.document, "group").children.map((item) => item.id).join(",") === "child1", "delete should preserve remaining siblings");
514
+ const deletedGroup = (0, src_1.deleteElement)(deleted.document, "group");
515
+ assert(!(0, src_1.findElementById)(deletedGroup.document, "group") && !(0, src_1.findElementById)(deletedGroup.document, "child1"), "deleting a group should remove its children");
516
+ });
465
517
  test("edits nested element properties and timeline keyframes", () => {
466
518
  const doc = {
467
519
  version: 1,
package/package.json CHANGED
@@ -1,59 +1,68 @@
1
- {
2
- "name": "sketchmark",
3
- "version": "2.1.6",
4
- "description": "Render kernel for Sketchmark visual documents.",
5
- "license": "MIT",
6
- "type": "commonjs",
7
- "main": "./dist/src/index.js",
8
- "types": "./dist/src/index.d.ts",
9
- "bin": {
10
- "sketchmark": "./bin/sketchmark.cjs"
11
- },
12
- "exports": {
13
- ".": {
14
- "types": "./dist/src/index.d.ts",
15
- "require": "./dist/src/index.js",
16
- "default": "./dist/src/index.js"
17
- },
18
- "./presets": {
19
- "types": "./dist/src/presets/index.d.ts",
20
- "require": "./dist/src/presets/index.js",
21
- "default": "./dist/src/presets/index.js"
22
- },
23
- "./browser-export": {
24
- "types": "./dist/src/browser-export.d.ts",
25
- "require": "./dist/src/browser-export.js",
26
- "default": "./dist/src/browser-export.js"
27
- },
28
- "./editor": {
29
- "types": "./bin/editor-ui.d.ts",
30
- "require": "./bin/editor-ui.cjs",
31
- "default": "./bin/editor-ui.cjs"
32
- },
33
- "./schema": {
34
- "default": "./schema/visual.schema.json"
35
- }
36
- },
37
- "scripts": {
38
- "build": "node scripts/build.cjs",
39
- "render": "node bin/sketchmark.cjs render",
40
- "test": "npm run build && node dist/tests/run.js",
41
- "prepublishOnly": "npm test"
42
- },
43
- "files": [
44
- "bin",
45
- "dist",
46
- "schema",
47
- "README.md"
48
- ],
49
- "dependencies": {
50
- "mp4-muxer": "^5.2.2"
51
- },
52
- "devDependencies": {
53
- "typescript": "^5.9.3"
54
- },
55
- "engines": {
56
- "node": ">=18"
57
- },
58
- "sideEffects": false
59
- }
1
+ {
2
+ "name": "sketchmark",
3
+ "version": "2.1.8",
4
+ "description": "Render kernel for Sketchmark visual documents.",
5
+ "license": "MIT",
6
+ "type": "commonjs",
7
+ "main": "./dist/src/index.js",
8
+ "types": "./dist/src/index.d.ts",
9
+ "bin": {
10
+ "sketchmark": "./bin/sketchmark.cjs"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/src/index.d.ts",
15
+ "require": "./dist/src/index.js",
16
+ "default": "./dist/src/index.js"
17
+ },
18
+ "./presets": {
19
+ "types": "./dist/src/presets/index.d.ts",
20
+ "require": "./dist/src/presets/index.js",
21
+ "default": "./dist/src/presets/index.js"
22
+ },
23
+ "./browser-export": {
24
+ "types": "./dist/src/browser-export.d.ts",
25
+ "require": "./dist/src/browser-export.js",
26
+ "default": "./dist/src/browser-export.js"
27
+ },
28
+ "./edit": {
29
+ "types": "./dist/src/edit.d.ts",
30
+ "require": "./dist/src/edit.js",
31
+ "default": "./dist/src/edit.js"
32
+ },
33
+ "./editor": {
34
+ "types": "./bin/editor-ui.d.ts",
35
+ "require": "./bin/editor-ui.cjs",
36
+ "default": "./bin/editor-ui.cjs"
37
+ },
38
+ "./schema": {
39
+ "default": "./schema/visual.schema.json"
40
+ }
41
+ },
42
+ "scripts": {
43
+ "build": "node scripts/build.cjs",
44
+ "build:exe": "node scripts/build-bun-executable.cjs",
45
+ "build:exe:win": "node scripts/build-bun-executable.cjs --target bun-windows-x64-baseline --outfile dist-bin/sketchmark.exe",
46
+ "build:exe:mac": "node scripts/build-bun-executable.cjs --target bun-darwin-arm64 --outfile dist-bin/sketchmark-macos-arm64",
47
+ "build:exe:linux": "node scripts/build-bun-executable.cjs --target bun-linux-x64-baseline --outfile dist-bin/sketchmark-linux-x64",
48
+ "render": "node bin/sketchmark.cjs render",
49
+ "test": "npm run build && node dist/tests/run.js",
50
+ "prepublishOnly": "npm test"
51
+ },
52
+ "files": [
53
+ "bin",
54
+ "dist",
55
+ "schema",
56
+ "README.md"
57
+ ],
58
+ "dependencies": {
59
+ "mp4-muxer": "^5.2.2"
60
+ },
61
+ "devDependencies": {
62
+ "typescript": "^5.9.3"
63
+ },
64
+ "engines": {
65
+ "node": ">=18"
66
+ },
67
+ "sideEffects": false
68
+ }