@zuilib/text-editor 0.3.1 → 0.5.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/CHANGELOG.md +63 -0
- package/DRAWING_FORMAT.md +240 -66
- package/README.md +121 -48
- package/dist/index.d.ts +522 -58
- package/dist/index.js +3308 -1361
- package/dist/styles.css +336 -91
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,70 @@
|
|
|
1
1
|
# Changelog — @zuilib/text-editor
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
**Diagram builder rewrite.** The drawing canvas, its payload format and the
|
|
6
|
+
authoring story for LLMs were rebuilt.
|
|
7
|
+
|
|
8
|
+
- **Format v2 (breaking)**: ` ```drawing ` payloads are now
|
|
9
|
+
`{"version":2,"canvasHeight":<px>,"canvasWidth"?:<px>,"width"?:"content","shapes":[...]}`.
|
|
10
|
+
No migration from version 1 and no legacy spellings: anything that is
|
|
11
|
+
not a version 2 payload renders as an empty canvas.
|
|
12
|
+
`canvasWidth` is optional: when set the drawing scales to fit narrower
|
|
13
|
+
layouts (SVG viewBox), when absent the canvas is fluid. `triangle` and
|
|
14
|
+
`pentagon` are removed. `DRAWING_DATA_JSON_SCHEMA` and
|
|
15
|
+
`DRAWING_FORMAT.md` rewritten for v2
|
|
16
|
+
- **Shapes**: boxes `rect`, `ellipse`, `diamond`, `note` (sticky note with
|
|
17
|
+
a folded corner, default fill `#ffec99`), `cylinder` (database), `cloud`,
|
|
18
|
+
`queue` (horizontal cylinder), `actor` (stick figure with a name under
|
|
19
|
+
it, `text` slot only); connectors `arrow`, `line`; free `text`. Palette
|
|
20
|
+
gains purple (`#7048e8` / `#d0bfff`)
|
|
21
|
+
- **Bindings** are objects: `{ id, fixedPoint?: [fx, fy], mode?: "orbit" |
|
|
22
|
+
"inside" }`. `fixedPoint` is a ratio in the box's bounding box; omitted,
|
|
23
|
+
the endpoint auto-aims from the box center at the other end. `orbit`
|
|
24
|
+
(default) projects the endpoint onto the outline with a 6px gap,
|
|
25
|
+
`inside` pins it exactly. The `side: "top" | "right" | "bottom" |
|
|
26
|
+
"left"` shorthand exists only in the ` ```diagram ` skeleton
|
|
27
|
+
- **Router**: `routing: "elbow"` is auto-routed. It leaves the box
|
|
28
|
+
perpendicular to the attach side, avoids every box on the canvas and
|
|
29
|
+
minimises bends. `elbow` (0 to 1) remains as an override of the middle
|
|
30
|
+
segment when the path is a simple three-segment Z; `waypoints` still
|
|
31
|
+
take precedence
|
|
32
|
+
- **Editor UX**: multi-select (shift-click, marquee on empty canvas; move,
|
|
33
|
+
delete and recolor together), redesigned toolbar with a "more shapes"
|
|
34
|
+
popover, contextual property bar, smoother drag and selection visuals,
|
|
35
|
+
**Copy as Mermaid** button
|
|
36
|
+
- **` ```diagram ` skeleton**: a fenced block with a coordinate-free JSON
|
|
37
|
+
skeleton (`boxes`, `connectors`, `texts`, `direction`, palette `color`
|
|
38
|
+
names) that the editor auto-lays out and expands into a ` ```drawing `
|
|
39
|
+
block on import (one-way). New exports `expandSkeleton`,
|
|
40
|
+
`parseDrawingSkeleton`, `DRAWING_SKELETON_JSON_SCHEMA`, `DIAGRAM`
|
|
41
|
+
transformer, types `DrawingSkeleton`, `SkeletonBox`, `SkeletonConnector`
|
|
42
|
+
- **Mermaid export**: `drawingToMermaid(data, { direction? })` returns a
|
|
43
|
+
`flowchart` with one node per box (shape-specific node syntax), edges for
|
|
44
|
+
connectors bound at both ends (labels as `|text|`), `style` lines for
|
|
45
|
+
coloured boxes and free text as `%% note:` comments
|
|
46
|
+
- Internal: the drawing code moved from `src/components/` into
|
|
47
|
+
`src/drawing/` (`types.ts`, `schema.ts`, `geometry.ts`, `mermaid.ts`,
|
|
48
|
+
`shapes/`, `canvas/`); shape geometry is data-driven so the router,
|
|
49
|
+
bindings, skeleton expansion and exporters run without a DOM
|
|
50
|
+
|
|
3
51
|
## 0.4.0
|
|
4
52
|
|
|
53
|
+
- **Table density**, Slab-style: the floating table toolbar (caret in a
|
|
54
|
+
table) gains *compact* / *comfortable* (default) / *spacious* row padding
|
|
55
|
+
and font size, next to the width toggle
|
|
56
|
+
- The settings comment above a GFM table now lists every non-default
|
|
57
|
+
setting, e.g. `<!-- width: content; density: compact -->`; the 0.3.1
|
|
58
|
+
`<!-- width: content -->` form is unchanged for width-only tables
|
|
59
|
+
- `useMarkdownEditor()` gains `tableDensity` / `setTableDensity`
|
|
60
|
+
- New exports: `TableDensity`, `TableSettings`, `DEFAULT_TABLE_SETTINGS`,
|
|
61
|
+
`$getTableSettings` / `$setTableSettings`, `$getTableDensity` /
|
|
62
|
+
`$setTableDensity`, `parseTableSettingsMarker` /
|
|
63
|
+
`formatTableSettingsMarker`. `TABLE_WIDTH_MARKER` is deprecated in favour
|
|
64
|
+
of `formatTableSettingsMarker`
|
|
65
|
+
|
|
66
|
+
## 0.3.1
|
|
67
|
+
|
|
5
68
|
- **Block width** for tables and drawings: switch any table or drawing
|
|
6
69
|
between *full width* (spans the editor, the previous behaviour) and
|
|
7
70
|
*content width* (shrinks to fit its columns / shapes, left-aligned with
|
package/DRAWING_FORMAT.md
CHANGED
|
@@ -1,43 +1,59 @@
|
|
|
1
|
-
# The `drawing` block
|
|
1
|
+
# The `drawing` and `diagram` block formats
|
|
2
2
|
|
|
3
3
|
`@zuilib/text-editor` documents are plain markdown. Diagrams are embedded as
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
fenced code blocks containing a single JSON object. Two languages exist:
|
|
5
|
+
|
|
6
|
+
- ```` ```diagram ```` holds a **skeleton**: boxes, connectors and colors, no
|
|
7
|
+
coordinates. The editor lays it out and expands it into a full drawing on
|
|
8
|
+
import. **Generators should emit this one.** See
|
|
9
|
+
[Prefer the ```diagram skeleton](#prefer-the-diagram-skeleton).
|
|
10
|
+
- ```` ```drawing ```` holds the **concrete format** (version 2): every shape
|
|
11
|
+
with its position, size and style. This is what the editor stores once a
|
|
12
|
+
drawing has been edited.
|
|
6
13
|
|
|
7
14
|
````md
|
|
8
15
|
```drawing
|
|
9
|
-
{"version":
|
|
16
|
+
{"version":2,"canvasHeight":320,"shapes":[ ... ]}
|
|
10
17
|
```
|
|
11
18
|
````
|
|
12
19
|
|
|
13
|
-
This document is the authoritative spec of
|
|
14
|
-
|
|
15
|
-
drawings**.
|
|
16
|
-
from the package as `DRAWING_DATA_JSON_SCHEMA
|
|
20
|
+
This document is the authoritative spec of both payloads. It is written so
|
|
21
|
+
it can be handed to a program or an LLM that needs to **generate or edit
|
|
22
|
+
drawings**. Machine-readable JSON Schemas of the same rules are exported
|
|
23
|
+
from the package as `DRAWING_DATA_JSON_SCHEMA` (concrete) and
|
|
24
|
+
`DRAWING_SKELETON_JSON_SCHEMA` (skeleton).
|
|
17
25
|
|
|
18
26
|
Malformed payloads never crash the editor: anything that fails validation
|
|
19
27
|
degrades to an empty canvas (invalid shapes are dropped individually).
|
|
20
28
|
|
|
29
|
+
Exactly one concrete format is supported: version 2, as written by the
|
|
30
|
+
editor. Payloads with any other `version` (or none) render as an empty
|
|
31
|
+
canvas. Convenience spellings such as `side` exist only in the ```diagram
|
|
32
|
+
skeleton, which is a separate block type.
|
|
33
|
+
|
|
21
34
|
## Coordinate system
|
|
22
35
|
|
|
23
36
|
- Origin `(0,0)` is the canvas's top-left; x grows right, y grows down.
|
|
24
37
|
- Units are CSS pixels.
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- `
|
|
38
|
+
- `canvasHeight` is the canvas height in pixels (minimum 80; 300 to 400 is
|
|
39
|
+
typical).
|
|
40
|
+
- `canvasWidth` is optional. When set, the drawing is scaled down to fit
|
|
41
|
+
narrower layouts (SVG viewBox), so coordinates can assume that width.
|
|
42
|
+
When omitted the canvas is fluid and as wide as the editor; keep shapes
|
|
43
|
+
within roughly x in [0, 700] to be safe on typical layouts.
|
|
44
|
+
- `"width":"content"` sizes the block to fit the rightmost shape (plus a
|
|
45
|
+
margin), left-aligned with the text. Use it for small diagrams that
|
|
46
|
+
should not stretch across the page. Default `"full"`, omit it.
|
|
32
47
|
|
|
33
48
|
## Top-level object
|
|
34
49
|
|
|
35
|
-
| Field
|
|
36
|
-
|
|
37
|
-
| `version`
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
50
|
+
| Field | Type | Notes |
|
|
51
|
+
|----------------|-----------|--------------------------------------------------------------|
|
|
52
|
+
| `version` | `2` | Literal `2` |
|
|
53
|
+
| `canvasHeight` | `number` | Canvas height in px (min 80) |
|
|
54
|
+
| `canvasWidth` | `number` | Optional logical width in px (min 120); scales to fit when set |
|
|
55
|
+
| `width` | `"full"` \| `"content"` | Optional; default `"full"`. `"content"` fits the shapes' extent |
|
|
56
|
+
| `shapes` | `Shape[]` | Render order: later shapes draw on top |
|
|
41
57
|
|
|
42
58
|
## Shape object
|
|
43
59
|
|
|
@@ -46,17 +62,33 @@ All shapes share these required fields:
|
|
|
46
62
|
| Field | Type | Meaning |
|
|
47
63
|
|---------------|----------|--------------------------------------------------------------------|
|
|
48
64
|
| `id` | `string` | Unique within the drawing; bindings reference it |
|
|
49
|
-
| `type` | `string` |
|
|
65
|
+
| `type` | `string` | One of the types below |
|
|
50
66
|
| `x`, `y` | `number` | Boxes/text: top-left of the bounding box. Connectors: start point. |
|
|
51
67
|
| `w`, `h` | `number` | Boxes/text: size (non-negative). Connectors: delta to the end point (`end = (x+w, y+h)`; may be negative). |
|
|
52
68
|
| `stroke` | `string` | CSS color of the outline and of all text on the shape |
|
|
53
69
|
| `fill` | `string` | CSS color of the interior; `"transparent"` for none |
|
|
54
70
|
| `strokeWidth` | `number` | Use `2` |
|
|
55
71
|
|
|
56
|
-
###
|
|
72
|
+
### Shape types
|
|
73
|
+
|
|
74
|
+
| Type | Kind | Default size | Notes |
|
|
75
|
+
|------------|-----------|--------------|--------------------------------------------------------------|
|
|
76
|
+
| `rect` | box | 160 x 90 | Rectangle; the general-purpose card |
|
|
77
|
+
| `ellipse` | box | 160 x 90 | Ellipse inscribed in the bounding box |
|
|
78
|
+
| `diamond` | box | 170 x 100 | Decision; text area is the inner half |
|
|
79
|
+
| `note` | box | 160 x 110 | Sticky note with a folded bottom-right corner; default fill `#ffec99` |
|
|
80
|
+
| `cylinder` | box | 140 x 110 | Database; text sits below the top ellipse |
|
|
81
|
+
| `cloud` | box | 180 x 110 | Cloud; outline is an ellipse for binding purposes |
|
|
82
|
+
| `queue` | box | 180 x 80 | Horizontal cylinder (queue, stream, pipe) |
|
|
83
|
+
| `actor` | box | 90 x 120 | Stick figure; `text` only, rendered as a name under the figure |
|
|
84
|
+
| `arrow` | connector | | Head at the end; `bidirectional` for both ends |
|
|
85
|
+
| `line` | connector | | No arrowhead |
|
|
86
|
+
| `text` | free text | | Floating annotation |
|
|
87
|
+
|
|
88
|
+
### Boxes ("cards")
|
|
57
89
|
|
|
58
90
|
Boxes are card-like: they can carry up to three text slots that render
|
|
59
|
-
inside the shape and move
|
|
91
|
+
inside the shape and move, resize and wrap with it:
|
|
60
92
|
|
|
61
93
|
| Field | Renders as |
|
|
62
94
|
|----------|-----------------------------------------------|
|
|
@@ -64,12 +96,14 @@ inside the shape and move/resize/wrap with it:
|
|
|
64
96
|
| `text` | Main content, centered |
|
|
65
97
|
| `footer` | Small dim line at the bottom |
|
|
66
98
|
|
|
67
|
-
Text wraps automatically to the box
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
Text wraps automatically to the box's text area; `\n` forces a line break.
|
|
100
|
+
The text area is smaller than the bounding box for non-rectangular shapes
|
|
101
|
+
(diamond, ellipse, cloud, cylinder), so give those a little more room.
|
|
102
|
+
`actor` has only the `text` slot: it is drawn under the figure and may be
|
|
103
|
+
wider than the box; `label` and `footer` are dropped. Prefer slots over
|
|
104
|
+
floating `text` shapes; slot text is attached to the card.
|
|
71
105
|
|
|
72
|
-
### `text
|
|
106
|
+
### `text`: free-floating annotation
|
|
73
107
|
|
|
74
108
|
`text` holds the content. `w`/`h` are advisory (the editor recomputes them
|
|
75
109
|
from the content); position is the top-left of the first line. Use only for
|
|
@@ -85,70 +119,210 @@ Geometry: from `(x, y)` to `(x+w, y+h)`. `arrow` has a head at the end;
|
|
|
85
119
|
|-----------------|------------|-------------------------------------------------------------------------|
|
|
86
120
|
| `text` | `string` | Label rendered at the path midpoint on a small backing plate |
|
|
87
121
|
| `bidirectional` | `boolean` | Arrows only: heads on both ends |
|
|
88
|
-
| `startBinding` | `
|
|
89
|
-
| `endBinding` | `
|
|
90
|
-
| `routing` | `"elbow"` |
|
|
91
|
-
| `elbow` | `number` | 0
|
|
92
|
-
| `waypoints` | `Point[]` | Explicit intermediate path points (`{x,y}`), ordered start
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
a
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
| `startBinding` | `Binding` | Box the start attaches to (see below) |
|
|
123
|
+
| `endBinding` | `Binding` | Box the end attaches to |
|
|
124
|
+
| `routing` | `"elbow"` | Right-angled auto-routed path |
|
|
125
|
+
| `elbow` | `number` | 0 to 1: override of the middle segment, only for a simple three-segment Z |
|
|
126
|
+
| `waypoints` | `Point[]` | Explicit intermediate path points (`{x,y}`), ordered start to end |
|
|
127
|
+
|
|
128
|
+
## Bindings
|
|
129
|
+
|
|
130
|
+
A binding attaches a connector endpoint to a box and keeps it attached as
|
|
131
|
+
the box moves:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{ "id": "<box id>", "fixedPoint": [fx, fy], "mode": "orbit" }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| Field | Type | Meaning |
|
|
138
|
+
|--------------|--------------------------|-------------------------------------------------------------------------|
|
|
139
|
+
| `id` | `string` | Required. Id of a box shape (bindings to unknown or non-box ids are dropped) |
|
|
140
|
+
| `fixedPoint` | `[number, number]` | Optional. Attach point as a ratio of the box's bounding box: `[0,0]` top-left, `[1,1]` bottom-right, `[0.5,0]` top edge midpoint. Values are clamped to 0 to 1 |
|
|
141
|
+
| `mode` | `"orbit"` \| `"inside"` | Optional, default `"orbit"` |
|
|
142
|
+
|
|
143
|
+
- **Omit `fixedPoint`** when you do not care where the connector attaches:
|
|
144
|
+
the endpoint auto-aims from the box center at the other end of the
|
|
145
|
+
connector, so the line always meets the outline on the facing side.
|
|
146
|
+
- **Set `fixedPoint` to an edge midpoint** when the attach edge matters (a
|
|
147
|
+
decision's "yes" leaves at the bottom `[0.5,1]`, "no" at the right
|
|
148
|
+
`[1,0.5]`). In the ```diagram skeleton the same thing is spelled
|
|
149
|
+
`"side":"bottom"`.
|
|
150
|
+
- `mode: "orbit"` (default) projects the endpoint onto the box outline with
|
|
151
|
+
a 6px gap. `mode: "inside"` pins the endpoint exactly on the fixed point
|
|
152
|
+
(useful for lines that end inside a shape).
|
|
153
|
+
|
|
154
|
+
Bound endpoints are re-anchored by the editor, so the connector's own
|
|
155
|
+
`x`/`y`/`w`/`h` only need to be approximately right: place them near the
|
|
156
|
+
intended boxes and the resolver snaps them. Don't bind both ends of one
|
|
157
|
+
connector to the same box.
|
|
158
|
+
|
|
159
|
+
## Routing
|
|
160
|
+
|
|
161
|
+
Omit `routing` for a straight (possibly diagonal) connector.
|
|
162
|
+
|
|
163
|
+
`"routing":"elbow"` renders an auto-routed orthogonal path: it leaves the
|
|
164
|
+
box perpendicular to the attach side, avoids every box on the canvas, and
|
|
165
|
+
uses as few bends as possible. `elbow` (0 to 1) is an optional override of
|
|
166
|
+
the middle segment's position along the span; it is applied only when the
|
|
167
|
+
routed path is a simple three-segment Z, and ignored otherwise.
|
|
168
|
+
|
|
169
|
+
If `waypoints` is non-empty it defines the path (`start, waypoints..., end`)
|
|
170
|
+
and `routing`/`elbow` are ignored. Waypoints are absolute canvas
|
|
106
171
|
coordinates; align consecutive points on x or y to get right angles.
|
|
107
172
|
|
|
108
173
|
## Recommended colors
|
|
109
174
|
|
|
110
|
-
Any CSS color works; the editor's palette (dark-mode safe
|
|
175
|
+
Any CSS color works; the editor's palette (dark-mode safe, the canvas
|
|
111
176
|
inverts colors Excalidraw-style in dark themes):
|
|
112
177
|
|
|
113
|
-
|
|
114
|
-
|
|
178
|
+
| Name | Stroke | Fill |
|
|
179
|
+
|--------|-----------|---------------|
|
|
180
|
+
| gray | `#1e1e1e` | `transparent` |
|
|
181
|
+
| red | `#e03131` | `#ffc9c9` |
|
|
182
|
+
| green | `#2f9e44` | `#b2f2bb` |
|
|
183
|
+
| blue | `#1971c2` | `#a5d8ff` |
|
|
184
|
+
| orange | `#f08c00` | `#ffec99` |
|
|
185
|
+
| purple | `#7048e8` | `#d0bfff` |
|
|
186
|
+
|
|
187
|
+
Pair a colored stroke with its pastel fill. `note` defaults to the yellow
|
|
188
|
+
fill `#ffec99`.
|
|
189
|
+
|
|
190
|
+
## Prefer the ```diagram skeleton
|
|
191
|
+
|
|
192
|
+
A fenced block with language `diagram` holds a skeleton: what the boxes are
|
|
193
|
+
and how they connect. The editor expands it into a full ```drawing block
|
|
194
|
+
on import (auto layout, default sizes, palette colors). The expansion is
|
|
195
|
+
one-way: once opened, the document stores the concrete ```drawing block.
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
{
|
|
199
|
+
"direction"?: "right" | "down", // auto-layout flow, default right
|
|
200
|
+
"canvasWidth"?: n, "canvasHeight"?: n, "width"?: "content",
|
|
201
|
+
"boxes": [ { "id", "type"?: <box type, default rect>, "label"?, "text"?, "footer"?,
|
|
202
|
+
"x"?, "y"?, // omitted: auto layout by connector rank
|
|
203
|
+
"w"?, "h"?, // omitted: sized to fit the text
|
|
204
|
+
"color"?: "gray"|"red"|"green"|"blue"|"orange"|"purple", // stroke + pastel fill
|
|
205
|
+
"stroke"?, "fill"? } ],
|
|
206
|
+
"connectors"?: [ { "id"?, "type"?: "arrow"|"line", "from": "<id>" | {"id","side"?}, "to": same,
|
|
207
|
+
"text"?, "bidirectional"?, "routing"?: "elbow"|"straight", "color"?, "stroke"? } ],
|
|
208
|
+
"texts"?: [ { "x", "y", "text", "color"? } ]
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Rules:
|
|
213
|
+
|
|
214
|
+
- `boxes` is required; every box needs a unique `id`. `type` is any box
|
|
215
|
+
type from the table above (default `rect`).
|
|
216
|
+
- `from`/`to` are box ids, or `{"id","side"}` to pick the attach edge.
|
|
217
|
+
- `color` picks a palette pair (stroke plus pastel fill) from the table
|
|
218
|
+
above. `stroke`/`fill` override it with explicit CSS colors. On
|
|
219
|
+
connectors only the stroke applies.
|
|
115
220
|
|
|
116
|
-
|
|
221
|
+
- Auto layout: boxes are ranked along `direction` by the longest path from
|
|
222
|
+
the sources (cycles are broken). Ranks are 90px apart, boxes within a
|
|
223
|
+
rank 40px apart, starting at (32,32). Boxes with explicit `x`/`y` keep
|
|
224
|
+
them. Boxes without `w`/`h` are sized to fit their text. `canvasHeight`
|
|
225
|
+
is computed when omitted.
|
|
226
|
+
- `texts` are free annotations and need explicit coordinates.
|
|
117
227
|
|
|
118
|
-
|
|
228
|
+
A three-box flow:
|
|
229
|
+
|
|
230
|
+
````md
|
|
231
|
+
```diagram
|
|
232
|
+
{"boxes":[
|
|
233
|
+
{"id":"web","label":"CLIENT","text":"Web App","footer":"React","color":"blue"},
|
|
234
|
+
{"id":"api","label":"SERVICE","text":"API","footer":"Kotlin","color":"green"},
|
|
235
|
+
{"id":"db","type":"cylinder","text":"Postgres"}
|
|
236
|
+
],"connectors":[
|
|
237
|
+
{"from":"web","to":"api","text":"REST"},
|
|
238
|
+
{"from":"api","to":"db"}
|
|
239
|
+
]}
|
|
240
|
+
```
|
|
241
|
+
````
|
|
242
|
+
|
|
243
|
+
A decision with attach sides and elbow routing, flowing down:
|
|
244
|
+
|
|
245
|
+
````md
|
|
246
|
+
```diagram
|
|
247
|
+
{"direction":"down","boxes":[
|
|
248
|
+
{"id":"user","type":"actor","text":"User"},
|
|
249
|
+
{"id":"check","type":"diamond","text":"Valid?"},
|
|
250
|
+
{"id":"save","text":"Save","color":"green"},
|
|
251
|
+
{"id":"err","type":"note","text":"Show error","color":"red"}
|
|
252
|
+
],"connectors":[
|
|
253
|
+
{"from":"user","to":"check"},
|
|
254
|
+
{"from":{"id":"check","side":"bottom"},"to":"save","text":"yes","routing":"elbow"},
|
|
255
|
+
{"from":{"id":"check","side":"right"},"to":{"id":"err","side":"left"},"text":"no","routing":"elbow"}
|
|
256
|
+
]}
|
|
257
|
+
```
|
|
258
|
+
````
|
|
259
|
+
|
|
260
|
+
Programmatic use: `parseDrawingSkeleton(json)` parses a skeleton,
|
|
261
|
+
`expandSkeleton(skeleton)` returns `DrawingData`, and
|
|
262
|
+
`DRAWING_SKELETON_JSON_SCHEMA` is the JSON Schema. Types: `DrawingSkeleton`,
|
|
263
|
+
`SkeletonBox`, `SkeletonConnector`.
|
|
264
|
+
|
|
265
|
+
## Concrete examples
|
|
119
266
|
|
|
120
267
|
Two bound cards with a labeled arrow:
|
|
121
268
|
|
|
122
269
|
````md
|
|
123
270
|
```drawing
|
|
124
|
-
{"version":
|
|
271
|
+
{"version":2,"canvasHeight":260,"shapes":[
|
|
125
272
|
{"id":"web","type":"rect","x":40,"y":70,"w":170,"h":100,"stroke":"#1971c2","fill":"#a5d8ff","strokeWidth":2,"label":"CLIENT","text":"Web App","footer":"React"},
|
|
126
273
|
{"id":"api","type":"rect","x":330,"y":70,"w":170,"h":100,"stroke":"#2f9e44","fill":"#b2f2bb","strokeWidth":2,"label":"SERVICE","text":"API","footer":"Kotlin"},
|
|
127
|
-
{"id":"e1","type":"arrow","x":216,"y":120,"w":108,"h":0,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"startBinding":"web","endBinding":"api","text":"REST"}
|
|
274
|
+
{"id":"e1","type":"arrow","x":216,"y":120,"w":108,"h":0,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"startBinding":{"id":"web"},"endBinding":{"id":"api"},"text":"REST"}
|
|
128
275
|
]}
|
|
129
276
|
```
|
|
130
277
|
````
|
|
131
278
|
|
|
132
|
-
An elbow arrow and a hand-routed multi-bend
|
|
279
|
+
An elbow arrow with attach sides, a database, and a hand-routed multi-bend
|
|
280
|
+
line:
|
|
133
281
|
|
|
134
282
|
````md
|
|
135
283
|
```drawing
|
|
136
|
-
{"version":
|
|
284
|
+
{"version":2,"canvasHeight":300,"canvasWidth":640,"shapes":[
|
|
137
285
|
{"id":"a","type":"rect","x":40,"y":40,"w":150,"h":80,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"text":"A"},
|
|
138
|
-
{"id":"b","type":"
|
|
139
|
-
{"id":"e1","type":"arrow","x":
|
|
140
|
-
{"id":"e2","type":"line","x":115,"y":126,"w":
|
|
286
|
+
{"id":"b","type":"cylinder","x":420,"y":180,"w":140,"h":110,"stroke":"#7048e8","fill":"#d0bfff","strokeWidth":2,"text":"B"},
|
|
287
|
+
{"id":"e1","type":"arrow","x":190,"y":80,"w":300,"h":100,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"startBinding":{"id":"a","fixedPoint":[1,0.5]},"endBinding":{"id":"b","fixedPoint":[0.5,0]},"routing":"elbow"},
|
|
288
|
+
{"id":"e2","type":"line","x":115,"y":126,"w":375,"h":54,"stroke":"#1971c2","fill":"transparent","strokeWidth":2,"startBinding":{"id":"a"},"endBinding":{"id":"b"},"waypoints":[{"x":115,"y":260},{"x":490,"y":260}]}
|
|
141
289
|
]}
|
|
142
290
|
```
|
|
143
291
|
````
|
|
144
292
|
|
|
293
|
+
## Mermaid export
|
|
294
|
+
|
|
295
|
+
The canvas has a **Copy as Mermaid** button. Programmatically,
|
|
296
|
+
`drawingToMermaid(data, { direction?: 'LR' | 'TD' })` returns a
|
|
297
|
+
`flowchart`:
|
|
298
|
+
|
|
299
|
+
- one node per box, in shape order: `rect` and `note` `["…"]`, `ellipse`
|
|
300
|
+
`(["…"])`, `diamond` `{"…"}`, `cylinder` `[("…")]`, `cloud` and `actor`
|
|
301
|
+
`(("…"))`, `queue` `[["…"]]`; node text joins `label`, `text` and
|
|
302
|
+
`footer`
|
|
303
|
+
- one edge per connector bound at both ends: `-->`, `<-->` (bidirectional),
|
|
304
|
+
`---` (line); connector `text` becomes `|text|`. Unbound connectors are
|
|
305
|
+
dropped
|
|
306
|
+
- a `style` line for every box with a non-default stroke or fill
|
|
307
|
+
- free `text` shapes as `%% note:` comments
|
|
308
|
+
|
|
309
|
+
When `direction` is omitted it is `LR` if most edges run horizontally,
|
|
310
|
+
otherwise `TD`.
|
|
311
|
+
|
|
145
312
|
## Generation checklist
|
|
146
313
|
|
|
147
|
-
1.
|
|
148
|
-
|
|
149
|
-
|
|
314
|
+
1. Prefer a ```diagram skeleton. Fall back to ```drawing only when you edit
|
|
315
|
+
an existing concrete payload or need exact positions.
|
|
316
|
+
2. One JSON object per fence, no comments or trailing commas.
|
|
317
|
+
3. Every `id` unique; every binding references an existing box id.
|
|
318
|
+
4. Concrete boxes: at least 120 x 70 when they carry text slots (use the
|
|
319
|
+
default sizes from the table); leave about 60px between cards for
|
|
150
320
|
connectors and labels.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
321
|
+
5. Box text goes in `label`/`text`/`footer`, not in floating `text` shapes.
|
|
322
|
+
`actor` only has `text`.
|
|
323
|
+
6. Connectors between cards bind both ends as objects (`{"id":"web"}`).
|
|
324
|
+
Add an edge-midpoint `fixedPoint` when the attach edge matters; omit it
|
|
325
|
+
otherwise.
|
|
326
|
+
7. Use `"routing":"elbow"` for flowcharts; leave `elbow` and `waypoints`
|
|
327
|
+
out unless you need to override the router.
|
|
328
|
+
8. `canvasHeight` large enough to contain every shape plus ~20px margin.
|