@zuilib/text-editor 0.0.2 → 0.1.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/README.md CHANGED
@@ -1,6 +1,6 @@
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, and fenced code highlighting.
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
4
 
5
5
  ## When to use
6
6
 
@@ -22,6 +22,8 @@ Peer dependencies (install in the consuming app):
22
22
  | `@lexical/code` | `^0.35.0` |
23
23
  | `@lexical/list` | `^0.35.0` |
24
24
  | `@lexical/link` | `^0.35.0` |
25
+ | `@lexical/table` | `^0.35.0` |
26
+ | `@lexical/utils` | `^0.35.0` |
25
27
  | `@zuilib/core` | workspace / published — import core styles |
26
28
 
27
29
  ## Installation
@@ -29,7 +31,7 @@ Peer dependencies (install in the consuming app):
29
31
  ```bash
30
32
  pnpm add @zuilib/text-editor @zuilib/core \
31
33
  lexical @lexical/react @lexical/markdown @lexical/rich-text \
32
- @lexical/code @lexical/list @lexical/link
34
+ @lexical/code @lexical/list @lexical/link @lexical/table @lexical/utils
33
35
  ```
34
36
 
35
37
  Monorepo:
@@ -45,7 +47,9 @@ Monorepo:
45
47
  "@lexical/rich-text": "^0.35.0",
46
48
  "@lexical/code": "^0.35.0",
47
49
  "@lexical/list": "^0.35.0",
48
- "@lexical/link": "^0.35.0"
50
+ "@lexical/link": "^0.35.0",
51
+ "@lexical/table": "^0.35.0",
52
+ "@lexical/utils": "^0.35.0"
49
53
  }
50
54
  }
51
55
  ```
@@ -115,15 +119,54 @@ Toggle modes in parent state; the editor preserves content via an internal ref w
115
119
  | `readOnly` | `boolean` | `false` | Disables editing in Lexical modes |
116
120
  | `autoFocus` | `boolean` | `false` | Focus on mount |
117
121
  | `className` | `string` | — | Root wrapper class |
122
+ | `toolbar` | `boolean` | `true` | Formatting/insert toolbar in `edit-md` mode |
118
123
 
119
124
  ## Markdown features
120
125
 
121
- **Shortcuts (edit-md):** headings (`#`), lists, blockquote, links, fenced code (via `CodeBlockShortcutPlugin`), checklists (`- [ ]` via `ChecklistShortcutPlugin`).
126
+ **Shortcuts (edit-md):** headings (`#`), lists, blockquote, links, fenced code (via `CodeBlockShortcutPlugin`), checklists (`- [ ]` via `ChecklistShortcutPlugin`), tables (`| a | b |`).
122
127
 
123
- **Built-in plugins:** history (undo/redo), lists, checklists, links, markdown sync (`MarkdownSyncPlugin`), code highlighting (`CodeHighlightPlugin`).
128
+ **Built-in plugins:** history (undo/redo), lists, checklists, links, tables (`TablePlugin`), markdown sync (`MarkdownSyncPlugin`), code highlighting (`CodeHighlightPlugin`), toolbar (`ToolbarPlugin`).
124
129
 
125
130
  **Not included:** file uploads, collaborative editing, or custom Lexical node registration — extend by forking or wrapping `MarkdownEditor`.
126
131
 
132
+ ## Tables
133
+
134
+ GFM pipe tables round-trip through markdown and render artifact-style (rounded outer border, shaded header row, per-cell rules):
135
+
136
+ ```md
137
+ | Metric | Q1 | Q2 |
138
+ | --- | --- | --- |
139
+ | Revenue | $1.2M | $1.8M |
140
+ ```
141
+
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
145
+
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
+ ````
167
+
168
+ In `view` mode (or `readOnly`) the canvas renders the shapes without any editing chrome.
169
+
127
170
  ## Form integration
128
171
 
129
172
  With `@zuilib/form`:
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ElementNode, NodeKey, EditorConfig, SerializedElementNode, LexicalNode } from 'lexical';
3
- import { MultilineElementTransformer } from '@lexical/markdown';
2
+ import { ElementNode, NodeKey, EditorConfig, SerializedElementNode, LexicalNode, DecoratorNode, Spread, SerializedLexicalNode, DOMExportOutput, DOMConversionMap, LexicalEditor } from 'lexical';
3
+ import { MultilineElementTransformer, ElementTransformer } from '@lexical/markdown';
4
+ import { ReactElement } from 'react';
4
5
 
5
6
  type Props = Readonly<{
6
7
  value?: string;
@@ -10,8 +11,10 @@ type Props = Readonly<{
10
11
  className?: string;
11
12
  mode?: 'edit-raw' | 'edit-md' | 'view';
12
13
  autoFocus?: boolean;
14
+ /** Show the formatting/insert toolbar in edit-md mode (default true) */
15
+ toolbar?: boolean;
13
16
  }>;
14
- declare function MarkdownEditor({ value, onChange, placeholder, readOnly, className, mode, autoFocus, }: Props): react_jsx_runtime.JSX.Element;
17
+ declare function MarkdownEditor({ value, onChange, placeholder, readOnly, className, mode, autoFocus, toolbar, }: Props): react_jsx_runtime.JSX.Element;
15
18
 
16
19
  type SerializedFrontmatterNode = SerializedElementNode;
17
20
  /**
@@ -54,4 +57,90 @@ declare function $isFrontmatterNode(node: LexicalNode | null | undefined): node
54
57
  */
55
58
  declare const FRONTMATTER: MultilineElementTransformer;
56
59
 
57
- export { $createFrontmatterNode, $isFrontmatterNode, FRONTMATTER, FrontmatterNode, MarkdownEditor, type Props as MarkdownEditorProps, type SerializedFrontmatterNode };
60
+ type DrawingShapeType = 'rect' | 'ellipse' | 'triangle' | 'pentagon' | 'arrow' | 'line' | 'text';
61
+ type DrawingShape = Readonly<{
62
+ id: string;
63
+ type: DrawingShapeType;
64
+ /** Top-left corner (rect/ellipse/text) or start point (arrow/line) */
65
+ x: number;
66
+ y: number;
67
+ /** Size (rect/ellipse) or delta to the end point (arrow/line) */
68
+ w: number;
69
+ h: number;
70
+ stroke: string;
71
+ fill: string;
72
+ strokeWidth: number;
73
+ /** Standalone text, main (center) content of a box, or connector label */
74
+ text?: string;
75
+ /** Small heading rendered at the top of a box */
76
+ label?: string;
77
+ /** Small line rendered at the bottom of a box */
78
+ footer?: string;
79
+ /** Arrow/line: id of the box the start point is attached to */
80
+ startBinding?: string;
81
+ /** Arrow/line: id of the box the end point is attached to */
82
+ endBinding?: string;
83
+ /** Arrow: render arrowheads on both ends */
84
+ bidirectional?: boolean;
85
+ /** Connector routing: right-angled elbow path instead of a straight line */
86
+ routing?: 'elbow';
87
+ /** Elbow middle-segment position as a fraction of the span (default 0.5) */
88
+ elbow?: number;
89
+ /**
90
+ * Connector: intermediate path points between start and end (absolute
91
+ * canvas coordinates). Takes precedence over `routing`.
92
+ */
93
+ waypoints?: ReadonlyArray<{
94
+ x: number;
95
+ y: number;
96
+ }>;
97
+ }>;
98
+ type DrawingData = Readonly<{
99
+ version: 1;
100
+ height: number;
101
+ shapes: readonly DrawingShape[];
102
+ }>;
103
+
104
+ type SerializedDrawingNode = Spread<{
105
+ data: string;
106
+ }, SerializedLexicalNode>;
107
+ /**
108
+ * A block-level decorator node embedding a vector drawing (rectangles,
109
+ * ellipses, arrows, lines, text) edited on an Excalidraw-style canvas.
110
+ *
111
+ * The drawing is stored as a JSON string and round-trips through markdown as
112
+ * a ```drawing fenced code block, so documents remain plain markdown.
113
+ */
114
+ declare class DrawingNode extends DecoratorNode<ReactElement> {
115
+ __data: string;
116
+ static getType(): string;
117
+ static clone(node: DrawingNode): DrawingNode;
118
+ constructor(data: string, key?: NodeKey);
119
+ static importJSON(serializedNode: SerializedDrawingNode): DrawingNode;
120
+ exportJSON(): SerializedDrawingNode;
121
+ createDOM(config: EditorConfig): HTMLElement;
122
+ updateDOM(): false;
123
+ exportDOM(): DOMExportOutput;
124
+ static importDOM(): DOMConversionMap | null;
125
+ getData(): DrawingData;
126
+ setData(data: DrawingData): void;
127
+ isInline(): false;
128
+ decorate(_editor: LexicalEditor, _config: EditorConfig): ReactElement;
129
+ }
130
+ declare function $createDrawingNode(data?: string): DrawingNode;
131
+ declare function $isDrawingNode(node: LexicalNode | null | undefined): node is DrawingNode;
132
+ /**
133
+ * Markdown transformer persisting drawings as fenced ```drawing blocks with a
134
+ * JSON payload. Must be ordered before the built-in CODE transformer so it
135
+ * claims the fence first on import.
136
+ */
137
+ declare const DRAWING: MultilineElementTransformer;
138
+
139
+ /**
140
+ * Markdown transformer for GFM tables. Adapted from the Lexical playground.
141
+ * Rows are matched line-by-line on import and stitched into a single
142
+ * TableNode; the divider row promotes the row above it to a header row.
143
+ */
144
+ declare const TABLE: ElementTransformer;
145
+
146
+ export { $createDrawingNode, $createFrontmatterNode, $isDrawingNode, $isFrontmatterNode, DRAWING, type DrawingData, DrawingNode, type DrawingShape, type DrawingShapeType, FRONTMATTER, FrontmatterNode, MarkdownEditor, type Props as MarkdownEditorProps, type SerializedDrawingNode, type SerializedFrontmatterNode, TABLE };