@zuilib/text-editor 0.1.0 → 0.2.1

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 ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog — @zuilib/text-editor
2
+
3
+ ## 0.2.1
4
+
5
+ - `DRAWING_FORMAT.md`: authoritative spec of the ```drawing JSON payload,
6
+ written for programmatic/LLM consumption; ships in the npm package
7
+ - New export `DRAWING_DATA_JSON_SCHEMA` (JSON Schema draft-07) for
8
+ validating or structured-output-generating drawing payloads
9
+ - New exports `parseDrawingData` / `serializeDrawingData`
10
+ - This changelog; README overhaul
11
+
12
+ ## 0.2.0
13
+
14
+ - **Outline** (`outline` prop, default off): docked, collapsible
15
+ table-of-contents sidebar — live heading list, click-to-scroll, current
16
+ section highlighted
17
+ - **Section folding** (`foldable` prop, default on): collapse everything
18
+ under a heading via a gutter chevron; view-layer only (markdown
19
+ untouched); auto-expands if the cursor enters a folded section
20
+
21
+ ## 0.1.0
22
+
23
+ - **Tables**: GFM pipe tables round-trip through markdown; artifact-style
24
+ rendering; in-place editing via Lexical `TablePlugin`; new peer deps
25
+ `@lexical/table` and `@lexical/utils`
26
+ - **Toolbar** (`toolbar` prop, default on): inline formatting, insert
27
+ table, insert drawing
28
+ - **Drawing canvas**: Excalidraw-style diagrams embedded as ```drawing
29
+ fenced JSON blocks — see `DRAWING_FORMAT.md`
30
+ - Shapes: rectangle, ellipse, triangle, pentagon, arrow, line, text
31
+ - Boxes are cards with three attached text slots (label / content /
32
+ footer) that move, resize, and wrap with the shape
33
+ - Connectors bind to boxes and track them; one-way or two-way arrows;
34
+ midpoint labels; straight, elbow, or waypoint-routed paths
35
+ - Stroke/fill palettes; dark mode via canvas color inversion
36
+ - Round-trip test suite (`pnpm test`)
37
+
38
+ ## 0.0.2 and earlier
39
+
40
+ - Markdown editor wrapping Lexical: `edit-md` / `edit-raw` / `view` modes,
41
+ markdown shortcuts, checklists, fenced code highlighting, YAML
42
+ frontmatter block, controlled `value`/`onChange`
@@ -0,0 +1,149 @@
1
+ # The `drawing` block format
2
+
3
+ `@zuilib/text-editor` documents are plain markdown. Diagrams are embedded as
4
+ a fenced code block with the language `drawing`, containing a single JSON
5
+ object:
6
+
7
+ ````md
8
+ ```drawing
9
+ {"version":1,"height":320,"shapes":[ ... ]}
10
+ ```
11
+ ````
12
+
13
+ This document is the authoritative spec of that JSON payload. It is written
14
+ so it can be handed to a program or an LLM that needs to **generate or edit
15
+ drawings**. A machine-readable JSON Schema of the same rules is exported
16
+ from the package as `DRAWING_DATA_JSON_SCHEMA`.
17
+
18
+ Malformed payloads never crash the editor: anything that fails validation
19
+ degrades to an empty canvas (invalid shapes are dropped individually).
20
+
21
+ ## Coordinate system
22
+
23
+ - Origin `(0,0)` is the canvas's top-left; x grows right, y grows down.
24
+ - Units are CSS pixels.
25
+ - The canvas is as wide as the editor (fluid). Keep shapes within roughly
26
+ x ∈ [0, 700] to be safe on typical layouts.
27
+ - `height` is the canvas height in pixels (minimum 80; 300–400 is typical).
28
+
29
+ ## Top-level object
30
+
31
+ | Field | Type | Notes |
32
+ |-----------|-----------|-----------------------------------------|
33
+ | `version` | `1` | Literal `1` |
34
+ | `height` | `number` | Canvas height in px |
35
+ | `shapes` | `Shape[]` | Render order: later shapes draw on top |
36
+
37
+ ## Shape object
38
+
39
+ All shapes share these required fields:
40
+
41
+ | Field | Type | Meaning |
42
+ |---------------|----------|--------------------------------------------------------------------|
43
+ | `id` | `string` | Unique within the drawing; bindings reference it |
44
+ | `type` | `string` | `rect` \| `ellipse` \| `triangle` \| `pentagon` \| `arrow` \| `line` \| `text` |
45
+ | `x`, `y` | `number` | Boxes/text: top-left of the bounding box. Connectors: start point. |
46
+ | `w`, `h` | `number` | Boxes/text: size (non-negative). Connectors: delta to the end point (`end = (x+w, y+h)`; may be negative). |
47
+ | `stroke` | `string` | CSS color of the outline and of all text on the shape |
48
+ | `fill` | `string` | CSS color of the interior; `"transparent"` for none |
49
+ | `strokeWidth` | `number` | Use `2` |
50
+
51
+ ### Boxes (`rect`, `ellipse`, `triangle`, `pentagon`) — "cards"
52
+
53
+ Boxes are card-like: they can carry up to three text slots that render
54
+ inside the shape and move/resize/wrap with it:
55
+
56
+ | Field | Renders as |
57
+ |----------|-----------------------------------------------|
58
+ | `label` | Small bold heading at the top of the box |
59
+ | `text` | Main content, centered |
60
+ | `footer` | Small dim line at the bottom |
61
+
62
+ Text wraps automatically to the box width; `\n` forces a line break.
63
+ Triangles are apex-up isoceles; pentagons are apex-up, inscribed in the
64
+ bounding box. Prefer these slots over floating `text` shapes — slot text is
65
+ attached to the card.
66
+
67
+ ### `text` — free-floating annotation
68
+
69
+ `text` holds the content. `w`/`h` are advisory (the editor recomputes them
70
+ from the content); position is the top-left of the first line. Use only for
71
+ annotations that belong to no box (the editor deletes a text shape when its
72
+ content is emptied).
73
+
74
+ ### Connectors (`arrow`, `line`)
75
+
76
+ Geometry: from `(x, y)` to `(x+w, y+h)`. `arrow` has a head at the end;
77
+ `line` has none.
78
+
79
+ | Field | Type | Meaning |
80
+ |-----------------|------------|-------------------------------------------------------------------------|
81
+ | `text` | `string` | Label rendered at the path midpoint on a small backing plate |
82
+ | `bidirectional` | `boolean` | Arrows only: heads on both ends |
83
+ | `startBinding` | `string` | id of the box the start attaches to |
84
+ | `endBinding` | `string` | id of the box the end attaches to |
85
+ | `routing` | `"elbow"` | Orthogonal auto-routing with up to two right-angle bends |
86
+ | `elbow` | `number` | 0–1: middle-segment position along the span (default 0.5) |
87
+ | `waypoints` | `Point[]` | Explicit intermediate path points (`{x,y}`), ordered start→end |
88
+
89
+ **Bindings.** When `startBinding`/`endBinding` name a box id, the editor
90
+ re-anchors that endpoint onto the box's border (aimed at the other end, with
91
+ a small gap) and keeps it attached as the box moves. Generated endpoints
92
+ therefore only need to be approximately right — place them near the intended
93
+ boxes and the resolver snaps them. Don't bind both ends of one connector to
94
+ the same box. Bindings to ids that don't exist are silently dropped.
95
+
96
+ **Routing.** Omit `routing` for a straight (possibly diagonal) connector.
97
+ `"elbow"` renders an orthogonal path: it runs along the dominant axis, turns
98
+ through a perpendicular middle segment (positioned by `elbow`), and turns
99
+ again. If `waypoints` is non-empty it defines the path (`start → waypoints…
100
+ → end`) and `routing`/`elbow` are ignored. Waypoints are absolute canvas
101
+ coordinates; align consecutive points on x or y to get right angles.
102
+
103
+ ## Recommended colors
104
+
105
+ Any CSS color works; the editor's palette (dark-mode safe — the canvas
106
+ inverts colors Excalidraw-style in dark themes):
107
+
108
+ - Strokes: `#1e1e1e` (default), `#e03131` red, `#2f9e44` green, `#1971c2` blue, `#f08c00` orange
109
+ - Fills: `transparent`, `#ffc9c9`, `#b2f2bb`, `#a5d8ff`, `#ffec99`
110
+
111
+ Pair a colored stroke with its pastel fill (`#1971c2` + `#a5d8ff`, etc.).
112
+
113
+ ## Examples
114
+
115
+ Two bound cards with a labeled arrow:
116
+
117
+ ````md
118
+ ```drawing
119
+ {"version":1,"height":260,"shapes":[
120
+ {"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"},
121
+ {"id":"api","type":"rect","x":330,"y":70,"w":170,"h":100,"stroke":"#2f9e44","fill":"#b2f2bb","strokeWidth":2,"label":"SERVICE","text":"API","footer":"Kotlin"},
122
+ {"id":"e1","type":"arrow","x":216,"y":120,"w":108,"h":0,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"startBinding":"web","endBinding":"api","text":"REST"}
123
+ ]}
124
+ ```
125
+ ````
126
+
127
+ An elbow arrow and a hand-routed multi-bend line:
128
+
129
+ ````md
130
+ ```drawing
131
+ {"version":1,"height":300,"shapes":[
132
+ {"id":"a","type":"rect","x":40,"y":40,"w":150,"h":80,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"text":"A"},
133
+ {"id":"b","type":"ellipse","x":420,"y":180,"w":150,"h":80,"stroke":"#e03131","fill":"#ffc9c9","strokeWidth":2,"text":"B"},
134
+ {"id":"e1","type":"arrow","x":196,"y":80,"w":224,"h":140,"stroke":"#1e1e1e","fill":"transparent","strokeWidth":2,"startBinding":"a","endBinding":"b","routing":"elbow"},
135
+ {"id":"e2","type":"line","x":115,"y":126,"w":380,"h":54,"stroke":"#1971c2","fill":"transparent","strokeWidth":2,"startBinding":"a","endBinding":"b","waypoints":[{"x":115,"y":260},{"x":495,"y":260}]}
136
+ ]}
137
+ ```
138
+ ````
139
+
140
+ ## Generation checklist
141
+
142
+ 1. One JSON object, single ```drawing fence, no comments or trailing commas.
143
+ 2. Every `id` unique; every binding references an existing box id.
144
+ 3. Boxes ≥ 120×70 when they carry text slots; leave ~60px between cards for
145
+ connectors and labels.
146
+ 4. Box text goes in `label`/`text`/`footer` — not in floating `text` shapes.
147
+ 5. Connectors between cards should bind both ends; approximate endpoints are
148
+ fine (the editor snaps them to borders).
149
+ 6. `height` large enough to contain every shape plus ~20px margin.
package/README.md CHANGED
@@ -1,30 +1,30 @@
1
1
  # @zuilib/text-editor
2
2
 
3
- Markdown editor for ZUI built on [Lexical](https://lexical.dev/): rich editing with shortcuts, raw markdown mode, read-only view, checklists, fenced code highlighting, GFM tables, and an embedded drawing canvas for diagrams.
4
-
5
- ## When to use
6
-
7
- - Notes, comments, or docs fields stored as **markdown strings**
8
- - Controlled `value` / `onChange` integration with forms or autosave
9
- - WYSIWYG-style editing without leaving markdown as the source of truth
10
-
11
- ## Prerequisites
12
-
13
- Peer dependencies (install in the consuming app):
14
-
15
- | Package | Version |
16
- |---------|---------|
17
- | `react`, `react-dom` | `^18.0.0 \|\| ^19.0.0` |
18
- | `lexical` | `^0.35.0` |
19
- | `@lexical/react` | `^0.35.0` |
20
- | `@lexical/markdown` | `^0.35.0` |
21
- | `@lexical/rich-text` | `^0.35.0` |
22
- | `@lexical/code` | `^0.35.0` |
23
- | `@lexical/list` | `^0.35.0` |
24
- | `@lexical/link` | `^0.35.0` |
25
- | `@lexical/table` | `^0.35.0` |
26
- | `@lexical/utils` | `^0.35.0` |
27
- | `@zuilib/core` | workspace / published — import core styles |
3
+ A markdown editor for ZUI built on [Lexical](https://lexical.dev/). Plain
4
+ markdown in, plain markdown out — with an artifact-style editing experience
5
+ on top: rich tables, an embedded Excalidraw-style diagram canvas, a
6
+ document outline, and collapsible sections.
7
+
8
+ **Features**
9
+
10
+ - **Three modes**: rich WYSIWYG (`edit-md`), raw markdown (`edit-raw`),
11
+ read-only render (`view`) — all driven by one controlled `value` string
12
+ - **Markdown shortcuts** while typing: headings, lists, checklists,
13
+ blockquotes, links, fenced code (with syntax highlighting), tables
14
+ - **Tables**: GFM pipe tables, edited in place (tab between cells, ranges,
15
+ inline formatting), styled like Claude artifacts
16
+ - **Diagrams**: a drawing canvas embedded in the document — cards with
17
+ attached text slots, bound arrows that follow their cards, elbow and
18
+ multi-waypoint routing, labels, color palettes, dark mode
19
+ - **Outline**: optional table-of-contents sidebar with click-to-scroll and
20
+ current-section highlight
21
+ - **Section folding**: collapse everything under a heading, view-layer only
22
+ - **Toolbar**: inline formatting plus insert-table / insert-drawing
23
+ - YAML **frontmatter** block support (`---` at the top)
24
+
25
+ Everything round-trips through the markdown string: tables as GFM,
26
+ diagrams as ` ```drawing ` fenced JSON ([format spec](./DRAWING_FORMAT.md)),
27
+ frontmatter as `---` blocks. Feature history: [CHANGELOG](./CHANGELOG.md).
28
28
 
29
29
  ## Installation
30
30
 
@@ -34,42 +34,8 @@ pnpm add @zuilib/text-editor @zuilib/core \
34
34
  @lexical/code @lexical/list @lexical/link @lexical/table @lexical/utils
35
35
  ```
36
36
 
37
- Monorepo:
38
-
39
- ```json
40
- {
41
- "dependencies": {
42
- "@zuilib/text-editor": "workspace:*",
43
- "@zuilib/core": "workspace:*",
44
- "lexical": "^0.35.0",
45
- "@lexical/react": "^0.35.0",
46
- "@lexical/markdown": "^0.35.0",
47
- "@lexical/rich-text": "^0.35.0",
48
- "@lexical/code": "^0.35.0",
49
- "@lexical/list": "^0.35.0",
50
- "@lexical/link": "^0.35.0",
51
- "@lexical/table": "^0.35.0",
52
- "@lexical/utils": "^0.35.0"
53
- }
54
- }
55
- ```
56
-
57
- ## Import rules
58
-
59
- | Rule | Detail |
60
- |------|--------|
61
- | **Named export** | `import { MarkdownEditor } from '@zuilib/text-editor'` |
62
- | **Types** | `import type { MarkdownEditorProps } from '@zuilib/text-editor'` |
63
- | **Styles** | `import '@zuilib/text-editor/styles.css'` and `@zuilib/core/styles.css` |
64
- | **Controlled** | Always pass `value` + `onChange` for persisted content |
65
- | **Mode switches** | Switching `edit-raw` → `edit-md` re-mounts Lexical with latest markdown (cursor-safe) |
66
-
67
- ## Exports
68
-
69
- | Subpath | Exports |
70
- |---------|---------|
71
- | `@zuilib/text-editor` | `MarkdownEditor` (default re-exported as named), `MarkdownEditorProps` |
72
- | `@zuilib/text-editor/styles.css` | Editor chrome + Lexical theme classes |
37
+ All `lexical`/`@lexical/*` packages are peer dependencies at `^0.35.0`;
38
+ `react`/`react-dom` at `^18 || ^19`.
73
39
 
74
40
  ## Quick start
75
41
 
@@ -81,32 +47,12 @@ import { MarkdownEditor } from '@zuilib/text-editor'
81
47
 
82
48
  function Notes() {
83
49
  const [value, setValue] = useState('# Hello\n\n- [ ] Try checklists')
84
-
85
- return (
86
- <MarkdownEditor
87
- value={value}
88
- onChange={setValue}
89
- placeholder="Start writing…"
90
- />
91
- )
50
+ return <MarkdownEditor value={value} onChange={setValue} outline />
92
51
  }
93
52
  ```
94
53
 
95
- ## Modes
96
-
97
- | `mode` | UI | `onChange` |
98
- |--------|-----|------------|
99
- | `'edit-md'` (default) | Lexical rich editor + markdown shortcuts | Emits markdown string |
100
- | `'edit-raw'` | Plain `<textarea>` with markdown source | Emits markdown string |
101
- | `'view'` | Read-only rendered content | No editing (`onChange` unused) |
102
-
103
- ```tsx
104
- <MarkdownEditor mode="edit-md" value={md} onChange={setMd} />
105
- <MarkdownEditor mode="edit-raw" value={md} onChange={setMd} />
106
- <MarkdownEditor mode="view" value={md} />
107
- ```
108
-
109
- Toggle modes in parent state; the editor preserves content via an internal ref when remounting Lexical after raw editing.
54
+ The component is controlled: pass `value` and persist what `onChange`
55
+ emits. The emitted string is always plain markdown.
110
56
 
111
57
  ## Props (`MarkdownEditorProps`)
112
58
 
@@ -115,23 +61,29 @@ Toggle modes in parent state; the editor preserves content via an internal ref w
115
61
  | `value` | `string` | `''` | Markdown source (controlled) |
116
62
  | `onChange` | `(value: string) => void` | — | Called on every edit |
117
63
  | `mode` | `'edit-md' \| 'edit-raw' \| 'view'` | `'edit-md'` | Editing surface |
118
- | `placeholder` | `string` | `'Start writing...'` | Empty state hint |
64
+ | `placeholder` | `string` | `'Start writing...'` | Empty-state hint |
119
65
  | `readOnly` | `boolean` | `false` | Disables editing in Lexical modes |
120
66
  | `autoFocus` | `boolean` | `false` | Focus on mount |
121
67
  | `className` | `string` | — | Root wrapper class |
122
- | `toolbar` | `boolean` | `true` | Formatting/insert toolbar in `edit-md` mode |
123
-
124
- ## Markdown features
68
+ | `toolbar` | `boolean` | `true` | Formatting/insert toolbar (`edit-md`) |
69
+ | `outline` | `boolean` | `false` | Table-of-contents sidebar |
70
+ | `foldable` | `boolean` | `true` | Collapse sections under headings |
125
71
 
126
- **Shortcuts (edit-md):** headings (`#`), lists, blockquote, links, fenced code (via `CodeBlockShortcutPlugin`), checklists (`- [ ]` via `ChecklistShortcutPlugin`), tables (`| a | b |`).
72
+ ### Modes
127
73
 
128
- **Built-in plugins:** history (undo/redo), lists, checklists, links, tables (`TablePlugin`), markdown sync (`MarkdownSyncPlugin`), code highlighting (`CodeHighlightPlugin`), toolbar (`ToolbarPlugin`).
74
+ | `mode` | UI | Notes |
75
+ |--------|-----|-------|
76
+ | `'edit-md'` | Lexical rich editor | Shortcuts, toolbar, tables, canvas |
77
+ | `'edit-raw'` | Plain `<textarea>` | Direct markdown source editing |
78
+ | `'view'` | Read-only render | Outline/folding still work |
129
79
 
130
- **Not included:** file uploads, collaborative editing, or custom Lexical node registration extend by forking or wrapping `MarkdownEditor`.
80
+ Switching `edit-raw` `edit-md` re-mounts Lexical with the latest text
81
+ (cursor-safe).
131
82
 
132
83
  ## Tables
133
84
 
134
- GFM pipe tables round-trip through markdown and render artifact-style (rounded outer border, shaded header row, per-cell rules):
85
+ Type a `| a | b |` row or use the toolbar's insert-table button. GFM
86
+ round-trip:
135
87
 
136
88
  ```md
137
89
  | Metric | Q1 | Q2 |
@@ -139,66 +91,110 @@ GFM pipe tables round-trip through markdown and render artifact-style (rounded o
139
91
  | Revenue | $1.2M | $1.8M |
140
92
  ```
141
93
 
142
- Insert a 3×3 table from the toolbar, or type a `| a | b |` row. Tab/arrow navigation, cell selection, and row/column operations come from Lexical's `TablePlugin`. Inline formatting (`**bold**`, `*italic*`, `` `code` ``) works inside cells.
143
-
144
- ## Drawing canvas
94
+ Rendering is artifact-style (rounded outer border, shaded header row).
95
+ Editing is in place: Tab/arrows between cells, cell range selection, inline
96
+ formatting inside cells. Cell content is single-line in markdown; newlines
97
+ are escaped as `\n`.
98
+
99
+ ## Diagrams (drawing canvas)
100
+
101
+ The toolbar's insert-drawing button embeds a canvas; the drawing persists
102
+ in the markdown as a ` ```drawing ` fenced JSON block, fully specified in
103
+ **[DRAWING_FORMAT.md](./DRAWING_FORMAT.md)**.
104
+
105
+ - **Shapes**: rectangle, ellipse, triangle, pentagon, arrow, line, text —
106
+ with stroke/fill palettes. Draw by picking a tool and dragging.
107
+ - **Cards**: every box carries three text slots that move, resize, and wrap
108
+ with it — a bold **label** on top, **content** in the center, a dim
109
+ **footer** at the bottom. Click a selected box (or double-click its
110
+ top/middle/bottom strip, or press Enter) to edit a slot.
111
+ - **Bound connectors**: an arrow drawn from one card to another attaches to
112
+ both — moving a card moves its arrows, endpoints anchored to the border.
113
+ Drag an endpoint off/onto a card to detach/re-attach.
114
+ - **Routing**: straight (diagonal) by default; toggle **elbow** for right
115
+ angles, or drag the dashed "+" handles on a selected connector to add any
116
+ number of **waypoints** (they snap to neighbors' axes for clean 90°
117
+ bends). One-way / two-way arrowhead toggle; midpoint **labels**.
118
+ - **Canvas**: resizable height, dot grid, white surface that inverts
119
+ Excalidraw-style in dark mode (`.dark` ancestor class).
120
+
121
+ ## Outline & section folding
122
+
123
+ `outline` docks a collapsible table-of-contents sidebar: live heading list
124
+ indented by level, click to scroll, current section highlighted. Not part
125
+ of the document — pure UI. Available in `edit-md` and `view`.
126
+
127
+ `foldable` (default on) shows a chevron in the gutter of each heading on
128
+ hover; clicking collapses the section (until the next heading of the same
129
+ or higher level). Folding never changes the markdown; fold state resets on
130
+ remount; a folded section auto-expands if the cursor enters it.
131
+
132
+ ## For AI agents / programmatic authoring
133
+
134
+ Documents are plain markdown, so LLMs can generate them — including
135
+ diagrams:
136
+
137
+ - **[DRAWING_FORMAT.md](./DRAWING_FORMAT.md)** is the authoritative
138
+ ` ```drawing ` payload spec, written to be pasted into a prompt (ships in
139
+ the npm package next to this README).
140
+ - `DRAWING_DATA_JSON_SCHEMA` (exported) is the same contract as JSON Schema
141
+ — use it to validate generated payloads or as a structured-output/tool
142
+ schema.
143
+ - `parseDrawingData(json)` is the editor's own lenient parser (invalid
144
+ shapes drop out; never throws); `serializeDrawingData` is its inverse.
145
+ - A ready-made Claude Code skill lives in the monorepo at
146
+ `.claude/skills/text-editor-documents/` — copy it into consuming repos so
147
+ agents there know the dialect.
145
148
 
146
- The toolbar's "Insert drawing" button embeds an Excalidraw-style canvas for simple diagrams: rectangles, ellipses, triangles, pentagons, arrows, lines, and text labels, with a stroke and fill color palette.
147
-
148
- - Draw a shape by picking a tool and dragging; the tool returns to **Select** after each shape
149
- - Select to move, resize (corner handles), or re-point arrows/lines (endpoint handles)
150
- - Color swatches restyle the selected shape (or set defaults for the next one)
151
- - `Delete`/`Backspace` removes the selected shape; drag the bottom pill to resize the canvas
152
- - Boxes (rects/ellipses) have three built-in text slots that move with the shape: a bold **label** at the top, main **content** in the center, and a dimmer **footer** at the bottom. Double-click the top strip of a box to edit the label, the middle for content, the bottom strip for the footer (or click a selected box again, or press Enter for content). Clearing a slot removes it; the box stays.
153
- - **Arrows bind to boxes**: draw an arrow starting or ending on a box and it attaches to the box border — moving or resizing the box drags the arrow along. Endpoints re-anchor toward the other end automatically. Drag an endpoint off a box to detach it; drop it on another box to re-attach. Deleting a box releases its arrows.
154
- - A selected arrow shows a **direction toggle** in the toolbar: one-way (head at the end) or two-way (heads on both ends).
155
- - A selected connector also shows a **routing toggle**: straight (freely diagonal) or **elbow** — an orthogonal path with right-angle bends. The elbow's middle segment has a drag handle to reposition the bend; its position is stored as a fraction of the span so it stays put while bound boxes move.
156
- - **Waypoints** for arbitrary multi-segment paths: a selected connector shows dashed "+" handles at each segment midpoint — drag one to insert a bend point there. Waypoints snap to their neighbors' axes near-alignment so right angles are easy; drag freely for diagonals. Double-click a waypoint to remove it. Adding a point to an elbow connector converts its corners into editable waypoints. Selecting a routing mode from the toolbar clears waypoints.
157
- - **Arrows and lines can carry a label**: double-click the connector (or select it and press Enter) to edit text that rides the midpoint on a small backing plate, and moves as the connector moves.
158
- - The standalone **Text** tool is for free-floating annotations (e.g. labeling an arrow); double-click to edit
159
-
160
- Drawings persist inside the markdown as a fenced block, so the document stays a plain string:
161
-
162
- ````md
163
- ```drawing
164
- {"version":1,"height":320,"shapes":[...]}
165
- ```
166
- ````
149
+ ## Exports
167
150
 
168
- In `view` mode (or `readOnly`) the canvas renders the shapes without any editing chrome.
151
+ | Export | Purpose |
152
+ |--------|---------|
153
+ | `MarkdownEditor`, `MarkdownEditorProps` | The component |
154
+ | `@zuilib/text-editor/styles.css` | Editor chrome + theme styles (required) |
155
+ | `TABLE` | GFM table markdown transformer |
156
+ | `DRAWING`, `DrawingNode`, `$createDrawingNode`, `$isDrawingNode` | Drawing node + markdown transformer |
157
+ | `FRONTMATTER`, `FrontmatterNode`, `$createFrontmatterNode`, `$isFrontmatterNode` | Frontmatter node + transformer |
158
+ | `DrawingData`, `DrawingShape`, `DrawingShapeType` | Drawing payload types |
159
+ | `parseDrawingData`, `serializeDrawingData` | Drawing payload (de)serialization |
160
+ | `DRAWING_DATA_JSON_SCHEMA` | JSON Schema of the drawing payload |
169
161
 
170
162
  ## Form integration
171
163
 
172
- With `@zuilib/form`:
173
-
174
164
  ```tsx
175
165
  <FormField control={form.control} name="body">
176
166
  {({ field }) => (
177
- <MarkdownEditor
178
- value={field.value}
179
- onChange={field.onChange}
180
- mode="edit-md"
181
- />
167
+ <MarkdownEditor value={field.value} onChange={field.onChange} />
182
168
  )}
183
169
  </FormField>
184
170
  ```
185
171
 
186
- Ensure both CSS entry points are loaded in the app layout.
172
+ Load both CSS entry points (`@zuilib/core/styles.css`,
173
+ `@zuilib/text-editor/styles.css`) in the app layout. Dark mode follows the
174
+ ZUI convention: a `dark` class on `<html>`.
187
175
 
188
176
  ## Architecture notes (for extenders)
189
177
 
190
- - Source: `src/MarkdownEditor.tsx`, plugins under `src/plugins/`
191
- - Lexical namespace: `ZuiTextEditor`
192
- - Markdown import/export uses `@lexical/markdown` transformers (`CHECK_LIST` prioritized for import)
193
- - `edit-raw` bypasses Lexical; switching back to `edit-md` captures `latestValueRef` and bumps `mountKey` to avoid cursor jumps
178
+ - Source: `src/MarkdownEditor.tsx`; plugins in `src/plugins/`; drawing
179
+ canvas in `src/components/`; custom nodes in `src/nodes/`;
180
+ transformers in `src/transformers/`
181
+ - Markdown import/export via `@lexical/markdown` transformers; order
182
+ matters: `FRONTMATTER` and `DRAWING` claim their blocks before `CODE`,
183
+ `CHECK_LIST` before `UNORDERED_LIST`
184
+ - Drawing geometry (bindings resolver, elbow/waypoint paths, wrapping) is
185
+ pure and lives in `src/components/drawingGeometry.ts`
186
+ - Tests: `pnpm test` runs a headless-Lexical markdown round-trip suite
187
+ (`tests/roundtrip.mjs`)
194
188
 
195
189
  ## Related packages
196
190
 
197
191
  - `@zuilib/core` — design tokens and base styles
198
192
  - `@zuilib/form` — react-hook-form wiring
199
193
 
200
- ## Build (maintainers)
194
+ ## Build & release (maintainers)
201
195
 
202
196
  ```bash
203
- pnpm --filter @zuilib/text-editor build
197
+ pnpm --filter @zuilib/text-editor build # tsup → dist/
198
+ pnpm --filter @zuilib/text-editor test # round-trip suite
199
+ pnpm --filter @zuilib/text-editor publish --access public
204
200
  ```