@rathkit/rte-react 0.0.0 → 0.1.0-alpha.3

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
@@ -1,11 +1,43 @@
1
1
  # @rathkit/rte-react
2
2
 
3
- This version is an empty placeholder. It holds no code.
3
+ > **Alpha** (0.1.0-alpha.0). Not for production: the APIs will change between releases. Published to the `next` tag only.
4
4
 
5
- @rathkit/rte-react is in alpha. Install the alpha with the `next` tag:
5
+ The editor for React: a component and hooks over the headless layer. A component re-renders only when the value it reads changes. No editor or accessibility logic lives here.
6
+
7
+ ## Install
6
8
 
7
9
  ```sh
8
- npm install @rathkit/rte-react@next
10
+ npm install @rathkit/rte-react@next @rathkit/rte-core@next @rathkit/rte-headless@next
11
+ ```
12
+
13
+ This package has no dependencies of its own. It works with `@rathkit/rte-core`, `@rathkit/rte-headless`, `react` (peer dependencies, all optional so that npm installs nothing you did not ask for), so install the ones you use alongside it; `react` comes from your own project.
14
+
15
+ ## Example
16
+
17
+ ```jsx
18
+ import { RteEditor, useCommand } from "@rathkit/rte-react";
19
+ import { useState } from "react";
20
+
21
+ function Bold({ editor }) {
22
+ const bold = useCommand(editor, "format.bold");
23
+ return <button aria-pressed={bold.active} onClick={() => editor.commands.run("format.bold")}>Bold</button>;
24
+ }
25
+
26
+ export function Field() {
27
+ const [editor, setEditor] = useState();
28
+ return (
29
+ <>
30
+ <Bold editor={editor} />
31
+ <RteEditor label="Body" defaultValue="<p>Hello</p>" onReady={setEditor} onChange={(html) => console.log(html)} />
32
+ </>
33
+ );
34
+ }
9
35
  ```
10
36
 
11
- About the project: https://rathkit.com
37
+ ## More
38
+
39
+ Documentation: <https://rathkit.com>. Source and issues: <https://github.com/rathkit/rte>. What is checked: [SUPPORT.md](https://github.com/rathkit/rte/blob/main/docs/SUPPORT.md). Changes: [CHANGELOG.md](https://github.com/rathkit/rte/blob/main/CHANGELOG.md). How a release is verified: [VERIFY.md](https://github.com/rathkit/rte/blob/main/docs/VERIFY.md).
40
+
41
+ ## License
42
+
43
+ [Apache-2.0](LICENSE)
@@ -0,0 +1,22 @@
1
+ import type { Editor } from "@rathkit/rte-core/dom";
2
+ import { type StandardOptions } from "@rathkit/rte-core/standard";
3
+ export type RteOptions = Omit<StandardOptions, "element" | "html" | "readOnly">;
4
+ export interface RteEditorProps extends RteOptions {
5
+ /** The starting HTML. Later changes to this prop are ignored: call `editor.setHtml` to replace the document. */
6
+ defaultValue?: string;
7
+ readOnly?: boolean;
8
+ /** The name screen readers announce for the editing surface. Required: an unnamed editor is an accessibility fault. */
9
+ label: string;
10
+ className?: string;
11
+ /** Called with the HTML after each change that alters it. */
12
+ onChange?: (html: string) => void;
13
+ /** Called once the editor exists, with the editor to run commands on and subscribe to. */
14
+ onReady?: (editor: Editor) => void;
15
+ }
16
+ /** The hook behind `RteEditor`, for those who draw their own surface: give `ref` to one element. */
17
+ export declare function useRteEditor(props: Omit<RteEditorProps, "label" | "className">): {
18
+ ref: (element: HTMLElement | null) => void;
19
+ editor: Editor | undefined;
20
+ };
21
+ /** The editing surface as a component. */
22
+ export declare function RteEditor(props: RteEditorProps): unknown;
package/dist/editor.js ADDED
@@ -0,0 +1,36 @@
1
+ import { createElement, useEffect, useRef, useState } from "react";
2
+ import { createEditor } from "@rathkit/rte-core/standard";
3
+ export function useRteEditor(props) {
4
+ const [element, ref] = useState(null);
5
+ const [editor, setEditor] = useState(undefined);
6
+ const latest = useRef(props);
7
+ latest.current = props;
8
+ useEffect(() => {
9
+ if (!element)
10
+ return;
11
+ const { defaultValue, readOnly, onChange: _c, onReady: _r, ...options } = latest.current;
12
+ const made = createEditor({ ...options, element, html: defaultValue ?? "", readOnly: readOnly ?? false });
13
+ let last = made.getHtml();
14
+ const stop = made.subscribe(() => {
15
+ const html = made.getHtml();
16
+ if (html === last)
17
+ return;
18
+ last = html;
19
+ latest.current.onChange?.(html);
20
+ });
21
+ setEditor(made);
22
+ latest.current.onReady?.(made);
23
+ return () => {
24
+ stop();
25
+ made.destroy();
26
+ setEditor(undefined);
27
+ };
28
+ }, [element]);
29
+ useEffect(() => { editor?.setReadOnly(props.readOnly ?? false); }, [editor, props.readOnly]);
30
+ return { ref, editor };
31
+ }
32
+ export function RteEditor(props) {
33
+ const { label, className, ...rest } = props;
34
+ const { ref } = useRteEditor(rest);
35
+ return createElement("div", { ref, "aria-label": label, className, "data-rk-surface": "" });
36
+ }
@@ -0,0 +1,11 @@
1
+ import { type ExternalStore, type HeadlessEditor } from "@rathkit/rte-headless";
2
+ import type { CommandState, EditorSnapshot } from "@rathkit/rte-core";
3
+ /** Reads an external store (any of `commandStore`, `selectStore`, `snapshotStore`) as React state. */
4
+ export declare function useStore<T>(store: ExternalStore<T>): T;
5
+ /** One command's state: whether it can run, whether it is on, its value. Before the editor exists it is off and disabled. */
6
+ export declare function useCommand(editor: HeadlessEditor | undefined, name: string): CommandState;
7
+ /**
8
+ * One value worked out from the editor state, such as a word count. The component re-renders only when that value changes. Give a
9
+ * stable `selector` (declare it outside the component or wrap it in `useCallback`), or the store is rebuilt on every render.
10
+ */
11
+ export declare function useSelector<T>(editor: HeadlessEditor | undefined, selector: (snapshot: EditorSnapshot) => T, equals?: (a: T, b: T) => boolean): T | undefined;
package/dist/hooks.js ADDED
@@ -0,0 +1,15 @@
1
+ import { useMemo, useSyncExternalStore } from "react";
2
+ import { commandStore, selectStore } from "@rathkit/rte-headless";
3
+ const idle = Object.freeze({ enabled: false, active: false, value: undefined });
4
+ const nothing = { subscribe: () => () => undefined, getSnapshot: () => undefined };
5
+ export function useStore(store) {
6
+ return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
7
+ }
8
+ export function useCommand(editor, name) {
9
+ const store = useMemo(() => (editor ? commandStore(editor, name) : { subscribe: nothing.subscribe, getSnapshot: () => idle }), [editor, name]);
10
+ return useStore(store);
11
+ }
12
+ export function useSelector(editor, selector, equals) {
13
+ const store = useMemo(() => (editor ? selectStore(editor, selector, equals) : { subscribe: nothing.subscribe, getSnapshot: () => undefined }), [editor, selector, equals]);
14
+ return useStore(store);
15
+ }
@@ -0,0 +1,2 @@
1
+ export { useCommand, useSelector, useStore } from "./hooks.ts";
2
+ export { RteEditor, useRteEditor, type RteEditorProps, type RteOptions } from "./editor.ts";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { useCommand, useSelector, useStore } from "./hooks.js";
2
+ export { RteEditor, useRteEditor } from "./editor.js";
package/package.json CHANGED
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "name": "@rathkit/rte-react",
3
- "version": "0.0.0",
4
- "description": "Placeholder. @rathkit/rte-react is in alpha: install it with the next tag.",
3
+ "version": "0.1.0-alpha.3",
4
+ "description": "The editor for React: a component and hooks over the headless layer, with no editor or accessibility logic of its own.",
5
+ "keywords": [
6
+ "rich-text",
7
+ "editor",
8
+ "wysiwyg",
9
+ "contenteditable",
10
+ "zero-dependencies",
11
+ "typescript",
12
+ "react",
13
+ "hooks"
14
+ ],
5
15
  "license": "Apache-2.0",
6
16
  "homepage": "https://rathkit.com",
7
17
  "repository": {
@@ -12,7 +22,59 @@
12
22
  "bugs": {
13
23
  "url": "https://github.com/rathkit/rte/issues"
14
24
  },
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "engines": {
28
+ "node": ">=22.14.0"
29
+ },
15
30
  "publishConfig": {
16
- "access": "public"
31
+ "access": "public",
32
+ "provenance": true,
33
+ "tag": "next"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "NOTICE",
38
+ "!dist/**/*.map"
39
+ ],
40
+ "exports": {
41
+ ".": {
42
+ "types": "./dist/index.d.ts",
43
+ "default": "./dist/index.js"
44
+ }
45
+ },
46
+ "peerDependencies": {
47
+ "@rathkit/rte-core": ">=0.1.0-alpha.3 <0.2.0",
48
+ "@rathkit/rte-headless": ">=0.1.0-alpha.3 <0.2.0",
49
+ "react": ">=18"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "@rathkit/rte-core": {
53
+ "optional": true
54
+ },
55
+ "@rathkit/rte-headless": {
56
+ "optional": true
57
+ },
58
+ "react": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "@rathkit/rte-core": "workspace:*",
64
+ "@rathkit/rte-headless": "workspace:*"
65
+ },
66
+ "scripts": {
67
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && tsc -p tsconfig.build.js.json",
68
+ "typecheck": "tsc -p tsconfig.json",
69
+ "test": "node --import ./test/register.mjs --test \"src/**/*.test.ts\""
70
+ },
71
+ "rathkit": {
72
+ "budgets": [
73
+ {
74
+ "name": "react",
75
+ "entry": "dist/index.js",
76
+ "gzipKB": 2
77
+ }
78
+ ]
17
79
  }
18
80
  }