@zuilib/text-editor 0.4.0 → 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 +48 -0
- package/DRAWING_FORMAT.md +240 -66
- package/README.md +94 -31
- package/dist/index.d.ts +492 -43
- package/dist/index.js +3123 -1324
- package/dist/styles.css +328 -84
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
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
|
|
|
5
53
|
- **Table density**, Slab-style: the floating table toolbar (caret in a
|
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.
|
package/README.md
CHANGED
|
@@ -15,9 +15,12 @@ document outline, and collapsible sections.
|
|
|
15
15
|
inline formatting), styled like Claude artifacts
|
|
16
16
|
- **Table settings**: full/content width and row density per table;
|
|
17
17
|
diagrams switch between full and content width too — Slab-style
|
|
18
|
-
- **Diagrams**: a drawing canvas embedded in the document
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
- **Diagrams**: a drawing canvas embedded in the document. Eight box
|
|
19
|
+
shapes (rectangle, ellipse, diamond, note, database, cloud, queue, actor)
|
|
20
|
+
with attached text slots, bound arrows that follow their boxes and attach
|
|
21
|
+
to a chosen side, auto-routed elbows that avoid other boxes, multi-select,
|
|
22
|
+
color palettes, dark mode, Mermaid export. LLMs author diagrams as a
|
|
23
|
+
coordinate-free ` ```diagram ` skeleton that the editor lays out
|
|
21
24
|
- **Outline**: optional table-of-contents sidebar with click-to-scroll and
|
|
22
25
|
current-section highlight
|
|
23
26
|
- **Section folding**: collapse everything under a heading, view-layer only
|
|
@@ -217,29 +220,79 @@ table containing the selection, `null` outside tables), `canUndo`,
|
|
|
217
220
|
## Diagrams (drawing canvas)
|
|
218
221
|
|
|
219
222
|
The toolbar's insert-drawing button embeds a canvas; the drawing persists
|
|
220
|
-
in the markdown as a ` ```drawing ` fenced JSON block
|
|
221
|
-
**[DRAWING_FORMAT.md](./DRAWING_FORMAT.md)**.
|
|
223
|
+
in the markdown as a ` ```drawing ` fenced JSON block (format version 2),
|
|
224
|
+
fully specified in **[DRAWING_FORMAT.md](./DRAWING_FORMAT.md)**.
|
|
222
225
|
|
|
223
|
-
- **Shapes**: rectangle, ellipse,
|
|
224
|
-
|
|
226
|
+
- **Shapes**: rectangle, ellipse, diamond, note (sticky note), database
|
|
227
|
+
(cylinder), cloud, queue, actor (stick figure), arrow, line, text. The
|
|
228
|
+
toolbar shows the common tools and a "more shapes" popover for the rest.
|
|
229
|
+
Draw by picking a tool and dragging.
|
|
225
230
|
- **Cards**: every box carries three text slots that move, resize, and wrap
|
|
226
|
-
with it
|
|
227
|
-
**footer** at the bottom
|
|
228
|
-
top/middle/bottom strip, or
|
|
231
|
+
with it: a bold **label** on top, **content** in the center, a dim
|
|
232
|
+
**footer** at the bottom (actor has only the name under the figure).
|
|
233
|
+
Click a selected box (or double-click its top/middle/bottom strip, or
|
|
234
|
+
press Enter) to edit a slot.
|
|
229
235
|
- **Bound connectors**: an arrow drawn from one card to another attaches to
|
|
230
|
-
both
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
+
both; moving a card moves its arrows, endpoints anchored to the outline.
|
|
237
|
+
An endpoint either auto-aims at the other end or sticks to a fixed point
|
|
238
|
+
on the box (a side, or a ratio inside the box). Drag an endpoint off/onto
|
|
239
|
+
a card to detach/re-attach.
|
|
240
|
+
- **Routing**: straight (diagonal) by default; toggle **elbow** for an
|
|
241
|
+
auto-routed right-angled path that leaves the box perpendicular to its
|
|
242
|
+
attach side and avoids the other boxes, or drag the dashed "+" handles on
|
|
243
|
+
a selected connector to add any number of **waypoints** (they snap to
|
|
244
|
+
neighbors' axes for clean 90° bends). One-way / two-way arrowhead toggle;
|
|
245
|
+
midpoint **labels**.
|
|
246
|
+
- **Multi-select**: shift-click, or drag a marquee on empty canvas, then
|
|
247
|
+
move, delete, or recolor the selection together. A contextual property
|
|
248
|
+
bar shows the options for whatever is selected.
|
|
249
|
+
- **Copy as Mermaid**: a canvas button copies the drawing as a Mermaid
|
|
250
|
+
`flowchart` (also available as `drawingToMermaid(data)`).
|
|
236
251
|
- **Canvas**: resizable height, dot grid, white surface that inverts
|
|
237
|
-
Excalidraw-style in dark mode (`.dark` ancestor class).
|
|
252
|
+
Excalidraw-style in dark mode (`.dark` ancestor class). An optional
|
|
253
|
+
`canvasWidth` scales the drawing to fit narrower layouts.
|
|
238
254
|
- **Width**: the toggle at the right end of the canvas toolbar switches
|
|
239
255
|
between **full width** (spans the editor) and **content width** (the
|
|
240
256
|
canvas fits the rightmost shape and grows as shapes move). Stored as
|
|
241
257
|
`"width":"content"` in the payload; omitted when full.
|
|
242
258
|
|
|
259
|
+
A stored drawing looks like this:
|
|
260
|
+
|
|
261
|
+
````md
|
|
262
|
+
```drawing
|
|
263
|
+
{"version":2,"canvasHeight":260,"shapes":[
|
|
264
|
+
{"id":"web","type":"rect","x":40,"y":70,"w":170,"h":100,"stroke":"#1971c2","fill":"#a5d8ff","strokeWidth":2,"label":"CLIENT","text":"Web App"},
|
|
265
|
+
{"id":"api","type":"rect","x":330,"y":70,"w":170,"h":100,"stroke":"#2f9e44","fill":"#b2f2bb","strokeWidth":2,"label":"SERVICE","text":"API"},
|
|
266
|
+
{"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"}
|
|
267
|
+
]}
|
|
268
|
+
```
|
|
269
|
+
````
|
|
270
|
+
|
|
271
|
+
### The ` ```diagram ` skeleton
|
|
272
|
+
|
|
273
|
+
Generators (LLMs, scripts) do not need coordinates. A ` ```diagram ` block
|
|
274
|
+
holds a skeleton: boxes, connectors, colors. On import the editor lays it
|
|
275
|
+
out along `direction` (`right` by default, or `down`), sizes boxes to their
|
|
276
|
+
text, and stores the result as a normal ` ```drawing ` block (one-way
|
|
277
|
+
expansion).
|
|
278
|
+
|
|
279
|
+
````md
|
|
280
|
+
```diagram
|
|
281
|
+
{"boxes":[
|
|
282
|
+
{"id":"web","label":"CLIENT","text":"Web App","color":"blue"},
|
|
283
|
+
{"id":"api","label":"SERVICE","text":"API","color":"green"},
|
|
284
|
+
{"id":"db","type":"cylinder","text":"Postgres"}
|
|
285
|
+
],"connectors":[
|
|
286
|
+
{"from":"web","to":"api","text":"REST"},
|
|
287
|
+
{"from":"api","to":"db","routing":"elbow"}
|
|
288
|
+
]}
|
|
289
|
+
```
|
|
290
|
+
````
|
|
291
|
+
|
|
292
|
+
The full skeleton spec (attach sides, explicit positions, free texts) is
|
|
293
|
+
in [DRAWING_FORMAT.md](./DRAWING_FORMAT.md). Programmatic access:
|
|
294
|
+
`parseDrawingSkeleton`, `expandSkeleton`, `DRAWING_SKELETON_JSON_SCHEMA`.
|
|
295
|
+
|
|
243
296
|
## Outline & section folding
|
|
244
297
|
|
|
245
298
|
`outline` docks a collapsible table-of-contents sidebar: live heading list
|
|
@@ -256,14 +309,19 @@ remount; a folded section auto-expands if the cursor enters it.
|
|
|
256
309
|
Documents are plain markdown, so LLMs can generate them — including
|
|
257
310
|
diagrams:
|
|
258
311
|
|
|
259
|
-
-
|
|
260
|
-
` ```drawing `
|
|
261
|
-
|
|
262
|
-
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
- `
|
|
266
|
-
|
|
312
|
+
- Emit ` ```diagram ` skeleton blocks: no coordinates, the editor lays them
|
|
313
|
+
out. Use ` ```drawing ` (format v2) only to edit an existing concrete
|
|
314
|
+
payload.
|
|
315
|
+
- **[DRAWING_FORMAT.md](./DRAWING_FORMAT.md)** is the authoritative spec of
|
|
316
|
+
both payloads, written to be pasted into a prompt (ships in the npm
|
|
317
|
+
package next to this README).
|
|
318
|
+
- `DRAWING_SKELETON_JSON_SCHEMA` and `DRAWING_DATA_JSON_SCHEMA` (exported)
|
|
319
|
+
are the same contracts as JSON Schema: use them to validate generated
|
|
320
|
+
payloads or as structured-output/tool schemas.
|
|
321
|
+
- `parseDrawingSkeleton(json)` / `expandSkeleton(skeleton)` turn a skeleton
|
|
322
|
+
into `DrawingData`. `parseDrawingData(json)` is the editor's own lenient
|
|
323
|
+
parser (invalid shapes drop out; never throws); `serializeDrawingData` is
|
|
324
|
+
its inverse. `drawingToMermaid(data)` exports a Mermaid `flowchart`.
|
|
267
325
|
- A ready-made Claude Code skill lives in the monorepo at
|
|
268
326
|
`.claude/skills/text-editor-documents/` — copy it into consuming repos so
|
|
269
327
|
agents there know the dialect.
|
|
@@ -280,11 +338,14 @@ diagrams:
|
|
|
280
338
|
| `BlockWidth`, `TableDensity`, `TableSettings`, `DEFAULT_TABLE_SETTINGS` | Table setting types |
|
|
281
339
|
| `$getTableSettings`, `$setTableSettings`, `$getTableWidth`, `$setTableWidth`, `$getTableDensity`, `$setTableDensity`, `$getSelectedTable` | Table setting helpers (inside `editor.update`/`read`) |
|
|
282
340
|
| `parseTableSettingsMarker`, `formatTableSettingsMarker` | Marker comment ⇄ `TableSettings` |
|
|
283
|
-
| `DRAWING`, `DrawingNode`, `$createDrawingNode`, `$isDrawingNode` | Drawing node + markdown
|
|
341
|
+
| `DRAWING`, `DIAGRAM`, `DrawingNode`, `$createDrawingNode`, `$isDrawingNode` | Drawing node + markdown transformers (` ```drawing ` and ` ```diagram `) |
|
|
284
342
|
| `FRONTMATTER`, `FrontmatterNode`, `$createFrontmatterNode`, `$isFrontmatterNode` | Frontmatter node + transformer |
|
|
285
343
|
| `DrawingData`, `DrawingShape`, `DrawingShapeType` | Drawing payload types |
|
|
286
344
|
| `parseDrawingData`, `serializeDrawingData` | Drawing payload (de)serialization |
|
|
287
345
|
| `DRAWING_DATA_JSON_SCHEMA` | JSON Schema of the drawing payload |
|
|
346
|
+
| `DrawingSkeleton`, `SkeletonBox`, `SkeletonConnector` | Skeleton types |
|
|
347
|
+
| `parseDrawingSkeleton`, `expandSkeleton`, `DRAWING_SKELETON_JSON_SCHEMA` | Skeleton parsing, expansion to `DrawingData`, JSON Schema |
|
|
348
|
+
| `drawingToMermaid` | Mermaid `flowchart` export |
|
|
288
349
|
|
|
289
350
|
## Form integration
|
|
290
351
|
|
|
@@ -305,13 +366,15 @@ ZUI convention: a `dark` class on `<html>`.
|
|
|
305
366
|
- Source: `src/EditorRoot.tsx` (composer + plugins), `src/EditorContent.tsx`,
|
|
306
367
|
`src/MarkdownEditor.tsx` (default composition), `src/useMarkdownEditor.ts`,
|
|
307
368
|
`src/components/Toolbar.tsx`; plugins in `src/plugins/`; drawing
|
|
308
|
-
canvas in `src/
|
|
369
|
+
canvas in `src/drawing/`; custom nodes in `src/nodes/`;
|
|
309
370
|
transformers in `src/transformers/`
|
|
310
371
|
- Markdown import/export via `@lexical/markdown` transformers; order
|
|
311
|
-
matters: `FRONTMATTER` and `
|
|
312
|
-
`CHECK_LIST` before `UNORDERED_LIST`
|
|
313
|
-
-
|
|
314
|
-
|
|
372
|
+
matters: `FRONTMATTER`, `DRAWING` and `DIAGRAM` claim their blocks before
|
|
373
|
+
`CODE`, `CHECK_LIST` before `UNORDERED_LIST`
|
|
374
|
+
- `src/drawing/` keeps the format (`types.ts`, `schema.ts`), shape
|
|
375
|
+
definitions (`shapes/`), pure geometry (`geometry.ts`), the Mermaid
|
|
376
|
+
exporter (`mermaid.ts`) and the React canvas (`canvas/`) apart, so the
|
|
377
|
+
router, bindings and skeleton expansion run without a DOM
|
|
315
378
|
- Tests: `pnpm test` runs a headless-Lexical markdown round-trip suite
|
|
316
379
|
(`tests/roundtrip.mjs`)
|
|
317
380
|
|