sketchmark 0.2.3 → 0.2.5

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
@@ -17,6 +17,7 @@ const KEYWORDS = new Set([
17
17
  "parallelogram",
18
18
  "text",
19
19
  "image",
20
+ "icon",
20
21
  "group",
21
22
  "style",
22
23
  "step",
@@ -230,6 +231,7 @@ const SHAPES = [
230
231
  "parallelogram",
231
232
  "text",
232
233
  "image",
234
+ "icon",
233
235
  ];
234
236
  const CHART_TYPES = [
235
237
  "bar-chart",
@@ -420,7 +422,7 @@ function parse(src) {
420
422
  kind: "node",
421
423
  id,
422
424
  shape,
423
- label: props.label || id,
425
+ label: props.label || (shape === "image" || shape === "icon" ? "" : id),
424
426
  ...(groupId ? { groupId } : {}),
425
427
  ...(props.width ? { width: parseFloat(props.width) } : {}),
426
428
  ...(props.height ? { height: parseFloat(props.height) } : {}),
@@ -429,6 +431,8 @@ function parse(src) {
429
431
  };
430
432
  if (props.url)
431
433
  node.imageUrl = props.url;
434
+ if (props.name)
435
+ node.iconName = props.name;
432
436
  return node;
433
437
  }
434
438
  function parseEdge(fromId, connector, rest) {
@@ -497,7 +501,7 @@ function parse(src) {
497
501
  j++;
498
502
  }
499
503
  // Support multiline via literal \n in label string
500
- const rawLabel = props.label ?? id;
504
+ const rawLabel = props.label ?? "";
501
505
  return {
502
506
  kind: "note",
503
507
  id,
@@ -1276,6 +1280,7 @@ function buildSceneGraph(ast) {
1276
1280
  height: n.height,
1277
1281
  meta: n.meta,
1278
1282
  imageUrl: n.imageUrl,
1283
+ iconName: n.iconName,
1279
1284
  x: 0,
1280
1285
  y: 0,
1281
1286
  w: 0,
@@ -1502,6 +1507,10 @@ function sizeNode(n) {
1502
1507
  }
1503
1508
  break;
1504
1509
  }
1510
+ case "icon":
1511
+ n.w = n.w || 48;
1512
+ n.h = n.h || (n.label !== n.id ? 64 : 48); // extra height for label
1513
+ break;
1505
1514
  default:
1506
1515
  n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
1507
1516
  n.h = n.h || 52;
@@ -1538,9 +1547,8 @@ function sizeTable(t) {
1538
1547
  const nData = rows.filter((r) => r.kind === "data").length;
1539
1548
  t.h = labelH + nHeader * headerH + nData * rowH;
1540
1549
  }
1541
- function sizeChart(c) {
1542
- c.w = c.w || 320;
1543
- c.h = c.h || 240;
1550
+ function sizeChart(_c) {
1551
+ // defaults already applied in buildSceneGraph
1544
1552
  }
1545
1553
  function sizeMarkdown(m) {
1546
1554
  const pad = Number(m.style?.padding ?? 16);
@@ -5098,6 +5106,58 @@ function renderShape$1(rc, n, palette) {
5098
5106
  }
5099
5107
  case "text":
5100
5108
  return [];
5109
+ case "icon": {
5110
+ if (n.iconName) {
5111
+ const [prefix, name] = n.iconName.includes(":")
5112
+ ? n.iconName.split(":", 2)
5113
+ : ["mdi", n.iconName];
5114
+ const iconColor = s.color
5115
+ ? encodeURIComponent(String(s.color))
5116
+ : encodeURIComponent(String(palette.nodeStroke));
5117
+ const iconSize = Math.min(n.w, n.h) - 4;
5118
+ const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
5119
+ const img = document.createElementNS(NS, "image");
5120
+ img.setAttribute("href", iconUrl);
5121
+ img.setAttribute("x", String(n.x + 1));
5122
+ img.setAttribute("y", String(n.y + 1));
5123
+ img.setAttribute("width", String(n.w - 2));
5124
+ img.setAttribute("height", String(n.h - 2));
5125
+ img.setAttribute("preserveAspectRatio", "xMidYMid meet");
5126
+ if (s.opacity != null)
5127
+ img.setAttribute("opacity", String(s.opacity));
5128
+ // clip-path for rounded corners (same as image)
5129
+ const clipId = `clip-${n.id}`;
5130
+ const defs = document.createElementNS(NS, "defs");
5131
+ const clip = document.createElementNS(NS, "clipPath");
5132
+ clip.setAttribute("id", clipId);
5133
+ const rect = document.createElementNS(NS, "rect");
5134
+ rect.setAttribute("x", String(n.x + 1));
5135
+ rect.setAttribute("y", String(n.y + 1));
5136
+ rect.setAttribute("width", String(n.w - 2));
5137
+ rect.setAttribute("height", String(n.h - 2));
5138
+ rect.setAttribute("rx", "6");
5139
+ clip.appendChild(rect);
5140
+ defs.appendChild(clip);
5141
+ img.setAttribute("clip-path", `url(#${clipId})`);
5142
+ // only draw border when stroke is explicitly set
5143
+ const els = [defs, img];
5144
+ if (s.stroke) {
5145
+ els.push(rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
5146
+ ...opts,
5147
+ fill: "none",
5148
+ }));
5149
+ }
5150
+ return els;
5151
+ }
5152
+ // fallback: placeholder square
5153
+ return [
5154
+ rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
5155
+ ...opts,
5156
+ fill: "#e0e0e0",
5157
+ stroke: "#999999",
5158
+ }),
5159
+ ];
5160
+ }
5101
5161
  case "image": {
5102
5162
  if (n.imageUrl) {
5103
5163
  const img = document.createElementNS(NS, "image");
@@ -5120,11 +5180,15 @@ function renderShape$1(rc, n, palette) {
5120
5180
  clip.appendChild(rect);
5121
5181
  defs.appendChild(clip);
5122
5182
  img.setAttribute("clip-path", `url(#${clipId})`);
5123
- const border = rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
5124
- ...opts,
5125
- fill: "none",
5126
- });
5127
- return [defs, img, border];
5183
+ // only draw border when stroke is explicitly set
5184
+ const els = [defs, img];
5185
+ if (s.stroke) {
5186
+ els.push(rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
5187
+ ...opts,
5188
+ fill: "none",
5189
+ }));
5190
+ }
5191
+ return els;
5128
5192
  }
5129
5193
  return [
5130
5194
  rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, {
@@ -5368,9 +5432,11 @@ function renderToSVG(sg, container, options = {}) {
5368
5432
  : verticalAlign === "bottom"
5369
5433
  ? nodeBodyBottom - blockH / 2
5370
5434
  : nodeBodyMid;
5371
- ng.appendChild(lines.length > 1
5372
- ? mkMultilineText(lines, textX, textCY, fontSize, fontWeight, textColor, textAnchor, lineHeight, nodeFont, letterSpacing)
5373
- : mkText(n.label, textX, textCY, fontSize, fontWeight, textColor, textAnchor, nodeFont, letterSpacing));
5435
+ if (n.label) {
5436
+ ng.appendChild(lines.length > 1
5437
+ ? mkMultilineText(lines, textX, textCY, fontSize, fontWeight, textColor, textAnchor, lineHeight, nodeFont, letterSpacing)
5438
+ : mkText(n.label, textX, textCY, fontSize, fontWeight, textColor, textAnchor, nodeFont, letterSpacing));
5439
+ }
5374
5440
  if (options.interactive) {
5375
5441
  ng.style.cursor = "pointer";
5376
5442
  ng.addEventListener("click", () => options.onNodeClick?.(n.id));
@@ -5562,7 +5628,6 @@ function renderToSVG(sg, container, options = {}) {
5562
5628
  NoteL.appendChild(ng);
5563
5629
  }
5564
5630
  svg.appendChild(NoteL);
5565
- markdownMap(sg);
5566
5631
  const MDL = mkGroup('markdown-layer');
5567
5632
  for (const m of sg.markdowns) {
5568
5633
  const mg = mkGroup(`markdown-${m.id}`, 'mdg');
@@ -6069,6 +6134,50 @@ function renderShape(rc, ctx, n, palette, R) {
6069
6134
  break;
6070
6135
  case 'text':
6071
6136
  break; // no shape drawn
6137
+ case 'icon': {
6138
+ if (n.iconName) {
6139
+ const [prefix, name] = n.iconName.includes(':')
6140
+ ? n.iconName.split(':', 2)
6141
+ : ['mdi', n.iconName];
6142
+ const iconColor = s.color
6143
+ ? encodeURIComponent(String(s.color))
6144
+ : encodeURIComponent(String(palette.nodeStroke));
6145
+ const iconSize = Math.min(n.w, n.h) - 4;
6146
+ const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
6147
+ const img = new Image();
6148
+ img.crossOrigin = 'anonymous';
6149
+ img.onload = () => {
6150
+ ctx.save();
6151
+ if (s.opacity != null)
6152
+ ctx.globalAlpha = Number(s.opacity);
6153
+ // clip-path for rounded corners (same as image)
6154
+ ctx.beginPath();
6155
+ const r = 6;
6156
+ ctx.moveTo(n.x + r, n.y);
6157
+ ctx.lineTo(n.x + n.w - r, n.y);
6158
+ ctx.quadraticCurveTo(n.x + n.w, n.y, n.x + n.w, n.y + r);
6159
+ ctx.lineTo(n.x + n.w, n.y + n.h - r);
6160
+ ctx.quadraticCurveTo(n.x + n.w, n.y + n.h, n.x + n.w - r, n.y + n.h);
6161
+ ctx.lineTo(n.x + r, n.y + n.h);
6162
+ ctx.quadraticCurveTo(n.x, n.y + n.h, n.x, n.y + n.h - r);
6163
+ ctx.lineTo(n.x, n.y + r);
6164
+ ctx.quadraticCurveTo(n.x, n.y, n.x + r, n.y);
6165
+ ctx.closePath();
6166
+ ctx.clip();
6167
+ ctx.drawImage(img, n.x + 1, n.y + 1, n.w - 2, n.h - 2);
6168
+ ctx.restore();
6169
+ // only draw border when stroke is explicitly set
6170
+ if (s.stroke) {
6171
+ rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: 'none' });
6172
+ }
6173
+ };
6174
+ img.src = iconUrl;
6175
+ }
6176
+ else {
6177
+ rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: '#e0e0e0', stroke: '#999999' });
6178
+ }
6179
+ return;
6180
+ }
6072
6181
  case 'image': {
6073
6182
  if (n.imageUrl) {
6074
6183
  const img = new Image();
@@ -6090,7 +6199,10 @@ function renderShape(rc, ctx, n, palette, R) {
6090
6199
  ctx.clip();
6091
6200
  ctx.drawImage(img, n.x + 1, n.y + 1, n.w - 2, n.h - 2);
6092
6201
  ctx.restore();
6093
- rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: 'none' });
6202
+ // only draw border when stroke is explicitly set
6203
+ if (s.stroke) {
6204
+ rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: 'none' });
6205
+ }
6094
6206
  };
6095
6207
  img.src = n.imageUrl;
6096
6208
  }
@@ -6280,11 +6392,13 @@ function renderToCanvas(sg, canvas, options = {}) {
6280
6392
  const textCY = vertAlign === 'top' ? nodeBodyTop + blockH / 2
6281
6393
  : vertAlign === 'bottom' ? nodeBodyBottom - blockH / 2
6282
6394
  : n.y + n.h / 2; // middle (default)
6283
- if (lines.length > 1) {
6284
- drawMultilineText(ctx, lines, textX, textCY, fontSize, fontWeight, textColor, textAlign, lineHeight, nodeFont, letterSpacing);
6285
- }
6286
- else {
6287
- drawText(ctx, lines[0] ?? '', textX, textCY, fontSize, fontWeight, textColor, textAlign, nodeFont, letterSpacing);
6395
+ if (n.label) {
6396
+ if (lines.length > 1) {
6397
+ drawMultilineText(ctx, lines, textX, textCY, fontSize, fontWeight, textColor, textAlign, lineHeight, nodeFont, letterSpacing);
6398
+ }
6399
+ else {
6400
+ drawText(ctx, lines[0] ?? '', textX, textCY, fontSize, fontWeight, textColor, textAlign, nodeFont, letterSpacing);
6401
+ }
6288
6402
  }
6289
6403
  if (n.style?.opacity != null)
6290
6404
  ctx.globalAlpha = 1;