stilo-editor 0.2.1 → 0.3.1

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
@@ -20,6 +20,9 @@
20
20
 
21
21
  <p align="center">
22
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> ·
23
26
  <a href="docs/API.md"><strong>API</strong></a> ·
24
27
  <a href="docs/FEATURES.md"><strong>All features</strong></a>
25
28
  </p>
@@ -76,6 +79,48 @@ document.querySelector('#undo')!.addEventListener('click', () => editor.commands
76
79
 
77
80
  Commands, button states and events: [API reference](docs/API.md).
78
81
 
82
+ ### React
83
+
84
+ ```tsx
85
+ import 'stilo-editor/style.css';
86
+ import { StiloEditor } from 'stilo-editor/react';
87
+
88
+ <StiloEditor content="<p>Hello</p>" onInput={({ editor }) => save(editor.getHTML())} />;
89
+ ```
90
+
91
+ React 18 and 19, server-side rendering included; `useStiloEditor` for your own UI:
92
+ [React guide](docs/frameworks/react.md).
93
+
94
+ ### Vue
95
+
96
+ ```vue
97
+ <script setup>
98
+ import 'stilo-editor/style.css';
99
+ import { StiloEditor } from 'stilo-editor/vue';
100
+ </script>
101
+
102
+ <template>
103
+ <StiloEditor content="<p>Hello</p>" @input="({ editor }) => save(editor.getHTML())" />
104
+ </template>
105
+ ```
106
+
107
+ Vue 3.4 and later, server-side rendering included; `useStiloEditor` for your own UI:
108
+ [Vue guide](docs/frameworks/vue.md).
109
+
110
+ ### Svelte
111
+
112
+ ```svelte
113
+ <script>
114
+ import 'stilo-editor/style.css';
115
+ import { StiloEditor } from 'stilo-editor/svelte';
116
+ </script>
117
+
118
+ <StiloEditor content="<p>Hello</p>" oninput={({ editor }) => save(editor.getHTML())} />
119
+ ```
120
+
121
+ A component for Svelte 5, actions for Svelte 4 and 5, server-side rendering included:
122
+ [Svelte guide](docs/frameworks/svelte.md).
123
+
79
124
  ## What's included
80
125
 
81
126
  - **Text:** bold, italic, underline, strikethrough, code, sub/superscript, fonts, sizes,
@@ -104,7 +149,7 @@ from the keyboard alone. Every feature, shortcut and option: [All features](docs
104
149
 
105
150
  `0.x` preview: it works, but APIs may change in minor versions until `1.0.0`.
106
151
  Read the [changelog](CHANGELOG.md) before updating.
107
- Next: framework wrappers, real-time collaboration ([roadmap](docs/SPEC.md)).
152
+ Next: real-time collaboration ([roadmap](docs/SPEC.md)).
108
153
 
109
154
  ## Contributing
110
155
 
@@ -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 };