sketchmark 0.2.5 → 0.2.7
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/README.md +58 -1
- package/dist/index.cjs +65 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +65 -42
- package/dist/index.js.map +1 -1
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/renderer/canvas/index.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +65 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ end
|
|
|
29
29
|
- [DSL Reference](#dsl-reference)
|
|
30
30
|
- [Diagram Header](#diagram-header)
|
|
31
31
|
- [Node Shapes](#node-shapes)
|
|
32
|
+
- [Icons](#icon-shape)
|
|
32
33
|
- [Edges](#edges)
|
|
33
34
|
- [Groups](#groups)
|
|
34
35
|
- [Bare Groups](#bare-groups)
|
|
@@ -37,6 +38,7 @@ end
|
|
|
37
38
|
- [Charts](#charts)
|
|
38
39
|
- [Markdown Blocks](#markdown-blocks)
|
|
39
40
|
- [Themes](#themes)
|
|
41
|
+
- [Style Directive](#style-directive)
|
|
40
42
|
- [Typography](#typography)
|
|
41
43
|
- [Animation Steps](#animation-steps)
|
|
42
44
|
- [Layout System](#layout-system)
|
|
@@ -127,11 +129,15 @@ end
|
|
|
127
129
|
| Keyword | Example | Description |
|
|
128
130
|
|---|---|---|
|
|
129
131
|
| `title` | `title label="My Diagram"` | Title shown above the diagram |
|
|
132
|
+
| `description` | `description "A brief summary"` | Diagram description (metadata) |
|
|
130
133
|
| `layout` | `layout row` | Root layout direction: `row`, `column`, `grid` |
|
|
131
134
|
| `config gap` | `config gap=60` | Gap between root-level items (default: 80) |
|
|
132
135
|
| `config margin` | `config margin=40` | Outer canvas margin (default: 60) |
|
|
133
136
|
| `config theme` | `config theme=ocean` | Global palette (see [Theme Palettes](#theme-palettes)) |
|
|
134
137
|
| `config font` | `config font=caveat` | Diagram-wide font (see [Font System](#font-system)) |
|
|
138
|
+
| `config title-color` | `config title-color=#333` | Title text color |
|
|
139
|
+
| `config title-size` | `config title-size=20` | Title font size in px |
|
|
140
|
+
| `config title-weight` | `config title-weight=700` | Title font weight |
|
|
135
141
|
|
|
136
142
|
---
|
|
137
143
|
|
|
@@ -147,6 +153,7 @@ cylinder id label="..."
|
|
|
147
153
|
parallelogram id label="..."
|
|
148
154
|
text id label="..."
|
|
149
155
|
image id label="..." url="https://..."
|
|
156
|
+
icon id label="..." name="prefix:name"
|
|
150
157
|
```
|
|
151
158
|
|
|
152
159
|
**Common properties:**
|
|
@@ -159,6 +166,9 @@ image id label="..." url="https://..."
|
|
|
159
166
|
| `height` | `height=55` | Override auto-height in px |
|
|
160
167
|
| `fill` | `fill="#e8f4ff"` | Background fill color |
|
|
161
168
|
| `stroke` | `stroke="#0044cc"` | Border color |
|
|
169
|
+
| `stroke-width` | `stroke-width=2` | Border thickness in px |
|
|
170
|
+
| `stroke-dash` | `stroke-dash=5,3` | Dashed border pattern (dash, gap) |
|
|
171
|
+
| `opacity` | `opacity=0.5` | Element opacity (0 to 1) |
|
|
162
172
|
| `color` | `color="#003399"` | Text color |
|
|
163
173
|
| `font` | `font=caveat` | Font family or built-in name |
|
|
164
174
|
| `font-size` | `font-size=12` | Label font size in px |
|
|
@@ -170,7 +180,9 @@ image id label="..." url="https://..."
|
|
|
170
180
|
|
|
171
181
|
> **`text` shape:** No border or background. Long labels auto word-wrap. Use `width=` to control the wrap width.
|
|
172
182
|
|
|
173
|
-
> **`image` shape:** Renders an image clipped to a rounded rect. Requires `url=` property.
|
|
183
|
+
> **`image` shape:** Renders an image clipped to a rounded rect. Requires `url=` property. Label renders below the image. Border only shown when `stroke=` is set.
|
|
184
|
+
|
|
185
|
+
> **`icon` shape:** Renders an icon from [Iconify](https://iconify.design/) (200,000+ open source icons). Requires `name=` property in `prefix:name` format (e.g. `mdi:database`). Defaults to `mdi` prefix if omitted. Use `color=` to tint the icon. Label renders below the icon. Border only shown when `stroke=` is set. Default size: 48x48.
|
|
174
186
|
|
|
175
187
|
**Example:**
|
|
176
188
|
```
|
|
@@ -178,11 +190,42 @@ box gateway label="API Gateway" theme=warning width=150 height=55
|
|
|
178
190
|
circle user label="User" fill="#e8f4ff" stroke="#0044cc" color="#003399"
|
|
179
191
|
cylinder db label="PostgreSQL" theme=success width=140 height=65
|
|
180
192
|
image logo label="Logo" url="https://example.com/logo.png" width=80 height=80
|
|
193
|
+
icon db label="Database" name="mdi:database" color="#1976D2"
|
|
194
|
+
icon cloud name="mdi:cloud" width=64 height=64
|
|
181
195
|
text caption label="This auto-wraps across multiple lines." width=300
|
|
182
196
|
```
|
|
183
197
|
|
|
184
198
|
---
|
|
185
199
|
|
|
200
|
+
### Icon Shape
|
|
201
|
+
|
|
202
|
+
Render any of 200,000+ open source vector icons from [Iconify](https://iconify.design/).
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
icon id [label="..."] name="prefix:name" [color="#hex"] [width=N] [height=N]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
| Property | Example | Description |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `name` | `name="mdi:database"` | Icon identifier in `prefix:name` format. Defaults to `mdi` prefix if omitted |
|
|
211
|
+
| `color` | `color="#1976D2"` | Icon tint color |
|
|
212
|
+
| `stroke` | `stroke="#333"` | Optional border (not shown by default) |
|
|
213
|
+
| `label` | `label="DB"` | Label shown below the icon (defaults to id) |
|
|
214
|
+
| `width` | `width=64` | Icon width (default: 48) |
|
|
215
|
+
| `height` | `height=64` | Icon height (default: 48) |
|
|
216
|
+
|
|
217
|
+
Browse available icons at [icon-sets.iconify.design](https://icon-sets.iconify.design/). Common prefixes: `mdi` (Material Design), `lucide`, `heroicons`, `tabler`, `ph` (Phosphor), `ri` (Remix), `carbon`.
|
|
218
|
+
|
|
219
|
+
**Example:**
|
|
220
|
+
```
|
|
221
|
+
icon db label="Database" name="mdi:database" color="#1976D2"
|
|
222
|
+
icon cloud label="Cloud" name="mdi:cloud-outline" color="#FF9800" width=64 height=64
|
|
223
|
+
icon lock name="mdi:lock" color="#E53935"
|
|
224
|
+
icon user name="lucide:user"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
186
229
|
### Edges
|
|
187
230
|
|
|
188
231
|
```
|
|
@@ -483,6 +526,19 @@ Apply to any element: `box a theme=primary`, `group g theme=muted`, `note n them
|
|
|
483
526
|
|
|
484
527
|
---
|
|
485
528
|
|
|
529
|
+
### Style Directive
|
|
530
|
+
|
|
531
|
+
Apply styles to any element after it's defined, by targeting its id:
|
|
532
|
+
|
|
533
|
+
```
|
|
534
|
+
box a label="Hello"
|
|
535
|
+
style a fill="#ff0000" stroke="#cc0000" font-size=16
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
This merges with any existing styles on the element. Useful for separating layout from styling.
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
486
542
|
### Typography
|
|
487
543
|
|
|
488
544
|
Typography properties work on all text-bearing elements.
|
|
@@ -540,6 +596,7 @@ All actions work on **all element types** — nodes, groups, tables, notes, char
|
|
|
540
596
|
| Option | Description |
|
|
541
597
|
|---|---|
|
|
542
598
|
| `duration=600` | Animation duration in ms |
|
|
599
|
+
| `delay=100` | Delay before animation starts in ms |
|
|
543
600
|
| `dx=100` | X offset for `move` |
|
|
544
601
|
| `dy=-80` | Y offset for `move` |
|
|
545
602
|
| `factor=1.5` | Scale multiplier |
|
package/dist/index.cjs
CHANGED
|
@@ -424,7 +424,7 @@ function parse(src) {
|
|
|
424
424
|
kind: "node",
|
|
425
425
|
id,
|
|
426
426
|
shape,
|
|
427
|
-
label: props.label ||
|
|
427
|
+
label: props.label || id,
|
|
428
428
|
...(groupId ? { groupId } : {}),
|
|
429
429
|
...(props.width ? { width: parseFloat(props.width) } : {}),
|
|
430
430
|
...(props.height ? { height: parseFloat(props.height) } : {}),
|
|
@@ -1509,10 +1509,13 @@ function sizeNode(n) {
|
|
|
1509
1509
|
}
|
|
1510
1510
|
break;
|
|
1511
1511
|
}
|
|
1512
|
-
case "icon":
|
|
1513
|
-
|
|
1514
|
-
|
|
1512
|
+
case "icon": {
|
|
1513
|
+
const iconBase = 48;
|
|
1514
|
+
const labelH = n.label ? 20 : 0;
|
|
1515
|
+
n.w = n.w || Math.max(iconBase, n.label ? labelW : 0);
|
|
1516
|
+
n.h = n.h || (iconBase + labelH);
|
|
1515
1517
|
break;
|
|
1518
|
+
}
|
|
1516
1519
|
default:
|
|
1517
1520
|
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
|
|
1518
1521
|
n.h = n.h || 52;
|
|
@@ -5116,27 +5119,32 @@ function renderShape$1(rc, n, palette) {
|
|
|
5116
5119
|
const iconColor = s.color
|
|
5117
5120
|
? encodeURIComponent(String(s.color))
|
|
5118
5121
|
: encodeURIComponent(String(palette.nodeStroke));
|
|
5119
|
-
|
|
5122
|
+
// reserve bottom 20px for label when present
|
|
5123
|
+
const labelSpace = n.label ? 20 : 0;
|
|
5124
|
+
const iconAreaH = n.h - labelSpace;
|
|
5125
|
+
const iconSize = Math.min(n.w, iconAreaH) - 4;
|
|
5120
5126
|
const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
|
|
5121
5127
|
const img = document.createElementNS(NS, "image");
|
|
5122
5128
|
img.setAttribute("href", iconUrl);
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
img.setAttribute("
|
|
5126
|
-
img.setAttribute("
|
|
5129
|
+
const iconX = n.x + (n.w - iconSize) / 2;
|
|
5130
|
+
const iconY = n.y + (iconAreaH - iconSize) / 2;
|
|
5131
|
+
img.setAttribute("x", String(iconX));
|
|
5132
|
+
img.setAttribute("y", String(iconY));
|
|
5133
|
+
img.setAttribute("width", String(iconSize));
|
|
5134
|
+
img.setAttribute("height", String(iconSize));
|
|
5127
5135
|
img.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
5128
5136
|
if (s.opacity != null)
|
|
5129
5137
|
img.setAttribute("opacity", String(s.opacity));
|
|
5130
|
-
// clip-path for rounded corners
|
|
5138
|
+
// clip-path for rounded corners
|
|
5131
5139
|
const clipId = `clip-${n.id}`;
|
|
5132
5140
|
const defs = document.createElementNS(NS, "defs");
|
|
5133
5141
|
const clip = document.createElementNS(NS, "clipPath");
|
|
5134
5142
|
clip.setAttribute("id", clipId);
|
|
5135
5143
|
const rect = document.createElementNS(NS, "rect");
|
|
5136
|
-
rect.setAttribute("x", String(
|
|
5137
|
-
rect.setAttribute("y", String(
|
|
5138
|
-
rect.setAttribute("width", String(
|
|
5139
|
-
rect.setAttribute("height", String(
|
|
5144
|
+
rect.setAttribute("x", String(iconX));
|
|
5145
|
+
rect.setAttribute("y", String(iconY));
|
|
5146
|
+
rect.setAttribute("width", String(iconSize));
|
|
5147
|
+
rect.setAttribute("height", String(iconSize));
|
|
5140
5148
|
rect.setAttribute("rx", "6");
|
|
5141
5149
|
clip.appendChild(rect);
|
|
5142
5150
|
defs.appendChild(clip);
|
|
@@ -5162,12 +5170,15 @@ function renderShape$1(rc, n, palette) {
|
|
|
5162
5170
|
}
|
|
5163
5171
|
case "image": {
|
|
5164
5172
|
if (n.imageUrl) {
|
|
5173
|
+
// reserve bottom 20px for label when present
|
|
5174
|
+
const imgLabelSpace = n.label ? 20 : 0;
|
|
5175
|
+
const imgAreaH = n.h - imgLabelSpace;
|
|
5165
5176
|
const img = document.createElementNS(NS, "image");
|
|
5166
5177
|
img.setAttribute("href", n.imageUrl);
|
|
5167
5178
|
img.setAttribute("x", String(n.x + 1));
|
|
5168
5179
|
img.setAttribute("y", String(n.y + 1));
|
|
5169
5180
|
img.setAttribute("width", String(n.w - 2));
|
|
5170
|
-
img.setAttribute("height", String(
|
|
5181
|
+
img.setAttribute("height", String(imgAreaH - 2));
|
|
5171
5182
|
img.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
5172
5183
|
const clipId = `clip-${n.id}`;
|
|
5173
5184
|
const defs = document.createElementNS(NS, "defs");
|
|
@@ -5177,7 +5188,7 @@ function renderShape$1(rc, n, palette) {
|
|
|
5177
5188
|
rect.setAttribute("x", String(n.x + 1));
|
|
5178
5189
|
rect.setAttribute("y", String(n.y + 1));
|
|
5179
5190
|
rect.setAttribute("width", String(n.w - 2));
|
|
5180
|
-
rect.setAttribute("height", String(
|
|
5191
|
+
rect.setAttribute("height", String(imgAreaH - 2));
|
|
5181
5192
|
rect.setAttribute("rx", "6");
|
|
5182
5193
|
clip.appendChild(rect);
|
|
5183
5194
|
defs.appendChild(clip);
|
|
@@ -5429,11 +5440,14 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5429
5440
|
const nodeBodyBottom = n.y + n.h - pad;
|
|
5430
5441
|
const nodeBodyMid = n.y + n.h / 2;
|
|
5431
5442
|
const blockH = (lines.length - 1) * lineHeight;
|
|
5432
|
-
const
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5443
|
+
const isMediaShape = n.shape === "icon" || n.shape === "image";
|
|
5444
|
+
const textCY = isMediaShape
|
|
5445
|
+
? n.y + n.h - 10 // label below the icon/image
|
|
5446
|
+
: verticalAlign === "top"
|
|
5447
|
+
? nodeBodyTop + blockH / 2
|
|
5448
|
+
: verticalAlign === "bottom"
|
|
5449
|
+
? nodeBodyBottom - blockH / 2
|
|
5450
|
+
: nodeBodyMid;
|
|
5437
5451
|
if (n.label) {
|
|
5438
5452
|
ng.appendChild(lines.length > 1
|
|
5439
5453
|
? mkMultilineText(lines, textX, textCY, fontSize, fontWeight, textColor, textAnchor, lineHeight, nodeFont, letterSpacing)
|
|
@@ -6144,7 +6158,10 @@ function renderShape(rc, ctx, n, palette, R) {
|
|
|
6144
6158
|
const iconColor = s.color
|
|
6145
6159
|
? encodeURIComponent(String(s.color))
|
|
6146
6160
|
: encodeURIComponent(String(palette.nodeStroke));
|
|
6147
|
-
|
|
6161
|
+
// reserve bottom for label
|
|
6162
|
+
const iconLabelSpace = n.label ? 20 : 0;
|
|
6163
|
+
const iconAreaH = n.h - iconLabelSpace;
|
|
6164
|
+
const iconSize = Math.min(n.w, iconAreaH) - 4;
|
|
6148
6165
|
const iconUrl = `https://api.iconify.design/${prefix}/${name}.svg?color=${iconColor}&width=${iconSize}&height=${iconSize}`;
|
|
6149
6166
|
const img = new Image();
|
|
6150
6167
|
img.crossOrigin = 'anonymous';
|
|
@@ -6152,23 +6169,23 @@ function renderShape(rc, ctx, n, palette, R) {
|
|
|
6152
6169
|
ctx.save();
|
|
6153
6170
|
if (s.opacity != null)
|
|
6154
6171
|
ctx.globalAlpha = Number(s.opacity);
|
|
6155
|
-
|
|
6172
|
+
const iconX = n.x + (n.w - iconSize) / 2;
|
|
6173
|
+
const iconY = n.y + (iconAreaH - iconSize) / 2;
|
|
6156
6174
|
ctx.beginPath();
|
|
6157
6175
|
const r = 6;
|
|
6158
|
-
ctx.moveTo(
|
|
6159
|
-
ctx.lineTo(
|
|
6160
|
-
ctx.quadraticCurveTo(
|
|
6161
|
-
ctx.lineTo(
|
|
6162
|
-
ctx.quadraticCurveTo(
|
|
6163
|
-
ctx.lineTo(
|
|
6164
|
-
ctx.quadraticCurveTo(
|
|
6165
|
-
ctx.lineTo(
|
|
6166
|
-
ctx.quadraticCurveTo(
|
|
6176
|
+
ctx.moveTo(iconX + r, iconY);
|
|
6177
|
+
ctx.lineTo(iconX + iconSize - r, iconY);
|
|
6178
|
+
ctx.quadraticCurveTo(iconX + iconSize, iconY, iconX + iconSize, iconY + r);
|
|
6179
|
+
ctx.lineTo(iconX + iconSize, iconY + iconSize - r);
|
|
6180
|
+
ctx.quadraticCurveTo(iconX + iconSize, iconY + iconSize, iconX + iconSize - r, iconY + iconSize);
|
|
6181
|
+
ctx.lineTo(iconX + r, iconY + iconSize);
|
|
6182
|
+
ctx.quadraticCurveTo(iconX, iconY + iconSize, iconX, iconY + iconSize - r);
|
|
6183
|
+
ctx.lineTo(iconX, iconY + r);
|
|
6184
|
+
ctx.quadraticCurveTo(iconX, iconY, iconX + r, iconY);
|
|
6167
6185
|
ctx.closePath();
|
|
6168
6186
|
ctx.clip();
|
|
6169
|
-
ctx.drawImage(img,
|
|
6187
|
+
ctx.drawImage(img, iconX, iconY, iconSize, iconSize);
|
|
6170
6188
|
ctx.restore();
|
|
6171
|
-
// only draw border when stroke is explicitly set
|
|
6172
6189
|
if (s.stroke) {
|
|
6173
6190
|
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, { ...opts, fill: 'none' });
|
|
6174
6191
|
}
|
|
@@ -6182,6 +6199,9 @@ function renderShape(rc, ctx, n, palette, R) {
|
|
|
6182
6199
|
}
|
|
6183
6200
|
case 'image': {
|
|
6184
6201
|
if (n.imageUrl) {
|
|
6202
|
+
// reserve bottom for label
|
|
6203
|
+
const imgLblSpace = n.label ? 20 : 0;
|
|
6204
|
+
const imgAreaH = n.h - imgLblSpace;
|
|
6185
6205
|
const img = new Image();
|
|
6186
6206
|
img.crossOrigin = 'anonymous';
|
|
6187
6207
|
img.onload = () => {
|
|
@@ -6191,15 +6211,15 @@ function renderShape(rc, ctx, n, palette, R) {
|
|
|
6191
6211
|
ctx.moveTo(n.x + r, n.y);
|
|
6192
6212
|
ctx.lineTo(n.x + n.w - r, n.y);
|
|
6193
6213
|
ctx.quadraticCurveTo(n.x + n.w, n.y, n.x + n.w, n.y + r);
|
|
6194
|
-
ctx.lineTo(n.x + n.w, n.y +
|
|
6195
|
-
ctx.quadraticCurveTo(n.x + n.w, n.y +
|
|
6196
|
-
ctx.lineTo(n.x + r, n.y +
|
|
6197
|
-
ctx.quadraticCurveTo(n.x, n.y +
|
|
6214
|
+
ctx.lineTo(n.x + n.w, n.y + imgAreaH - r);
|
|
6215
|
+
ctx.quadraticCurveTo(n.x + n.w, n.y + imgAreaH, n.x + n.w - r, n.y + imgAreaH);
|
|
6216
|
+
ctx.lineTo(n.x + r, n.y + imgAreaH);
|
|
6217
|
+
ctx.quadraticCurveTo(n.x, n.y + imgAreaH, n.x, n.y + imgAreaH - r);
|
|
6198
6218
|
ctx.lineTo(n.x, n.y + r);
|
|
6199
6219
|
ctx.quadraticCurveTo(n.x, n.y, n.x + r, n.y);
|
|
6200
6220
|
ctx.closePath();
|
|
6201
6221
|
ctx.clip();
|
|
6202
|
-
ctx.drawImage(img, n.x + 1, n.y + 1, n.w - 2,
|
|
6222
|
+
ctx.drawImage(img, n.x + 1, n.y + 1, n.w - 2, imgAreaH - 2);
|
|
6203
6223
|
ctx.restore();
|
|
6204
6224
|
// only draw border when stroke is explicitly set
|
|
6205
6225
|
if (s.stroke) {
|
|
@@ -6391,9 +6411,12 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6391
6411
|
const nodeBodyTop = n.y + pad;
|
|
6392
6412
|
const nodeBodyBottom = n.y + n.h - pad;
|
|
6393
6413
|
const blockH = (lines.length - 1) * lineHeight;
|
|
6394
|
-
const
|
|
6395
|
-
|
|
6396
|
-
|
|
6414
|
+
const isMediaShape = n.shape === 'icon' || n.shape === 'image';
|
|
6415
|
+
const textCY = isMediaShape
|
|
6416
|
+
? n.y + n.h - 10 // label below the icon/image
|
|
6417
|
+
: vertAlign === 'top' ? nodeBodyTop + blockH / 2
|
|
6418
|
+
: vertAlign === 'bottom' ? nodeBodyBottom - blockH / 2
|
|
6419
|
+
: n.y + n.h / 2; // middle (default)
|
|
6397
6420
|
if (n.label) {
|
|
6398
6421
|
if (lines.length > 1) {
|
|
6399
6422
|
drawMultilineText(ctx, lines, textX, textCY, fontSize, fontWeight, textColor, textAlign, lineHeight, nodeFont, letterSpacing);
|