stilo-editor 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,144 +5,43 @@
5
5
  </picture>
6
6
  </p>
7
7
 
8
+ <h3 align="center">A complete rich text editor. Everything included, ready in minutes.</h3>
9
+
10
+ <p align="center">
11
+ Free and open source (MIT) · clean HTML in and out · built on ProseMirror
12
+ </p>
13
+
8
14
  <p align="center">
9
15
  <a href="https://www.npmjs.com/package/stilo-editor"><img src="https://img.shields.io/npm/v/stilo-editor" alt="npm version"></a>
10
- <a href="LICENSE"><img src="https://img.shields.io/npm/l/stilo-editor" alt="MIT license"></a>
16
+ <a href="https://github.com/kojak8791/stilo-editor/actions/workflows/ci.yml"><img src="https://github.com/kojak8791/stilo-editor/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
11
17
  <img src="https://img.shields.io/npm/types/stilo-editor" alt="TypeScript types included">
18
+ <a href="LICENSE"><img src="https://img.shields.io/npm/l/stilo-editor" alt="MIT license"></a>
12
19
  </p>
13
20
 
14
- Open-source HTML5 WYSIWYG editor built on [ProseMirror](https://prosemirror.net).
15
- Clean HTML in and out, headless core, no paid tiers.
21
+ <p align="center">
22
+ <a href="https://kojak8791.github.io/stilo-editor/"><strong>Live demo</strong></a> ·
23
+ <a href="docs/frameworks/react.md"><strong>React</strong></a> ·
24
+ <a href="docs/frameworks/vue.md"><strong>Vue</strong></a> ·
25
+ <a href="docs/frameworks/svelte.md"><strong>Svelte</strong></a> ·
26
+ <a href="docs/API.md"><strong>API</strong></a> ·
27
+ <a href="docs/FEATURES.md"><strong>All features</strong></a>
28
+ </p>
16
29
 
17
30
  <p align="center">
18
31
  <img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot.png" alt="Stilo with the default toolbar: formatted text, a checklist, a table and the link popover open" width="800">
19
32
  </p>
20
33
 
21
- <h2 align="center">
22
- <a href="https://kojak8791.github.io/stilo-editor/">See it in action: try the live demo →</a>
23
- </h2>
24
-
25
- > [!WARNING]
26
- > **Preview release.** Versions `0.x` are early previews: the editor works, but APIs may change
27
- > in any minor version until `1.0.0`. Read the release notes before updating.
28
-
29
- ## Why Stilo
30
-
31
- - **Built for very large documents.** Typing stays immediate in a document longer than most
32
- novels (see [Large documents](https://github.com/kojak8791/stilo-editor#large-documents)).
33
- Measured in a real browser, checked against budgets, not assumed.
34
- - **Entirely open source (MIT).** Aiming at the feature level of the established rich text
35
- editors, with no paid tiers and no closed "pro" extensions.
36
- - **HTML is the public format.** Content goes in as HTML and comes out as clean, semantic,
37
- self-contained HTML, with a strict allowlist for tags, attributes and inline styles.
38
- - **Headless core, optional UI.** The core works without any UI; the default toolbar and menus are
39
- a separate, replaceable module.
40
- - **Extensible by design.** Every feature is an extension with the same shape.
41
- - **Framework-agnostic.** Vanilla TypeScript first; React/Vue wrappers later as thin layers.
42
-
43
- ## Large documents
44
-
45
- Stilo is made for long documents: contracts, manuals, books, reports. Typing feels immediate in
46
- a document of 10,000 paragraphs — about 400,000 words, longer than most novels — and the editor
47
- stays usable at five times that size, without pagination tricks and without splitting the
48
- content. Formatting, search and selection keep up with the size of the text.
49
-
50
- This is measured, not promised. A benchmark drives the editor in a real browser on generated
51
- documents and times everything from loading and exporting the HTML down to the milliseconds
52
- between a key press and the character on screen; every change is checked against performance
53
- budgets. What it means in practice, the numbers and the limits:
54
- [Performance](docs/PERFORMANCE.md).
55
-
56
- ## Features
57
-
58
- What the editor does today, with `defaultExtensions` and the default toolbar. On macOS, use
59
- <kbd>⌘</kbd> for <kbd>Ctrl</kbd> and <kbd>Option</kbd> for <kbd>Alt</kbd>. The *Markdown*
60
- column is what you type to get the format as you write.
61
-
62
- | Feature | Keyboard | Markdown | Notes |
63
- |---|---|---|---|
64
- | **Text** | | | |
65
- | Bold | <kbd>Ctrl</kbd>+<kbd>B</kbd> | `**text**` | |
66
- | Italic | <kbd>Ctrl</kbd>+<kbd>I</kbd> | `*text*` `_text_` | |
67
- | Underline | <kbd>Ctrl</kbd>+<kbd>U</kbd> | | |
68
- | Strikethrough | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd> | `~~text~~` | |
69
- | Inline code | <kbd>Ctrl</kbd>+<kbd>E</kbd> | `` `text` `` | |
70
- | Subscript | <kbd>Ctrl</kbd>+<kbd>,</kbd> | | Subscript and superscript exclude each other |
71
- | Superscript | <kbd>Ctrl</kbd>+<kbd>.</kbd> | | |
72
- | Font family | | | Toolbar dropdown: generic families (sans serif, serif, monospace) and common fonts, each shown in its own font; a font that is not in the list (e.g. pasted from Word) is still shown |
73
- | Font size | | | − and + change the size by one point; the field between them shows the size, takes a typed one and opens a list. Unit pt by default (px or rem as an option) |
74
- | Text color | | | The button applies the last color; its arrow opens a palette, "No color" and a custom color |
75
- | Highlight | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>H</kbd> | `==text==` | Same button as text color; the shortcut applies the default highlight |
76
- | Clear formatting | <kbd>Ctrl</kbd>+<kbd>\\</kbd> | | Links stay |
77
- | Text case | | | A menu: UPPERCASE, lowercase, Title Case, Sentence case, tOGGLE cASE; the letters change, the formatting stays |
78
- | **Links** | | | |
79
- | Link | <kbd>Ctrl</kbd>+<kbd>K</kbd> | | A popover sets the URL and "open in new tab"; on a link, a small bubble offers open, edit and remove |
80
- | **Blocks** | | | |
81
- | Paragraph | <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>0</kbd> | | |
82
- | Headings 1–6 | <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>1</kbd>…<kbd>6</kbd> | `# ` … `###### ` | Also in the toolbar dropdown |
83
- | Quote | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>B</kbd> | `> ` | |
84
- | Code block | <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>C</kbd> | ```` ``` ```` + space, ```` ```js ```` + space for a language | Plain text in a monospace font, no formatting; the selected paragraphs become one block, a line each, and back. <kbd>Enter</kbd> on an empty last line, or <kbd>Ctrl</kbd>+<kbd>Enter</kbd>, leaves it. The language is written as `class="language-js"` for syntax highlighters |
85
- | Horizontal line | | `---` | |
86
- | Line break | <kbd>Shift</kbd>+<kbd>Enter</kbd> | | New line in the same paragraph |
87
- | Align left / center / right / justify | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>L</kbd> / <kbd>E</kbd> / <kbd>R</kbd> / <kbd>J</kbd> | | Paragraphs and headings; the toolbar button opens the four alignments; changing the block type (also with `# `) keeps the alignment |
88
- | Line spacing | | | Paragraphs and headings; the toolbar menu offers Default, 1, 1.15, 1.5 and 2; changing the block type keeps it |
89
- | Move block up / down | <kbd>Alt</kbd>+<kbd>↑</kbd> / <kbd>Alt</kbd>+<kbd>↓</kbd> | | The block or list item at the cursor, or all the selected ones; sub-items move along |
90
- | **Lists** | | | |
91
- | Bulleted list | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>8</kbd> | `- ` `* ` `+ ` | |
92
- | Numbered list | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>7</kbd> | `1. ` | Starts from the number you type |
93
- | Checklist | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>9</kbd> | `[ ] ` `[x] ` | Click a checkbox, or press <kbd>Ctrl</kbd>+<kbd>Enter</kbd> to check the selected items; checked items are struck through |
94
- | Indent / outdent item | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | | |
95
- | **Tables** | | | |
96
- | Insert a table | | | Toolbar grid up to 10 × 8, with or without a header row |
97
- | Rows, columns, cells | | | With the cursor in a table, the same button opens: insert or delete rows and columns, merge and split cells, header row and column, delete table |
98
- | Table handles | | | Handles on the row and column under the mouse select them and open their menu (insert, delete); a corner handle opens merge, split and delete table |
99
- | Move between cells | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | | <kbd>Tab</kbd> in the last cell adds a row |
100
- | Select cells | | | Drag across cells |
101
- | Resize a column | | | Drag the border of a column; the width is kept in the HTML |
102
- | **Images** | | | |
103
- | Paste an image | <kbd>Ctrl</kbd>+<kbd>V</kbd> | | A copied image or screenshot is embedded in the HTML (PNG, JPEG, GIF, WebP, up to 5 MB by default); an image copied from a web page keeps its address |
104
- | Resize an image | | | Select it and drag one of its side handles; the proportions stay |
105
- | **Insert** | | | |
106
- | Special characters and emoji | | | A popover with search, symbols and emoji; it stays open to insert several in a row |
107
- | **Editing** | | | |
108
- | Undo | <kbd>Ctrl</kbd>+<kbd>Z</kbd> | | <kbd>Backspace</kbd> right after a Markdown shortcut undoes it |
109
- | Redo | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> or <kbd>Ctrl</kbd>+<kbd>Y</kbd> | | |
110
- | Paste | <kbd>Ctrl</kbd>+<kbd>V</kbd> | | HTML is cleaned up; plain text lines become paragraphs. From Word and Google Docs the body font, size and color are dropped (text that differs keeps them) and Word lists become real lists; tables keep their column widths |
111
- | Paste as plain text | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>V</kbd> | | Only the text, without formatting |
112
- | Find and replace | <kbd>Ctrl</kbd>+<kbd>F</kbd> | | A bar under the toolbar: match case, whole word, previous and next match, replace one or all; the matches are counted exactly and the first 1,000 are highlighted; <kbd>Enter</kbd> / <kbd>Shift</kbd>+<kbd>Enter</kbd> go to the next / previous match, <kbd>Esc</kbd> closes it |
113
- | Word and character count | | | Optional status bar under the editor; also `getWordCount()` in the API |
114
-
115
- <table>
116
- <tr>
117
- <td width="50%"><img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot-find.png" alt="The find bar under the toolbar: five matches of espresso highlighted, the current one in orange, and ristretto in the replace field" width="400"></td>
118
- <td width="50%"><img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot-table.png" alt="A coffee menu table with a row selected by its handle and the row menu open: insert row above, insert row below, delete row" width="400"></td>
119
- </tr>
120
- <tr>
121
- <td>Find and replace: match case, whole word, an exact count of the matches.</td>
122
- <td>Tables: handles on rows and columns open their menu.</td>
123
- </tr>
124
- <tr>
125
- <td width="50%"><img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot-fonts.png" alt="The font dropdown open on a Times New Roman text, each font drawn in its own font" width="400"></td>
126
- <td width="50%"><img src="https://raw.githubusercontent.com/kojak8791/stilo-editor/main/assets/screenshot-color.png" alt="The text color palette open, with No color, the color grid and a custom color field" width="400"></td>
127
- </tr>
128
- <tr>
129
- <td>Fonts and sizes: the toolbar shows the font and the size at the cursor.</td>
130
- <td>Text color and highlight: a palette, the last color one click away, a custom color.</td>
131
- </tr>
132
- </table>
133
-
134
- **Keyboard and accessibility.** Most shortcuts are the ones of Google Docs; the others follow
135
- the usual conventions of web editors, and every toolbar tooltip shows its shortcut. The toolbar
136
- follows the WAI-ARIA *Toolbar* pattern: <kbd>Alt</kbd>+<kbd>F10</kbd> moves the focus from the
137
- text to the toolbar, the arrow keys move between buttons, <kbd>Esc</kbd> goes back to the text.
138
- Every popover works with the keyboard alone and <kbd>Esc</kbd> closes it.
139
-
140
- ## Usage
34
+ ## Quick start
141
35
 
142
36
  ```sh
143
37
  npm install stilo-editor
144
38
  ```
145
39
 
40
+ ```html
41
+ <div id="toolbar"></div>
42
+ <div id="editor"></div>
43
+ ```
44
+
146
45
  ```ts
147
46
  import 'stilo-editor/style.css';
148
47
  import { Editor, defaultExtensions } from 'stilo-editor';
@@ -159,69 +58,108 @@ createToolbar(editor, { container: document.querySelector<HTMLElement>('#toolbar
159
58
  editor.getHTML(); // '<p>Hello <strong>world</strong></p>'
160
59
  ```
161
60
 
162
- Options, commands, events, extensions and the toolbar: **[API reference](docs/API.md)**.
163
- Without the default toolbar, import `stilo-editor/content.css` instead of `style.css` and build
164
- your own controls on the same API.
61
+ ✅ That is the whole setup.
62
+
63
+ ### Want your own UI?
64
+
65
+ Skip the toolbar: the core is headless.
66
+
67
+ ```ts
68
+ import 'stilo-editor/content.css';
69
+ import { Editor, defaultExtensions } from 'stilo-editor';
70
+
71
+ const editor = new Editor({
72
+ element: document.querySelector<HTMLElement>('#editor')!,
73
+ extensions: [defaultExtensions()],
74
+ });
75
+
76
+ document.querySelector('#bold')!.addEventListener('click', () => editor.commands.toggleBold());
77
+ document.querySelector('#undo')!.addEventListener('click', () => editor.commands.undo());
78
+ ```
165
79
 
166
- ## Support
80
+ Commands, button states and events: [API reference](docs/API.md).
167
81
 
168
- - **Bugs and feature requests:** open an
169
- [issue](https://github.com/kojak8791/stilo-editor/issues/new/choose); the forms ask for what
170
- is needed to reproduce a bug (steps, Stilo version, browser).
171
- - **Questions:** open an issue too, with a short title that says what you are trying to do.
172
- - **Security vulnerabilities:** never in a public issue; report them privately as described in
173
- [SECURITY.md](SECURITY.md).
82
+ ### React
174
83
 
175
- Contributions are welcome: start from [CONTRIBUTING.md](CONTRIBUTING.md).
84
+ ```tsx
85
+ import 'stilo-editor/style.css';
86
+ import { StiloEditor } from 'stilo-editor/react';
176
87
 
177
- ## Roadmap
88
+ <StiloEditor content="<p>Hello</p>" onInput={({ editor }) => save(editor.getHTML())} />;
89
+ ```
178
90
 
179
- | Milestone | Content | Status |
180
- |---|---|---|
181
- | M0 — Foundations | Docs, scaffolding, core contracts, HTML pipeline, test helpers, `Bold` reference extension | Done |
182
- | M1 — MVP | Core editing, history, basic marks and blocks, lists, paste cleanup, default toolbar, demo, benchmarks | Done |
183
- | M2 — v1.0 | Preview releases `0.x` on npm, then tables, images, fonts and colors, productivity tools up to `1.0.0` | In progress |
184
- | M3 — Later | Real-time collaboration (Yjs), framework wrappers, embeds | Planned |
91
+ React 18 and 19, server-side rendering included; `useStiloEditor` for your own UI:
92
+ [React guide](docs/frameworks/react.md).
185
93
 
186
- Full feature list and priorities in [docs/SPEC.md](docs/SPEC.md).
94
+ ### Vue
187
95
 
188
- ## How it is built
96
+ ```vue
97
+ <script setup>
98
+ import 'stilo-editor/style.css';
99
+ import { StiloEditor } from 'stilo-editor/vue';
100
+ </script>
189
101
 
190
- Stilo is designed and owned by Alessio Coggiola and developed with AI assistance
191
- ([Claude](https://claude.com/claude-code)). Every decision is written down before the code
192
- ([docs/](docs/), [ADRs](docs/decisions/)), every change goes through the quality gate
193
- (typecheck, lint, about 950 unit tests including XSS vectors) and is tried in the demo before
194
- it is merged, and performance is measured against explicit budgets (`npm run bench`).
102
+ <template>
103
+ <StiloEditor content="<p>Hello</p>" @input="({ editor }) => save(editor.getHTML())" />
104
+ </template>
105
+ ```
195
106
 
196
- | Document | Content |
197
- |---|---|
198
- | [API.md](docs/API.md) | API reference for applications |
199
- | [VISION.md](docs/VISION.md) | Why and what |
200
- | [SPEC.md](docs/SPEC.md) | Features, non-functional requirements, roadmap |
201
- | [ARCHITECTURE.md](docs/ARCHITECTURE.md) | Stack, file layout, extension API, HTML contract |
202
- | [UI.md](docs/UI.md) | Design of the default UI |
203
- | [PERFORMANCE.md](docs/PERFORMANCE.md) | Performance in practice, how it is measured, limits |
204
- | [BACKLOG.md](docs/BACKLOG.md) | Tasks done and planned |
205
- | [AGENTS.md](AGENTS.md) | Rules for contributors and coding agents |
206
- | [CONTRIBUTING.md](CONTRIBUTING.md) | How to report bugs and propose changes |
207
- | [SECURITY.md](SECURITY.md) | How to report a vulnerability |
107
+ Vue 3.4 and later, server-side rendering included; `useStiloEditor` for your own UI:
108
+ [Vue guide](docs/frameworks/vue.md).
208
109
 
209
- ## Development
110
+ ### Svelte
210
111
 
211
- Requires Node.js 22 or later.
112
+ ```svelte
113
+ <script>
114
+ import 'stilo-editor/style.css';
115
+ import { StiloEditor } from 'stilo-editor/svelte';
116
+ </script>
212
117
 
213
- ```sh
214
- npm install
215
- npm run check # typecheck + lint + unit tests
216
- npm run dev # demo page
217
- npm run site # public site (published on GitHub Pages from main)
218
- npm run bench # performance benchmarks (needs: npx playwright install chromium)
219
- npm run test:e2e # E2E tests in Chromium, Firefox and WebKit (needs: npx playwright install)
118
+ <StiloEditor content="<p>Hello</p>" oninput={({ editor }) => save(editor.getHTML())} />
220
119
  ```
221
120
 
222
- ## License
121
+ A component for Svelte 5, actions for Svelte 4 and 5, server-side rendering included:
122
+ [Svelte guide](docs/frameworks/svelte.md).
123
+
124
+ ## What's included
223
125
 
224
- [MIT](LICENSE) © 2026 Alessio Coggiola
126
+ - **Text:** bold, italic, underline, strikethrough, code, sub/superscript, fonts, sizes,
127
+ colors, highlight, text case
128
+ - **Blocks:** headings, quotes, code blocks, alignment, line spacing, move up/down
129
+ - **Lists:** bulleted, numbered, checklists
130
+ - **Tables:** insert, merge and split, row and column handles, resizable columns
131
+ - **Images:** paste and resize
132
+ - **Tools:** links, find and replace, special characters and emoji, word count
133
+
134
+ Markdown shortcuts as you type, a keyboard shortcut for everything, a toolbar you can drive
135
+ from the keyboard alone. Every feature, shortcut and option: [All features](docs/FEATURES.md).
136
+
137
+ ## Why Stilo
138
+
139
+ - **Clean, safe HTML.** HTML in, semantic HTML out, through a strict allowlist of tags,
140
+ attributes and styles.
141
+ - **Paste from Word and Google Docs.** Real lists, kept tables, no stray fonts and colors.
142
+ - **Big documents welcome.** Typing stays instant at 10,000 paragraphs
143
+ ([numbers](docs/PERFORMANCE.md)).
144
+ - **Tested in Chromium, Firefox and WebKit**, with TypeScript types included.
145
+ - **Nothing behind a paywall.** Tables, paste from Word, find and replace: all in the free,
146
+ MIT-licensed package.
147
+
148
+ ## Status
149
+
150
+ `0.x` preview: it works, but APIs may change in minor versions until `1.0.0`.
151
+ Read the [changelog](CHANGELOG.md) before updating.
152
+ Next: real-time collaboration ([roadmap](docs/SPEC.md)).
153
+
154
+ ## Contributing
155
+
156
+ Bugs, ideas and questions: [issues](https://github.com/kojak8791/stilo-editor/issues/new/choose).
157
+ Code: [CONTRIBUTING.md](CONTRIBUTING.md). Vulnerabilities: [SECURITY.md](SECURITY.md).
158
+
159
+ Designed by Alessio Coggiola and built with [Claude](https://claude.com/claude-code), from a
160
+ written [architecture](docs/ARCHITECTURE.md) and [decision records](docs/decisions/).
161
+
162
+ ## License
225
163
 
226
- The logo wordmark is drawn from [IBM Plex](https://github.com/IBM/plex), licensed under the
227
- [SIL Open Font License 1.1](https://openfontlicense.org).
164
+ [MIT](LICENSE) © 2026 Alessio Coggiola. Logo wordmark from
165
+ [IBM Plex](https://github.com/IBM/plex) ([OFL 1.1](https://openfontlicense.org)).
@@ -0,0 +1,66 @@
1
+ import { Q as e, t } from "./src-DxTXpULc.js";
2
+ import { h as n, t as r } from "./ui-WFNvrkbM.js";
3
+ //#region src/svelte/editor-action.ts
4
+ function i(t, n) {
5
+ let { onready: r, ...i } = n, o = new e({
6
+ ...i,
7
+ element: t
8
+ });
9
+ return r?.(o), {
10
+ update: (e) => a(o, e.editable),
11
+ destroy: () => o.destroy()
12
+ };
13
+ }
14
+ function a(e, t) {
15
+ let n = t ?? !0;
16
+ e.isEditable !== n && e.setEditable(n);
17
+ }
18
+ //#endregion
19
+ //#region src/svelte/editor-with-ui-action.ts
20
+ var o = {
21
+ input: "oninput",
22
+ selectionchange: "onselectionchange",
23
+ transaction: "ontransaction",
24
+ focus: "onfocus",
25
+ blur: "onblur"
26
+ };
27
+ function s(i, s = {}) {
28
+ let u = s, { extensions: d = [t()], toolbar: f = !0, statusBar: p = !1, messages: m, onready: h, ...g } = s, _ = f ? c(i) : null, v = c(i), y = p ? c(i) : null, b = new e({
29
+ ...g,
30
+ extensions: d,
31
+ element: v
32
+ });
33
+ _ && r(b, {
34
+ ...f === !0 ? {} : f,
35
+ container: _,
36
+ messages: m
37
+ }), y && n(b, {
38
+ container: y,
39
+ messages: m
40
+ });
41
+ for (let e of Object.keys(o)) l(b, e, () => u);
42
+ return h?.(b), {
43
+ update: (e) => {
44
+ u = e, a(b, e.editable);
45
+ },
46
+ destroy: () => {
47
+ b.destroy();
48
+ for (let e of [
49
+ _,
50
+ v,
51
+ y
52
+ ]) e?.remove();
53
+ }
54
+ };
55
+ }
56
+ function c(e) {
57
+ return e.appendChild(document.createElement("div"));
58
+ }
59
+ function l(e, t, n) {
60
+ e.on(t, (e) => {
61
+ let r = n()[o[t]];
62
+ r?.(e);
63
+ });
64
+ }
65
+ //#endregion
66
+ export { i as n, s as t };
package/dist/content.css CHANGED
@@ -152,6 +152,15 @@ li.ProseMirror-selectednode:after {
152
152
 
153
153
  /* Stilo content defaults */
154
154
 
155
+ /*
156
+ * The editable area gets some room around the text and a visible height when empty.
157
+ * `:where()` has no specificity, so any rule of the page replaces these values.
158
+ */
159
+ :where(.stilo-content.ProseMirror) {
160
+ min-height: 6em;
161
+ padding: 12px 16px;
162
+ }
163
+
155
164
  /* Lists: list items hold paragraphs, which must not add their own margins. */
156
165
  .stilo-content ul,
157
166
  .stilo-content ol {
@@ -94,7 +94,10 @@ export declare class Editor {
94
94
  * the name is unknown.
95
95
  */
96
96
  getAttributes(name: string): Record<string, unknown>;
97
- /** Switches read-only mode on/off. */
97
+ /**
98
+ * Switches read-only mode on/off. A change dispatches an empty transaction, so the UIs that
99
+ * refresh on `transaction` follow it.
100
+ */
98
101
  setEditable(editable: boolean): void;
99
102
  /** Focuses the editor; optionally moves the cursor to the start or end of the document. */
100
103
  focus(position?: 'start' | 'end'): void;
@@ -107,8 +110,15 @@ export declare class Editor {
107
110
  /** Emits `destroy`, destroys the view, removes all handlers. Calling it twice is a no-op. */
108
111
  destroy(): void;
109
112
  private createState;
110
- /** Builds `commands` from the extension commands; `can` runs without `dispatch`. */
113
+ /** Builds `commands` from the extension commands; `can` does not apply them. */
111
114
  private createRunners;
115
+ /**
116
+ * Runs `command`, or only tells whether it would apply. In read-only mode a command that
117
+ * changes the document or the stored marks does not apply and returns `false`; the others
118
+ * (selection, search) do. To know, the command builds its transaction: `can` costs as much
119
+ * as a run there.
120
+ */
121
+ private runCommand;
112
122
  private setContent;
113
123
  /**
114
124
  * Removes the DOM selection when it lies inside the editor. Replacing many nodes while the
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { B as Blockquote, z as Bold, R as BulletList, L as ClearFormatting, I as Code, F as CodeBlock, X as DEFAULT_PRIORITY, P as Document, N as DropCursor, Q as Editor, M as GapCursor, j as HardBreak, A as Heading, k as Highlight, O as History, D as HorizontalRule, E as Image, T as Italic, w as LineHeight, C as Link, S as ListItem, x as MoveBlock, b as OrderedList, y as Paragraph, v as Placeholder, g as Search, nt as StiloError, h as Strike, m as Subscript, p as Superscript, d as Table, u as TaskItem, l as TaskList, c as Text, s as TextAlign, o as TextCase, r as TextStyle, n as Underline, $ as attrsMatch, t as defaultExtensions, Z as defineExtension, i as getFontSize, _ as getSearchResults, f as getTableHeaders, V as getWordCount, U as insertBlock, W as insertBlockNode, et as isMarkActive, tt as isNodeActive, Y as markInputRule, G as setMark, H as textDecorationRules, a as toFontSize, J as toggleList, K as toggleMark, q as unsetMark } from "./src-CV6pgfTS.js";
1
+ export { B as Blockquote, z as Bold, R as BulletList, L as ClearFormatting, I as Code, F as CodeBlock, X as DEFAULT_PRIORITY, P as Document, N as DropCursor, Q as Editor, M as GapCursor, j as HardBreak, A as Heading, k as Highlight, O as History, D as HorizontalRule, E as Image, T as Italic, w as LineHeight, C as Link, S as ListItem, x as MoveBlock, b as OrderedList, y as Paragraph, v as Placeholder, g as Search, nt as StiloError, h as Strike, m as Subscript, p as Superscript, d as Table, u as TaskItem, l as TaskList, c as Text, s as TextAlign, o as TextCase, r as TextStyle, n as Underline, $ as attrsMatch, t as defaultExtensions, Z as defineExtension, i as getFontSize, _ as getSearchResults, f as getTableHeaders, V as getWordCount, U as insertBlock, W as insertBlockNode, et as isMarkActive, tt as isNodeActive, Y as markInputRule, G as setMark, H as textDecorationRules, a as toFontSize, J as toggleList, K as toggleMark, q as unsetMark } from "./src-DxTXpULc.js";
@@ -0,0 +1,5 @@
1
+ /** Public API of the Stilo React wrapper. */
2
+ /** The editor with the default UI. */
3
+ export { StiloEditor, type StiloEditorEventProps, type StiloEditorProps } from './stilo-editor';
4
+ /** The editor alone, for a custom UI. */
5
+ export { type UseStiloEditorOptions, type UseStiloEditorResult, useStiloEditor, } from './use-stilo-editor';
@@ -0,0 +1,37 @@
1
+ import { type ReactElement } from 'react';
2
+ import { type Editor, type EditorEvents, type EditorOptions, type EventHandler } from '../index';
3
+ import { type Messages, type ToolbarOptions } from '../ui/index';
4
+ /** Editor events forwarded to the props of {@link StiloEditor}. */
5
+ declare const EVENT_PROPS: {
6
+ readonly input: 'onInput';
7
+ readonly selectionchange: 'onSelectionChange';
8
+ readonly transaction: 'onTransaction';
9
+ readonly focus: 'onFocus';
10
+ readonly blur: 'onBlur';
11
+ };
12
+ type ForwardedEvent = keyof typeof EVENT_PROPS;
13
+ /** Event props of {@link StiloEditor}: `onInput` for `input`, and so on. */
14
+ export type StiloEditorEventProps = {
15
+ [E in ForwardedEvent as (typeof EVENT_PROPS)[E]]?: EventHandler<EditorEvents[E]>;
16
+ };
17
+ /** Props of {@link StiloEditor}. */
18
+ export interface StiloEditorProps extends Omit<EditorOptions, 'element' | 'extensions'>, StiloEditorEventProps {
19
+ /** Extensions to load. Default: `[defaultExtensions()]`. */
20
+ extensions?: EditorOptions['extensions'];
21
+ /** The default toolbar: `true` (default), `false`, or its options without `container`. */
22
+ toolbar?: boolean | Omit<ToolbarOptions, 'container' | 'messages'>;
23
+ /** The status bar with the word and character count, under the editor. Default: `false`. */
24
+ statusBar?: boolean;
25
+ /** Strings of the toolbar and the status bar. Default: `en`. */
26
+ messages?: Messages;
27
+ /** Class of the element that holds the toolbar, the editor and the status bar. */
28
+ className?: string;
29
+ /** Called once the editor is created, with the editor. */
30
+ onReady?: (editor: Editor) => void;
31
+ }
32
+ /**
33
+ * The editor with the default UI. The props are read once, when the editor is created, except
34
+ * `editable` and the event handlers, which follow their changes.
35
+ */
36
+ export declare function StiloEditor(props: StiloEditorProps): ReactElement;
37
+ export {};
@@ -0,0 +1,15 @@
1
+ import { Editor, type EditorOptions } from '../index';
2
+ /** Options of {@link useStiloEditor}: the editor options without `element`. */
3
+ export type UseStiloEditorOptions = Omit<EditorOptions, 'element'>;
4
+ /** What {@link useStiloEditor} returns. */
5
+ export interface UseStiloEditorResult {
6
+ /** Ref of the element that holds the editor. */
7
+ ref: (element: HTMLElement | null) => void;
8
+ /** The editor; `null` before the element is mounted, on the server and after unmount. */
9
+ editor: Editor | null;
10
+ }
11
+ /**
12
+ * Creates an editor inside the element that gets `ref` and destroys it when the element goes.
13
+ * The options are read once, when the editor is created; only `editable` follows its changes.
14
+ */
15
+ export declare function useStiloEditor(options: UseStiloEditorOptions): UseStiloEditorResult;
package/dist/react.js ADDED
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { Q as e, t } from "./src-DxTXpULc.js";
3
+ import { h as n, t as r } from "./ui-WFNvrkbM.js";
4
+ import { createElement as i, useEffect as a, useRef as o, useState as s } from "react";
5
+ //#region src/react/use-stilo-editor.ts
6
+ function c(t) {
7
+ let [n, r] = s(null), [i, c] = s(null), l = o(t);
8
+ a(() => {
9
+ if (!n) return;
10
+ let t = new e({
11
+ ...l.current,
12
+ element: n
13
+ });
14
+ return c(t), () => {
15
+ t.destroy(), c(null);
16
+ };
17
+ }, [n]);
18
+ let u = t.editable ?? !0;
19
+ return a(() => {
20
+ i && !i.isDestroyed && i.isEditable !== u && i.setEditable(u);
21
+ }, [i, u]), {
22
+ ref: r,
23
+ editor: i
24
+ };
25
+ }
26
+ //#endregion
27
+ //#region src/react/stilo-editor.ts
28
+ var l = {
29
+ input: "onInput",
30
+ selectionchange: "onSelectionChange",
31
+ transaction: "onTransaction",
32
+ focus: "onFocus",
33
+ blur: "onBlur"
34
+ };
35
+ function u(e) {
36
+ let [u] = s(() => ({
37
+ extensions: e.extensions ?? [t()],
38
+ toolbar: e.toolbar ?? !0,
39
+ statusBar: e.statusBar ?? !1,
40
+ messages: e.messages
41
+ })), { ref: f, editor: p } = c({
42
+ ...e,
43
+ extensions: u.extensions
44
+ }), m = o(null), h = o(null), g = o(e);
45
+ return a(() => {
46
+ g.current = e;
47
+ }), a(() => {
48
+ if (!p || p.isDestroyed) return;
49
+ let { toolbar: e, statusBar: t, messages: i } = u, a, o;
50
+ e && m.current && (a = r(p, {
51
+ ...e === !0 ? {} : e,
52
+ container: m.current,
53
+ messages: i
54
+ })), t && h.current && (o = n(p, {
55
+ container: h.current,
56
+ messages: i
57
+ }));
58
+ let s = Object.keys(l).map((e) => d(p, e, g));
59
+ return g.current.onReady?.(p), () => {
60
+ if (!p.isDestroyed) {
61
+ a?.destroy(), o?.destroy();
62
+ for (let e of s) e();
63
+ }
64
+ };
65
+ }, [p, u]), i("div", { className: e.className }, u.toolbar ? i("div", { ref: m }) : null, i("div", { ref: f }), u.statusBar ? i("div", { ref: h }) : null);
66
+ }
67
+ function d(e, t, n) {
68
+ let r = (e) => {
69
+ let r = n.current[l[t]];
70
+ r?.(e);
71
+ };
72
+ return e.on(t, r), () => e.off(t, r);
73
+ }
74
+ //#endregion
75
+ export { u as StiloEditor, c as useStiloEditor };