@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 +16 -0
- package/README.md +93 -3
- package/dist/index.d.ts +120 -9
- package/dist/index.js +844 -708
- package/dist/styles.css +6 -0
- package/package.json +7 -7
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/
|
|
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 {
|
|
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
|
|
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?:
|
|
12
|
+
mode?: EditorMode;
|
|
13
13
|
autoFocus?: boolean;
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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 };
|