@zuilib/text-editor 0.2.1 → 0.3.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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog — @zuilib/text-editor
2
2
 
3
+ ## 0.3.0
4
+
5
+ - **Custom toolbars**
6
+ - `toolbar` prop accepts a render function receiving the default groups
7
+ (`format`, `insert`, `history`) so you can add your own buttons
8
+ - Compound components: `MarkdownEditor.Root` / `.Content` / `.Toolbar` /
9
+ `.Outline` plus `.FormatButtons` / `.InsertButtons` / `.HistoryButtons` /
10
+ `.ToolbarButton` / `.ToolbarDivider` — place the toolbar anywhere under
11
+ `Root`
12
+ - `useMarkdownEditor()` headless hook: `editor`, `activeFormats`,
13
+ `toggleFormat`, `insertTable`, `insertDrawing`, `undo`/`redo`
14
+ - Undo/redo buttons (`HistoryButtons`; in the default toolbar via the
15
+ function form only — default toolbar layout unchanged)
16
+ - Internal: `LexicalComposer` is now mounted in `edit-raw` mode too, so
17
+ hooks keep working across mode switches
18
+
3
19
  ## 0.2.1
4
20
 
5
21
  - `DRAWING_FORMAT.md`: authoritative spec of the ```drawing JSON payload,
package/README.md CHANGED
@@ -65,7 +65,7 @@ emits. The emitted string is always plain markdown.
65
65
  | `readOnly` | `boolean` | `false` | Disables editing in Lexical modes |
66
66
  | `autoFocus` | `boolean` | `false` | Focus on mount |
67
67
  | `className` | `string` | — | Root wrapper class |
68
- | `toolbar` | `boolean` | `true` | Formatting/insert toolbar (`edit-md`) |
68
+ | `toolbar` | `boolean \| (items) => ReactNode` | `true` | Formatting/insert toolbar (`edit-md`); function form customises it |
69
69
  | `outline` | `boolean` | `false` | Table-of-contents sidebar |
70
70
  | `foldable` | `boolean` | `true` | Collapse sections under headings |
71
71
 
@@ -96,6 +96,92 @@ Editing is in place: Tab/arrows between cells, cell range selection, inline
96
96
  formatting inside cells. Cell content is single-line in markdown; newlines
97
97
  are escaped as `\n`.
98
98
 
99
+ ## Toolbar & custom toolbars
100
+
101
+ In `edit-md` mode a toolbar offers inline formatting (bold, italic,
102
+ strikethrough, inline code) plus **Insert table** and **Insert drawing**.
103
+ Hide it with `toolbar={false}`.
104
+
105
+ ### Extending the toolbar
106
+
107
+ Pass a function as `toolbar`. It receives the default button groups and
108
+ returns the toolbar to render. Use `MarkdownEditor.ToolbarButton` for your
109
+ own buttons so they match the built-ins and keep the editor selection when
110
+ clicked.
111
+
112
+ ```tsx
113
+ <MarkdownEditor
114
+ value={value}
115
+ onChange={setValue}
116
+ toolbar={(items) => (
117
+ <MarkdownEditor.Toolbar>
118
+ {items.format}
119
+ <MarkdownEditor.ToolbarDivider />
120
+ {items.insert}
121
+ <MarkdownEditor.ToolbarDivider />
122
+ {items.history}
123
+ <MarkdownEditor.ToolbarButton label="Save" onClick={save}>
124
+ 💾
125
+ </MarkdownEditor.ToolbarButton>
126
+ </MarkdownEditor.Toolbar>
127
+ )}
128
+ />
129
+ ```
130
+
131
+ `MarkdownEditor.Toolbar` hides itself in `view` / `edit-raw` / `readOnly`.
132
+
133
+ ### Placing your own toolbar (compound components)
134
+
135
+ When the toolbar must live somewhere else in your layout (an app bar, a
136
+ panel header), compose the editor from its parts. Everything under
137
+ `MarkdownEditor.Root` shares one editor instance, so the toolbar can sit
138
+ anywhere in that subtree.
139
+
140
+ ```tsx
141
+ <MarkdownEditor.Root value={value} onChange={setValue}>
142
+ <header className="app-bar">
143
+ <MarkdownEditor.Toolbar>
144
+ <MarkdownEditor.FormatButtons />
145
+ <MarkdownEditor.ToolbarDivider />
146
+ <MarkdownEditor.InsertButtons />
147
+ </MarkdownEditor.Toolbar>
148
+ <MyAppButtons />
149
+ </header>
150
+ <MarkdownEditor.Content placeholder="Write…">
151
+ <MarkdownEditor.Outline />
152
+ </MarkdownEditor.Content>
153
+ </MarkdownEditor.Root>
154
+ ```
155
+
156
+ | Part | Role |
157
+ |------|------|
158
+ | `Root` | Lexical composer + plugins. Takes `value`, `onChange`, `mode`, `readOnly`, `autoFocus`, `className` |
159
+ | `Content` | The editable surface. Takes `placeholder`, `foldable`; children are docked sidebars |
160
+ | `Toolbar` | Container; renders the default groups when empty |
161
+ | `FormatButtons`, `InsertButtons`, `HistoryButtons` | Built-in groups |
162
+ | `ToolbarButton`, `ToolbarDivider` | Primitives for your own items |
163
+ | `Outline` | Table-of-contents sidebar |
164
+
165
+ ### Headless: `useMarkdownEditor()`
166
+
167
+ For fully custom UI (e.g. buttons in your own design system), call the hook
168
+ from any component rendered under `MarkdownEditor.Root`:
169
+
170
+ ```tsx
171
+ function BoldButton() {
172
+ const { activeFormats, toggleFormat } = useMarkdownEditor()
173
+ return (
174
+ <MyButton pressed={activeFormats.has('bold')} onClick={() => toggleFormat('bold')}>
175
+ B
176
+ </MyButton>
177
+ )
178
+ }
179
+ ```
180
+
181
+ It returns `editor` (the Lexical instance), `activeFormats`,
182
+ `toggleFormat`, `insertTable`, `insertDrawing`, `canUndo`, `canRedo`,
183
+ `undo`, `redo`.
184
+
99
185
  ## Diagrams (drawing canvas)
100
186
 
101
187
  The toolbar's insert-drawing button embeds a canvas; the drawing persists
@@ -150,7 +236,9 @@ diagrams:
150
236
 
151
237
  | Export | Purpose |
152
238
  |--------|---------|
153
- | `MarkdownEditor`, `MarkdownEditorProps` | The component |
239
+ | `MarkdownEditor`, `MarkdownEditorProps` | The component; compound parts as statics (`.Root`, `.Content`, `.Toolbar`, …) |
240
+ | `useMarkdownEditor`, `MarkdownEditorApi` | Headless editor hook |
241
+ | `Toolbar`, `ToolbarButton`, `ToolbarDivider`, `FormatButtons`, `InsertButtons`, `HistoryButtons` | Toolbar primitives |
154
242
  | `@zuilib/text-editor/styles.css` | Editor chrome + theme styles (required) |
155
243
  | `TABLE` | GFM table markdown transformer |
156
244
  | `DRAWING`, `DrawingNode`, `$createDrawingNode`, `$isDrawingNode` | Drawing node + markdown transformer |
@@ -175,7 +263,9 @@ ZUI convention: a `dark` class on `<html>`.
175
263
 
176
264
  ## Architecture notes (for extenders)
177
265
 
178
- - Source: `src/MarkdownEditor.tsx`; plugins in `src/plugins/`; drawing
266
+ - Source: `src/EditorRoot.tsx` (composer + plugins), `src/EditorContent.tsx`,
267
+ `src/MarkdownEditor.tsx` (default composition), `src/useMarkdownEditor.ts`,
268
+ `src/components/Toolbar.tsx`; plugins in `src/plugins/`; drawing
179
269
  canvas in `src/components/`; custom nodes in `src/nodes/`;
180
270
  transformers in `src/transformers/`
181
271
  - Markdown import/export via `@lexical/markdown` transformers; order
package/dist/index.d.ts CHANGED
@@ -1,24 +1,135 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ElementNode, NodeKey, EditorConfig, SerializedElementNode, LexicalNode, DecoratorNode, Spread, SerializedLexicalNode, DOMExportOutput, DOMConversionMap, LexicalEditor } from 'lexical';
2
+ import { ReactNode, ReactElement } from 'react';
3
+ import { LexicalEditor, TextFormatType, ElementNode, NodeKey, EditorConfig, SerializedElementNode, LexicalNode, DecoratorNode, Spread, SerializedLexicalNode, DOMExportOutput, DOMConversionMap } from 'lexical';
3
4
  import { MultilineElementTransformer, ElementTransformer } from '@lexical/markdown';
4
- import { ReactElement } from 'react';
5
5
 
6
- type Props = Readonly<{
6
+ type EditorMode = 'edit-raw' | 'edit-md' | 'view';
7
+ type EditorRootProps = Readonly<{
7
8
  value?: string;
8
9
  onChange?: (value: string) => void;
9
- placeholder?: string;
10
10
  readOnly?: boolean;
11
11
  className?: string;
12
- mode?: 'edit-raw' | 'edit-md' | 'view';
12
+ mode?: EditorMode;
13
13
  autoFocus?: boolean;
14
- /** Show the formatting/insert toolbar in edit-md mode (default true) */
15
- toolbar?: boolean;
14
+ children: ReactNode;
15
+ }>;
16
+ /**
17
+ * Owns the Lexical composer and every non-visual plugin. Anything rendered
18
+ * beneath it (`Content`, `Toolbar`, `Outline`, or your own components using
19
+ * `useMarkdownEditor`) shares the same editor instance.
20
+ */
21
+ declare function EditorRoot({ value, onChange, readOnly, className, mode, autoFocus, children, }: EditorRootProps): ReactElement;
22
+
23
+ type EditorContentProps = Readonly<{
24
+ placeholder?: string;
25
+ /** Allow collapsing sections under their headings (default true) */
26
+ foldable?: boolean;
27
+ /** Sidebars docked next to the content, e.g. `<MarkdownEditor.Outline />` */
28
+ children?: ReactNode;
29
+ }>;
30
+ /**
31
+ * The editable surface. Renders a plain textarea in `edit-raw` mode and the
32
+ * Lexical rich-text surface otherwise.
33
+ */
34
+ declare function EditorContent({ placeholder, foldable, children, }: EditorContentProps): ReactElement;
35
+
36
+ /**
37
+ * Docked, collapsible table-of-contents sidebar. Lists the document's
38
+ * headings (live), scrolls to a heading on click, and highlights the section
39
+ * the viewport is currently in. Pure UI — nothing is added to the document.
40
+ */
41
+ declare function OutlinePlugin(): ReactElement;
42
+
43
+ type MarkdownEditorApi = Readonly<{
44
+ /** The underlying Lexical editor, for dispatching your own commands */
45
+ editor: LexicalEditor;
46
+ /** Text formats active at the current selection */
47
+ activeFormats: ReadonlySet<TextFormatType>;
48
+ toggleFormat: (format: TextFormatType) => void;
49
+ insertTable: (options?: {
50
+ rows?: number;
51
+ columns?: number;
52
+ }) => void;
53
+ insertDrawing: () => void;
54
+ canUndo: boolean;
55
+ canRedo: boolean;
56
+ undo: () => void;
57
+ redo: () => void;
58
+ }>;
59
+ /**
60
+ * Headless access to the editor. Must be rendered under `MarkdownEditor.Root`
61
+ * (or `MarkdownEditor`). Use it to wire editor actions into your own buttons.
62
+ */
63
+ declare function useMarkdownEditor(): MarkdownEditorApi;
64
+
65
+ /** Default toolbar groups, handed to a `toolbar` render function */
66
+ type ToolbarItems = Readonly<{
67
+ format: ReactElement;
68
+ insert: ReactElement;
69
+ history: ReactElement;
70
+ }>;
71
+ type Props = Omit<EditorRootProps, 'children'> & Readonly<{
72
+ placeholder?: string;
73
+ /**
74
+ * Toolbar in edit-md mode (default true). Pass a function to customise
75
+ * it: it receives the default groups and returns the toolbar to render.
76
+ */
77
+ toolbar?: boolean | ((items: ToolbarItems) => ReactNode);
16
78
  /** Show a table-of-contents sidebar listing the document's headings */
17
79
  outline?: boolean;
18
80
  /** Allow collapsing sections under their headings (default true) */
19
81
  foldable?: boolean;
20
82
  }>;
21
- declare function MarkdownEditor({ value, onChange, placeholder, readOnly, className, mode, autoFocus, toolbar, outline, foldable, }: Props): react_jsx_runtime.JSX.Element;
83
+ /**
84
+ * Toolbar that only shows while the document is editable in `edit-md` mode.
85
+ * Used by `MarkdownEditor` and available as `MarkdownEditor.Toolbar` for
86
+ * compound layouts.
87
+ */
88
+ declare function EditorToolbar({ children, className, }: Readonly<{
89
+ children?: ReactNode;
90
+ className?: string;
91
+ }>): ReactElement | null;
92
+ declare function MarkdownEditor({ placeholder, toolbar, outline, foldable, ...rootProps }: Props): react_jsx_runtime.JSX.Element;
93
+ declare namespace MarkdownEditor {
94
+ var Root: typeof EditorRoot;
95
+ var Content: typeof EditorContent;
96
+ var Toolbar: typeof EditorToolbar;
97
+ var ToolbarButton: typeof ToolbarButton;
98
+ var ToolbarDivider: typeof ToolbarDivider;
99
+ var FormatButtons: typeof FormatButtons;
100
+ var InsertButtons: typeof InsertButtons;
101
+ var HistoryButtons: typeof HistoryButtons;
102
+ var Outline: typeof OutlinePlugin;
103
+ var useEditor: typeof useMarkdownEditor;
104
+ }
105
+
106
+ type ToolbarProps = Readonly<{
107
+ children?: ReactNode;
108
+ className?: string;
109
+ }>;
110
+ /** Toolbar container. Renders the default items when given no children. */
111
+ declare function Toolbar({ children, className }: ToolbarProps): ReactElement;
112
+ declare function ToolbarDivider(): ReactElement;
113
+ type ToolbarButtonProps = Readonly<{
114
+ label: string;
115
+ onClick: () => void;
116
+ /** Icon content — an SVG, emoji, or text. 16px SVGs match the built-ins. */
117
+ children: ReactNode;
118
+ active?: boolean;
119
+ disabled?: boolean;
120
+ className?: string;
121
+ }>;
122
+ /**
123
+ * Button styled like the built-in toolbar buttons. Prevents focus from
124
+ * leaving the editor on click so the selection is preserved.
125
+ */
126
+ declare function ToolbarButton({ label, onClick, children, active, disabled, className, }: ToolbarButtonProps): ReactElement;
127
+ /** Bold / italic / strikethrough / inline-code buttons */
128
+ declare function FormatButtons(): ReactElement;
129
+ /** Insert-table / insert-drawing buttons */
130
+ declare function InsertButtons(): ReactElement;
131
+ /** Undo / redo buttons */
132
+ declare function HistoryButtons(): ReactElement;
22
133
 
23
134
  type SerializedFrontmatterNode = SerializedElementNode;
24
135
  /**
@@ -281,4 +392,4 @@ declare const DRAWING_DATA_JSON_SCHEMA: {
281
392
  };
282
393
  };
283
394
 
284
- export { $createDrawingNode, $createFrontmatterNode, $isDrawingNode, $isFrontmatterNode, DRAWING, DRAWING_DATA_JSON_SCHEMA, type DrawingData, DrawingNode, type DrawingShape, type DrawingShapeType, FRONTMATTER, FrontmatterNode, MarkdownEditor, type Props as MarkdownEditorProps, type SerializedDrawingNode, type SerializedFrontmatterNode, TABLE, parseDrawingData, serializeDrawingData };
395
+ export { $createDrawingNode, $createFrontmatterNode, $isDrawingNode, $isFrontmatterNode, DRAWING, DRAWING_DATA_JSON_SCHEMA, type DrawingData, DrawingNode, type DrawingShape, type DrawingShapeType, type EditorContentProps, type EditorMode, type EditorRootProps, FRONTMATTER, FormatButtons, FrontmatterNode, HistoryButtons, InsertButtons, MarkdownEditor, type MarkdownEditorApi, type Props as MarkdownEditorProps, type SerializedDrawingNode, type SerializedFrontmatterNode, TABLE, Toolbar, ToolbarButton, ToolbarDivider, type ToolbarItems, parseDrawingData, serializeDrawingData, useMarkdownEditor };