@lofcz/edix 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/LICENSE +21 -0
- package/README.md +246 -0
- package/lib/commands.d.ts +41 -0
- package/lib/doc/edit.d.ts +41 -0
- package/lib/doc/position.d.ts +1 -0
- package/lib/doc/types.d.ts +15 -0
- package/lib/doc/utils.d.ts +1 -0
- package/lib/dom/default.d.ts +1 -0
- package/lib/dom/index.d.ts +1 -0
- package/lib/dom/parser.d.ts +2 -0
- package/lib/editor.d.ts +102 -0
- package/lib/extensions/copy/html.d.ts +5 -0
- package/lib/extensions/copy/index.d.ts +4 -0
- package/lib/extensions/copy/internal.d.ts +7 -0
- package/lib/extensions/copy/plain.d.ts +6 -0
- package/lib/extensions/copy/types.d.ts +2 -0
- package/lib/extensions/index.d.ts +2 -0
- package/lib/extensions/paste/file.d.ts +6 -0
- package/lib/extensions/paste/html.d.ts +6 -0
- package/lib/extensions/paste/index.d.ts +5 -0
- package/lib/extensions/paste/internal.d.ts +7 -0
- package/lib/extensions/paste/plain.d.ts +5 -0
- package/lib/extensions/paste/types.d.ts +3 -0
- package/lib/extensions/utils.d.ts +1 -0
- package/lib/history.d.ts +1 -0
- package/lib/hotkey.d.ts +9 -0
- package/lib/index.cjs +720 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +737 -0
- package/lib/index.js.map +1 -0
- package/lib/mutation.d.ts +1 -0
- package/lib/plugins/index.d.ts +2 -0
- package/lib/plugins/singleline.d.ts +2 -0
- package/lib/plugins/types.d.ts +5 -0
- package/lib/presets/index.d.ts +2 -0
- package/lib/presets/plain.d.ts +36 -0
- package/lib/utils.d.ts +1 -0
- package/package.json +115 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 inokawa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# edix
|
|
2
|
+
|
|
3
|
+
   [](https://github.com/lofcz/edix/actions/workflows/check.yml) [](https://github.com/lofcz/edix/actions/workflows/demo.yml)
|
|
4
|
+
|
|
5
|
+
> An experimental, framework agnostic, small (4kB+) [contenteditable](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable) state manager.
|
|
6
|
+
|
|
7
|
+
## Motivation
|
|
8
|
+
|
|
9
|
+
Web editing is so hard even today. There are excellent libraries to make complex rich text editor, but they are too much for small purposes. Native [textarea](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) element is accessible and easy to use, but it's hardly customizable.
|
|
10
|
+
|
|
11
|
+
[contenteditable](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable) attribute is a primitive for rich text editing, [that was designed for Internet Explorer and copied by other browsers](https://blog.whatwg.org/the-road-to-html-5-contenteditable). As you may know it has [so many problems](https://github.com/grammarly/contenteditable). It has no clear spec, it has many edge case bugs, and has cross browser/OS/input device problems. And it doesn't work well with declarative frontend frameworks... However, at least the core of contenteditable is stable and it works in all browsers except the inconsistencies. This library aims to fill that gap, fix contenteditable to fit modern web development.
|
|
12
|
+
|
|
13
|
+
## Demo
|
|
14
|
+
|
|
15
|
+
- [React Storybook](https://lofcz.github.io/edix/)
|
|
16
|
+
- [Framework examples](#other-examples)
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install edix@npm:@lofcz/edix
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This uses an [npm alias](https://docs.npmjs.com/cli/commands/npm-install) so you can keep using `import { ... } from "edix"` in your code while installing the `@lofcz/edix` fork.
|
|
25
|
+
|
|
26
|
+
Alternatively, install directly and import from `@lofcz/edix`:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install @lofcz/edix
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`typescript >=5.0` is recommended.
|
|
33
|
+
|
|
34
|
+
### Supported browsers
|
|
35
|
+
|
|
36
|
+
Browser versions supporting [beforeinput event](https://developer.mozilla.org/en-US/docs/Web/API/Element/beforeinput_event#browser_compatibility) are supported.
|
|
37
|
+
|
|
38
|
+
Mobile browsers are also supported, but with some issues (https://github.com/lofcz/edix/issues/97).
|
|
39
|
+
|
|
40
|
+
## Getting started
|
|
41
|
+
|
|
42
|
+
1. Define your contents declaratively. There are rules you have to follow:
|
|
43
|
+
- You must render `<br/>` in empty row (limitation of contenteditable).
|
|
44
|
+
- If `singleline` option is
|
|
45
|
+
- `false` or undefined, direct children of the root are treated as rows. They must be elements, not text.
|
|
46
|
+
- `true`, direct children of the root are treated as inline nodes.
|
|
47
|
+
- (TODO)
|
|
48
|
+
|
|
49
|
+
2. Initialize `Editor` with `createPlainEditor`/`createEditor`.
|
|
50
|
+
3. Call `Editor.input` on mount, with `HTMLElement` which is the root of editable contents.
|
|
51
|
+
4. Update your state with `onChange`, which will be called on edit.
|
|
52
|
+
5. Call returned function from `Editor.input` on unmount for cleanup.
|
|
53
|
+
|
|
54
|
+
Here is an example for React.
|
|
55
|
+
|
|
56
|
+
### Plain text
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { useState, useEffect, useRef } from "react";
|
|
60
|
+
import { createPlainEditor } from "edix";
|
|
61
|
+
|
|
62
|
+
export const App = () => {
|
|
63
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
64
|
+
const [text, setText] = useState("Hello world.");
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
// 2. init
|
|
68
|
+
const editor = createPlainEditor({
|
|
69
|
+
text: text,
|
|
70
|
+
onChange: (v) => {
|
|
71
|
+
// 4. update state
|
|
72
|
+
setText(v);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
// 3. bind to DOM
|
|
76
|
+
const cleanup = editor.input(ref.current);
|
|
77
|
+
return () => {
|
|
78
|
+
// 5. cleanup DOM
|
|
79
|
+
cleanup();
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
// 1. render contents from state
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
ref={ref}
|
|
87
|
+
style={{
|
|
88
|
+
backgroundColor: "white",
|
|
89
|
+
border: "solid 1px darkgray",
|
|
90
|
+
padding: 8,
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{text.split("\n").map((t, i) => (
|
|
94
|
+
<div key={i}>{t ? t : <br />}</div>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Rich text
|
|
102
|
+
|
|
103
|
+
[Standard Schema](https://github.com/standard-schema/standard-schema) is supported.
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { useState, useEffect, useRef, useMemo } from "react";
|
|
107
|
+
import { createEditor, ToggleFormat } from "edix";
|
|
108
|
+
import * as v from "valibot";
|
|
109
|
+
|
|
110
|
+
const schema = v.strictObject({
|
|
111
|
+
children: v.array(
|
|
112
|
+
v.array(
|
|
113
|
+
v.strictObject({
|
|
114
|
+
text: v.string(),
|
|
115
|
+
bold: v.optional(v.boolean()),
|
|
116
|
+
italic: v.optional(v.boolean()),
|
|
117
|
+
}),
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export const App = () => {
|
|
123
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
124
|
+
|
|
125
|
+
type Doc = v.InferOutput<typeof schema>;
|
|
126
|
+
const [doc, setDoc] = useState<Doc>({
|
|
127
|
+
children: [
|
|
128
|
+
[
|
|
129
|
+
{ text: "Hello", bold: true },
|
|
130
|
+
{ text: " " },
|
|
131
|
+
{ text: "World", italic: true },
|
|
132
|
+
{ text: "." },
|
|
133
|
+
],
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const editor = useMemo(
|
|
138
|
+
() =>
|
|
139
|
+
createEditor({
|
|
140
|
+
doc,
|
|
141
|
+
schema,
|
|
142
|
+
onChange: setDoc,
|
|
143
|
+
}),
|
|
144
|
+
[],
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
return editor.input(ref.current);
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div>
|
|
153
|
+
<div>
|
|
154
|
+
<button
|
|
155
|
+
onClick={() => {
|
|
156
|
+
editor.apply(ToggleFormat, "bold");
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
bold
|
|
160
|
+
</button>
|
|
161
|
+
<button
|
|
162
|
+
onClick={() => {
|
|
163
|
+
editor.apply(ToggleFormat, "italic");
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
italic
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
<div
|
|
170
|
+
ref={ref}
|
|
171
|
+
style={{
|
|
172
|
+
backgroundColor: "white",
|
|
173
|
+
border: "solid 1px darkgray",
|
|
174
|
+
padding: 8,
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
{doc.children.map((r, i) => (
|
|
178
|
+
<div key={i}>
|
|
179
|
+
{r.map((n, j) => (
|
|
180
|
+
<span
|
|
181
|
+
key={j}
|
|
182
|
+
style={{
|
|
183
|
+
fontWeight: n.bold ? "bold" : undefined,
|
|
184
|
+
fontStyle: n.italic ? "italic" : undefined,
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
{n.text || <br />}
|
|
188
|
+
</span>
|
|
189
|
+
))}
|
|
190
|
+
</div>
|
|
191
|
+
))}
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Other examples
|
|
199
|
+
|
|
200
|
+
- React ([Demo](https://lofcz.github.io/edix/react), [Source](./examples/react))
|
|
201
|
+
- Vue ([Demo](https://lofcz.github.io/edix/vue), [Source](./examples/vue))
|
|
202
|
+
- Svelte ([Demo](https://lofcz.github.io/edix/svelte), [Source](./examples/svelte))
|
|
203
|
+
- Solid ([Demo](https://lofcz.github.io/edix/solid), [Source](./examples/solid))
|
|
204
|
+
- Angular ([Demo](https://lofcz.github.io/edix/angular), [Source](./examples/angular))
|
|
205
|
+
- Preact ([Demo](https://lofcz.github.io/edix/preact), [Source](./examples/preact))
|
|
206
|
+
- Vanilla ([Demo](https://lofcz.github.io/edix/vanilla), [Source](./examples/vanilla))
|
|
207
|
+
|
|
208
|
+
...and more! Contribution welcome!
|
|
209
|
+
|
|
210
|
+
## Documentation
|
|
211
|
+
|
|
212
|
+
- [API reference](./docs/API.md)
|
|
213
|
+
- [Storybook examples](./stories) for more usages
|
|
214
|
+
|
|
215
|
+
## Contribute
|
|
216
|
+
|
|
217
|
+
All contributions are welcome.
|
|
218
|
+
If you find a problem, feel free to create an [issue](https://github.com/lofcz/edix/issues) or a [PR](https://github.com/lofcz/edix/pulls). If you have a question, ask in [discussions](https://github.com/lofcz/edix/discussions).
|
|
219
|
+
|
|
220
|
+
### Making a Pull Request
|
|
221
|
+
|
|
222
|
+
1. Fork this repo.
|
|
223
|
+
2. Run `npm install`.
|
|
224
|
+
3. Commit your fix.
|
|
225
|
+
4. Make a PR and confirm all the CI checks passed.
|
|
226
|
+
|
|
227
|
+
## Inspirations
|
|
228
|
+
|
|
229
|
+
- [ProseMirror](https://prosemirror.net/)
|
|
230
|
+
- [Slate](https://github.com/ianstormtaylor/slate)
|
|
231
|
+
- [Lexical](https://github.com/facebook/lexical)
|
|
232
|
+
- [Quill](https://github.com/slab/quill)
|
|
233
|
+
- [Tiptap](https://github.com/ueberdosis/tiptap)
|
|
234
|
+
- [Draft.js](https://github.com/facebookarchive/draft-js)
|
|
235
|
+
- [rich-textarea](https://github.com/inokawa/rich-textarea) (my early project)
|
|
236
|
+
- [use-editable](https://github.com/FormidableLabs/use-editable)
|
|
237
|
+
- [@react-libraries/markdown-editor](https://github.com/ReactLibraries/markdown-editor)
|
|
238
|
+
- [VS Code](https://github.com/microsoft/vscode)
|
|
239
|
+
- [Language Server Protocol](https://github.com/microsoft/language-server-protocol)
|
|
240
|
+
- [urql](https://github.com/urql-graphql/urql)
|
|
241
|
+
- [TanStack DB](https://github.com/tanstack/db)
|
|
242
|
+
- [Hono](https://github.com/honojs/hono)
|
|
243
|
+
- [Textbus](https://github.com/textbus/textbus)
|
|
244
|
+
- [vistree](https://github.com/mizchi/vistree)
|
|
245
|
+
- Proposed [EditContext API](https://github.com/w3c/edit-context)
|
|
246
|
+
- Proposed [Richer Text Fields](https://open-ui.org/components/richer-text-fields.explainer/) in [Open UI](https://open-ui.org/)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Editor } from "./editor.js";
|
|
2
|
+
import type { DocNode, InferNode, Position, PositionRange, TextNode } from "./doc/types.js";
|
|
3
|
+
export type EditorCommand<A extends unknown[], T extends DocNode> = (this: Editor<T>, ...args: A) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Delete content in the selection or specified range.
|
|
6
|
+
*/
|
|
7
|
+
export declare function Delete(this: Editor, range?: PositionRange): void;
|
|
8
|
+
/**
|
|
9
|
+
* Insert text at the caret or specified position.
|
|
10
|
+
*/
|
|
11
|
+
export declare function InsertText(this: Editor, text: string, position?: Position): void;
|
|
12
|
+
/**
|
|
13
|
+
* Insert node at the caret or specified position.
|
|
14
|
+
*/
|
|
15
|
+
export declare function InsertNode<T extends DocNode>(this: Editor<T>, node: Exclude<InferNode<T>, TextNode>, position?: Position): void;
|
|
16
|
+
/**
|
|
17
|
+
* Insert multiple inline nodes as a single line fragment in one transaction.
|
|
18
|
+
* When `moveCaret` is true (default), the caret moves to the end of the
|
|
19
|
+
* inserted content.
|
|
20
|
+
*/
|
|
21
|
+
export declare function InsertNodes<T extends DocNode>(this: Editor<T>, nodes: InferNode<T>[], position?: Position, moveCaret?: boolean): void;
|
|
22
|
+
/**
|
|
23
|
+
* Replace text in the selection or specified range.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ReplaceText(this: Editor, text: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Replace all content in the editor.
|
|
28
|
+
*/
|
|
29
|
+
export declare function ReplaceAll(this: Editor, text: string): void;
|
|
30
|
+
type ToggleableKey<T> = {
|
|
31
|
+
[K in keyof T]-?: T[K] extends boolean | undefined ? K : never;
|
|
32
|
+
}[keyof T];
|
|
33
|
+
/**
|
|
34
|
+
* Format content in the selection or specified range.
|
|
35
|
+
*/
|
|
36
|
+
export declare function Format<T extends DocNode, N extends Omit<InferNode<T>, "text">, K extends Extract<keyof N, string>>(this: Editor<T>, key: K, value: N[K], range?: PositionRange): void;
|
|
37
|
+
/**
|
|
38
|
+
* Toggle formatting in the selection or specified range.
|
|
39
|
+
*/
|
|
40
|
+
export declare function ToggleFormat<T extends DocNode>(this: Editor<T>, key: Extract<ToggleableKey<Omit<InferNode<T>, "text">>, string>, range?: PositionRange): void;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Fragment, Position, SelectionSnapshot } from "./types.js";
|
|
2
|
+
declare const TYPE_DELETE = "delete";
|
|
3
|
+
type DeleteOperation = Readonly<{
|
|
4
|
+
type: typeof TYPE_DELETE;
|
|
5
|
+
start: Position;
|
|
6
|
+
end: Position;
|
|
7
|
+
}>;
|
|
8
|
+
declare const TYPE_INSERT_TEXT = "insert_text";
|
|
9
|
+
type InsertOperation = Readonly<{
|
|
10
|
+
type: typeof TYPE_INSERT_TEXT;
|
|
11
|
+
at: Position;
|
|
12
|
+
text: string;
|
|
13
|
+
}>;
|
|
14
|
+
declare const TYPE_INSERT_NODE = "insert_node";
|
|
15
|
+
type InsertNodeOperation = Readonly<{
|
|
16
|
+
type: typeof TYPE_INSERT_NODE;
|
|
17
|
+
at: Position;
|
|
18
|
+
fragment: Fragment;
|
|
19
|
+
}>;
|
|
20
|
+
declare const TYPE_SET_ATTR = "set_attr";
|
|
21
|
+
type SetAttrOperation = Readonly<{
|
|
22
|
+
type: typeof TYPE_SET_ATTR;
|
|
23
|
+
start: Position;
|
|
24
|
+
end: Position;
|
|
25
|
+
key: string;
|
|
26
|
+
value: unknown;
|
|
27
|
+
}>;
|
|
28
|
+
export type Operation = DeleteOperation | InsertOperation | InsertNodeOperation | SetAttrOperation;
|
|
29
|
+
export declare class Transaction {
|
|
30
|
+
private readonly _ops;
|
|
31
|
+
selection?: SelectionSnapshot;
|
|
32
|
+
constructor(ops?: readonly Operation[]);
|
|
33
|
+
get ops(): readonly Operation[];
|
|
34
|
+
insertText(start: Position, text: string): this;
|
|
35
|
+
insertFragment(start: Position, fragment: Fragment): this;
|
|
36
|
+
delete(start: Position, end: Position): this;
|
|
37
|
+
attr(start: Position, end: Position, key: string, value: unknown): this;
|
|
38
|
+
transform(position: Position): Position;
|
|
39
|
+
}
|
|
40
|
+
export declare const rebasePosition: (position: Position, op: Operation) => Position;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface TextNode {
|
|
2
|
+
readonly text: string;
|
|
3
|
+
}
|
|
4
|
+
export interface VoidNode {
|
|
5
|
+
}
|
|
6
|
+
export type InlineNode = TextNode | VoidNode;
|
|
7
|
+
export interface DocNode {
|
|
8
|
+
readonly children: readonly (readonly InlineNode[])[];
|
|
9
|
+
}
|
|
10
|
+
export type Fragment = DocNode["children"];
|
|
11
|
+
export type InferNode<T extends DocNode> = T["children"][number][number];
|
|
12
|
+
export type Path = readonly [number?];
|
|
13
|
+
export type Position = readonly [path: Path, offset: number];
|
|
14
|
+
export type PositionRange = readonly [start: Position, end: Position];
|
|
15
|
+
export type SelectionSnapshot = readonly [anchor: Position, focus: Position];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defaultIsBlockNode, defaultIsVoidNode } from "./default.js";
|
package/lib/editor.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import type { DocNode, SelectionSnapshot } from "./doc/types.js";
|
|
3
|
+
import type { EditorCommand } from "./commands.js";
|
|
4
|
+
import { Transaction } from "./doc/edit.js";
|
|
5
|
+
import type { EditorPlugin } from "./plugins/types.js";
|
|
6
|
+
import { type CopyExtension, type PasteExtension } from "./extensions/index.js";
|
|
7
|
+
import { type KeyboardHandler } from "./hotkey.js";
|
|
8
|
+
/**
|
|
9
|
+
* Options of {@link createEditor}.
|
|
10
|
+
*/
|
|
11
|
+
export interface EditorOptions<T extends DocNode, S extends StandardSchemaV1<T, T> | void = void> {
|
|
12
|
+
/**
|
|
13
|
+
* Optional [Standard Schema](https://github.com/standard-schema/standard-schema) to validate unsafe edits.
|
|
14
|
+
*/
|
|
15
|
+
schema?: S;
|
|
16
|
+
/**
|
|
17
|
+
* Initial document.
|
|
18
|
+
*/
|
|
19
|
+
doc: T;
|
|
20
|
+
/**
|
|
21
|
+
* The state editable or not.
|
|
22
|
+
*/
|
|
23
|
+
readonly?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* TODO
|
|
26
|
+
*/
|
|
27
|
+
plugins?: EditorPlugin[];
|
|
28
|
+
/**
|
|
29
|
+
* Functions to handle keyboard events.
|
|
30
|
+
*
|
|
31
|
+
* Return `true` if you want to stop propagation.
|
|
32
|
+
*/
|
|
33
|
+
keyboard?: KeyboardHandler[];
|
|
34
|
+
/**
|
|
35
|
+
* Functions to handle copy events
|
|
36
|
+
* @default [plainCopy()]
|
|
37
|
+
*/
|
|
38
|
+
copy?: [CopyExtension, ...rest: CopyExtension[]];
|
|
39
|
+
/**
|
|
40
|
+
* Functions to handle paste / drop events
|
|
41
|
+
* @default [plainPaste()]
|
|
42
|
+
*/
|
|
43
|
+
paste?: [PasteExtension, ...rest: PasteExtension[]];
|
|
44
|
+
/**
|
|
45
|
+
* TODO
|
|
46
|
+
*/
|
|
47
|
+
isBlock?: (node: HTMLElement) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Automatically scroll the mounted element to keep the caret visible
|
|
50
|
+
* after document changes. Scroll is coalesced via rAF for zero
|
|
51
|
+
* synchronous layout cost during input handling.
|
|
52
|
+
*/
|
|
53
|
+
autoScroll?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Callback invoked when document changes.
|
|
56
|
+
*/
|
|
57
|
+
onChange: (doc: T) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Callback invoked when errors happen.
|
|
60
|
+
*
|
|
61
|
+
* @default console.error
|
|
62
|
+
*/
|
|
63
|
+
onError?: (message: string) => void;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The editor instance.
|
|
67
|
+
*/
|
|
68
|
+
export interface Editor<T extends DocNode = DocNode> {
|
|
69
|
+
readonly doc: T;
|
|
70
|
+
/**
|
|
71
|
+
* Whether the document is empty (no text content, no void nodes).
|
|
72
|
+
* Recomputed once per commit — O(1) read.
|
|
73
|
+
*/
|
|
74
|
+
readonly isEmpty: boolean;
|
|
75
|
+
selection: SelectionSnapshot;
|
|
76
|
+
/**
|
|
77
|
+
* The getter/setter for the editor's read-only state.
|
|
78
|
+
* `true` to read-only. `false` to editable.
|
|
79
|
+
*/
|
|
80
|
+
readonly: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Enable/disable auto-scroll after document changes.
|
|
83
|
+
*/
|
|
84
|
+
autoScroll: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Dispatches editing operations.
|
|
87
|
+
* @param tr {@link Transaction} or {@link EditorCommand}
|
|
88
|
+
* @param args arguments of {@link EditorCommand}
|
|
89
|
+
* @param immediate If true, flushes queued operations immediately.
|
|
90
|
+
*/
|
|
91
|
+
apply(tr: Transaction, immediate?: boolean): this;
|
|
92
|
+
apply<A extends unknown[]>(fn: EditorCommand<A, T>, ...args: A): this;
|
|
93
|
+
/**
|
|
94
|
+
* A function to make DOM editable.
|
|
95
|
+
* @returns A function to stop subscribing DOM changes and restores previous DOM state.
|
|
96
|
+
*/
|
|
97
|
+
input: (element: HTMLElement) => () => void;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A function to initialize {@link Editor}.
|
|
101
|
+
*/
|
|
102
|
+
export declare const createEditor: <T extends DocNode, S extends StandardSchemaV1<T, T> | void = void>({ doc, readonly, schema, plugins, keyboard, copy: copyExtensions, paste: pasteExtensions, isBlock, autoScroll: _autoScroll, onChange, onError, }: EditorOptions<T, S>) => Editor<T>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DocNode, InferNode } from "../../doc/types.js";
|
|
2
|
+
import type { CopyExtension } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* An extension to handle copying to plain text.
|
|
5
|
+
*/
|
|
6
|
+
export declare const plainCopy: <T extends DocNode>(serializer?: (node: InferNode<T>) => string) => CopyExtension;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { InlineNode } from "../../doc/types.js";
|
|
2
|
+
import type { PasteExtension } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* An extension to handle pasting / dropping from File.
|
|
5
|
+
*/
|
|
6
|
+
export declare const filePaste: (handlerByMime: Record<string, (file: File) => InlineNode>) => PasteExtension;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DocNode, InferNode, TextNode } from "../../doc/types.js";
|
|
2
|
+
import type { PasteExtension } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* An extension to handle pasting / dropping from HTML.
|
|
5
|
+
*/
|
|
6
|
+
export declare const htmlPaste: <T extends DocNode>(serializeText: (t: string) => Extract<InferNode<T>, TextNode>, serializers?: ((node: HTMLElement) => Exclude<InferNode<T>, TextNode> | void)[]) => PasteExtension;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/history.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/hotkey.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type KeyboardHandler = (keyboard: KeyboardEvent) => boolean | void;
|
|
2
|
+
/**
|
|
3
|
+
* TODO
|
|
4
|
+
*/
|
|
5
|
+
export declare const hotkey: (key: string, cb: (e: KeyboardEvent) => void, { mod, shift, alt, }?: {
|
|
6
|
+
mod?: boolean;
|
|
7
|
+
shift?: boolean;
|
|
8
|
+
alt?: boolean;
|
|
9
|
+
}) => KeyboardHandler;
|