@zuilib/text-editor 0.11.1 → 0.13.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +202 -18
  2. package/DRAWING_FORMAT.md +23 -21
  3. package/README.md +531 -87
  4. package/dist/chunk-32RX4GZF.js +2 -0
  5. package/dist/chunk-3KAABUZQ.js +2 -0
  6. package/dist/chunk-5J4TV6XA.js +2 -0
  7. package/dist/chunk-5VRVC56Y.js +2 -0
  8. package/dist/chunk-6JQG6QDN.js +16 -0
  9. package/dist/chunk-6LHJXZZ2.js +2 -0
  10. package/dist/chunk-7Z3CW6Q3.js +2 -0
  11. package/dist/chunk-A7MCBSAZ.js +2 -0
  12. package/dist/chunk-AGTPMKLK.js +2 -0
  13. package/dist/chunk-LDVPFZCN.js +2 -0
  14. package/dist/chunk-N5JWZ2VW.js +9 -0
  15. package/dist/chunk-N6OUUWQO.js +5 -0
  16. package/dist/chunk-QKHMJQO3.js +2 -0
  17. package/dist/chunk-TGVEPLDM.js +1 -0
  18. package/dist/chunk-XRJNLGI4.js +2 -0
  19. package/dist/chunk-YFFSTMIR.js +2 -0
  20. package/dist/chunk-ZXZMYJ7F.js +2 -0
  21. package/dist/comment-markers-BrrrAdSL.d.ts +27 -0
  22. package/dist/comments.d.ts +68 -0
  23. package/dist/comments.js +2 -0
  24. package/dist/drawing-data-EWzEQ1_i.d.ts +111 -0
  25. package/dist/drawing-preset-Ag7a7F9h.d.ts +69 -0
  26. package/dist/drawing.d.ts +7 -0
  27. package/dist/drawing.js +2 -0
  28. package/dist/image-node-kR1Zf5kz.d.ts +70 -0
  29. package/dist/images.d.ts +66 -0
  30. package/dist/images.js +2 -0
  31. package/dist/index-A4S7bRwQ.d.ts +425 -0
  32. package/dist/index.d.ts +36 -1394
  33. package/dist/index.js +1 -7449
  34. package/dist/lexical.d.ts +117 -0
  35. package/dist/lexical.js +2 -0
  36. package/dist/markdown.d.ts +29 -0
  37. package/dist/markdown.js +2 -0
  38. package/dist/mention-node-eSsjUpaE.d.ts +44 -0
  39. package/dist/mentions-plugin-gio3_XKd.d.ts +74 -0
  40. package/dist/mentions.d.ts +5 -0
  41. package/dist/mentions.js +2 -0
  42. package/dist/mermaid-BwGHjwpk.d.ts +553 -0
  43. package/dist/paste.d.ts +45 -0
  44. package/dist/paste.js +2 -0
  45. package/dist/presets-BtNPH0pa.d.ts +78 -0
  46. package/dist/styles.css +612 -96
  47. package/dist/table-grid-D70tNhp2.d.ts +459 -0
  48. package/dist/tailwind.css +7 -0
  49. package/package.json +58 -27
  50. package/dist/index.css +0 -79
package/CHANGELOG.md CHANGED
@@ -1,13 +1,197 @@
1
1
  # Changelog — @zuilib/text-editor
2
2
 
3
- ## 0.11.1
3
+ ## 0.13.0 — Unreleased
4
+
5
+ ### Breaking
6
+
7
+ - Lexical and `@zuilib/tokens` are now regular dependencies, so consumers
8
+ install only `@zuilib/text-editor`; React and React DOM remain peers.
9
+ Lexical is pinned to 0.35.0. The new `tailwind.css` entry owns token styles
10
+ and source registration, while `styles.css` includes tokens and editor
11
+ chrome for hosts without Tailwind.
12
+
13
+ ### Added
14
+
15
+ - **Small screens and touch.** `.zui-text-editor` is now an inline-size
16
+ container (and so a stacking context), and the layout follows the
17
+ editor's own width: below 40rem the toolbar scrolls sideways with a
18
+ hidden scrollbar and scroll snapping instead of overflowing, the outline
19
+ becomes a collapsible strip above the document, each table scrolls
20
+ horizontally inside its own box with a 6rem column minimum, headings
21
+ step down, and any drawing wider than its pane scales to fit (aspect
22
+ preserved). Link, mention and comment panels stay inside the pane
23
+ (`useFloatingClamp`, internal) and the mention list caps at `50dvh`.
24
+ On coarse pointers buttons, tools and rail handles get 44px targets,
25
+ the hover-only fold chevrons and table rail handles are visible, and a
26
+ tap on a table rail pins the `+` / `−` handle where the finger was (a
27
+ second tap acts). A finger on an idle drawing scrolls the page; the
28
+ canvas captures touches once tapped (focused). Editable text renders at
29
+ 16px or more on phones so iOS does not zoom on focus. Desktop geometry
30
+ is unchanged.
31
+
32
+ ## 0.12.0 — 2026-09-01
33
+
34
+ This entry carries everything since 0.11.1: the library-wide naming overhaul (clean break; only the
35
+ persisted drawing format migrates), the extension API and first-party plugins,
36
+ and the controlled-`value` rewrite. Features new in this release are described
37
+ under their final names.
38
+
39
+ ### Breaking
40
+
41
+ - Value triad: `onChange` → `onValueChange`; `foldable` → `collapsible`;
42
+ `defaultBlockWidth` → `newBlockWidth`; `measure` → `maxTextWidth`;
43
+ mode `'edit-md'` → `'edit-rich'`.
44
+ - Lexical containment: all `$`-helpers, node classes and presets moved to the
45
+ new `@zuilib/text-editor/lexical` entry; the root barrel is Lexical-free.
46
+ - Drawing data v3: shape `w`/`h` → `width`/`height` (v2 documents migrate on
47
+ load); `parseDrawingData` → `deserializeDrawingData`; `BoxType` →
48
+ `NodeShapeType`; geometry internals un-exported.
49
+ - All module files kebab-cased.
50
+ - Optional peer `@zuilib/tokens` is `^0.2.0` (token contract 2.0.0: the
51
+ stylesheet reads `--danger`, `--shadow-2`, …; `--zui-drawing-*` is declared
52
+ here now, not in the tokens package).
53
+ - Peer dependencies added: `@lexical/selection` (block types, max length) and,
54
+ for the plugins, `@lexical/mark`, `@lexical/html`, `@lexical/clipboard`.
55
+ `tailwindcss` (^4) is declared as an optional peer: the document typography
56
+ classes need a Tailwind host that `@source`s this package.
57
+
58
+ ### Added
59
+
60
+ - **Extension API.** `nodes` (appended to the built-in node classes),
61
+ `transformers` (an array goes before the built-ins; a function receives
62
+ them and returns the whole list), `nodeClassNames` (merged over the
63
+ built-in class map one level deep) and `onEditorReady` on `MarkdownEditor` /
64
+ `MarkdownEditor.Root`. All three lists are read on mount (the composer
65
+ cannot change nodes afterwards). Children of the root are plugins and
66
+ share the editor. `createMarkdownEditor(preset)` builds the component
67
+ around an `EditorPreset`
68
+ - New entry points: `@zuilib/text-editor/markdown` (the editor without the
69
+ drawing canvas: no `DrawingNode`, transformers, button or canvas code),
70
+ `@zuilib/text-editor/drawing` (the canvas on its own: node, transformers,
71
+ `DrawingPlugin`, helpers) and `@zuilib/text-editor/lexical` (everything
72
+ Lexical-shaped). Insertion goes through `INSERT_DRAWING_COMMAND` so the
73
+ markdown hook has no drawing dependency
74
+ - Exported the production lists on `./lexical`: `MARKDOWN_NODES`,
75
+ `MARKDOWN_TRANSFORMERS`, `MARKDOWN_PRESET`, `DRAWING_PRESET`,
76
+ `DRAWING_PRESET_NODES`, `DRAWING_PRESET_TRANSFORMERS`, `LISTS`,
77
+ `resolveTransformers`, `shortcutTransformers`, `editorNodeClassNames`,
78
+ `mergeNodeClassNames`
79
+ - **Localisation.** One `EditorLabels` type covers every chrome string
80
+ (toolbar, outline, collapsible headings, table rails and shortcuts, layout
81
+ presets, drawing canvas, placeholder, character count); pass a deep partial
82
+ as `labels`. `DEFAULT_LABELS`, `resolveLabels`, `useLabels` exported. The
83
+ `CollapsibleHeadingsLabels`, `TableRowShortcutsLabels`,
84
+ `TableColumnShortcutsLabels` types are exported and their plugins default
85
+ to the editor's labels
86
+ - **Toolbar.** A new **block** group (heading 1 to 3, bullet / numbered /
87
+ check list, quote) and a link button. `ToolbarItems` gains `block`;
88
+ `BlockButtons` and `MarkdownEditor.BlockButtons` exported.
89
+ `useMarkdownEditor()` gains `blockType` / `setBlockType`, `link` / `setLink`
90
+ and `hasSelection`. Toolbar dividers are `role="separator"`
91
+ - **Link bubble.** The link button opens an edit bubble (URL input, apply,
92
+ remove) rather than inserting a placeholder `https://` link: it needs a
93
+ non-empty selection (disabled for a bare caret, so it cannot link the whole
94
+ text node around the caret) and nothing is written to the document until a
95
+ URL is applied. Escape, moving the caret or focus leaving the bubble
96
+ abandons it without any `onValueChange`. `setLink(url)` is a no-op for a
97
+ collapsed caret outside a link; `EDIT_LINK_COMMAND` opens the bubble
98
+ - **Link safety.** `LinkPlugin` validates URLs with `isSafeUrl`:
99
+ `javascript:`, `data:` and other script-capable schemes are refused for
100
+ typed, pasted and bubble-entered links (`SAFE_LINK_PROTOCOLS`). HTML pastes
101
+ keep only the text of links whose `href` fails `isSafeUrl`, in the paste
102
+ normaliser and, via a paste-time `LinkNode` transform, on Lexical's
103
+ built-in paste path, so `javascript:` URLs never reach the emitted markdown
104
+ - **Form fields.** `id`, `name`, `aria-label`, `aria-labelledby`,
105
+ `aria-describedby`, `aria-invalid`, `aria-required` are forwarded to the
106
+ editable surface (`name` renders a hidden input with the markdown); a
107
+ visible `:focus-visible` ring on the surface (`--ring` / `--primary`)
108
+ replaces the removed outline; `maxLength` rejects input past the
109
+ plain-text limit, `onCharacterCountChange` reports the length,
110
+ `showCharacterCount` / `CharacterCount` render it
111
+ - **Controlled and uncontrolled editing.** `value` is truly controlled:
112
+ whenever the prop differs from the last markdown the editor emitted through
113
+ `onValueChange`, the document is replaced in one update (a single undo
114
+ step) and the caret is kept at the same position when it still exists.
115
+ `value=""` clears the editor. Hosts no longer need to `key`-remount the
116
+ editor to push content in. A remounted sync plugin (e.g. switching
117
+ `edit-raw` → `edit-rich`) always applies the host value, so an emptied raw
118
+ document comes back empty. New `defaultValue` prop for the uncontrolled
119
+ path; `edit-raw` without a `value` keeps its keystrokes (they were
120
+ discarded)
121
+ - **Sync.** `debounceMs` delays `onValueChange` (the document is never
122
+ delayed); a change still pending when the editor unmounts is delivered
123
+ instead of dropped, so the last keystrokes before a dialog closes or a
124
+ route changes reach the host. Updates that dirty no leaf and mark no
125
+ element on purpose skip serialisation, and a serialisation identical to
126
+ the last emitted value is not reported. A controlled `value` equal to one
127
+ of the last 8 emitted values from the last second is treated as a stale
128
+ echo and ignored instead of stomping what has been typed since
129
+ (`EMITTED_HISTORY`, `EMITTED_STALE_MS`); older values are re-imported so a
130
+ deliberate reset still wins
131
+ - **SSR.** `mode="view"` renders a non-editable fallback (the document's
132
+ block text, no `contenteditable`, no placeholder) on the server and until
133
+ hydration; the Lexical surface then mounts with the document already in
134
+ its state so the placeholder never flashes. `readOnly` renders
135
+ `contenteditable="false"` from the first paint
136
+ - New `onError` prop on `MarkdownEditor` / `MarkdownEditor.Root` (default
137
+ `console.error`), forwarded to Lexical
138
+ - **Round-trip escaping.** Image markdown percent-encodes whitespace and
139
+ parentheses in the source (on the node itself, so an uploaded
140
+ "my photo (1).png" stays an image) and backslash-escapes `]` in alt and
141
+ `"` in titles; mention markdown escapes `]` / `\` in names and
142
+ percent-encodes `%`, `)` and whitespace in ids. Values that used to
143
+ degrade to literal text now re-import
144
+ - **Accessibility.** An `aria-invalid` surface keeps the 2px focus ring
145
+ when focused (the danger line sits inside it); the character counter is
146
+ no longer a live region read on every keystroke: a hidden status announces
147
+ only when the document passes `maxLength` or returns within it
148
+ (`labels.limitExceeded` / `labels.limitRestored`)
149
+ - **Performance.** `DrawingNode.getData()` memoizes parsing on the payload
150
+ string; `normalizeDrawingData` takes an `onWarn` callback (default: one
151
+ console warning per session) instead of warning on every parse; the
152
+ toolbar keyboard hook only sweeps elements marked `data-toolbar-item`
153
+ (nested widgets keep their own focus handling) and measures the writing
154
+ direction once, not per keydown
155
+ - **Code highlighting.** TypeScript / TSX is its own grammar (`ts`, `tsx`,
156
+ `mts`, `cts`; type-level keywords, primitive types, utility types and
157
+ type arguments); `javascript` no longer answers to those aliases. 16
158
+ grammars. The registry loads the built-ins on first lookup, so no module
159
+ has import-time side effects apart from the stylesheet and
160
+ `suppress-global-highlighter`, which stops the Prism instance pulled in by
161
+ `@lexical/code` from re-highlighting the host page's own code blocks (both
162
+ are listed in `sideEffects`; keep them in `sideEffects`-sensitive bundler
163
+ configs)
164
+ - **Tokens.** The stylesheet no longer carries a foreign brand colour: the
165
+ outline marker, table selection and rail accent fall back to `--primary`
166
+ / `currentColor`; every radius reads `--radius-sm|md|lg|full`; the code
167
+ palette reads `--zui-code-<type>-light` / `-dark`, the canvas surface and
168
+ grid `--zui-drawing-surface-color` / `--zui-drawing-grid-color`, all with
169
+ the previous literals as fallbacks
170
+ - **Plugins.** Four opt-in first-party plugins, each on its own entry
171
+ point and on the default entry (not on `./markdown`): `./mentions`
172
+ (`MentionNode`, `MENTION`, `MentionsPlugin`: typeahead listbox with
173
+ `aria-activedescendant`, several triggers, `[@name](mention:id)`
174
+ markdown), `./comments` (`CommentsPlugin` on `@lexical/mark`: controlled
175
+ `comments`, floating add button, `Mod+Shift+M`, caret bubble,
176
+ `<!-- zui:comment id -->` markers), `./images` (`ImageNode`, `IMAGE`,
177
+ `ImagesPlugin`, `ImageButton`: paste / drop upload with a progress
178
+ placeholder, `accept` / `maxSize`, alt-text editing, `![alt](src)`
179
+ markdown) and `./paste` (`PastePlugin`, `normalizePastedHtml`: Word,
180
+ Google Docs, Confluence and Outlook HTML reduced to structure)
181
+ - Tests: the round-trip script is a `node --test` file that imports the
182
+ exported production transformer list; jsdom and axe-core are
183
+ devDependencies, so mounted tests fail instead of skipping
184
+ - Build: dist is minified and cleaned on every build (removes the stale
185
+ `dist/index.css`)
186
+
187
+ ## 0.11.1 — 2026-08-29
4
188
 
5
189
  - Fix: mounting an editor no longer steals focus and scrolls to its first
6
190
  code block. The initial markdown import and the one-off re-highlight of
7
191
  existing code blocks now run with Lexical's `skip-dom-selection` tag, so
8
192
  their selection stays internal until the user clicks in
9
193
 
10
- ## 0.11.0
194
+ ## 0.11.0 — 2026-08-27
11
195
 
12
196
  - Depends on **`@zuilib/tokens`** (optional peer) instead of `@zuilib/core`:
13
197
  the editor only ever consumed the design tokens, never a component.
@@ -17,7 +201,7 @@
17
201
  - Documented that a Tailwind host must `@source` this package for the
18
202
  document-content classes (headings, lists, code) to be generated
19
203
 
20
- ## 0.10.1
204
+ ## 0.10.1 — 2026-08-26
21
205
 
22
206
  - **Table columns** get the same rail as rows: a strip along the table's
23
207
  top edge with a bar on the caret's column and a `+` handle after the
@@ -35,7 +219,7 @@
35
219
  `$insertTableColumnNear`, `$deleteSelectedTableColumn`,
36
220
  `insertableColumnIndices`, `canDeleteColumn`
37
221
 
38
- ## 0.10.0
222
+ ## 0.10.0 — 2026-08-26
39
223
 
40
224
  - **Table rows.** Put the caret in a table and a **row rail** appears along
41
225
  its left edge: a bar marks the caret's row, and a `+` handle rests under
@@ -56,7 +240,7 @@
56
240
  - Rail and toolbar use the theme tokens (`--primary`, `--popover`,
57
241
  `--border`, `--shadow-medium`); motion respects `prefers-reduced-motion`
58
242
 
59
- ## 0.9.0
243
+ ## 0.9.0 — 2026-08-26
60
244
 
61
245
  - **Ink drawing style**: `drawingStyle="ink"` on the editor renders shapes
62
246
  as one seeded, pressure-varying pen stroke with misregistered fills and
@@ -64,7 +248,7 @@
64
248
  axes) when the host loads it. Renderer-only: the drawing format is
65
249
  unchanged. Geometry helpers are exported (`inkStroke`, `seedFrom`, …)
66
250
 
67
- ## 0.8.0
251
+ ## 0.8.0 — 2026-08-26
68
252
 
69
253
  - **Syntax highlighting** for fenced code blocks, driven by the package's
70
254
  own grammars instead of Prism: JavaScript/TypeScript, Kotlin, Java, C/C++,
@@ -79,7 +263,7 @@
79
263
  - Unknown languages get the same line structure as known ones, so cursor
80
264
  movement and indentation behave identically
81
265
 
82
- ## 0.7.2
266
+ ## 0.7.2 — 2026-08-25
83
267
 
84
268
  - Canvas properties are one **color** setting (outline plus its matching
85
269
  fill on boxes: plain (no outline), black, gray, red, green, blue, orange,
@@ -95,7 +279,7 @@
95
279
  - The floating table settings use the theme tokens (`--popover`,
96
280
  `--border`, `--shadow-medium`) instead of fixed light/dark colors
97
281
 
98
- ## 0.7.1
282
+ ## 0.7.1 — 2026-08-25
99
283
 
100
284
  - Fix: free `text` shapes could not be typed into. The text tool's editor
101
285
  opened on pointerdown and was blurred by the same click; it now opens on
@@ -107,7 +291,7 @@
107
291
  - Fix: keyboard shortcuts keep working after committing text (the canvas
108
292
  regains focus); `Home`/`End` work inside the inline text editor
109
293
 
110
- ## 0.7.0
294
+ ## 0.7.0 — 2026-08-25
111
295
 
112
296
  **Themed drawing canvas.** The canvas chrome now follows the host theme
113
297
  instead of shipping its own look.
@@ -126,7 +310,7 @@ instead of shipping its own look.
126
310
  - `--zui-drawing-dark-filter` exposes the dark-mode inversion of the
127
311
  drawing area (`none` opts out)
128
312
 
129
- ## 0.6.0
313
+ ## 0.6.0 — 2026-08-25
130
314
 
131
315
  **Text measure and `text` block width.** Additive: markdown, JSON, class
132
316
  names and props keep their meaning; with no configuration the layout is
@@ -178,7 +362,7 @@ unchanged.
178
362
  - **Adopting**: set `measure` (or the CSS variable) and delete any external
179
363
  `max-width` / `margin-inline` overrides on `.zui-text-editor-content > *`
180
364
 
181
- ## 0.5.0
365
+ ## 0.5.0 — 2026-08-25
182
366
 
183
367
  **Diagram builder rewrite.** The drawing canvas, its payload format and the
184
368
  authoring story for LLMs were rebuilt.
@@ -226,7 +410,7 @@ authoring story for LLMs were rebuilt.
226
410
  `shapes/`, `canvas/`); shape geometry is data-driven so the router,
227
411
  bindings, skeleton expansion and exporters run without a DOM
228
412
 
229
- ## 0.4.0
413
+ ## 0.4.0 — 2026-08-25
230
414
 
231
415
  - **Table density**, Slab-style: the floating table toolbar (caret in a
232
416
  table) gains *compact* / *comfortable* (default) / *spacious* row padding
@@ -241,7 +425,7 @@ authoring story for LLMs were rebuilt.
241
425
  `formatTableSettingsMarker`. `TABLE_WIDTH_MARKER` is deprecated in favour
242
426
  of `formatTableSettingsMarker`
243
427
 
244
- ## 0.3.1
428
+ ## 0.3.1 — 2026-08-25
245
429
 
246
430
  - **Block width** for tables and drawings: switch any table or drawing
247
431
  between *full width* (spans the editor, the previous behaviour) and
@@ -261,7 +445,7 @@ authoring story for LLMs were rebuilt.
261
445
  - New exports: `BlockWidth`, `$getTableWidth`, `$setTableWidth`,
262
446
  `$getSelectedTable`, `TABLE_WIDTH_MARKER`
263
447
 
264
- ## 0.3.0
448
+ ## 0.3.0 — 2026-08-23
265
449
 
266
450
  - **Custom toolbars**
267
451
  - `toolbar` prop accepts a render function receiving the default groups
@@ -277,7 +461,7 @@ authoring story for LLMs were rebuilt.
277
461
  - Internal: `LexicalComposer` is now mounted in `edit-raw` mode too, so
278
462
  hooks keep working across mode switches
279
463
 
280
- ## 0.2.1
464
+ ## 0.2.1 — 2026-08-09
281
465
 
282
466
  - `DRAWING_FORMAT.md`: authoritative spec of the ```drawing JSON payload,
283
467
  written for programmatic/LLM consumption; ships in the npm package
@@ -286,7 +470,7 @@ authoring story for LLMs were rebuilt.
286
470
  - New exports `parseDrawingData` / `serializeDrawingData`
287
471
  - This changelog; README overhaul
288
472
 
289
- ## 0.2.0
473
+ ## 0.2.0 — 2026-08-09
290
474
 
291
475
  - **Outline** (`outline` prop, default off): docked, collapsible
292
476
  table-of-contents sidebar — live heading list, click-to-scroll, current
@@ -295,7 +479,7 @@ authoring story for LLMs were rebuilt.
295
479
  under a heading via a gutter chevron; view-layer only (markdown
296
480
  untouched); auto-expands if the cursor enters a folded section
297
481
 
298
- ## 0.1.0
482
+ ## 0.1.0 — 2026-08-09
299
483
 
300
484
  - **Tables**: GFM pipe tables round-trip through markdown; artifact-style
301
485
  rendering; in-place editing via Lexical `TablePlugin`; new peer deps
@@ -312,7 +496,7 @@ authoring story for LLMs were rebuilt.
312
496
  - Stroke/fill palettes; dark mode via canvas color inversion
313
497
  - Round-trip test suite (`pnpm test`)
314
498
 
315
- ## 0.0.2 and earlier
499
+ ## 0.0.2 and earlier — 2026-04-17 to 2026-07-05
316
500
 
317
501
  - Markdown editor wrapping Lexical: `edit-md` / `edit-raw` / `view` modes,
318
502
  markdown shortcuts, checklists, fenced code highlighting, YAML
package/DRAWING_FORMAT.md CHANGED
@@ -7,13 +7,13 @@ fenced code blocks containing a single JSON object. Two languages exist:
7
7
  coordinates. The editor lays it out and expands it into a full drawing on
8
8
  import. **Generators should emit this one.** See
9
9
  [Prefer the ```diagram skeleton](#prefer-the-diagram-skeleton).
10
- - ```` ```drawing ```` holds the **concrete format** (version 2): every shape
10
+ - ```` ```drawing ```` holds the **concrete format** (version 3): every shape
11
11
  with its position, size and style. This is what the editor stores once a
12
12
  drawing has been edited.
13
13
 
14
14
  ````md
15
15
  ```drawing
16
- {"version":2,"canvasHeight":320,"shapes":[ ... ]}
16
+ {"version":3,"canvasHeight":320,"shapes":[ ... ]}
17
17
  ```
18
18
  ````
19
19
 
@@ -26,10 +26,12 @@ from the package as `DRAWING_DATA_JSON_SCHEMA` (concrete) and
26
26
  Malformed payloads never crash the editor: anything that fails validation
27
27
  degrades to an empty canvas (invalid shapes are dropped individually).
28
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.
29
+ The editor writes version 3. Version 2 (identical except shape sizes were
30
+ spelled `w`/`h`) is migrated on read, so persisted drawings keep loading;
31
+ the editor re-serializes them as version 3. Payloads with any other
32
+ `version` (or none) render as an empty canvas. Convenience spellings such
33
+ as `side` exist only in the ```diagram skeleton, which is a separate block
34
+ type.
33
35
 
34
36
  ## Coordinate system
35
37
 
@@ -53,7 +55,7 @@ skeleton, which is a separate block type.
53
55
 
54
56
  | Field | Type | Notes |
55
57
  |----------------|-----------|--------------------------------------------------------------|
56
- | `version` | `2` | Literal `2` |
58
+ | `version` | `3` | Literal `3` (`2` accepted on read: shape `w`/`h` migrate to `width`/`height`) |
57
59
  | `canvasHeight` | `number` | Canvas height in px (min 80) |
58
60
  | `canvasWidth` | `number` | Optional logical width in px (min 120); scales to fit when set |
59
61
  | `width` | `"full"` \| `"text"` \| `"content"` | Optional; default `"full"`. `"text"` aligns with the text column, `"content"` fits the shapes' extent |
@@ -68,7 +70,7 @@ All shapes share these required fields:
68
70
  | `id` | `string` | Unique within the drawing; bindings reference it |
69
71
  | `type` | `string` | One of the types below |
70
72
  | `x`, `y` | `number` | Boxes/text: top-left of the bounding box. Connectors: start point. |
71
- | `w`, `h` | `number` | Boxes/text: size (non-negative). Connectors: delta to the end point (`end = (x+w, y+h)`; may be negative). |
73
+ | `width`, `height` | `number` | Boxes/text: size (non-negative). Connectors: delta to the end point (`end = (x+width, y+height)`; may be negative). |
72
74
  | `stroke` | `string` | CSS color of the outline and of all text on the shape |
73
75
  | `fill` | `string` | CSS color of the interior; `"transparent"` for none |
74
76
  | `strokeWidth` | `number` | Use `2` |
@@ -109,14 +111,14 @@ floating `text` shapes; slot text is attached to the card.
109
111
 
110
112
  ### `text`: free-floating annotation
111
113
 
112
- `text` holds the content. `w`/`h` are advisory (the editor recomputes them
114
+ `text` holds the content. `width`/`height` are advisory (the editor recomputes them
113
115
  from the content); position is the top-left of the first line. Use only for
114
116
  annotations that belong to no box (the editor deletes a text shape when its
115
117
  content is emptied).
116
118
 
117
119
  ### Connectors (`arrow`, `line`)
118
120
 
119
- Geometry: from `(x, y)` to `(x+w, y+h)`. `arrow` has a head at the end;
121
+ Geometry: from `(x, y)` to `(x+width, y+height)`. `arrow` has a head at the end;
120
122
  `line` has none.
121
123
 
122
124
  | Field | Type | Meaning |
@@ -156,7 +158,7 @@ the box moves:
156
158
  (useful for lines that end inside a shape).
157
159
 
158
160
  Bound endpoints are re-anchored by the editor, so the connector's own
159
- `x`/`y`/`w`/`h` only need to be approximately right: place them near the
161
+ `x`/`y`/`width`/`height` only need to be approximately right: place them near the
160
162
  intended boxes and the resolver snaps them. Don't bind both ends of one
161
163
  connector to the same box.
162
164
 
@@ -266,7 +268,7 @@ A decision with attach sides and elbow routing, flowing down:
266
268
  ````
267
269
 
268
270
  Programmatic use: `parseDrawingSkeleton(json)` parses a skeleton,
269
- `expandSkeleton(skeleton)` returns `DrawingData`, and
271
+ `expandDrawingSkeleton(skeleton)` returns `DrawingData`, and
270
272
  `DRAWING_SKELETON_JSON_SCHEMA` is the JSON Schema. Types: `DrawingSkeleton`,
271
273
  `SkeletonBox`, `SkeletonConnector`.
272
274
 
@@ -276,10 +278,10 @@ Two bound cards with a labeled arrow:
276
278
 
277
279
  ````md
278
280
  ```drawing
279
- {"version":2,"canvasHeight":260,"shapes":[
280
- {"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"},
281
- {"id":"api","type":"rect","x":330,"y":70,"w":170,"h":100,"stroke":"#2f9e44","fill":"#b2f2bb","strokeWidth":2,"label":"SERVICE","text":"API","footer":"Kotlin"},
282
- {"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"}
281
+ {"version":3,"canvasHeight":260,"shapes":[
282
+ {"id":"web","type":"rect","x":40,"y":70,"width":170,"height":100,"stroke":"#1971c2","fill":"#a5d8ff","strokeWidth":2,"label":"CLIENT","text":"Web App","footer":"React"},
283
+ {"id":"api","type":"rect","x":330,"y":70,"width":170,"height":100,"stroke":"#2f9e44","fill":"#b2f2bb","strokeWidth":2,"label":"SERVICE","text":"API","footer":"Kotlin"},
284
+ {"id":"e1","type":"arrow","x":216,"y":120,"width":108,"height":0,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"startBinding":{"id":"web"},"endBinding":{"id":"api"},"text":"REST"}
283
285
  ]}
284
286
  ```
285
287
  ````
@@ -289,11 +291,11 @@ line:
289
291
 
290
292
  ````md
291
293
  ```drawing
292
- {"version":2,"canvasHeight":300,"canvasWidth":640,"shapes":[
293
- {"id":"a","type":"rect","x":40,"y":40,"w":150,"h":80,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"text":"A"},
294
- {"id":"b","type":"cylinder","x":420,"y":180,"w":140,"h":110,"stroke":"#7048e8","fill":"#d0bfff","strokeWidth":2,"text":"B"},
295
- {"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"},
296
- {"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}]}
294
+ {"version":3,"canvasHeight":300,"canvasWidth":640,"shapes":[
295
+ {"id":"a","type":"rect","x":40,"y":40,"width":150,"height":80,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"text":"A"},
296
+ {"id":"b","type":"cylinder","x":420,"y":180,"width":140,"height":110,"stroke":"#7048e8","fill":"#d0bfff","strokeWidth":2,"text":"B"},
297
+ {"id":"e1","type":"arrow","x":190,"y":80,"width":300,"height":100,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"startBinding":{"id":"a","fixedPoint":[1,0.5]},"endBinding":{"id":"b","fixedPoint":[0.5,0]},"routing":"elbow"},
298
+ {"id":"e2","type":"line","x":115,"y":126,"width":375,"height":54,"stroke":"#1971c2","fill":"transparent","strokeWidth":2,"startBinding":{"id":"a"},"endBinding":{"id":"b"},"waypoints":[{"x":115,"y":260},{"x":490,"y":260}]}
297
299
  ]}
298
300
  ```
299
301
  ````