sketchmark 1.2.1 → 1.3.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.
package/dist/index.js CHANGED
@@ -377,6 +377,7 @@ function parse(src, options = {}) {
377
377
  const ast = {
378
378
  kind: "diagram",
379
379
  layout: "column",
380
+ style: {},
380
381
  nodes: [],
381
382
  edges: [],
382
383
  groups: [],
@@ -433,6 +434,54 @@ function parse(src, options = {}) {
433
434
  }
434
435
  return props;
435
436
  }
437
+ function parseConfigValue(value) {
438
+ if (value === "true" || value === "on")
439
+ return true;
440
+ if (value === "false" || value === "off")
441
+ return false;
442
+ const numeric = Number(value);
443
+ return Number.isNaN(numeric) ? value : numeric;
444
+ }
445
+ function applyRootProps(props) {
446
+ const styleProps = propsToStyle(props);
447
+ const styleKeys = new Set([
448
+ "fill",
449
+ "stroke",
450
+ "stroke-width",
451
+ "color",
452
+ "opacity",
453
+ "font-size",
454
+ "font-weight",
455
+ "font",
456
+ "dash",
457
+ "stroke-dash",
458
+ "padding",
459
+ "text-align",
460
+ "vertical-align",
461
+ "line-height",
462
+ "letter-spacing",
463
+ ]);
464
+ ast.style = { ...(ast.style ?? {}), ...styleProps };
465
+ if (props.layout) {
466
+ ast.layout = props.layout;
467
+ }
468
+ if (props.width !== undefined) {
469
+ ast.width = parseFloat(props.width);
470
+ }
471
+ if (props.height !== undefined) {
472
+ ast.height = parseFloat(props.height);
473
+ }
474
+ if (props.font !== undefined) {
475
+ ast.config.font = parseConfigValue(props.font);
476
+ }
477
+ for (const [key, value] of Object.entries(props)) {
478
+ if (key === "layout" || key === "width" || key === "height")
479
+ continue;
480
+ if (styleKeys.has(key))
481
+ continue;
482
+ ast.config[key] = parseConfigValue(value);
483
+ }
484
+ }
436
485
  function parseGroupProps(toks, startIndex) {
437
486
  const props = {};
438
487
  const itemIds = [];
@@ -914,8 +963,12 @@ function parse(src, options = {}) {
914
963
  };
915
964
  }
916
965
  skipNL();
917
- if (cur().value === "diagram")
966
+ if (cur().value === "diagram") {
918
967
  skip();
968
+ const toks = lineTokens();
969
+ const props = parseSimpleProps(toks, 0);
970
+ applyRootProps(props);
971
+ }
919
972
  skipNL();
920
973
  while (cur().type !== "EOF" && cur().value !== "end") {
921
974
  skipNL();
@@ -926,7 +979,7 @@ function parse(src, options = {}) {
926
979
  continue;
927
980
  }
928
981
  if (v === "diagram") {
929
- skip();
982
+ lineTokens();
930
983
  continue;
931
984
  }
932
985
  if (v === "end")
@@ -936,10 +989,7 @@ function parse(src, options = {}) {
936
989
  continue;
937
990
  }
938
991
  if (v === "layout") {
939
- skip();
940
- ast.layout = cur().value ?? "column";
941
- skip();
942
- continue;
992
+ throw new ParseError(`Root layout must be declared on the diagram line, e.g. diagram layout=absolute`, t.line, t.col);
943
993
  }
944
994
  if (v === "title") {
945
995
  skip();
@@ -963,15 +1013,7 @@ function parse(src, options = {}) {
963
1013
  continue;
964
1014
  }
965
1015
  if (v === "config") {
966
- skip();
967
- const key = cur().value;
968
- skip();
969
- if (cur().type === "EQUALS")
970
- skip();
971
- const value = cur().value;
972
- skip();
973
- ast.config[key] = value;
974
- continue;
1016
+ throw new ParseError(`Root config must be declared on the diagram line, e.g. diagram gap=40 margin=0 tts=true`, t.line, t.col);
975
1017
  }
976
1018
  if (v === "style") {
977
1019
  skip();
@@ -3624,6 +3666,7 @@ function buildSceneGraph(ast) {
3624
3666
  title: ast.title,
3625
3667
  description: ast.description,
3626
3668
  layout: ast.layout,
3669
+ style: ast.style ?? {},
3627
3670
  nodes,
3628
3671
  edges,
3629
3672
  groups,
@@ -3636,6 +3679,8 @@ function buildSceneGraph(ast) {
3636
3679
  rootOrder: ast.rootOrder ?? [],
3637
3680
  width: 0,
3638
3681
  height: 0,
3682
+ fixedWidth: ast.width,
3683
+ fixedHeight: ast.height,
3639
3684
  };
3640
3685
  }
3641
3686
  // ── Helpers ───────────────────────────────────────────────
@@ -5212,8 +5257,10 @@ function computeBounds(sg, margin) {
5212
5257
  ...sg.charts.map((c) => c.y + c.h),
5213
5258
  ...sg.markdowns.map((m) => m.y + m.h),
5214
5259
  ];
5215
- sg.width = (allX.length ? Math.max(...allX) : 400) + margin;
5216
- sg.height = (allY.length ? Math.max(...allY) : 300) + margin;
5260
+ const autoWidth = (allX.length ? Math.max(...allX) : 400) + margin;
5261
+ const autoHeight = (allY.length ? Math.max(...allY) : 300) + margin;
5262
+ sg.width = sg.fixedWidth ?? autoWidth;
5263
+ sg.height = sg.fixedHeight ?? autoHeight;
5217
5264
  }
5218
5265
  // ── Public entry point ────────────────────────────────────
5219
5266
  function layout(sg) {
@@ -8249,7 +8296,7 @@ function renderToSVG(sg, container, options = {}) {
8249
8296
  const palette = resolvePalette(themeName);
8250
8297
  // ── Diagram-level font ──────────────────────────────────
8251
8298
  const diagramFont = (() => {
8252
- const raw = String(sg.config["font"] ?? "");
8299
+ const raw = String(sg.style?.font ?? sg.config["font"] ?? "");
8253
8300
  if (raw) {
8254
8301
  loadFont(raw);
8255
8302
  return resolveFont(raw);
@@ -8279,8 +8326,22 @@ function renderToSVG(sg, container, options = {}) {
8279
8326
  bgRect.setAttribute("y", "0");
8280
8327
  bgRect.setAttribute("width", String(sg.width));
8281
8328
  bgRect.setAttribute("height", String(sg.height));
8282
- bgRect.setAttribute("fill", palette.background);
8329
+ bgRect.setAttribute("fill", String(sg.style?.fill ?? palette.background));
8283
8330
  svg.appendChild(bgRect);
8331
+ const rootStroke = sg.style?.stroke;
8332
+ const rootStrokeWidth = Number(sg.style?.strokeWidth ?? 0);
8333
+ if (rootStroke && rootStroke !== "none" && rootStrokeWidth > 0) {
8334
+ const frame = se("rect");
8335
+ const inset = rootStrokeWidth / 2;
8336
+ frame.setAttribute("x", String(inset));
8337
+ frame.setAttribute("y", String(inset));
8338
+ frame.setAttribute("width", String(Math.max(0, sg.width - rootStrokeWidth)));
8339
+ frame.setAttribute("height", String(Math.max(0, sg.height - rootStrokeWidth)));
8340
+ frame.setAttribute("fill", "none");
8341
+ frame.setAttribute("stroke", String(rootStroke));
8342
+ frame.setAttribute("stroke-width", String(rootStrokeWidth));
8343
+ svg.appendChild(frame);
8344
+ }
8284
8345
  }
8285
8346
  const rc = rough.svg(svg);
8286
8347
  // ── Title ────────────────────────────────────────────────
@@ -9008,7 +9069,7 @@ function renderToCanvas(sg, canvas, options = {}) {
9008
9069
  const palette = resolvePalette(themeName);
9009
9070
  // ── Diagram-level font ───────────────────────────────────
9010
9071
  const diagramFont = (() => {
9011
- const raw = String(sg.config['font'] ?? '');
9072
+ const raw = String(sg.style?.font ?? sg.config['font'] ?? '');
9012
9073
  if (raw) {
9013
9074
  loadFont(raw);
9014
9075
  return resolveFont(raw);
@@ -9017,8 +9078,18 @@ function renderToCanvas(sg, canvas, options = {}) {
9017
9078
  })();
9018
9079
  // ── Background ───────────────────────────────────────────
9019
9080
  if (!options.transparent) {
9020
- ctx.fillStyle = options.background ?? palette.background;
9081
+ ctx.fillStyle = options.background ?? String(sg.style?.fill ?? palette.background);
9021
9082
  ctx.fillRect(0, 0, sg.width, sg.height);
9083
+ const rootStroke = sg.style?.stroke;
9084
+ const rootStrokeWidth = Number(sg.style?.strokeWidth ?? 0);
9085
+ if (rootStroke && rootStroke !== 'none' && rootStrokeWidth > 0) {
9086
+ const inset = rootStrokeWidth / 2;
9087
+ ctx.save();
9088
+ ctx.strokeStyle = String(rootStroke);
9089
+ ctx.lineWidth = rootStrokeWidth;
9090
+ ctx.strokeRect(inset, inset, Math.max(0, sg.width - rootStrokeWidth), Math.max(0, sg.height - rootStrokeWidth));
9091
+ ctx.restore();
9092
+ }
9022
9093
  }
9023
9094
  else {
9024
9095
  ctx.clearRect(0, 0, sg.width, sg.height);
@@ -10068,6 +10139,10 @@ class AnimationController {
10068
10139
  if (!el.id.startsWith("group-")) {
10069
10140
  const ids = new Set([el.id]);
10070
10141
  this._relatedElementIdsByPrimaryId.get(el.id)?.forEach((id) => ids.add(id));
10142
+ this.svg.querySelectorAll(POSITIONABLE_SELECTOR).forEach((candidate) => {
10143
+ if (candidate.dataset.animationParent === target)
10144
+ ids.add(candidate.id);
10145
+ });
10071
10146
  return Array.from(ids)
10072
10147
  .map((id) => getEl(this.svg, id))
10073
10148
  .filter((candidate) => candidate != null);
@@ -10268,6 +10343,7 @@ class AnimationController {
10268
10343
  el.classList.remove("hl", "faded", "hidden");
10269
10344
  el.style.opacity = el.style.filter = "";
10270
10345
  if (this.drawTargetNodes.has(el.id)) {
10346
+ hideDrawEl(el);
10271
10347
  prepareNodeForDraw(el);
10272
10348
  }
10273
10349
  else {
@@ -10766,6 +10842,15 @@ class AnimationController {
10766
10842
  _doColor(target, color) {
10767
10843
  if (!color)
10768
10844
  return;
10845
+ const applyTextColor = (root) => {
10846
+ root.querySelectorAll("text").forEach((t) => {
10847
+ t.style.fill = color;
10848
+ const existingStyle = t.getAttribute("style") ?? "";
10849
+ const nextStyle = `${existingStyle.replace(/(?:^|;)\s*fill\s*:[^;]*/g, "").trim().replace(/;?$/, ";")}fill:${color};`;
10850
+ t.setAttribute("style", nextStyle);
10851
+ t.setAttribute("fill", color);
10852
+ });
10853
+ };
10769
10854
  for (const el of this._resolveCascadeTargets(target)) {
10770
10855
  if (parseEdgeTarget(target)) {
10771
10856
  el.querySelectorAll("path, line, polyline").forEach((p) => {
@@ -10788,11 +10873,14 @@ class AnimationController {
10788
10873
  hit = true;
10789
10874
  });
10790
10875
  if (!hit) {
10791
- el.querySelectorAll("text").forEach((t) => {
10792
- t.style.fill = color;
10793
- });
10876
+ applyTextColor(el);
10794
10877
  }
10795
10878
  }
10879
+ this.svg.querySelectorAll(`${POSITIONABLE_SELECTOR}[data-animation-parent]`).forEach((el) => {
10880
+ if (el.dataset.animationParent === target) {
10881
+ applyTextColor(el);
10882
+ }
10883
+ });
10796
10884
  }
10797
10885
  // ── narration ───────────────────────────────────────────
10798
10886
  _initCaption() {