smartrte-react 1.0.0-beta.12 → 1.0.0-beta.13

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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0-beta.13
4
+
5
+ - Fix toolbar dropdowns still landing far from their trigger inside a dialog whose ancestor uses `contain: layout` without `paint` — `1.0.0-beta.11`'s `contain` fix only checked for `paint`/`strict`/`content`, missing that bare `layout` alone also establishes a `position: fixed` containing block per spec (confirmed directly in real Chromium). Menus rendered hundreds of pixels from their trigger, overlapping unrelated page content, with no error. See `docs/bugs/toolbar-overlay-misplaced-inside-contain-layout-ancestor.md`.
6
+
3
7
  ## 1.0.0-beta.12
4
8
 
5
9
  - Fix toolbar dropdowns/popovers inside a `contain: paint | strict | content` ancestor rendering off-screen or clipped to invisible near that ancestor's own edge — `1.0.0-beta.11`'s `contain` fix corrected the coordinate origin but still clamped against the full viewport instead of the actual clipping box, so a menu near the host's own right/bottom edge could compute a position outside what the ancestor actually lets paint, making it invisible even though its coordinates looked reasonable. Affects `ToolbarDropdown`, `MobileMoreMenu`, the color picker popover, and the table border popover — all four shared the same viewport-only clamping logic. See `docs/bugs/toolbar-overlay-clamped-to-wrong-viewport-inside-contain-paint.md`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Smart RTE 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,885 +1,374 @@
1
- # Smart RTE (Rich Text Editor) - React
1
+ # smartrte-react
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/smartrte-react.svg)](https://www.npmjs.com/package/smartrte-react)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- A powerful, feature-rich Rich Text Editor built for React applications with support for tables, formulas (LaTeX/KaTeX), media management, and advanced text formatting.
6
+ A rich text editor for React, built on a document-model core rather than raw `contentEditable` state — tables, LaTeX/KaTeX formulas, media, DOCX/PDF/Markdown import-export, per-tool toolbar visibility, and host-owned integration points for version history, comments, and suggestions/track-changes.
7
7
 
8
- ## 🌟 Features
8
+ It pairs with [`smartrte-core`](https://www.npmjs.com/package/smartrte-core), a framework-agnostic document engine — you only need this package to use it from React.
9
9
 
10
- - **📝 Rich Text Editing**: Full-featured WYSIWYG editor with all standard formatting options
11
- - **📊 Advanced Table Support**: Create, edit, merge, split cells, and customize tables
12
- - **🔢 Mathematical Formulas**: LaTeX/KaTeX integration for rendering mathematical expressions
13
- - **🖼️ Media Management**: Image upload, resize, drag-and-drop, and custom media manager integration
14
- - **🎨 Styling Options**: Font sizes (8-96pt), text colors, background colors, and more
15
- - **🔗 Link Management**: Easy insertion and editing of hyperlinks
16
- - **📱 Responsive**: Works seamlessly across different screen sizes
17
- - **⚡ Lightweight**: Minimal dependencies, optimized for performance
18
- - **🎯 TypeScript Support**: Fully typed for better developer experience
19
- - **🔧 Customizable**: Toggle features on/off, custom media managers, and more
10
+ ## Contents
20
11
 
21
- ## 📦 Installation
12
+ - [Install](#install)
13
+ - [Quick start](#quick-start)
14
+ - [Which component do I use?](#which-component-do-i-use-canonicalauthorityeditor-vs-classiceditor)
15
+ - [Props](#props)
16
+ - [Toolbar customization](#toolbar-customization)
17
+ - [Capability presets](#capability-presets-table-onoff)
18
+ - [Host-owned providers](#host-owned-providers-media-versions-comments-suggestions)
19
+ - [Imperative handle](#imperative-handle-ref)
20
+ - [Import & export formats](#import--export-formats)
21
+ - [Theming](#theming)
22
+ - [Standalone / non-React embed](#standalone--non-react-embed)
23
+ - [Security](#security)
24
+ - [Browser support](#browser-support)
25
+ - [Development](#development)
26
+ - [Contributing](#contributing)
27
+ - [License](#license)
22
28
 
23
- ### Using npm
29
+ ## Install
24
30
 
25
31
  ```bash
26
32
  npm install smartrte-react
33
+ # or: pnpm add smartrte-react / yarn add smartrte-react
27
34
  ```
28
35
 
29
- ### Using yarn
36
+ `react` and `react-dom` (`>=18`) are peer dependencies — install them if your project doesn't already have them. No separate CSS import is required; the editor injects its own stylesheet on mount.
30
37
 
31
- ```bash
32
- yarn add smartrte-react
33
- ```
34
-
35
- ### Using pnpm
36
-
37
- ```bash
38
- pnpm add smartrte-react
39
- ```
40
-
41
- ## 🚀 Quick Start
42
-
43
- ### 🎮 Live Demo
44
-
45
- Try the editor instantly in your browser:
46
-
47
- - **[Live Demo](https://playground-k9l44ah7t-ayush1852017s-projects.vercel.app/)** (Deployed Version)
48
- - **[CodeSandbox Playground](https://codesandbox.io/s/github/ayush1852017/smart-rte/tree/master/packages/react/playground)** (Interactive)
49
-
50
- ### Basic Usage
38
+ ## Quick start
51
39
 
52
40
  ```tsx
53
- import React, { useState } from 'react';
54
- import { ClassicEditor } from 'smartrte-react';
41
+ import { useState } from "react";
42
+ import { CanonicalAuthorityEditor } from "smartrte-react";
55
43
 
56
44
  function App() {
57
- const [content, setContent] = useState('<p>Start typing...</p>');
45
+ const [content, setContent] = useState("<p>Start typing…</p>");
58
46
 
59
47
  return (
60
- <div>
61
- <ClassicEditor
62
- value={content}
63
- onChange={(html) => setContent(html)}
64
- placeholder="Type here…"
65
- />
66
- </div>
48
+ <CanonicalAuthorityEditor
49
+ defaultValue={content}
50
+ onHtmlChange={setContent}
51
+ placeholder="Type here…"
52
+ />
67
53
  );
68
54
  }
69
-
70
- export default App;
71
55
  ```
72
56
 
73
- ## 📚 Documentation
57
+ `defaultValue` is uncontrolled — it seeds the editor once on mount, not on every render (see [Props](#props) for why, and how to programmatically replace content later via the imperative handle).
74
58
 
75
- ### Component API
59
+ ## Which component do I use? `CanonicalAuthorityEditor` vs `ClassicEditor`
76
60
 
77
- #### ClassicEditor Props
78
-
79
- | Prop | Type | Default | Description |
80
- |------|------|---------|-------------|
81
- | `value` | `string` | `undefined` | HTML content of the editor |
82
- | `onChange` | `(html: string) => void` | `undefined` | Callback fired when content changes |
83
- | `placeholder` | `string` | `"Type here…"` | Placeholder text when editor is empty |
84
- | `minHeight` | `number \| string` | `200` | Minimum height of the editor (in pixels) |
85
- | `maxHeight` | `number \| string` | `500` | Maximum height of the editor (in pixels) |
86
- | `readOnly` | `boolean` | `false` | Make the editor read-only |
87
- | `table` | `boolean` | `true` | Enable/disable table functionality |
88
- | `media` | `boolean` | `true` | Enable/disable media/image functionality |
89
- | `formula` | `boolean` | `true` | Enable/disable formula/LaTeX functionality |
90
- | `features` | `CoreFeatureConfig` | standard preset | Enable or disable individual core plugins |
91
- | `plugins` | `ReactEditorPlugin[]` | standard preset | Use an exact custom plugin set; replaces the standard preset |
92
- | `formats` | `EditorFormatConfig` | all built-ins | Enable or disable HTML, Markdown, DOCX, and PDF adapters |
93
- | `formatDefinitions` | `EditorFormatDefinition[]` | built-ins | Add or replace import/export adapters |
94
- | `mediaManager` | `MediaManagerAdapter` | `undefined` | Custom media manager for handling images |
95
- | `fonts` | `{ name: string; value: string }[]` | Default web-safe fonts | Custom font list for the toolbar |
96
- | `defaultFont` | `string` | `undefined` | Default font family for the editor content |
97
- | `theme` | `"light" \| "dark"` | `"light"` | Built-in theme mode |
98
- | `className` | `string` | `undefined` | Custom CSS class for theming via CSS variable overrides |
99
- | `tools` | `Partial<ToolbarTools>` | every tool `true` | Hide individual toolbar tools (Bold, Video, Version history, etc.) — see [Hiding individual toolbar tools](#hiding-individual-toolbar-tools) below |
100
-
101
- ### Advanced Examples
102
-
103
- #### Feature plugins
104
-
105
- The editor uses the same plugin registry for core commands and React toolbar
106
- state. For the architecture and custom plugin API, see
107
- [`docs/PLUGIN_ARCHITECTURE.md`](../../docs/PLUGIN_ARCHITECTURE.md).
61
+ - **`CanonicalAuthorityEditor`** — the actual editor. Everything in this guide (tools, providers, presets, the imperative handle) is its API. Use this for anything new.
62
+ - **`ClassicEditor`** — a thin backwards-compatibility wrapper around `CanonicalAuthorityEditor`, kept for integrations written against the package's older `value`/`onChange: (html: string) => void` shape. It forwards everything it can (`tools`, `mediaProvider`, `versionProvider`, etc.) but silently ignores a handful of props from an even older, now-retired plugin system (`features`, `plugins`, `formats`, `fonts`, `theme`, `mediaManager` as an adapter object). If you're starting fresh, use `CanonicalAuthorityEditor` directly — `ClassicEditor` exists so old call sites keep compiling, not as a recommended entry point.
108
63
 
109
64
  ```tsx
110
- import { ClassicEditor } from 'smartrte-react';
111
-
112
- <ClassicEditor
113
- features={{
114
- table: false,
115
- media: false,
116
- formula: false,
117
- checklist: true,
118
- }}
119
- />
65
+ // Legacy-compatible shape - only use this if migrating an existing integration
66
+ import { ClassicEditor } from "smartrte-react";
67
+
68
+ <ClassicEditor value={htmlString} onChange={(html) => setHtmlString(html)} />
120
69
  ```
121
70
 
122
- #### Hiding individual toolbar tools
71
+ ## Props
72
+
73
+ The commonly-used `CanonicalAuthorityEditor` props:
123
74
 
124
- The `table`/`media`/`formula` props above turn off a whole *capability* — no
125
- tables anywhere, or media/formulas removed from the schema entirely. If you
126
- just want a **smaller toolbar** while keeping every capability intact (e.g.
127
- hide Video, Audio, Version history, and Review for a specific product
128
- surface, without touching what content the editor can actually store), use
129
- `tools` instead. It's a plain object: name the tools you want off, everything
130
- else stays on by default. No wrapping, no CSS overrides, no forking the
131
- component.
75
+ | Prop | Type | Default | Description |
76
+ |---|---|---|---|
77
+ | `defaultValue` | `string \| PersistedEditorDocument` | `undefined` | Initial content (HTML string or a previously-saved document envelope). Uncontrolled after mount — see [Imperative handle](#imperative-handle-ref) to replace content later. |
78
+ | `onChange` | `(change: SmartEditorChange) => void` | `undefined` | Fires per transaction with the structured change event. |
79
+ | `onHtmlChange` | `(html: string) => void` | `undefined` | Debounced (~250ms after the last edit) plain-HTML serialization — the simplest way to persist content as a string. |
80
+ | `preset` | `"full" \| "simple"` | `"full"` | Construction-time capability preset — `"simple"` excludes the table plugin entirely (schema-level, not just hidden in the toolbar). See [Capability presets](#capability-presets-table-onoff). |
81
+ | `tools` | `Partial<ToolbarTools>` | every tool `true` | Hide individual toolbar tools without touching document capability. See [Toolbar customization](#toolbar-customization). |
82
+ | `mediaProvider` | `MediaProvider` | `undefined` | Host-owned upload/search/remove boundary for images, video, and audio. Absent ⇒ media tools don't render. |
83
+ | `mediaManager` | `boolean` | `true` when `mediaProvider` is set | Use the library/search/duplicate-detection picker for images (vs. the plain file-input default). |
84
+ | `mediaPicker` | `MediaPickerComponent` | built-in file picker | Replace the default file-picker UI for video/audio (and images, if `mediaManager` is `false`). |
85
+ | `versionProvider` | `VersionProvider` | `undefined` | Host-owned save/list/load/remove boundary for version history. Absent ⇒ Version History tool doesn't render. |
86
+ | `commentProvider` | `CommentProvider` | `undefined` | Host-owned boundary for comment threads. Absent ⇒ comment tools/markers don't render. |
87
+ | `suggestionProvider` | `SuggestionProvider` | `undefined` | Host-owned boundary for *structural* suggestions (track-changes). Absent ⇒ suggestion tools/markers don't render. |
88
+ | `authorId` | `string` | `"anonymous"` | Attributed to new comment replies and suggestions. |
89
+ | `renderFormulaHtml` | `boolean` | `false` | Bake real KaTeX-rendered HTML into `onHtmlChange`'s formula markup instead of an empty placeholder — turn this on if you render that HTML anywhere outside the editor (email, PDF export, a read-only view without KaTeX loaded). |
90
+ | `onClipboardDiagnostic` | `(report: ClipboardDiagnosticReport) => void` | `undefined` | Inspect what a paste was parsed as / why it was rejected — useful while debugging a host's own copy sources. |
91
+ | `placeholder` | `string` | `undefined` | Placeholder text shown when the editor is empty. |
92
+ | `minHeight` / `maxHeight` | `number \| string` | `undefined` | Editing-surface height bounds. |
93
+ | `readOnly` | `boolean` | `false` | Disables editing; toolbar tools become inert. |
94
+ | `className` | `string` | `undefined` | Extra class(es) on the editor's root element — this is also how you enable [dark mode](#theming). |
95
+ | `onRuntime` | `(runtime: CanonicalEditorRuntime) => void` | `undefined` | Escape hatch for tests/diagnostics; not part of the stable editing contract. |
96
+
97
+ `ClassicEditor` accepts the same props under `value`/`onChange: (html) => void` instead of `defaultValue`/`onHtmlChange`, plus a legacy `table?: boolean` (equivalent to `preset={table === false ? "simple" : "full"}`).
98
+
99
+ ## Toolbar customization
100
+
101
+ `tools` hides individual toolbar entries — Bold, Video, Version history, whatever you name — without touching what the document itself can *store*. Every tool defaults to visible; only name the ones you want off:
132
102
 
133
103
  ```tsx
134
- import { ClassicEditor } from 'smartrte-react';
135
-
136
- <ClassicEditor
137
- tools={{
138
- video: false,
139
- audio: false,
140
- versionHistory: false,
141
- comments: false,
142
- suggestions: false,
143
- }}
104
+ <CanonicalAuthorityEditor
105
+ tools={{ video: false, audio: false, versionHistory: false, comments: false, suggestions: false }}
144
106
  />
145
107
  ```
146
108
 
147
- **How it composes with everything else:** a tool only ever needs *both*
148
- things to be true to show up — your `tools` flag says yes, *and* whatever
149
- that tool actually depends on is present. So:
109
+ `tools` can only ever hide something, never conjure it into existence — a tool still needs its underlying capability to actually be there:
150
110
 
151
- - `tools.video`/`tools.audio`/`tools.image` also need a `mediaProvider` (or
152
- `mediaManager`) to be configured at all — turning the flag on can't make
153
- media insertion appear out of nowhere if you never wired up a provider.
154
- - `tools.versionHistory` also needs a `versionProvider`; `tools.comments`
155
- needs a `commentProvider`; `tools.suggestions` needs a `suggestionProvider`.
156
- - `tools.insertTable` also needs the table capability to actually be enabled
157
- (i.e. you haven't set `table={false}`) — you can't use `tools` to bring
158
- back a capability you turned off elsewhere.
111
+ - `image`/`video`/`audio` also need `mediaProvider` configured.
112
+ - `versionHistory` also needs `versionProvider`; `comments` needs `commentProvider`; `suggestions` needs `suggestionProvider`.
113
+ - `insertTable` also needs the table capability enabled (i.e. you haven't set `preset="simple"`).
159
114
 
160
- In other words: `tools` can only ever hide something, never force something
161
- into existence that isn't otherwise configured. This means it's always safe
162
- to leave `tools` unset — every consumer who doesn't pass it sees the exact
163
- toolbar they see today.
115
+ This means it's always safe to leave `tools` unset — a consumer who never passes it sees every tool their other configuration already supports.
164
116
 
165
117
  **Every toggleable key**, grouped the way they appear in the toolbar:
166
118
 
167
119
  ```ts
168
120
  interface ToolbarTools {
169
121
  // Text formatting
170
- bold, italic, underline, strikethrough, code,
171
- superscript, subscript, textColor, backgroundColor, fontSize, fontFamily,
122
+ bold: boolean; italic: boolean; underline: boolean; strikethrough: boolean; code: boolean;
123
+ superscript: boolean; subscript: boolean; textColor: boolean; backgroundColor: boolean;
124
+ fontSize: boolean; fontFamily: boolean;
172
125
 
173
126
  // Paragraph
174
- blockType, // the Paragraph/Heading/Code block dropdown
175
- alignLeft, alignCenter, alignRight, alignJustify,
176
- lineHeight, // the line-spacing dropdown (1/1.15/1.5/2/2.5 presets plus a custom value)
177
- quote,
127
+ blockType: boolean; // the Paragraph/Heading 1-6/Code block dropdown
128
+ alignLeft: boolean; alignCenter: boolean; alignRight: boolean; alignJustify: boolean;
129
+ lineHeight: boolean; // the line-spacing dropdown (1/1.15/1.5/2/2.5 presets plus a custom value)
130
+ quote: boolean;
178
131
 
179
132
  // Lists
180
- bulletedList, numberedList, checklist,
181
- listPreset, // the numbered-list style picker (1,2,3 / a,b,c / i,ii,iii / A,B,C / I,II,III)
133
+ bulletedList: boolean; numberedList: boolean; checklist: boolean;
134
+ listPreset: boolean; // the named marker-preset picker (decimal/alpha/roman/outline/bullet glyphs)
182
135
 
183
136
  // Insert
184
- link, removeLink, image, video, audio, insertFormula, specialCharacters,
185
- horizontalLine, // inserts a divider (<hr>)
186
- pageBreak, // inserts a print/export pagination marker - distinct from horizontalLine
187
- insertTable,
137
+ link: boolean; removeLink: boolean;
138
+ image: boolean; video: boolean; audio: boolean; // each requires mediaProvider
139
+ insertFormula: boolean; specialCharacters: boolean;
140
+ horizontalLine: boolean; // inserts a divider (<hr>)
141
+ pageBreak: boolean; // a print/export pagination marker, distinct from horizontalLine
142
+ insertTable: boolean; // requires the table capability (preset)
188
143
 
189
144
  // Document
190
- import,
191
- saveAsHtml, saveAsMarkdown, saveAsWord, saveAsPdf, saveAsSmartRte,
192
- versionHistory, comments, suggestions,
145
+ import: boolean;
146
+ saveAsHtml: boolean; saveAsMarkdown: boolean; saveAsWord: boolean; saveAsPdf: boolean; saveAsSmartRte: boolean;
147
+ versionHistory: boolean; // requires versionProvider
148
+ comments: boolean; // requires commentProvider
149
+ suggestions: boolean; // requires suggestionProvider
193
150
 
194
151
  // History
195
- undo, redo,
152
+ undo: boolean; redo: boolean;
196
153
  }
197
154
  ```
198
155
 
199
- A few notes on what's *not* in this list, on purpose: actions that only ever
200
- apply to something you already have selected — moving a block up/down,
201
- indenting a list item, adding/removing a table row or column, resizing a
202
- selected image — aren't individually toggleable. They only make sense as
203
- part of using the tool that created them (a table, a list, an image), so
204
- they follow that tool's own visibility rather than needing a separate flag
205
- each. If you turn off `insertTable`, its row/column tools go with it
206
- automatically — there's no separate flag to remember.
156
+ Purely contextual actions that only ever act on something already selected — moving a block up/down, indenting a list item, adding/removing a table row, resizing a selected image — aren't individually toggleable; they follow their owning tool's visibility (turn off `insertTable` and its row/column actions go with it, with no separate flag to remember).
207
157
 
208
- If you're using the exported `CanonicalAuthorityEditor` directly instead of
209
- `ClassicEditor`, `tools` works exactly the same way. And if you're using the
210
- standalone embed (`window.SmartRTE.ClassicEditor.init(...)`), pass `tools`
211
- in the same options object you pass `target`/`value`/etc.
158
+ ## Capability presets (table on/off)
212
159
 
213
- ```ts
214
- window.SmartRTE.ClassicEditor.init({
215
- target: document.getElementById('editor'),
216
- tools: { video: false, audio: false },
217
- });
218
- ```
219
-
220
- #### Complete Example with All Features
160
+ `preset` is a construction-time, host/integrator-level setting (there's no in-editor UI for a user to change their own preset) — it decides which plugins the document's *schema* is built with, not just what the toolbar shows:
221
161
 
222
162
  ```tsx
223
- import React, { useState } from 'react';
224
- import { ClassicEditor, MediaManagerAdapter } from 'smartrte-react';
225
-
226
- // Custom media manager implementation
227
- const customMediaManager: MediaManagerAdapter = {
228
- async search(query) {
229
- // Implement your media search logic
230
- const response = await fetch(`/api/media/search?q=${query.text}`);
231
- const data = await response.json();
232
- return {
233
- items: data.items.map(item => ({
234
- id: item.id,
235
- url: item.url,
236
- thumbnailUrl: item.thumbnailUrl,
237
- title: item.title,
238
- })),
239
- };
240
- },
241
- async upload(file) {
242
- // Implement your file upload logic
243
- const formData = new FormData();
244
- formData.append('file', file);
245
- const response = await fetch('/api/media/upload', {
246
- method: 'POST',
247
- body: formData,
248
- });
249
- const data = await response.json();
250
- return {
251
- id: data.id,
252
- url: data.url,
253
- thumbnailUrl: data.thumbnailUrl,
254
- title: data.title,
255
- };
256
- },
257
- };
258
-
259
- function AdvancedEditor() {
260
- const [content, setContent] = useState('');
261
-
262
- return (
263
- <ClassicEditor
264
- value={content}
265
- onChange={(html) => {
266
- console.log('Content changed:', html);
267
- setContent(html);
268
- }}
269
- placeholder="Start editing..."
270
- minHeight={300}
271
- maxHeight={800}
272
- table={true}
273
- media={true}
274
- formula={true}
275
- mediaManager={customMediaManager}
276
- />
277
- );
278
- }
279
-
280
- export default AdvancedEditor;
163
+ <CanonicalAuthorityEditor preset="simple" /> // excludes the table plugin entirely
164
+ <CanonicalAuthorityEditor preset="full" /> // default - excludes nothing
281
165
  ```
282
166
 
283
- #### Read-Only Mode
167
+ `"simple"` exists for content that should never contain tables at all (e.g. a short-answer question editor) — `preset="simple"` and `tools={{ insertTable: false }}` are not equivalent: the latter only hides the button, the former means the schema itself will reject a pasted or imported table.
284
168
 
285
- ```tsx
286
- import { ClassicEditor } from 'smartrte-react';
169
+ ## Host-owned providers (media, versions, comments, suggestions)
287
170
 
288
- function ReadOnlyEditor({ content }) {
289
- return (
290
- <ClassicEditor
291
- value={content}
292
- readOnly={true}
293
- minHeight={200}
294
- />
295
- );
296
- }
297
- ```
171
+ Four features are opt-in via a provider interface the *host* implements — the package never holds storage credentials, a socket, or a database connection itself. Absent provider ⇒ that feature's toolbar entries simply don't render; nothing crashes or shows a broken control.
298
172
 
299
- #### Minimal Editor (No Tables, Media, or Formulas)
173
+ ```ts
174
+ interface MediaProvider {
175
+ upload(file: File, opts?: { signal?: AbortSignal }): Promise<{ url: string; id: string }>;
176
+ search(query: string, filters?: MediaFilters, page?: number): Promise<MediaItem[]>;
177
+ remove(id: string): Promise<void>;
178
+ }
300
179
 
301
- ```tsx
302
- import { ClassicEditor } from 'smartrte-react';
180
+ interface VersionProvider {
181
+ save(version: DocumentVersion): Promise<VersionListEntry>;
182
+ list(): Promise<readonly VersionListEntry[]>;
183
+ load(id: string): Promise<DocumentVersion>;
184
+ remove(id: string): Promise<void>;
185
+ }
303
186
 
304
- function MinimalEditor() {
305
- const [content, setContent] = useState('');
187
+ interface CommentProvider {
188
+ list(): Promise<readonly CommentThread[]>;
189
+ save(thread: CommentThread): Promise<void>;
190
+ remove(threadId: string): Promise<void>;
191
+ }
306
192
 
307
- return (
308
- <ClassicEditor
309
- value={content}
310
- onChange={setContent}
311
- table={false}
312
- media={false}
313
- formula={false}
314
- placeholder="Simple text editor"
315
- />
316
- );
193
+ interface SuggestionProvider {
194
+ list(): Promise<readonly StructuralSuggestion[]>;
195
+ save(suggestion: StructuralSuggestion): Promise<void>;
196
+ remove(suggestionId: string): Promise<void>;
317
197
  }
318
198
  ```
319
199
 
320
- #### Next.js Integration
321
-
322
- For Next.js applications, you may need to use dynamic imports to avoid SSR issues:
323
-
324
200
  ```tsx
325
- import dynamic from 'next/dynamic';
326
- import { useState } from 'react';
327
-
328
- const ClassicEditor = dynamic(
329
- () => import('smartrte-react').then(mod => mod.ClassicEditor),
330
- { ssr: false }
331
- );
332
-
333
- export default function Page() {
334
- const [content, setContent] = useState('');
335
-
336
- return (
337
- <div>
338
- <ClassicEditor
339
- value={content}
340
- onChange={setContent}
341
- placeholder="Start typing..."
342
- />
343
- </div>
344
- );
345
- }
201
+ <CanonicalAuthorityEditor
202
+ mediaProvider={{
203
+ async upload(file) {
204
+ const body = new FormData();
205
+ body.append("file", file);
206
+ const res = await fetch("/api/media", { method: "POST", body });
207
+ return res.json(); // { url, id }
208
+ },
209
+ async search(query) {
210
+ const res = await fetch(`/api/media?q=${encodeURIComponent(query)}`);
211
+ return res.json();
212
+ },
213
+ async remove(id) {
214
+ await fetch(`/api/media/${id}`, { method: "DELETE" });
215
+ },
216
+ }}
217
+ />
346
218
  ```
347
219
 
348
- ## 🔧 Features Deep Dive
349
-
350
- ### Text Formatting
351
-
352
- The editor supports all standard text formatting options:
353
-
354
- - **Bold**, *Italic*, <u>Underline</u>, ~~Strikethrough~~
355
- - Font sizes from 8pt to 96pt
356
- - Text color and background color
357
- - Headings (H1-H6)
358
- - Paragraph, blockquote, code block
359
- - Ordered and unordered lists
360
- - Text alignment (left, center, right, justify)
361
- - Superscript and subscript
362
-
363
- ### Tables
364
-
365
- Full-featured table support includes:
366
-
367
- - Create tables with custom rows and columns
368
- - Add/delete rows and columns
369
- - Merge and split cells
370
- - Toggle header rows/cells
371
- - Cell background colors
372
- - Cell borders toggle
373
- - Right-click context menu for table operations
374
- - Keyboard navigation (Tab, Shift+Tab, Arrow keys)
220
+ Your `upload` implementation must independently validate file type, size, and content server-side — the editor applies only a best-effort client-side allow-list check as a UX nicety, not a security boundary.
375
221
 
376
- **Keyboard Shortcuts:**
377
- - `Tab` - Move to next cell
378
- - `Shift+Tab` - Move to previous cell
379
- - `Arrow keys` - Navigate between cells
380
- - Right-click on cell - Open context menu
222
+ There's a fifth contract, **`CollabTransport`** (real-time multi-writer editing), exported for hosts building against it — it defines `sendTransaction`/`onRemoteTransaction`/`onPresenceUpdate`/`sendPresence`/`getRevisionHistory`, but isn't wired into the editor's runtime yet. Without one connected, the editor behaves exactly as it does today: single-writer, no rebase path ever triggers.
381
223
 
382
- ### Mathematical Formulas
224
+ ## Imperative handle (ref)
383
225
 
384
- LaTeX/KaTeX support for mathematical expressions:
226
+ `CanonicalAuthorityEditor`/`ClassicEditor` forward a `SmartEditorHandle` ref for everything `defaultValue`/props alone can't do — replacing content programmatically, reading the current document, and version snapshots:
385
227
 
386
228
  ```tsx
387
- // The editor automatically loads KaTeX
388
- // Users can insert formulas using the formula button
389
- // Examples of supported LaTeX:
390
- // - E=mc^2
391
- // - \frac{a}{b}
392
- // - \sqrt{x}
393
- // - \sum_{i=1}^{n} x_i
394
- ```
229
+ import { useRef } from "react";
230
+ import { CanonicalAuthorityEditor, type SmartEditorHandle } from "smartrte-react";
395
231
 
396
- **Required External Dependency:**
232
+ function Editor() {
233
+ const ref = useRef<SmartEditorHandle>(null);
397
234
 
398
- To use formulas, include KaTeX in your HTML:
235
+ const loadDocument = (doc) => ref.current?.replaceValue(doc, { keepSelection: false });
236
+ const currentDoc = () => ref.current?.getValue();
399
237
 
400
- ```html
401
- <!-- In your public/index.html or _app.tsx -->
402
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">
403
- <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
238
+ return <CanonicalAuthorityEditor ref={ref} />;
239
+ }
404
240
  ```
405
241
 
406
- ### Media Management
407
-
408
- Built-in image support with optional custom media manager:
409
-
410
- **Default behavior:**
411
- - Local file upload
412
- - Drag and drop images
413
- - Image resize handles
414
- - Right-click context menu for image operations
415
-
416
- **Custom Media Manager Implementation:**
417
-
418
- ```typescript
419
- import { MediaManagerAdapter, MediaItem, MediaSearchQuery } from 'smartrte-react';
420
-
421
- const myMediaManager: MediaManagerAdapter = {
422
- async search(query: MediaSearchQuery) {
423
- // Search your media library
424
- return {
425
- items: [/* array of MediaItem */],
426
- hasMore: false,
427
- nextPage: undefined,
428
- };
429
- },
430
-
431
- async upload(file: File) {
432
- // Upload file to your server
433
- return {
434
- id: 'unique-id',
435
- url: 'https://example.com/image.jpg',
436
- thumbnailUrl: 'https://example.com/thumb.jpg',
437
- title: 'Image title',
438
- };
439
- },
440
- };
242
+ ```ts
243
+ interface SmartEditorHandle {
244
+ getValue(): PersistedEditorDocument;
245
+ replaceValue(doc: PersistedEditorDocument, opts?: { keepSelection?: boolean }): void;
246
+ isDirty(): boolean;
247
+ markSaved(revision: number): void;
248
+ getRevision(): number;
249
+ focus(): void;
250
+ executeOperations(operations: readonly SmartOperation[], opts?: ExecuteOperationsOptions): void;
251
+ createCheckpoint(): SmartEditorCheckpoint;
252
+ restoreCheckpoint(checkpoint: SmartEditorCheckpoint): void;
253
+ saveVersion(opts?: { label?: string; authorId?: string }): DocumentVersion;
254
+ restoreVersion(version: DocumentVersion, opts?: { keepSelection?: boolean }): void;
255
+ }
441
256
  ```
442
257
 
443
- ## 🎨 Theming & Dark Mode
258
+ `saveVersion`/`restoreVersion` are the same operations the toolbar's Version History panel calls — use them directly if you want your own save-version UI instead of (or alongside) the built-in one.
444
259
 
445
- The editor uses CSS custom properties (CSS variables) for all colors, making it fully customizable. No hardcoded colors — everything can be themed.
260
+ ## Import & export formats
446
261
 
447
- ### Quick Start: Dark Mode
262
+ The toolbar's "Import" and "Save as ..." tools cover HTML, Markdown, DOCX (Word), PDF, and the package's own JSON document format out of the box — no extra setup. DOCX import preserves real Word styling (fonts, colors, spacing) where possible; PDF export prints the same HTML the editor renders, so formulas, tables, and images all appear as they do live.
448
263
 
449
- ```tsx
450
- <ClassicEditor theme="dark" onChange={handleChange} />
451
- ```
264
+ For a custom import/export pipeline (e.g. converting on a server, or a "Save as..." flow outside the toolbar), the underlying codecs are re-exported from `smartrte-core/foundation`: `exportDocxDocument`, `importDocxDocumentWithMammoth`, `importStyledDocxDocument`, `buildPdfPrintDocument`, `importPdfDocument`, and the format-fidelity contract (`builtInFormatFidelity`) describing exactly what's lossless vs. lossy per format.
452
265
 
453
- ### Custom Themes via CSS
266
+ ## Theming
454
267
 
455
- Apply a custom class and override any CSS variables:
268
+ The editor uses CSS custom properties for every color — there's no `theme` prop; dark mode is a CSS class.
456
269
 
457
270
  ```tsx
458
- <ClassicEditor className="my-theme" onChange={handleChange} />
271
+ <CanonicalAuthorityEditor className="srte-dark" />
459
272
  ```
460
273
 
461
274
  ```css
462
- .my-theme {
463
- --srte-bg: #1a1a2e;
464
- --srte-text: #eaeaea;
465
- --srte-border: #3a3a5c;
466
- /* Override only the variables you need */
275
+ /* Or follow system preference yourself and toggle the class conditionally */
276
+ @media (prefers-color-scheme: dark) {
277
+ .srte-editor:not(.srte-dark) { /* your own light/dark logic here */ }
467
278
  }
468
279
  ```
469
280
 
470
- ### Responding to System Preference
471
-
472
- ```tsx
473
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
474
-
475
- <ClassicEditor theme={prefersDark ? 'dark' : 'light'} />
476
- ```
477
-
478
- Or purely via CSS (without the `theme` prop):
281
+ Override individual variables (scoped to your own class, composed alongside `srte-dark` or standalone) to build a custom palette:
479
282
 
480
283
  ```css
481
- @media (prefers-color-scheme: dark) {
482
- .srte-editor {
483
- --srte-bg: #1e1e1e;
484
- --srte-text: #e0e0e0;
485
- --srte-border: #3a3a3a;
486
- /* ... */
487
- }
284
+ .my-theme {
285
+ --srte-background: #1a1a2e;
286
+ --srte-foreground: #eaeaea;
287
+ --srte-border: #3a3a5c;
288
+ --srte-accent: #7c3aed;
289
+ /* override only what you need - everything else falls back to the default */
488
290
  }
489
291
  ```
490
292
 
491
- ### Available CSS Custom Properties
492
-
493
- | Variable | Default (Light) | Description |
494
- |---|---|---|
495
- | `--srte-bg` | `#ffffff` | Editor container & content background |
496
- | `--srte-text` | `#111111` | Primary UI text color |
497
- | `--srte-text-muted` | `#4b5563` | Secondary/subtle text |
498
- | `--srte-border` | `#dddddd` | Standard border color |
499
- | `--srte-border-light` | `#eeeeee` | Light separator borders |
500
- | `--srte-toolbar-bg` | `#ffffff` | Toolbar background |
501
- | `--srte-input-bg` | `#ffffff` | Button, input, select backgrounds |
502
- | `--srte-input-text` | `#111111` | Button, input, select text |
503
- | `--srte-input-border` | `#e5e7eb` | Button, input, select borders |
504
- | `--srte-modal-backdrop` | `rgba(0,0,0,0.35)` | Modal overlay background |
505
- | `--srte-modal-bg` | `#ffffff` | Modal/dialog background |
506
- | `--srte-modal-text` | `#000000` | Modal text color |
507
- | `--srte-menu-bg` | `#ffffff` | Context menu background |
508
- | `--srte-menu-text` | `#111111` | Context menu text |
509
- | `--srte-menu-shadow` | `0 8px 24px rgba(0,0,0,0.18)` | Menu box-shadow |
510
- | `--srte-accent` | `#1e90ff` | Accent/selection color |
511
- | `--srte-accent-bg` | `rgba(30,144,255,0.15)` | Accent background (translucent) |
512
- | `--srte-danger` | `#dc2626` | Destructive action color |
513
- | `--srte-primary` | `#2563eb` | Primary action button color |
514
- | `--srte-surface-subtle` | `#f3f4f6` | Subtle surface (dropzone, preset buttons) |
515
- | `--srte-on-primary` | `#ffffff` | Text on primary/danger buttons |
516
- | `--srte-cancel-bg` | `#f3f4f6` | Cancel button background |
517
-
518
- ### Important Notes
519
-
520
- - **User content colors are preserved** — colors set via the color picker (text/background) are inline styles on content elements and are not affected by theming.
521
- - **Color picker swatches are not themed** — they display actual color values regardless of theme.
522
- - **The theme only affects editor chrome** — toolbar, dialogs, menus, and container. User content remains unchanged.
523
-
524
- ## 🛠️ Development
525
-
526
- ### Prerequisites
527
-
528
- - Node.js 18+
529
- - pnpm 9.10.0+
530
-
531
- ### Setting Up Development Environment
532
-
533
- 1. **Clone the repository**
534
-
535
- ```bash
536
- git clone https://github.com/ayush1852017/smart-rte.git
537
- cd smart-rte
538
- ```
539
-
540
- 2. **Install dependencies**
541
-
542
- ```bash
543
- pnpm install
544
- ```
545
-
546
- 3. **Build the project**
547
-
548
- ```bash
549
- # Build TypeScript packages
550
- pnpm build
551
- ```
552
-
553
- 4. **Run the development playground**
554
-
555
- ```bash
556
- cd packages/react/playground
557
- pnpm install
558
- pnpm dev
559
- ```
560
-
561
- The playground will be available at `http://localhost:5173`
293
+ | Variable | Description |
294
+ |---|---|
295
+ | `--srte-background` / `--srte-canvas` | Toolbar/chrome background vs. editing-surface background |
296
+ | `--srte-foreground` / `--srte-muted-foreground` | Primary vs. secondary text |
297
+ | `--srte-border` | Standard border color |
298
+ | `--srte-ring` | Focus ring color |
299
+ | `--srte-accent` / `--srte-accent-bg` | Selection/active-state color and its translucent background |
300
+ | `--srte-primary` / `--srte-on-primary` | Primary action button background/text |
301
+ | `--srte-danger` | Destructive action color |
302
+ | `--srte-modal-bg` / `--srte-modal-backdrop` | Dialog background and overlay |
303
+ | `--srte-menu-bg` / `--srte-menu-shadow` | Dropdown/context-menu background and shadow |
304
+ | `--srte-code-bg` / `--srte-code-text` | Code block colors |
562
305
 
563
- ### Project Structure
306
+ These fall back to sensible defaults, and also read from common shadcn/ui-style tokens (`--card`, `--background`, `--foreground`, `--muted`, `--border`, `--ring`) if your app already defines those — so a Tailwind/shadcn app may need no overrides at all. Colors set via the color picker (text/background) are inline styles on content and are unaffected by theming — only editor chrome (toolbar, dialogs, menus) is themed.
564
307
 
565
- ```
566
- smart-rte/
567
- ├── packages/
568
- │ └── react/ # Main React package (smartrte-react)
569
- │ ├── src/
570
- │ │ ├── components/
571
- │ │ │ ├── ClassicEditor.tsx # Main editor component
572
- │ │ │ └── MediaManager.tsx # Media management component
573
- │ │ └── index.ts
574
- │ ├── playground/ # Development playground
575
- │ └── package.json
576
- ├── dart/ # Flutter/Dart packages
577
- │ ├── smartrte_flutter/ # Flutter WebView integration
578
- │ └── example_app/ # Flutter example
579
- └── package.json
580
- ```
308
+ ## Standalone / non-React embed
581
309
 
582
- ### Building for Production
583
-
584
- ```bash
585
- # Build the React package
586
- cd packages/react
587
- pnpm build
588
-
589
- # This creates:
590
- # - dist/index.js - ES module
591
- # - dist/index.d.ts - TypeScript definitions
592
- # - dist/embed.js - Standalone embed bundle
593
- ```
310
+ For a host that isn't a React app (or embeds via WebView — the [Flutter package](https://github.com/ayush1852017/smart-rte/tree/master/dart/smartrte_flutter) uses exactly this), a global-script build is available:
594
311
 
595
- ### Running Tests
596
-
597
- ```bash
598
- # Run vitest
599
- pnpm test
600
-
601
- # Run E2E tests with Playwright
602
- pnpm e2e
603
- ```
604
-
605
- ### Running Storybook
312
+ ```ts
313
+ import "smartrte-react/standalone/classic-editor-embed";
606
314
 
607
- ```bash
608
- cd packages/react
609
- pnpm storybook
315
+ window.SmartRTE.ClassicEditor.init({
316
+ target: document.getElementById("editor"),
317
+ value: "<p>Hello</p>",
318
+ tools: { video: false, audio: false },
319
+ onChange: (html) => console.log(html),
320
+ });
610
321
  ```
611
322
 
612
- Storybook will be available at `http://localhost:6006`
613
-
614
- ## 📝 Publishing
323
+ Returns a controller: `{ setHtml, getHtml, focus, blur, destroy }`.
615
324
 
616
- ### For Package Maintainers
325
+ ## Security
617
326
 
618
- The package is published to npm as `smartrte-react`.
327
+ The editor outputs HTML and never persists anything itself — storage, credentials, and the actual save are always the host's. Pasted HTML is sanitized on the way in (DOMPurify), but **always sanitize before rendering elsewhere**: if you take the editor's HTML output and `dangerouslySetInnerHTML` it in a different context (an email, a public page), treat it the same as any other user-generated HTML.
619
328
 
620
- ```bash
621
- # Make sure you're in packages/react
622
- cd packages/react
329
+ ```tsx
330
+ import DOMPurify from "dompurify";
623
331
 
624
- # Update version in package.json
625
- # Then publish
626
- pnpm publish
332
+ function DisplayContent({ html }: { html: string }) {
333
+ return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />;
334
+ }
627
335
  ```
628
336
 
629
- The `prepublishOnly` script automatically runs `build:all` before publishing.
630
-
631
- ### Version Management
632
-
633
- We follow [Semantic Versioning](https://semver.org/):
634
-
635
- - **MAJOR** version for incompatible API changes
636
- - **MINOR** version for backwards-compatible functionality
637
- - **PATCH** version for backwards-compatible bug fixes
638
-
639
- ## 🤝 Contributing
337
+ Found a security issue? Please email support@openstash.in rather than opening a public issue.
640
338
 
641
- We welcome contributions! Here's how you can help:
339
+ ## Browser support
642
340
 
643
- ### Reporting Bugs
341
+ Chromium, Firefox, and WebKit (Safari) — the full end-to-end suite runs against all three, headless and current, on every change.
644
342
 
645
- 1. Check if the bug has already been reported in [Issues](https://github.com/ayush1852017/smart-rte/issues)
646
- 2. If not, create a new issue with:
647
- - Clear title and description
648
- - Steps to reproduce
649
- - Expected vs actual behavior
650
- - Screenshots if applicable
651
- - Your environment (browser, OS, React version)
343
+ ## Development
652
344
 
653
- ### Suggesting Features
654
-
655
- 1. Check [existing feature requests](https://github.com/ayush1852017/smart-rte/issues?q=is%3Aissue+label%3Aenhancement)
656
- 2. Create a new issue with:
657
- - Clear description of the feature
658
- - Use cases
659
- - Proposed API (if applicable)
660
-
661
- ### Pull Requests
662
-
663
- 1. **Fork** the repository
664
- 2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
665
- 3. **Make** your changes
666
- 4. **Test** your changes thoroughly
667
- 5. **Commit** with clear messages (`git commit -m 'Add amazing feature'`)
668
- 6. **Push** to your fork (`git push origin feature/amazing-feature`)
669
- 7. **Open** a Pull Request
670
-
671
- #### PR Guidelines
672
-
673
- - Follow the existing code style
674
- - Add tests for new features
675
- - Update documentation
676
- - Keep PRs focused on a single feature/fix
677
- - Write clear commit messages
678
-
679
- ### Development Workflow
345
+ This package lives in a pnpm workspace monorepo alongside `smartrte-core`.
680
346
 
681
347
  ```bash
682
- # 1. Create a feature branch
683
- git checkout -b feature/my-feature
684
-
685
- # 2. Make changes and test
686
- pnpm dev # Run playground
687
- pnpm test # Run tests
688
-
689
- # 3. Build to ensure no errors
690
- pnpm build
691
-
692
- # 4. Commit and push
693
- git add .
694
- git commit -m "feat: add my feature"
695
- git push origin feature/my-feature
696
-
697
- # 5. Create PR on GitHub
348
+ git clone https://github.com/ayush1852017/smart-rte.git
349
+ cd smart-rte
350
+ pnpm install
351
+ pnpm build # builds every package
698
352
  ```
699
353
 
700
- ## 🐛 Troubleshooting
701
-
702
- ### Common Issues
703
-
704
- #### Issue: Editor not showing/rendering
705
-
706
- **Solution:** Make sure React and React-DOM are installed as peer dependencies:
707
-
708
354
  ```bash
709
- npm install react@18 react-dom@18
710
- ```
711
-
712
- #### Issue: Formula rendering not working
713
-
714
- **Solution:** Ensure KaTeX is loaded in your HTML:
715
-
716
- ```html
717
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">
718
- <script src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
355
+ # Live playground (aliased to workspace source, not the built dist - edits hot-reload)
356
+ cd packages/react/playground
357
+ pnpm install
358
+ pnpm dev # http://localhost:5173
719
359
  ```
720
360
 
721
- #### Issue: TypeScript errors
722
-
723
- **Solution:** Make sure you have the latest type definitions:
724
-
725
361
  ```bash
726
- npm install --save-dev @types/react@18 @types/react-dom@18
727
- ```
728
-
729
- #### Issue: Build errors in Next.js
730
-
731
- **Solution:** Use dynamic imports to disable SSR:
732
-
733
- ```tsx
734
- const ClassicEditor = dynamic(
735
- () => import('smartrte-react').then(mod => mod.ClassicEditor),
736
- { ssr: false }
737
- );
738
- ```
739
-
740
- #### Issue: Images not uploading
741
-
742
- **Solution:** Check that the `media` prop is set to `true` and implement a custom `mediaManager` if you need server-side uploads.
743
-
744
- ## 🔐 Security
745
-
746
- ### Reporting Security Issues
747
-
748
- If you discover a security vulnerability, please email [support@openstash.in] instead of using the issue tracker.
749
-
750
- ### Content Sanitization
751
-
752
- **⚠️ Important:** The editor outputs raw HTML. Always sanitize user-generated content before displaying it to prevent XSS attacks.
753
-
754
- Recommended libraries:
755
- - [DOMPurify](https://github.com/cure53/DOMPurify)
756
- - [sanitize-html](https://github.com/apostrophecms/sanitize-html)
757
-
758
- Example:
759
-
760
- ```tsx
761
- import DOMPurify from 'dompurify';
762
-
763
- function DisplayContent({ html }) {
764
- const sanitized = DOMPurify.sanitize(html);
765
- return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
766
- }
767
- ```
768
-
769
- ## 📄 License
770
-
771
- This project is licensed under the MIT License - see the [LICENSE](../../dart/smartrte_flutter/LICENSE) file for details.
772
-
773
- ```
774
- MIT License
775
-
776
- Copyright (c) 2025 Smart RTE Contributors
777
-
778
- Permission is hereby granted, free of charge, to any person obtaining a copy
779
- of this software and associated documentation files (the "Software"), to deal
780
- in the Software without restriction, including without limitation the rights
781
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
782
- copies of the Software, and to permit persons to whom the Software is
783
- furnished to do so, subject to the following conditions:
784
-
785
- The above copyright notice and this permission notice shall be included in all
786
- copies or substantial portions of the Software.
787
-
788
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
789
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
790
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
791
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
792
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
793
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
794
- SOFTWARE.
362
+ # From packages/react
363
+ pnpm test # vitest unit suite
364
+ pnpm e2e # Playwright, all 3 browsers
365
+ pnpm storybook # component stories, http://localhost:6006
795
366
  ```
796
367
 
797
- ## 👥 Authors & Contributors
798
-
799
- - **Smart RTE Team** - Initial work and maintenance
800
-
801
- See the list of [contributors](https://github.com/ayush1852017/smart-rte/contributors) who participated in this project.
802
-
803
- ## 🙏 Acknowledgments
804
-
805
- - [KaTeX](https://katex.org/) - For mathematical formula rendering
806
- - [React](https://reactjs.org/) - The UI library
807
- - [Vite](https://vitejs.dev/) - Build tool
808
- - All our amazing [contributors](https://github.com/ayush1852017/smart-rte/contributors)
809
-
810
- ## 📞 Support
811
-
812
- - **Documentation:** You're reading it! 📖
813
- - **Issues:** [GitHub Issues](https://github.com/ayush1852017/smart-rte/issues)
814
- - **Discussions:** [GitHub Discussions](https://github.com/ayush1852017/smart-rte/discussions)
815
- - **Twitter:** [@smartrte](https://twitter.com/smartrte) (if applicable)
816
-
817
- ## 🗺️ Roadmap
818
-
819
- ### Current Version (0.2.x)
820
-
821
- - ✅ Rich text editing
822
- - ✅ Table support
823
- - ✅ Formula support (LaTeX/KaTeX)
824
- - ✅ Media management
825
- - ✅ TypeScript support
826
- - ✅ Dark mode & theming (CSS custom properties)
827
-
828
- ### Upcoming Features
829
-
830
- - 🔄 Collaborative editing
831
- - 🔄 Undo/Redo improvements
832
- - 🔄 Code syntax highlighting
833
- - 🔄 Markdown import/export
834
- - 🔄 Custom toolbar configuration
835
- - 🔄 Mobile optimization
836
- - 🔄 Accessibility improvements (ARIA labels, keyboard shortcuts)
837
-
838
- ## 📊 Browser Support
839
-
840
- | Browser | Version |
841
- |---------|---------|
842
- | Chrome | Last 2 versions |
843
- | Firefox | Last 2 versions |
844
- | Safari | Last 2 versions |
845
- | Edge | Last 2 versions |
846
-
847
- ## 🔗 Related Packages
848
-
849
- - **smartrte-flutter** - Flutter/Dart WebView implementation
850
-
851
- ## 💡 Tips & Best Practices
852
-
853
- 1. **Performance**: For large documents, consider implementing lazy loading or pagination
854
- 2. **State Management**: Use React state or a state management library (Redux, Zustand) for complex applications
855
- 3. **Validation**: Always validate and sanitize HTML content before storing or displaying
856
- 4. **Accessibility**: Test with screen readers and keyboard navigation
857
- 5. **Mobile**: Test on mobile devices as touch interactions may differ
858
- 6. **Auto-save**: Implement auto-save functionality to prevent data loss
859
-
860
- ## 🎓 Learning Resources
861
-
862
- ### For Entry-Level Developers
863
-
864
- 1. **Getting Started with React**: [React Official Tutorial](https://react.dev/learn)
865
- 2. **Understanding Rich Text Editors**: [MDN ContentEditable](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable)
866
- 3. **TypeScript Basics**: [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html)
867
-
868
- ### For Mid-Level Developers
869
-
870
- 1. **Advanced React Patterns**: Hooks, Context, Performance Optimization
871
- 2. **Component Design**: Building reusable, maintainable components
872
- 3. **State Management**: When and how to use external state management
873
-
874
- ### For Senior Developers
875
-
876
- 1. **Architecture**: Designing scalable editor implementations
877
- 2. **Performance**: Optimization techniques for large documents
878
- 3. **Extensibility**: Building plugin systems and custom extensions
879
- 4. **Cross-platform**: Adapting the editor for different frameworks
368
+ ## Contributing
880
369
 
881
- ---
370
+ Issues and PRs are welcome at [github.com/ayush1852017/smart-rte](https://github.com/ayush1852017/smart-rte/issues). For a bug report, include a minimal repro, expected vs. actual behavior, and your browser/OS. For a PR: keep it focused on one change, add test coverage (unit and/or a Playwright spec, matching whichever existing test file is closest to what you touched), and run `pnpm build && pnpm test` before pushing.
882
371
 
883
- **Happy Editing! 🎉**
372
+ ## License
884
373
 
885
- If you find this package useful, please consider giving it a ⭐ on [GitHub](https://github.com/ayush1852017/smart-rte)!
374
+ MIT — see [LICENSE](https://github.com/ayush1852017/smart-rte/blob/master/LICENSE).
@@ -9,11 +9,23 @@
9
9
  * (`translate-x-[-50%] translate-y-[-50%]`), confirmed live via a Sootr
10
10
  * host rendering the editor inside such a dialog
11
11
  * (docs/bugs/toolbar-menu-misplaced-inside-transformed-ancestor.md).
12
- * - `contain: paint | strict | content` (CSS Containment) - a common
13
- * perf/isolation optimization on large scrollable containers, e.g. a
14
- * second, different Sootr dialog reported still misplacing every toolbar
15
- * dropdown ("More list tools", "More to insert", "Save a copy", "More
16
- * text styles") despite already running the transform-only fix above.
12
+ * - `contain: layout | paint | strict | content` (CSS Containment) - a
13
+ * common perf/isolation optimization on large scrollable containers, e.g.
14
+ * a second, different Sootr dialog reported still misplacing every
15
+ * toolbar dropdown ("More list tools", "More to insert", "Save a copy",
16
+ * "More text styles") despite already running the transform-only fix
17
+ * above. `layout` alone (without `paint`) was missed in that first pass -
18
+ * only checked for `paint`/`strict`/`content` - and confirmed directly in
19
+ * real Chromium to establish the containing block on its own regardless:
20
+ * a `position: fixed` descendant of a bare `contain: layout` ancestor (no
21
+ * transform, no paint) resolves its `top`/`left` against that ancestor's
22
+ * box, not the viewport. A THIRD Sootr dialog using exactly this
23
+ * (`contain: layout`, no `paint`) reported the same misplacement symptom
24
+ * again - not a clipping/invisibility symptom (see `getPositioningBounds`
25
+ * below for that distinct failure mode), just landing in the wrong place
26
+ * with no error, since the previous check silently treated this ancestor
27
+ * as if it weren't a containing block at all and fell through to
28
+ * `{ left: 0, top: 0 }` (viewport-relative) origin math instead.
17
29
  *
18
30
  * Deliberately NOT checking bare `container-type` (CSS Containers) -
19
31
  * despite implying `contain: layout style` per spec, verified directly in
@@ -57,10 +69,13 @@ export declare function getFixedPositioningOrigin(el: HTMLElement): {
57
69
  * the actual, much narrower, clipping ancestor.
58
70
  *
59
71
  * Deliberately NOT applied for a containing block established only by
60
- * `transform`/`perspective`/`filter`/`backdrop-filter`/`will-change` with
61
- * no `contain` of its own - none of those clip overflow, so a menu is free
62
- * to visually extend past that ancestor's box exactly as it could before
63
- * any of this positioning logic existed. Verified directly: constraining
72
+ * `transform`/`perspective`/`filter`/`backdrop-filter`/`will-change`, or by
73
+ * `contain: layout` with no `paint`/`strict`/`content` alongside it - none
74
+ * of those clip overflow (`layout` containment only isolates layout
75
+ * calculations, it does not imply paint containment - see the spec note
76
+ * above `findFixedPositioningContainer`), so a menu is free to visually
77
+ * extend past that ancestor's box exactly as it could before any of this
78
+ * positioning logic existed. Verified directly: constraining
64
79
  * to such an ancestor's bounds regardless made a mobile "More tools" menu
65
80
  * genuinely too tall to fit inside a short transformed host overlap its
66
81
  * own trigger instead of just extending past the host's edge (harmless,
@@ -9,11 +9,23 @@
9
9
  * (`translate-x-[-50%] translate-y-[-50%]`), confirmed live via a Sootr
10
10
  * host rendering the editor inside such a dialog
11
11
  * (docs/bugs/toolbar-menu-misplaced-inside-transformed-ancestor.md).
12
- * - `contain: paint | strict | content` (CSS Containment) - a common
13
- * perf/isolation optimization on large scrollable containers, e.g. a
14
- * second, different Sootr dialog reported still misplacing every toolbar
15
- * dropdown ("More list tools", "More to insert", "Save a copy", "More
16
- * text styles") despite already running the transform-only fix above.
12
+ * - `contain: layout | paint | strict | content` (CSS Containment) - a
13
+ * common perf/isolation optimization on large scrollable containers, e.g.
14
+ * a second, different Sootr dialog reported still misplacing every
15
+ * toolbar dropdown ("More list tools", "More to insert", "Save a copy",
16
+ * "More text styles") despite already running the transform-only fix
17
+ * above. `layout` alone (without `paint`) was missed in that first pass -
18
+ * only checked for `paint`/`strict`/`content` - and confirmed directly in
19
+ * real Chromium to establish the containing block on its own regardless:
20
+ * a `position: fixed` descendant of a bare `contain: layout` ancestor (no
21
+ * transform, no paint) resolves its `top`/`left` against that ancestor's
22
+ * box, not the viewport. A THIRD Sootr dialog using exactly this
23
+ * (`contain: layout`, no `paint`) reported the same misplacement symptom
24
+ * again - not a clipping/invisibility symptom (see `getPositioningBounds`
25
+ * below for that distinct failure mode), just landing in the wrong place
26
+ * with no error, since the previous check silently treated this ancestor
27
+ * as if it weren't a containing block at all and fell through to
28
+ * `{ left: 0, top: 0 }` (viewport-relative) origin math instead.
17
29
  *
18
30
  * Deliberately NOT checking bare `container-type` (CSS Containers) -
19
31
  * despite implying `contain: layout style` per spec, verified directly in
@@ -44,7 +56,7 @@ export function findFixedPositioningContainer(el) {
44
56
  (style.filter && style.filter !== "none") ||
45
57
  (style.backdropFilter && style.backdropFilter !== "none") ||
46
58
  (style.willChange && /transform|perspective|filter/.test(style.willChange)) ||
47
- (style.contain && /\b(paint|strict|content)\b/.test(style.contain))) {
59
+ (style.contain && /\b(layout|paint|strict|content)\b/.test(style.contain))) {
48
60
  return node;
49
61
  }
50
62
  node = node.parentElement;
@@ -75,10 +87,13 @@ export function getFixedPositioningOrigin(el) {
75
87
  * the actual, much narrower, clipping ancestor.
76
88
  *
77
89
  * Deliberately NOT applied for a containing block established only by
78
- * `transform`/`perspective`/`filter`/`backdrop-filter`/`will-change` with
79
- * no `contain` of its own - none of those clip overflow, so a menu is free
80
- * to visually extend past that ancestor's box exactly as it could before
81
- * any of this positioning logic existed. Verified directly: constraining
90
+ * `transform`/`perspective`/`filter`/`backdrop-filter`/`will-change`, or by
91
+ * `contain: layout` with no `paint`/`strict`/`content` alongside it - none
92
+ * of those clip overflow (`layout` containment only isolates layout
93
+ * calculations, it does not imply paint containment - see the spec note
94
+ * above `findFixedPositioningContainer`), so a menu is free to visually
95
+ * extend past that ancestor's box exactly as it could before any of this
96
+ * positioning logic existed. Verified directly: constraining
82
97
  * to such an ancestor's bounds regardless made a mobile "More tools" menu
83
98
  * genuinely too tall to fit inside a short transformed host overlap its
84
99
  * own trigger instead of just extending past the host's edge (harmless,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smartrte-react",
3
- "version": "1.0.0-beta.12",
3
+ "version": "1.0.0-beta.13",
4
4
  "description": "A powerful, feature-rich Rich Text Editor for React with support for tables, mathematical formulas (LaTeX/KaTeX), and media management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",