@open-wysiwyg-editor/react 0.0.0-stage → 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 alhassan-ahmed and open-wysiwyg-editor contributors
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 CHANGED
@@ -1,3 +1,160 @@
1
- # Temporary Holding Version
1
+ <p align="center"><a href="https://alhassan73.github.io/open-wysiwyg-editor/"><img src="https://raw.githubusercontent.com/alhassan73/open-wysiwyg-editor/main/.github/assets/banner.png" alt="Open WYSIWYG Editor — accessible, RTL-first rich text editor for every framework" width="100%"></a></p>
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ # @open-wysiwyg-editor/react
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@open-wysiwyg-editor/react)](https://www.npmjs.com/package/@open-wysiwyg-editor/react)
6
+ [![license](https://img.shields.io/npm/l/@open-wysiwyg-editor/react)](https://github.com/alhassan73/open-wysiwyg-editor/blob/main/LICENSE)
7
+
8
+ **[Live demo →](https://alhassan73.github.io/open-wysiwyg-editor/)** · [GitHub](https://github.com/alhassan73/open-wysiwyg-editor) · [npm](https://www.npmjs.com/package/@open-wysiwyg-editor/react)
9
+
10
+ React component for [open-wysiwyg-editor](https://github.com/alhassan73/open-wysiwyg-editor), an accessible (WCAG 2.2 AA), RTL-first, CSP-safe rich text editor. It gives you `<RichTextEditor>` (toolbar, dialogs, status bar) and a `useEditor()` hook for custom UIs. Works with React 18+, Vite, Remix and Gatsby. For Next.js use [`@open-wysiwyg-editor/next`](https://www.npmjs.com/package/@open-wysiwyg-editor/next).
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @open-wysiwyg-editor/react
16
+ ```
17
+
18
+ One install is enough: this package re-exports the whole core API (`createEditor`, `StarterKit`, `getUI`, the types…), and ships the stylesheets.
19
+
20
+ ## Usage
21
+
22
+ Import the styles once, in your entry file:
23
+
24
+ ```tsx
25
+ import "@open-wysiwyg-editor/react/style.css";
26
+ ```
27
+
28
+ ### Basic
29
+
30
+ ```tsx
31
+ import { RichTextEditor } from "@open-wysiwyg-editor/react";
32
+
33
+ export function Notes() {
34
+ return <RichTextEditor defaultValue="<p>Hello</p>" onChange={(html) => console.log(html)} />;
35
+ }
36
+ ```
37
+
38
+ ### Controlled
39
+
40
+ ```tsx
41
+ import { useState } from "react";
42
+ import { RichTextEditor } from "@open-wysiwyg-editor/react";
43
+
44
+ export function Article() {
45
+ const [html, setHtml] = useState("<p>Hello</p>");
46
+ return <RichTextEditor value={html} onChange={setHtml} />;
47
+ }
48
+ ```
49
+
50
+ The editor loads `value` only when it differs from the HTML it last produced, so typing never resets the cursor.
51
+
52
+ ### In a form
53
+
54
+ `name` keeps a hidden `<input>` in sync with the HTML, so the editor works in plain form posts and React 19 form actions:
55
+
56
+ ```tsx
57
+ <form action={save}>
58
+ <RichTextEditor name="body" defaultValue={post.body} />
59
+ <button type="submit">Save</button>
60
+ </form>
61
+ ```
62
+
63
+ ### Custom toolbar
64
+
65
+ `ui` takes the toolbar layout (item names and `"|"` separators), custom items, status bar, theme and more:
66
+
67
+ ```tsx
68
+ <RichTextEditor
69
+ ui={{
70
+ toolbar: ["bold", "italic", "link", "|", "bulletList", "orderedList", "|", "clear"],
71
+ items: {
72
+ clear: { label: "Clear", text: "Clear", run: (editor) => editor.clearContent() },
73
+ },
74
+ statusbar: false,
75
+ }}
76
+ />
77
+ ```
78
+
79
+ ### Headless with `useEditor`
80
+
81
+ Build your own UI. `ui: false` turns the built-in toolbar off:
82
+
83
+ ```tsx
84
+ import { useEditor } from "@open-wysiwyg-editor/react";
85
+
86
+ export function Minimal() {
87
+ const { ref, editor } = useEditor({ ui: false, content: "<p>Hello</p>" });
88
+ return (
89
+ <>
90
+ <button onClick={() => editor?.chain().focus().toggleBold().run()}>Bold</button>
91
+ <div ref={ref} />
92
+ </>
93
+ );
94
+ }
95
+ ```
96
+
97
+ `editor` is `null` before mount and during server rendering.
98
+
99
+ ### Arabic / RTL
100
+
101
+ ```tsx
102
+ <RichTextEditor language="ar" dir="rtl" contentLang="ar" />
103
+ ```
104
+
105
+ `language` sets the UI language (built in: `en`, `ar`). Without `dir`, each block follows its own text direction.
106
+
107
+ ## Props
108
+
109
+ `<RichTextEditor>` accepts every [`createEditor` option](https://github.com/alhassan73/open-wysiwyg-editor#readme) except `element` and `content`, plus these:
110
+
111
+ | Prop | Type | Default | Description |
112
+ | --- | --- | --- | --- |
113
+ | `value` | `string` | none | Controlled HTML. |
114
+ | `defaultValue` | `string \| JSON` | none | Initial content (HTML or ProseMirror JSON) for an uncontrolled editor. |
115
+ | `onChange` | `(html: string, editor: Editor) => void` | none | Called with the new HTML on every change. |
116
+ | `name` | `string` | none | Form field name. Adds a hidden `<input>` that holds the HTML. |
117
+ | `className` | `string` | none | Class on the wrapper `<div>`. |
118
+ | `id` | `string` | none | `id` on the wrapper `<div>`. |
119
+ | `extensions` | `AnyExtension[]` | `[StarterKit]` | Feature set. Read at mount. |
120
+ | `ui` | `UIOptions \| false` | built-in UI | Toolbar, items, status bar, theme, shortcuts. `false` = headless. Read at mount. |
121
+ | `editable` | `boolean` | `true` | Allow editing. Updates live. |
122
+ | `autofocus` | `boolean \| "start" \| "end"` | `false` | Focus on mount. |
123
+ | `placeholder` | `string \| false` | none | Text shown when empty. Updates live. |
124
+ | `ariaLabel` | `string` | `"Rich text editor"` | Accessible name of the editing area. Updates live. |
125
+ | `ariaLabelledBy` | `string` | none | `id` of an element that labels the editor. Updates live. |
126
+ | `ariaDescribedBy` | `string` | none | `id` of an element that describes the editor. Updates live. |
127
+ | `language` | `string` | `<html lang>`, then first language | UI language code (`"en"`, `"ar"`). Read at mount. |
128
+ | `languages` | `EditorLanguage[]` | `en`, `ar` | Languages available to the UI. Read at mount. |
129
+ | `labels` | `Partial<Labels>` | none | Override UI strings. Read at mount. |
130
+ | `dir` | `"ltr" \| "rtl" \| "auto"` | auto per block | Base direction of the content. Updates live. |
131
+ | `contentLang` | `string` | none | `lang` of the editing area (spellcheck, screen readers). Updates live. |
132
+ | `urlPolicy` | `UrlPolicy` | http, https, mailto, tel; relative URLs allowed | Allowed link and image URLs. Read at mount. |
133
+ | `inputRules` | `boolean` | `true` | Markdown-style typing shortcuts (`# `, `* `, `**bold**`). Read at mount. |
134
+ | `onCreate` `onUpdate` `onSelectionUpdate` `onFocus` `onBlur` `onDestroy` `onContentError` | callbacks | none | Editor events. Always call the latest function. |
135
+
136
+ Options marked "Read at mount" are applied once. Give the component a new `key` to apply a change.
137
+
138
+ `useEditor(options)` takes the same options (with `content` instead of `value`) and returns `{ ref, editor }`.
139
+
140
+ ## Styling
141
+
142
+ ```tsx
143
+ import "@open-wysiwyg-editor/react/style.css"; // editor UI + content (or style.min.css)
144
+ ```
145
+
146
+ To show saved HTML outside the editor, use `@open-wysiwyg-editor/react/content.css` (or `content.min.css`) and put the HTML in an element with the class `owe-content-root`.
147
+
148
+ ## Server-side rendering
149
+
150
+ The component is SSR-safe. On the server it renders an empty container and the hidden input; the editor starts in the browser after hydration. `useEditor` returns `editor: null` until then. `createEditor` itself needs a DOM, so call it only in effects.
151
+
152
+ ## Documentation
153
+
154
+ - Full documentation, extensions and API: [GitHub README](https://github.com/alhassan73/open-wysiwyg-editor#readme)
155
+ - Live demo and website: [alhassan73.github.io/open-wysiwyg-editor](https://alhassan73.github.io/open-wysiwyg-editor/)
156
+ - Issues: [GitHub issues](https://github.com/alhassan73/open-wysiwyg-editor/issues)
157
+
158
+ ## License
159
+
160
+ MIT © alhassan-ahmed
@@ -0,0 +1,323 @@
1
+ /*! open-wysiwyg-editor | MIT License */
2
+ /*
3
+ * Design tokens. Everything lives in the `owe` cascade layer, so any unlayered host CSS wins
4
+ * without !important. Override tokens on .owe (or :root) to theme the editor.
5
+ * Contrast: text >= 4.5:1, borders/focus/states >= 3:1 against --owe-bg (WCAG 1.4.3 / 1.4.11).
6
+ *
7
+ * Tokens added in 1.0 (existing names keep working):
8
+ * --owe-radius card radius (24px) --owe-radius-sm inner controls (10px)
9
+ * --owe-radius-md menus (12px) --owe-radius-lg dialogs (16px)
10
+ * --owe-btn-size toolbar button (40px) --owe-pad-x/-y editing-area padding
11
+ * --owe-chrome toolbar / status bar surface
12
+ * --owe-field-bg inputs and source view --owe-popup-border menu / dialog edge
13
+ * --owe-body paragraph text --owe-text-2 secondary text (quotes)
14
+ * --owe-icon toolbar icon color --owe-caret caret color
15
+ * --owe-primary(-hover) solid fills: pressed buttons, primary buttons (white text, 4.8:1)
16
+ * --owe-gradient bottom edge of the card --owe-backdrop dialog backdrop
17
+ * --owe-accent is the TEXT/link/focus blue; --owe-primary is the FILL blue.
18
+ * Set --owe-font (e.g. "Almarai", system-ui) to change the typeface; no web fonts are bundled.
19
+ */
20
+ @layer owe {
21
+ .owe,
22
+ .owe-content-root {
23
+ --owe-font: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans Arabic", "Noto Sans", Tahoma, sans-serif;
24
+ --owe-font-mono: ui-monospace, "JetBrains Mono", "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
25
+ --owe-font-size: 1rem;
26
+ --owe-line-height: 1.65;
27
+ --owe-radius: 24px;
28
+ --owe-radius-sm: 10px;
29
+ --owe-radius-md: 12px;
30
+ --owe-radius-lg: 16px;
31
+ --owe-space: 8px;
32
+ --owe-target: 32px; /* >= 24px (WCAG 2.5.8); 32px is comfortable */
33
+ --owe-btn-size: 40px;
34
+ --owe-pad-x: clamp(16px, 4vw, 32px);
35
+ --owe-pad-y: 28px;
36
+ --owe-gradient: linear-gradient(90deg, #0066ff, #9644ff, #af47c0);
37
+
38
+ --owe-bg: #ffffff;
39
+ --owe-chrome: #fafbfc;
40
+ --owe-surface: #f4f6f8;
41
+ --owe-surface-2: #f1f3f5;
42
+ --owe-field-bg: #ffffff;
43
+ --owe-text: #101112;
44
+ --owe-body: #1b1f24;
45
+ --owe-text-2: #3d434a;
46
+ --owe-muted: #54595f; /* 7:1 on white */
47
+ --owe-icon: #3a3f45;
48
+ --owe-border: #8a929a; /* 3.2:1 on white */
49
+ --owe-border-subtle: #e3e6ea;
50
+ --owe-popup-border: #d5d9de;
51
+ --owe-accent: #0057d9; /* 6.2:1 on white */
52
+ --owe-on-accent: #ffffff;
53
+ --owe-primary: #0066ff;
54
+ --owe-primary-hover: #0057d9;
55
+ --owe-accent-soft: #d3e3fd;
56
+ --owe-focus: #0066ff;
57
+ --owe-caret: #0066ff;
58
+ --owe-danger: #b3261e;
59
+ --owe-warning: #8a5300;
60
+ --owe-success: #146c2e;
61
+ --owe-selection: rgb(0 102 255 / 0.22);
62
+ --owe-code-bg: #f4f6f8;
63
+ --owe-mark: #fff2a8;
64
+ --owe-shadow: 0 16px 40px rgb(16 17 18 / 0.16), 0 2px 8px rgb(16 17 18 / 0.08);
65
+ --owe-backdrop: rgb(16 17 18 / 0.45);
66
+ --owe-z-popover: 1000;
67
+
68
+ color-scheme: light;
69
+ }
70
+
71
+ @media (prefers-color-scheme: dark) {
72
+ .owe:not([data-theme="light"]),
73
+ .owe-content-root:not([data-theme="light"]) {
74
+ --owe-bg: #17191b;
75
+ --owe-chrome: #131517;
76
+ --owe-surface: #1e2124;
77
+ --owe-surface-2: #25282b;
78
+ --owe-field-bg: #101214;
79
+ --owe-text: #ffffff;
80
+ --owe-body: #dcdedf;
81
+ --owe-text-2: #c3c7cb;
82
+ --owe-muted: #a1a7ad;
83
+ --owe-icon: #d2d4d5;
84
+ --owe-border: #6b7279;
85
+ --owe-border-subtle: #25282b;
86
+ --owe-popup-border: #33383d;
87
+ --owe-accent: #4d94ff;
88
+ --owe-on-accent: #ffffff;
89
+ --owe-primary: #0066ff;
90
+ --owe-primary-hover: #0057d9;
91
+ --owe-accent-soft: #14315e;
92
+ --owe-focus: #4d94ff;
93
+ --owe-caret: #5b9dff;
94
+ --owe-danger: #f2b8b5;
95
+ --owe-warning: #f6c66b;
96
+ --owe-success: #8bd6a0;
97
+ --owe-selection: rgb(0 102 255 / 0.55);
98
+ --owe-code-bg: #0f1112;
99
+ --owe-mark: #4a35b8;
100
+ --owe-shadow: 0 16px 40px rgb(0 0 0 / 0.55), 0 2px 8px rgb(0 0 0 / 0.4);
101
+ --owe-backdrop: rgb(4 5 16 / 0.65);
102
+ color-scheme: dark;
103
+ }
104
+ }
105
+
106
+ .owe[data-theme="dark"],
107
+ .owe-content-root[data-theme="dark"] {
108
+ --owe-bg: #17191b;
109
+ --owe-chrome: #131517;
110
+ --owe-surface: #1e2124;
111
+ --owe-surface-2: #25282b;
112
+ --owe-field-bg: #101214;
113
+ --owe-text: #ffffff;
114
+ --owe-body: #dcdedf;
115
+ --owe-text-2: #c3c7cb;
116
+ --owe-muted: #a1a7ad;
117
+ --owe-icon: #d2d4d5;
118
+ --owe-border: #6b7279;
119
+ --owe-border-subtle: #25282b;
120
+ --owe-popup-border: #33383d;
121
+ --owe-accent: #4d94ff;
122
+ --owe-on-accent: #ffffff;
123
+ --owe-primary: #0066ff;
124
+ --owe-primary-hover: #0057d9;
125
+ --owe-accent-soft: #14315e;
126
+ --owe-focus: #4d94ff;
127
+ --owe-caret: #5b9dff;
128
+ --owe-danger: #f2b8b5;
129
+ --owe-warning: #f6c66b;
130
+ --owe-success: #8bd6a0;
131
+ --owe-selection: rgb(0 102 255 / 0.55);
132
+ --owe-code-bg: #0f1112;
133
+ --owe-mark: #4a35b8;
134
+ --owe-shadow: 0 16px 40px rgb(0 0 0 / 0.55), 0 2px 8px rgb(0 0 0 / 0.4);
135
+ --owe-backdrop: rgb(4 5 16 / 0.65);
136
+ color-scheme: dark;
137
+ }
138
+
139
+ @media (prefers-contrast: more) {
140
+ .owe {
141
+ --owe-border: currentColor;
142
+ --owe-border-subtle: var(--owe-border);
143
+ --owe-popup-border: var(--owe-border);
144
+ --owe-muted: var(--owe-text);
145
+ --owe-text-2: var(--owe-text);
146
+ }
147
+ }
148
+ }
149
+
150
+ /*
151
+ * Content styles — shared by the editing area (.owe-content) and published pages
152
+ * (wrap saved HTML in <div class="owe-content-root">), so what authors see is what readers get.
153
+ */
154
+ @layer owe {
155
+ :is(.owe-content, .owe-content-root) {
156
+ font-family: var(--owe-font);
157
+ font-size: var(--owe-font-size);
158
+ line-height: var(--owe-line-height);
159
+ color: var(--owe-body);
160
+ overflow-wrap: break-word;
161
+ }
162
+
163
+ :is(.owe-content, .owe-content-root) > :first-child {
164
+ margin-block-start: 0;
165
+ }
166
+
167
+ :is(.owe-content, .owe-content-root) :is(p, ul, ol, blockquote, pre, table, figure, hr) {
168
+ margin-block: 0 0.85em;
169
+ }
170
+
171
+ :is(.owe-content, .owe-content-root) :is(h1, h2, h3, h4, h5, h6) {
172
+ line-height: 1.2;
173
+ margin-block: 1.3em 0.5em;
174
+ font-weight: 700;
175
+ letter-spacing: -0.02em;
176
+ color: var(--owe-text);
177
+ }
178
+ /* Negative tracking breaks Arabic joining: reset for RTL / Arabic-script content. */
179
+ :is(.owe-content, .owe-content-root) :is(h1, h2, h3, h4, h5, h6):is([dir="rtl"], :lang(ar), :lang(fa), :lang(ur), :lang(he)),
180
+ :is([dir="rtl"], :lang(ar), :lang(fa), :lang(ur), :lang(he)) :is(.owe-content, .owe-content-root) :is(h1, h2, h3, h4, h5, h6),
181
+ :is(.owe-content, .owe-content-root):is([dir="rtl"], :lang(ar), :lang(fa), :lang(ur), :lang(he)) :is(h1, h2, h3, h4, h5, h6) {
182
+ letter-spacing: 0;
183
+ }
184
+ :is(.owe-content, .owe-content-root) h1 { font-size: 2.1em; }
185
+ :is(.owe-content, .owe-content-root) h2 { font-size: 1.5em; }
186
+ :is(.owe-content, .owe-content-root) h3 { font-size: 1.25em; }
187
+ :is(.owe-content, .owe-content-root) h4 { font-size: 1.1em; }
188
+ :is(.owe-content, .owe-content-root) h5 { font-size: 1em; }
189
+ :is(.owe-content, .owe-content-root) h6 { font-size: 0.9em; }
190
+
191
+ :is(.owe-content, .owe-content-root) a {
192
+ color: var(--owe-accent);
193
+ text-decoration: underline; /* never colour alone (WCAG 1.4.1) */
194
+ text-underline-offset: 0.15em;
195
+ }
196
+
197
+ :is(.owe-content, .owe-content-root) :is(ul, ol) {
198
+ padding-inline-start: 1.6em;
199
+ }
200
+ :is(.owe-content, .owe-content-root) li > :is(p, ul, ol) {
201
+ margin-block: 0.15em;
202
+ }
203
+
204
+ :is(.owe-content, .owe-content-root) ul[data-type="taskList"] {
205
+ list-style: none;
206
+ padding-inline-start: 0.25em;
207
+ }
208
+ :is(.owe-content, .owe-content-root) li[data-type="taskItem"] {
209
+ display: flex;
210
+ align-items: flex-start;
211
+ gap: 0.5em;
212
+ }
213
+ :is(.owe-content, .owe-content-root) li[data-type="taskItem"] > input,
214
+ .owe-task-control > input {
215
+ inline-size: 1.1em;
216
+ block-size: 1.1em;
217
+ margin-block-start: 0.3em;
218
+ accent-color: var(--owe-primary);
219
+ flex: none;
220
+ }
221
+ :is(.owe-content, .owe-content-root) li[data-type="taskItem"] > div {
222
+ flex: 1;
223
+ min-inline-size: 0;
224
+ }
225
+ :is(.owe-content, .owe-content-root) li[data-checked="true"] > div {
226
+ text-decoration: line-through;
227
+ color: var(--owe-muted);
228
+ }
229
+
230
+ :is(.owe-content, .owe-content-root) blockquote {
231
+ margin-inline: 0;
232
+ padding-inline-start: 1em;
233
+ border-inline-start: 3px solid var(--owe-accent);
234
+ border-start-start-radius: 2px;
235
+ border-end-start-radius: 2px;
236
+ color: var(--owe-text-2);
237
+ }
238
+
239
+ :is(.owe-content, .owe-content-root) code {
240
+ font-family: var(--owe-font-mono);
241
+ font-size: 0.9em;
242
+ background: var(--owe-code-bg);
243
+ border: 1px solid var(--owe-border-subtle);
244
+ border-radius: 6px;
245
+ padding: 0.1em 0.35em;
246
+ }
247
+ :is(.owe-content, .owe-content-root) pre {
248
+ font-family: var(--owe-font-mono);
249
+ background: var(--owe-code-bg);
250
+ border: 1px solid var(--owe-border-subtle);
251
+ border-radius: var(--owe-radius-md);
252
+ padding: 1em 1.25em;
253
+ overflow-x: auto;
254
+ direction: ltr; /* code is left-to-right even in RTL documents */
255
+ text-align: start;
256
+ tab-size: 2;
257
+ }
258
+ :is(.owe-content, .owe-content-root) pre code {
259
+ background: none;
260
+ border: 0;
261
+ padding: 0;
262
+ font-size: 0.9em;
263
+ }
264
+
265
+ :is(.owe-content, .owe-content-root) hr {
266
+ border: none;
267
+ border-block-start: 2px solid var(--owe-border-subtle);
268
+ margin-block: 1.5em;
269
+ }
270
+
271
+ :is(.owe-content, .owe-content-root) img {
272
+ max-inline-size: 100%;
273
+ block-size: auto;
274
+ display: block;
275
+ }
276
+ :is(.owe-content, .owe-content-root) figure {
277
+ margin-inline: 0;
278
+ }
279
+ :is(.owe-content, .owe-content-root) figcaption {
280
+ color: var(--owe-muted);
281
+ font-size: 0.9em;
282
+ margin-block-start: 0.4em;
283
+ }
284
+
285
+ :is(.owe-content, .owe-content-root) table {
286
+ border-collapse: collapse;
287
+ inline-size: 100%;
288
+ table-layout: fixed;
289
+ }
290
+ :is(.owe-content, .owe-content-root) caption {
291
+ caption-side: top;
292
+ text-align: start;
293
+ font-weight: 600;
294
+ padding-block-end: 0.4em;
295
+ }
296
+ :is(.owe-content, .owe-content-root) :is(td, th) {
297
+ border: 1px solid var(--owe-border-subtle);
298
+ padding: 0.55em 0.8em;
299
+ vertical-align: top;
300
+ text-align: start;
301
+ min-inline-size: 3em;
302
+ }
303
+ :is(.owe-content, .owe-content-root) th {
304
+ background: var(--owe-surface);
305
+ color: var(--owe-text);
306
+ font-weight: 700;
307
+ }
308
+ :is(.owe-content, .owe-content-root) :is(td, th) > :last-child {
309
+ margin-block-end: 0;
310
+ }
311
+
312
+ :is(.owe-content, .owe-content-root) mark {
313
+ background-color: var(--owe-mark);
314
+ color: inherit;
315
+ border-radius: 0.2em;
316
+ padding-inline: 0.15em;
317
+ }
318
+
319
+ /* Alignment classes (used by the editor always, and by output when TextAlign output: "class"). */
320
+ .owe-align-center { text-align: center; }
321
+ .owe-align-end { text-align: end; }
322
+ .owe-align-justify { text-align: justify; }
323
+ }
@@ -0,0 +1,2 @@
1
+ /*! open-wysiwyg-editor | MIT License */
2
+ /*! open-wysiwyg-editor | MIT License */ @layer owe{.owe,.owe-content-root{--owe-font:system-ui,-apple-system,"Segoe UI",Roboto,"Noto Sans Arabic","Noto Sans",Tahoma,sans-serif;--owe-font-mono:ui-monospace,"JetBrains Mono","SFMono-Regular",Menlo,Consolas,"Liberation Mono",monospace;--owe-font-size:1rem;--owe-line-height:1.65;--owe-radius:24px;--owe-radius-sm:10px;--owe-radius-md:12px;--owe-radius-lg:16px;--owe-space:8px;--owe-target:32px;--owe-btn-size:40px;--owe-pad-x:clamp(16px,4vw,32px);--owe-pad-y:28px;--owe-gradient:linear-gradient(90deg,#0066ff,#9644ff,#af47c0);--owe-bg:#ffffff;--owe-chrome:#fafbfc;--owe-surface:#f4f6f8;--owe-surface-2:#f1f3f5;--owe-field-bg:#ffffff;--owe-text:#101112;--owe-body:#1b1f24;--owe-text-2:#3d434a;--owe-muted:#54595f;--owe-icon:#3a3f45;--owe-border:#8a929a;--owe-border-subtle:#e3e6ea;--owe-popup-border:#d5d9de;--owe-accent:#0057d9;--owe-on-accent:#ffffff;--owe-primary:#0066ff;--owe-primary-hover:#0057d9;--owe-accent-soft:#d3e3fd;--owe-focus:#0066ff;--owe-caret:#0066ff;--owe-danger:#b3261e;--owe-warning:#8a5300;--owe-success:#146c2e;--owe-selection:rgb(0 102 255 / 0.22);--owe-code-bg:#f4f6f8;--owe-mark:#fff2a8;--owe-shadow:0 16px 40px rgb(16 17 18 / 0.16),0 2px 8px rgb(16 17 18 / 0.08);--owe-backdrop:rgb(16 17 18 / 0.45);--owe-z-popover:1000;color-scheme:light}@media (prefers-color-scheme:dark){.owe:not([data-theme="light"]),.owe-content-root:not([data-theme="light"]){--owe-bg:#17191b;--owe-chrome:#131517;--owe-surface:#1e2124;--owe-surface-2:#25282b;--owe-field-bg:#101214;--owe-text:#ffffff;--owe-body:#dcdedf;--owe-text-2:#c3c7cb;--owe-muted:#a1a7ad;--owe-icon:#d2d4d5;--owe-border:#6b7279;--owe-border-subtle:#25282b;--owe-popup-border:#33383d;--owe-accent:#4d94ff;--owe-on-accent:#ffffff;--owe-primary:#0066ff;--owe-primary-hover:#0057d9;--owe-accent-soft:#14315e;--owe-focus:#4d94ff;--owe-caret:#5b9dff;--owe-danger:#f2b8b5;--owe-warning:#f6c66b;--owe-success:#8bd6a0;--owe-selection:rgb(0 102 255 / 0.55);--owe-code-bg:#0f1112;--owe-mark:#4a35b8;--owe-shadow:0 16px 40px rgb(0 0 0 / 0.55),0 2px 8px rgb(0 0 0 / 0.4);--owe-backdrop:rgb(4 5 16 / 0.65);color-scheme:dark}}.owe[data-theme="dark"],.owe-content-root[data-theme="dark"]{--owe-bg:#17191b;--owe-chrome:#131517;--owe-surface:#1e2124;--owe-surface-2:#25282b;--owe-field-bg:#101214;--owe-text:#ffffff;--owe-body:#dcdedf;--owe-text-2:#c3c7cb;--owe-muted:#a1a7ad;--owe-icon:#d2d4d5;--owe-border:#6b7279;--owe-border-subtle:#25282b;--owe-popup-border:#33383d;--owe-accent:#4d94ff;--owe-on-accent:#ffffff;--owe-primary:#0066ff;--owe-primary-hover:#0057d9;--owe-accent-soft:#14315e;--owe-focus:#4d94ff;--owe-caret:#5b9dff;--owe-danger:#f2b8b5;--owe-warning:#f6c66b;--owe-success:#8bd6a0;--owe-selection:rgb(0 102 255 / 0.55);--owe-code-bg:#0f1112;--owe-mark:#4a35b8;--owe-shadow:0 16px 40px rgb(0 0 0 / 0.55),0 2px 8px rgb(0 0 0 / 0.4);--owe-backdrop:rgb(4 5 16 / 0.65);color-scheme:dark}@media (prefers-contrast:more){.owe{--owe-border:currentColor;--owe-border-subtle:var(--owe-border);--owe-popup-border:var(--owe-border);--owe-muted:var(--owe-text);--owe-text-2:var(--owe-text)}}}@layer owe{:is(.owe-content,.owe-content-root){font-family:var(--owe-font);font-size:var(--owe-font-size);line-height:var(--owe-line-height);color:var(--owe-body);overflow-wrap:break-word}:is(.owe-content,.owe-content-root)>:first-child{margin-block-start:0}:is(.owe-content,.owe-content-root) :is(p,ul,ol,blockquote,pre,table,figure,hr){margin-block:0 0.85em}:is(.owe-content,.owe-content-root) :is(h1,h2,h3,h4,h5,h6){line-height:1.2;margin-block:1.3em 0.5em;font-weight:700;letter-spacing:-0.02em;color:var(--owe-text)}:is(.owe-content,.owe-content-root) :is(h1,h2,h3,h4,h5,h6):is([dir="rtl"],:lang(ar),:lang(fa),:lang(ur),:lang(he)),:is([dir="rtl"],:lang(ar),:lang(fa),:lang(ur),:lang(he)) :is(.owe-content,.owe-content-root) :is(h1,h2,h3,h4,h5,h6),:is(.owe-content,.owe-content-root):is([dir="rtl"],:lang(ar),:lang(fa),:lang(ur),:lang(he)) :is(h1,h2,h3,h4,h5,h6){letter-spacing:0}:is(.owe-content,.owe-content-root) h1{font-size:2.1em}:is(.owe-content,.owe-content-root) h2{font-size:1.5em}:is(.owe-content,.owe-content-root) h3{font-size:1.25em}:is(.owe-content,.owe-content-root) h4{font-size:1.1em}:is(.owe-content,.owe-content-root) h5{font-size:1em}:is(.owe-content,.owe-content-root) h6{font-size:0.9em}:is(.owe-content,.owe-content-root) a{color:var(--owe-accent);text-decoration:underline;text-underline-offset:0.15em}:is(.owe-content,.owe-content-root) :is(ul,ol){padding-inline-start:1.6em}:is(.owe-content,.owe-content-root) li>:is(p,ul,ol){margin-block:0.15em}:is(.owe-content,.owe-content-root) ul[data-type="taskList"]{list-style:none;padding-inline-start:0.25em}:is(.owe-content,.owe-content-root) li[data-type="taskItem"]{display:flex;align-items:flex-start;gap:0.5em}:is(.owe-content,.owe-content-root) li[data-type="taskItem"]>input,.owe-task-control>input{inline-size:1.1em;block-size:1.1em;margin-block-start:0.3em;accent-color:var(--owe-primary);flex:none}:is(.owe-content,.owe-content-root) li[data-type="taskItem"]>div{flex:1;min-inline-size:0}:is(.owe-content,.owe-content-root) li[data-checked="true"]>div{text-decoration:line-through;color:var(--owe-muted)}:is(.owe-content,.owe-content-root) blockquote{margin-inline:0;padding-inline-start:1em;border-inline-start:3px solid var(--owe-accent);border-start-start-radius:2px;border-end-start-radius:2px;color:var(--owe-text-2)}:is(.owe-content,.owe-content-root) code{font-family:var(--owe-font-mono);font-size:0.9em;background:var(--owe-code-bg);border:1px solid var(--owe-border-subtle);border-radius:6px;padding:0.1em 0.35em}:is(.owe-content,.owe-content-root) pre{font-family:var(--owe-font-mono);background:var(--owe-code-bg);border:1px solid var(--owe-border-subtle);border-radius:var(--owe-radius-md);padding:1em 1.25em;overflow-x:auto;direction:ltr;text-align:start;tab-size:2}:is(.owe-content,.owe-content-root) pre code{background:none;border:0;padding:0;font-size:0.9em}:is(.owe-content,.owe-content-root) hr{border:none;border-block-start:2px solid var(--owe-border-subtle);margin-block:1.5em}:is(.owe-content,.owe-content-root) img{max-inline-size:100%;block-size:auto;display:block}:is(.owe-content,.owe-content-root) figure{margin-inline:0}:is(.owe-content,.owe-content-root) figcaption{color:var(--owe-muted);font-size:0.9em;margin-block-start:0.4em}:is(.owe-content,.owe-content-root) table{border-collapse:collapse;inline-size:100%;table-layout:fixed}:is(.owe-content,.owe-content-root) caption{caption-side:top;text-align:start;font-weight:600;padding-block-end:0.4em}:is(.owe-content,.owe-content-root) :is(td,th){border:1px solid var(--owe-border-subtle);padding:0.55em 0.8em;vertical-align:top;text-align:start;min-inline-size:3em}:is(.owe-content,.owe-content-root) th{background:var(--owe-surface);color:var(--owe-text);font-weight:700}:is(.owe-content,.owe-content-root) :is(td,th)>:last-child{margin-block-end:0}:is(.owe-content,.owe-content-root) mark{background-color:var(--owe-mark);color:inherit;border-radius:0.2em;padding-inline:0.15em}.owe-align-center{text-align:center}.owe-align-end{text-align:end}.owe-align-justify{text-align:justify}}
package/dist/index.cjs ADDED
@@ -0,0 +1,96 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/index.ts
23
+ var src_exports = {};
24
+ __export(src_exports, {
25
+ RichTextEditor: () => RichTextEditor,
26
+ useEditor: () => useEditor
27
+ });
28
+ module.exports = __toCommonJS(src_exports);
29
+ var import_react = require("react");
30
+ var import_open_wysiwyg_editor = require("open-wysiwyg-editor");
31
+ __reExport(src_exports, require("open-wysiwyg-editor"), module.exports);
32
+ function useEditor(options = {}) {
33
+ const ref = (0, import_react.useRef)(null);
34
+ const [editor, setEditor] = (0, import_react.useState)(null);
35
+ const latest = (0, import_react.useRef)(options);
36
+ (0, import_react.useEffect)(() => {
37
+ latest.current = options;
38
+ });
39
+ (0, import_react.useEffect)(() => {
40
+ const instance = (0, import_open_wysiwyg_editor.createEditor)({
41
+ ...latest.current,
42
+ element: ref.current,
43
+ ...(0, import_open_wysiwyg_editor.forwardCallbacks)(() => latest.current)
44
+ });
45
+ setEditor(instance);
46
+ return () => {
47
+ instance.destroy();
48
+ setEditor(null);
49
+ };
50
+ }, []);
51
+ const runtime = import_open_wysiwyg_editor.RUNTIME_OPTIONS.map((key) => options[key]);
52
+ (0, import_react.useEffect)(() => {
53
+ editor?.setOptions((0, import_open_wysiwyg_editor.pickRuntimeOptions)(latest.current));
54
+ }, [editor, ...runtime]);
55
+ return { ref, editor };
56
+ }
57
+ function RichTextEditor(props) {
58
+ const { value, defaultValue, onChange, name, className, id, ...options } = props;
59
+ const applied = (0, import_react.useRef)(value);
60
+ const field = (0, import_react.useRef)(null);
61
+ const { ref, editor } = useEditor({
62
+ ...options,
63
+ content: value ?? defaultValue,
64
+ onCreate: (instance) => {
65
+ if (field.current) field.current.value = instance.getHTML();
66
+ options.onCreate?.(instance);
67
+ },
68
+ onUpdate: (instance) => {
69
+ const html = instance.getHTML();
70
+ applied.current = html;
71
+ if (field.current) field.current.value = html;
72
+ options.onUpdate?.(instance);
73
+ onChange?.(html, instance);
74
+ }
75
+ });
76
+ (0, import_react.useEffect)(() => {
77
+ if (!editor || value === void 0 || value === applied.current) return;
78
+ applied.current = value;
79
+ (0, import_open_wysiwyg_editor.syncContent)(editor, value);
80
+ if (field.current) field.current.value = editor.getHTML();
81
+ }, [editor, value]);
82
+ const initial = typeof (value ?? defaultValue) === "string" ? value ?? defaultValue : "";
83
+ return (0, import_react.createElement)(
84
+ "div",
85
+ { className, id },
86
+ (0, import_react.createElement)("div", { ref }),
87
+ name ? (0, import_react.createElement)("input", { ref: field, type: "hidden", name, defaultValue: initial }) : null
88
+ );
89
+ }
90
+ // Annotate the CommonJS export names for ESM import in node:
91
+ 0 && (module.exports = {
92
+ RichTextEditor,
93
+ useEditor,
94
+ ...require("open-wysiwyg-editor")
95
+ });
96
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// React adapter (React 18+, Next.js App and Pages Router, Remix, Gatsby, Vite). The build marks\n// this file \"use client\", so Server Components can render <RichTextEditor> directly.\nimport { createElement, useEffect, useRef, useState, type ReactElement, type RefObject } from \"react\";\nimport {\n RUNTIME_OPTIONS,\n createEditor,\n forwardCallbacks,\n pickRuntimeOptions,\n syncContent,\n type Content,\n type Editor,\n type EditorOptions,\n} from \"open-wysiwyg-editor\";\n\n// One install gives the whole API: StarterKit, getUI, types…\nexport * from \"open-wysiwyg-editor\";\n\nexport interface UseEditorResult {\n /** Attach to the element the editor mounts in: `<div ref={ref} />`. */\n ref: RefObject<HTMLDivElement | null>;\n /** The live editor, or `null` before mount and during server rendering. */\n editor: Editor | null;\n}\n\n/**\n * Creates an editor in the element behind `ref` and destroys it on unmount.\n * Callbacks and the runtime options (`editable`, `placeholder`, `aria*`, `dir`, `contentLang`)\n * follow every render; everything else (`extensions`, `ui`, `language`…) is read once at mount.\n * Give the component a new `key` to apply those.\n */\nexport function useEditor(options: EditorOptions = {}): UseEditorResult {\n const ref = useRef<HTMLDivElement>(null);\n const [editor, setEditor] = useState<Editor | null>(null);\n const latest = useRef(options);\n\n useEffect(() => {\n latest.current = options;\n });\n\n useEffect(() => {\n const instance = createEditor({\n ...latest.current,\n element: ref.current,\n ...forwardCallbacks(() => latest.current),\n });\n setEditor(instance);\n return () => {\n instance.destroy();\n setEditor(null);\n };\n }, []);\n\n const runtime = RUNTIME_OPTIONS.map((key) => options[key]);\n useEffect(() => {\n editor?.setOptions(pickRuntimeOptions(latest.current));\n }, [editor, ...runtime]);\n\n return { ref, editor };\n}\n\nexport interface RichTextEditorProps extends Omit<EditorOptions, \"element\" | \"content\"> {\n /** Controlled HTML. The editor loads it whenever it differs from what the editor last produced. */\n value?: string;\n /** Initial content (HTML or JSON) for an uncontrolled editor. */\n defaultValue?: Content;\n /** Called with the new HTML on every change. */\n onChange?: (html: string, editor: Editor) => void;\n /**\n * Form field name. The HTML is kept in a hidden input, so `<form action={serverAction}>`\n * (Next.js, React 19) and plain form posts receive it.\n */\n name?: string;\n className?: string;\n id?: string;\n}\n\n/**\n * The full editor (toolbar, dialogs, status bar) as a component.\n *\n * ```tsx\n * const [html, setHtml] = useState(\"<p>Hello</p>\");\n * <RichTextEditor value={html} onChange={setHtml} />\n * ```\n */\nexport function RichTextEditor(props: RichTextEditorProps): ReactElement {\n const { value, defaultValue, onChange, name, className, id, ...options } = props;\n const applied = useRef<Content>(value);\n const field = useRef<HTMLInputElement>(null);\n const { ref, editor } = useEditor({\n ...options,\n content: value ?? defaultValue,\n onCreate: (instance) => {\n if (field.current) field.current.value = instance.getHTML();\n options.onCreate?.(instance);\n },\n onUpdate: (instance) => {\n const html = instance.getHTML();\n applied.current = html;\n if (field.current) field.current.value = html;\n options.onUpdate?.(instance);\n onChange?.(html, instance);\n },\n });\n\n useEffect(() => {\n if (!editor || value === undefined || value === applied.current) return;\n applied.current = value;\n syncContent(editor, value);\n if (field.current) field.current.value = editor.getHTML();\n }, [editor, value]);\n\n // Uncontrolled hidden input: the server renders the initial HTML, the editor keeps it current.\n const initial = typeof (value ?? defaultValue) === \"string\" ? ((value ?? defaultValue) as string) : \"\";\n return createElement(\n \"div\",\n { className, id },\n createElement(\"div\", { ref }),\n name ? createElement(\"input\", { ref: field, type: \"hidden\", name, defaultValue: initial }) : null,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAA8F;AAC9F,iCASO;AAGP,wBAAc,gCAfd;AA8BO,SAAS,UAAU,UAAyB,CAAC,GAAoB;AACtE,QAAM,UAAM,qBAAuB,IAAI;AACvC,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAwB,IAAI;AACxD,QAAM,aAAS,qBAAO,OAAO;AAE7B,8BAAU,MAAM;AACd,WAAO,UAAU;AAAA,EACnB,CAAC;AAED,8BAAU,MAAM;AACd,UAAM,eAAW,yCAAa;AAAA,MAC5B,GAAG,OAAO;AAAA,MACV,SAAS,IAAI;AAAA,MACb,OAAG,6CAAiB,MAAM,OAAO,OAAO;AAAA,IAC1C,CAAC;AACD,cAAU,QAAQ;AAClB,WAAO,MAAM;AACX,eAAS,QAAQ;AACjB,gBAAU,IAAI;AAAA,IAChB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,2CAAgB,IAAI,CAAC,QAAQ,QAAQ,GAAG,CAAC;AACzD,8BAAU,MAAM;AACd,YAAQ,eAAW,+CAAmB,OAAO,OAAO,CAAC;AAAA,EACvD,GAAG,CAAC,QAAQ,GAAG,OAAO,CAAC;AAEvB,SAAO,EAAE,KAAK,OAAO;AACvB;AA0BO,SAAS,eAAe,OAA0C;AACvE,QAAM,EAAE,OAAO,cAAc,UAAU,MAAM,WAAW,IAAI,GAAG,QAAQ,IAAI;AAC3E,QAAM,cAAU,qBAAgB,KAAK;AACrC,QAAM,YAAQ,qBAAyB,IAAI;AAC3C,QAAM,EAAE,KAAK,OAAO,IAAI,UAAU;AAAA,IAChC,GAAG;AAAA,IACH,SAAS,SAAS;AAAA,IAClB,UAAU,CAAC,aAAa;AACtB,UAAI,MAAM,QAAS,OAAM,QAAQ,QAAQ,SAAS,QAAQ;AAC1D,cAAQ,WAAW,QAAQ;AAAA,IAC7B;AAAA,IACA,UAAU,CAAC,aAAa;AACtB,YAAM,OAAO,SAAS,QAAQ;AAC9B,cAAQ,UAAU;AAClB,UAAI,MAAM,QAAS,OAAM,QAAQ,QAAQ;AACzC,cAAQ,WAAW,QAAQ;AAC3B,iBAAW,MAAM,QAAQ;AAAA,IAC3B;AAAA,EACF,CAAC;AAED,8BAAU,MAAM;AACd,QAAI,CAAC,UAAU,UAAU,UAAa,UAAU,QAAQ,QAAS;AACjE,YAAQ,UAAU;AAClB,gDAAY,QAAQ,KAAK;AACzB,QAAI,MAAM,QAAS,OAAM,QAAQ,QAAQ,OAAO,QAAQ;AAAA,EAC1D,GAAG,CAAC,QAAQ,KAAK,CAAC;AAGlB,QAAM,UAAU,QAAQ,SAAS,kBAAkB,WAAa,SAAS,eAA2B;AACpG,aAAO;AAAA,IACL;AAAA,IACA,EAAE,WAAW,GAAG;AAAA,QAChB,4BAAc,OAAO,EAAE,IAAI,CAAC;AAAA,IAC5B,WAAO,4BAAc,SAAS,EAAE,KAAK,OAAO,MAAM,UAAU,MAAM,cAAc,QAAQ,CAAC,IAAI;AAAA,EAC/F;AACF;","names":[]}
@@ -0,0 +1,43 @@
1
+ import { ReactElement, RefObject } from 'react';
2
+ import { EditorOptions, Content, Editor } from 'open-wysiwyg-editor';
3
+ export * from 'open-wysiwyg-editor';
4
+
5
+ interface UseEditorResult {
6
+ /** Attach to the element the editor mounts in: `<div ref={ref} />`. */
7
+ ref: RefObject<HTMLDivElement | null>;
8
+ /** The live editor, or `null` before mount and during server rendering. */
9
+ editor: Editor | null;
10
+ }
11
+ /**
12
+ * Creates an editor in the element behind `ref` and destroys it on unmount.
13
+ * Callbacks and the runtime options (`editable`, `placeholder`, `aria*`, `dir`, `contentLang`)
14
+ * follow every render; everything else (`extensions`, `ui`, `language`…) is read once at mount.
15
+ * Give the component a new `key` to apply those.
16
+ */
17
+ declare function useEditor(options?: EditorOptions): UseEditorResult;
18
+ interface RichTextEditorProps extends Omit<EditorOptions, "element" | "content"> {
19
+ /** Controlled HTML. The editor loads it whenever it differs from what the editor last produced. */
20
+ value?: string;
21
+ /** Initial content (HTML or JSON) for an uncontrolled editor. */
22
+ defaultValue?: Content;
23
+ /** Called with the new HTML on every change. */
24
+ onChange?: (html: string, editor: Editor) => void;
25
+ /**
26
+ * Form field name. The HTML is kept in a hidden input, so `<form action={serverAction}>`
27
+ * (Next.js, React 19) and plain form posts receive it.
28
+ */
29
+ name?: string;
30
+ className?: string;
31
+ id?: string;
32
+ }
33
+ /**
34
+ * The full editor (toolbar, dialogs, status bar) as a component.
35
+ *
36
+ * ```tsx
37
+ * const [html, setHtml] = useState("<p>Hello</p>");
38
+ * <RichTextEditor value={html} onChange={setHtml} />
39
+ * ```
40
+ */
41
+ declare function RichTextEditor(props: RichTextEditorProps): ReactElement;
42
+
43
+ export { RichTextEditor, type RichTextEditorProps, type UseEditorResult, useEditor };