@stesura/react-wordgard 0.0.0-stage → 0.1.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/CHANGELOG.md +35 -0
- package/LICENSE.txt +21 -0
- package/README.md +450 -2
- package/THIRD_PARTY_LICENSES.txt +93 -0
- package/dist/ReactWordgard.d.ts +13 -0
- package/dist/ReactWordgard.js +96 -0
- package/dist/changes.d.ts +14 -0
- package/dist/changes.js +163 -0
- package/dist/components/ChildNodeViews.d.ts +8 -0
- package/dist/components/ChildNodeViews.js +38 -0
- package/dist/components/DecorationRoot.d.ts +7 -0
- package/dist/components/DecorationRoot.js +28 -0
- package/dist/components/ErrorBoundary.d.ts +18 -0
- package/dist/components/ErrorBoundary.js +16 -0
- package/dist/components/SeparatorHackView.d.ts +2 -0
- package/dist/components/SeparatorHackView.js +5 -0
- package/dist/components/ShapeView.d.ts +12 -0
- package/dist/components/ShapeView.js +26 -0
- package/dist/components/TextNodeView.d.ts +5 -0
- package/dist/components/TextNodeView.js +5 -0
- package/dist/components/TrailingHackView.d.ts +2 -0
- package/dist/components/TrailingHackView.js +5 -0
- package/dist/components/WidgetView.d.ts +10 -0
- package/dist/components/WidgetView.js +35 -0
- package/dist/components/WordgardDoc.d.ts +4 -0
- package/dist/components/WordgardDoc.js +34 -0
- package/dist/components/WordgardEditor.d.ts +7 -0
- package/dist/components/WordgardEditor.js +13 -0
- package/dist/components/WrapperView.d.ts +12 -0
- package/dist/components/WrapperView.js +26 -0
- package/dist/components/marks/MarkView.d.ts +11 -0
- package/dist/components/marks/MarkView.js +13 -0
- package/dist/components/marks/ReactMarkView.d.ts +10 -0
- package/dist/components/marks/ReactMarkView.js +32 -0
- package/dist/components/nodes/DefaultNodeView.d.ts +16 -0
- package/dist/components/nodes/DefaultNodeView.js +116 -0
- package/dist/components/nodes/DocNodeView.d.ts +7 -0
- package/dist/components/nodes/DocNodeView.js +6 -0
- package/dist/composition.d.ts +23 -0
- package/dist/composition.js +97 -0
- package/dist/contexts/DecorationContext.d.ts +15 -0
- package/dist/contexts/DecorationContext.js +38 -0
- package/dist/contexts/DecorationRootContext.d.ts +6 -0
- package/dist/contexts/DecorationRootContext.js +6 -0
- package/dist/contexts/EditorContext.d.ts +26 -0
- package/dist/contexts/EditorContext.js +87 -0
- package/dist/contexts/RenderStoreContext.d.ts +15 -0
- package/dist/contexts/RenderStoreContext.js +43 -0
- package/dist/contexts/editorEvents.d.ts +12 -0
- package/dist/contexts/editorEvents.js +95 -0
- package/dist/decorations/DecoIterator.d.ts +43 -0
- package/dist/decorations/DecoIterator.js +462 -0
- package/dist/decorations/ReactMarkType.d.ts +30 -0
- package/dist/decorations/ReactMarkType.js +48 -0
- package/dist/decorations/ReactShapeType.d.ts +49 -0
- package/dist/decorations/ReactShapeType.js +204 -0
- package/dist/decorations/ReactWidgetType.d.ts +19 -0
- package/dist/decorations/ReactWidgetType.js +69 -0
- package/dist/decorations/ReactWrapperType.d.ts +22 -0
- package/dist/decorations/ReactWrapperType.js +44 -0
- package/dist/decorations/iterDeco.d.ts +82 -0
- package/dist/decorations/iterDeco.js +409 -0
- package/dist/helpers/attributes.d.ts +2 -0
- package/dist/helpers/attributes.js +11 -0
- package/dist/helpers/index.d.ts +2 -0
- package/dist/helpers/index.js +2 -0
- package/dist/helpers/props.d.ts +8 -0
- package/dist/helpers/props.js +248 -0
- package/dist/helpers/refs.d.ts +6 -0
- package/dist/helpers/refs.js +20 -0
- package/dist/hooks/useEditorEffect.d.ts +3 -0
- package/dist/hooks/useEditorEffect.js +24 -0
- package/dist/hooks/useEditorEventCallback.d.ts +2 -0
- package/dist/hooks/useEditorEventCallback.js +13 -0
- package/dist/hooks/useEditorEventListener.d.ts +3 -0
- package/dist/hooks/useEditorEventListener.js +26 -0
- package/dist/hooks/useEditorLayoutEffect.d.ts +6 -0
- package/dist/hooks/useEditorLayoutEffect.js +38 -0
- package/dist/hooks/useEditorState.d.ts +1 -0
- package/dist/hooks/useEditorState.js +10 -0
- package/dist/hooks/useEditorStateSelector.d.ts +2 -0
- package/dist/hooks/useEditorStateSelector.js +6 -0
- package/dist/hooks/useIgnoreMutation.d.ts +2 -0
- package/dist/hooks/useIgnoreMutation.js +18 -0
- package/dist/hooks/useIsNodeSelected.d.ts +1 -0
- package/dist/hooks/useIsNodeSelected.js +6 -0
- package/dist/hooks/useMergedDOMRefs.d.ts +2 -0
- package/dist/hooks/useMergedDOMRefs.js +17 -0
- package/dist/hooks/useNodePos.d.ts +1 -0
- package/dist/hooks/useNodePos.js +8 -0
- package/dist/hooks/useSelectNode.d.ts +2 -0
- package/dist/hooks/useSelectNode.js +24 -0
- package/dist/hooks/useStopEvent.d.ts +2 -0
- package/dist/hooks/useStopEvent.js +20 -0
- package/dist/hooks/useWordgard.d.ts +1 -0
- package/dist/hooks/useWordgard.js +4 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/reactKeys.d.ts +39 -0
- package/dist/reactKeys.js +188 -0
- package/dist/tile.d.ts +32 -0
- package/dist/tile.js +1324 -0
- package/dist/viewRoots.d.ts +2 -0
- package/dist/viewRoots.js +8 -0
- package/dist/wordgardInternals.d.ts +211 -0
- package/dist/wordgardInternals.js +85 -0
- package/package.json +119 -4
- package/skills/react-wordgard/SKILL.md +397 -0
- package/skills/react-wordgard/references/wordgard.md +189 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## 0.1.0 (2026-11-03)
|
|
2
|
+
|
|
3
|
+
### New features
|
|
4
|
+
|
|
5
|
+
First release: React draws Wordgard's documents inside Wordgard's own update, in the app's React tree, and Wordgard keeps handling input, selection, composition and decorations.
|
|
6
|
+
|
|
7
|
+
Requires `wordgard` ~0.6.2 and React 18 or 19.
|
|
8
|
+
|
|
9
|
+
`WordgardEditor` creates an editor and provides it to its children; `WordgardDoc` puts it in the page and renders its document through a portal, so the app's contexts reach what it draws.
|
|
10
|
+
|
|
11
|
+
`createShape` makes a node's shape (`Decoration.Tag.shape`) a React component that renders the node's content as `children` and may change the elements around it on its own; its root is the node type's own outer element, and `shape.of(element, { atom? })` gives one for `Decoration.Point.shape` or another root.
|
|
12
|
+
|
|
13
|
+
A React shape can draw an atom (an image, a mention, a plot drawn as one), its component getting the node and its attributes, everything it draws chrome.
|
|
14
|
+
|
|
15
|
+
`createWrapper` does the same for `Decoration.Tag.wrapper` and `Decoration.Point.wrapper`, and `createMarkShape` for a mark's shape (`Decoration.Mark.shape`, or a mark type's own), each taking the component and an `element` for its root.
|
|
16
|
+
|
|
17
|
+
`createWidgetType` defines a Wordgard widget type drawn by a React component, usable wherever Wordgard takes a widget, its value and element types inferred from the component.
|
|
18
|
+
|
|
19
|
+
`DecorationRoot` draws the root of a React shape, wrapper, mark or widget: the tag its decoration says, Wordgard's attributes and the root's ref, merged with the component's own props, with no `forwardRef` needed on React 18.
|
|
20
|
+
|
|
21
|
+
A component can draw its root by hand instead, from the ref and Wordgard's attributes it gets as props, its style as an object to merge with its own.
|
|
22
|
+
|
|
23
|
+
`createShape`, `createWrapper` and `createMarkShape` give the same value for the same component (and `element`).
|
|
24
|
+
|
|
25
|
+
A component keeps its state through a mark change on its node, a retag of the node's opening token, and a move within its parent (the same node object deleted and inserted in one transaction); replacing the node, changing its type or moving it to another parent remounts it.
|
|
26
|
+
|
|
27
|
+
`useWordgard`, `useEditorState` and `useEditorStateSelector` read the editor and its state as of its last update.
|
|
28
|
+
|
|
29
|
+
`useEditorEffect` runs after the editor's update; `useEditorLayoutEffect` reads and writes within it, on the same paint as the document, a read returning `undefined` skipping its write; `useEditorEventCallback` gives a stable callback getting the editor.
|
|
30
|
+
|
|
31
|
+
`useEditorEventListener` handles the editor's DOM events before Wordgard's own handlers and keymaps.
|
|
32
|
+
|
|
33
|
+
`useNodePos`, `useStopEvent`, `useIgnoreMutation`, `useIsNodeSelected`, `useSelectNode` and `useMergedDOMRefs` give React decorations what react-prosemirror's node views had; `useStopEvent` is asked for events in a decoration's chrome, on its own elements and in React widgets, never in its content.
|
|
34
|
+
|
|
35
|
+
The package ships a skill for AI coding agents, `skills/react-wordgard/SKILL.md`, on building editors with Wordgard and react-wordgard, with a reference on Wordgard's model for ProseMirror developers.
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Edoardo Caratti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,451 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-wordgard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Under construction.** react-wordgard isn't ready for production yet, and its API may still
|
|
4
|
+
> change.
|
|
5
|
+
|
|
6
|
+
react-wordgard stays on 0.x for as long as Wordgard does: it follows Wordgard's API and reaches
|
|
7
|
+
into its internals, so it can't be more stable than Wordgard. Its 1.0 comes after Wordgard's.
|
|
8
|
+
|
|
9
|
+
A React view for [Wordgard](https://www.npmjs.com/package/wordgard): React draws Wordgard's
|
|
10
|
+
document, inside Wordgard's own update, and Wordgard keeps doing what it does (input, selection,
|
|
11
|
+
composition, decorations). The API is Wordgard's; react-wordgard adds what React needs to draw it.
|
|
12
|
+
|
|
13
|
+
This repository is derived from
|
|
14
|
+
[react-prosemirror](https://github.com/handlewithcarecollective/react-prosemirror),
|
|
15
|
+
by [Handle with Care Collective](https://github.com/handlewithcarecollective)
|
|
16
|
+
and originally The New York Times Company. Thanks to them for the design this
|
|
17
|
+
builds on.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @stesura/react-wordgard wordgard react react-dom
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Peer dependencies: `wordgard` 0.6 (`~0.6.2`: react-wordgard reaches into Wordgard's internals, so
|
|
26
|
+
only its patches), `react` and `react-dom` 18 or 19. An app must have one copy of `wordgard`, the
|
|
27
|
+
one react-wordgard uses. With TypeScript, Wordgard's own typings need `skipLibCheck`.
|
|
28
|
+
|
|
29
|
+
With an AI coding agent: the package ships a skill for building editors with Wordgard and
|
|
30
|
+
react-wordgard, `skills/react-wordgard/SKILL.md` (in `node_modules/@stesura/react-wordgard/`). Point your
|
|
31
|
+
agent to it, or copy the `react-wordgard` folder into your project's `.claude/skills/` for Claude
|
|
32
|
+
Code.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { useState } from "react";
|
|
38
|
+
import { Decoration, Wordgard } from "wordgard/editor";
|
|
39
|
+
// `image()` adds images to the schema; `Image` is their node type, which decorations name.
|
|
40
|
+
import { basicSchema, image } from "wordgard/schema";
|
|
41
|
+
import { Image } from "wordgard/types";
|
|
42
|
+
import {
|
|
43
|
+
DecorationRoot,
|
|
44
|
+
WordgardDoc,
|
|
45
|
+
WordgardEditor,
|
|
46
|
+
createShape,
|
|
47
|
+
useEditorStateSelector,
|
|
48
|
+
} from "@stesura/react-wordgard";
|
|
49
|
+
|
|
50
|
+
// An image's shape, drawn by a React component with state of its own: hovering outlines it.
|
|
51
|
+
// DecorationRoot is its root: the node type's element, an <img>, with the node's attributes (its
|
|
52
|
+
// `src` and `alt`, the selected node's class) and the component's own props.
|
|
53
|
+
const ImageShape = createShape(function ImageShape() {
|
|
54
|
+
const [hover, setHover] = useState(false);
|
|
55
|
+
return (
|
|
56
|
+
<DecorationRoot
|
|
57
|
+
style={hover ? { outline: "2px solid" } : undefined}
|
|
58
|
+
onMouseEnter={() => setHover(true)}
|
|
59
|
+
onMouseLeave={() => setHover(false)}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
function WordCount() {
|
|
65
|
+
const words = useEditorStateSelector((state) => state.doc.textContent().split(/\s+/).length);
|
|
66
|
+
return <output>{words} words</output>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function Editor() {
|
|
70
|
+
return (
|
|
71
|
+
<WordgardEditor
|
|
72
|
+
doc='<p>Hello <strong>world</strong> <img src="/cat.png" alt="A cat"></p>'
|
|
73
|
+
config={[
|
|
74
|
+
basicSchema(),
|
|
75
|
+
image(),
|
|
76
|
+
Decoration.Tag.shape(Image, ImageShape),
|
|
77
|
+
Wordgard.contentAttributes.of({ "aria-label": "Document" }),
|
|
78
|
+
]}
|
|
79
|
+
>
|
|
80
|
+
<WordCount />
|
|
81
|
+
<WordgardDoc className="editor" />
|
|
82
|
+
</WordgardEditor>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- `<WordgardEditor>` creates the editor from a `Wordgard.Spec` and provides it to what it holds.
|
|
88
|
+
The spec is read once, when it mounts: changing `doc` or `config` afterwards does nothing. The
|
|
89
|
+
editor owns its state: change it with `dispatch`, and its configuration with Wordgard's
|
|
90
|
+
compartments or `GardState.appendConfig`. `<WordgardDoc>` puts the editor in the page and
|
|
91
|
+
renders the document through a portal, in your React tree: your contexts reach React
|
|
92
|
+
decorations. One `<WordgardDoc>` per editor; a second one throws.
|
|
93
|
+
- `createShape`, `createWrapper`, `createMarkShape` and `createWidgetType` make React components
|
|
94
|
+
Wordgard decorations: below. Call them at module level, with components defined there.
|
|
95
|
+
`createShape`, `createWrapper` and `createMarkShape` give the same value for the same component
|
|
96
|
+
(and `element`), so a config built again is the same; a component defined inside a render is a
|
|
97
|
+
new one each time, and remounts everything it draws. The components don't need
|
|
98
|
+
memoizing: React draws a node again only when it changed (its content, its shape, its
|
|
99
|
+
attributes, its widget's value), or from the component's own state, contexts and hooks.
|
|
100
|
+
- Hooks, from inside `<WordgardEditor>`:
|
|
101
|
+
- `useWordgard()`: the editor.
|
|
102
|
+
- `useEditorState()`, `useEditorStateSelector(selector, isEqual?)`: the state as of the
|
|
103
|
+
editor's last flush, rendered in the same React commit as the document. The selector's result
|
|
104
|
+
re-renders when it changes, by `Object.is` or by `isEqual`.
|
|
105
|
+
- `useEditorEffect(effect, deps)`: an effect run once the editor's DOM is up to date, where it
|
|
106
|
+
may read layout (`coordsAtPos`) and dispatch.
|
|
107
|
+
- `useEditorEventCallback(callback)`: a stable callback getting the editor.
|
|
108
|
+
- `useEditorLayoutEffect({ read, write })`: reads in every update's read phase, writes in its write
|
|
109
|
+
phase, on the same paint as the document, for what must move with the text (markers in the
|
|
110
|
+
margin, pagination). It can't dispatch: Wordgard throws during its update. A `read` returning
|
|
111
|
+
`undefined` skips its write; when none asks for one, no write is scheduled, which spares
|
|
112
|
+
Wordgard measuring again. Reads run in the order the effects were registered: an engine whose
|
|
113
|
+
parts must run in another order registers one effect and drives them.
|
|
114
|
+
- `useEditorEventListener(type, (wg, event) => handled)`: the editor's DOM events of `type`
|
|
115
|
+
before Wordgard's handlers and keymaps; returning true handles the event. Key events during
|
|
116
|
+
a composition don't come; handling `input`, `beforeinput` or composition events skips
|
|
117
|
+
Wordgard's own handling of them.
|
|
118
|
+
- In React's StrictMode (development only), `<WordgardEditor>` creates its editor twice and
|
|
119
|
+
drops one, which is never put in the page.
|
|
120
|
+
- Don't read the editor's layout from a plain React effect: it runs inside Wordgard's update,
|
|
121
|
+
before the editor's view of the new DOM exists. Use `useEditorEffect` or `useEditorLayoutEffect`.
|
|
122
|
+
|
|
123
|
+
### React decorations
|
|
124
|
+
|
|
125
|
+
A node's shape, a wrapper, a mark's shape and a widget can each be a React component. Shapes,
|
|
126
|
+
wrappers and marks render what they hold as `children`; each can change the DOM around it (its
|
|
127
|
+
"chrome") on its own: from its state, a context or a store. Each factory returns Wordgard's own
|
|
128
|
+
value (a shape, a wrapper, a mark's shape, a widget type), registered as drawn by the component,
|
|
129
|
+
which you pass to Wordgard's decorations:
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { Decoration } from "wordgard/editor";
|
|
133
|
+
import { Blockquote, Link } from "wordgard/types";
|
|
134
|
+
import { DecorationRoot, createMarkShape, createShape } from "@stesura/react-wordgard";
|
|
135
|
+
|
|
136
|
+
// A blockquote drawn as a callout: a decorative icon, then the content.
|
|
137
|
+
const CalloutShape = createShape(function Callout({ nodeProps: { contentDOMRef }, children }) {
|
|
138
|
+
return (
|
|
139
|
+
<DecorationRoot>
|
|
140
|
+
<span aria-hidden="true">💡</span>
|
|
141
|
+
<div className="body" ref={contentDOMRef}>
|
|
142
|
+
{children}
|
|
143
|
+
</div>
|
|
144
|
+
</DecorationRoot>
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// A link, its target the mark's value.
|
|
149
|
+
const LinkMark = createMarkShape(
|
|
150
|
+
function LinkMark({ markProps: { mark, contentDOMRef }, children }) {
|
|
151
|
+
return (
|
|
152
|
+
<DecorationRoot href={String(mark.value)}>
|
|
153
|
+
<span ref={contentDOMRef}>{children}</span>
|
|
154
|
+
</DecorationRoot>
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
{ element: "a" }
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const config = [
|
|
161
|
+
Decoration.Tag.shape(Blockquote, CalloutShape),
|
|
162
|
+
Decoration.Mark.shape(Link, LinkMark),
|
|
163
|
+
];
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- `createShape(component)` returns a node's shape for `Decoration.Tag.shape`, a function of the
|
|
167
|
+
node's tag (a form `Decoration.Tag.shape` takes): the type's nodes are drawn by the component.
|
|
168
|
+
- Its root is the node type's own outer element, with its tag for each node (a heading's level
|
|
169
|
+
gives `h1` to `h6`) and its attributes.
|
|
170
|
+
- The component gets `nodeProps` (`node`, `getPos`, `contentDOMRef`) and `children` (the
|
|
171
|
+
content), which goes in the `contentDOMRef` element, else in the root. Define the component
|
|
172
|
+
inline, as a named function, and its props are typed for you.
|
|
173
|
+
- Only the outer element comes from the node type: the elements inside a structure shape (a
|
|
174
|
+
figure's `img`, a table's `tbody`) are the component's to draw, from `nodeProps.node`. Attributes
|
|
175
|
+
aimed at them land on the root, as a `target` that matches nothing picks the root (see "A shape,
|
|
176
|
+
a wrapper, or both").
|
|
177
|
+
- Where the node type draws an atom (a leaf: an image, a mention; a plot whose shape has no
|
|
178
|
+
hole), so does its React shape: `children` is empty, everything the component draws is chrome,
|
|
179
|
+
and a `contentDOMRef` throws.
|
|
180
|
+
- `shape.of(element, { atom? })` is the shape with `element` for its root, for
|
|
181
|
+
`Decoration.Point.shape` (which applies to whatever node follows, so no type says the element)
|
|
182
|
+
or a root other than the type's: `Decoration.Tag.shape(Blockquote, CalloutShape.of("aside"))`.
|
|
183
|
+
With `atom: true`, it has no hole: a leaf needs it (Wordgard refuses a hole in a leaf's shape),
|
|
184
|
+
and it draws a plot as an atom.
|
|
185
|
+
- `createWrapper(component, { element? })`, for `Decoration.Tag.wrapper` and
|
|
186
|
+
`Decoration.Point.wrapper`: the same props without `contentDOMRef`; `children` is what it
|
|
187
|
+
wraps (the node's shape, or the element its `target` picks). `element` is its root, `div` by
|
|
188
|
+
default: a wrapper is an element the node type doesn't have.
|
|
189
|
+
- `createMarkShape(component, { element? })`, for `Decoration.Mark.shape` or a mark type's own
|
|
190
|
+
`shape`: `markProps` (`mark`, the document's, `getPos`, `contentDOMRef`) and `children`. One
|
|
191
|
+
component draws each run of an equal mark. `element` is its root (`span` by default): a mark
|
|
192
|
+
shape is a static spec, so the element can't be read from the mark type. A React mark replacing
|
|
193
|
+
an attribute-shaped mark makes it element-shaped.
|
|
194
|
+
- The root is the element the decoration says, and stays that element. Wordgard applies
|
|
195
|
+
decorations to a shape before anything is drawn (`target` selectors match its tag, attributes
|
|
196
|
+
merge onto its elements), so the tag has to be known up front.
|
|
197
|
+
- `<DecorationRoot>` draws it, in a shape's, wrapper's or mark's component: that tag, with
|
|
198
|
+
Wordgard's attributes (the node type's own, the selection classes `wg-selected-node` and
|
|
199
|
+
`wg-selected-cell`, tag, point and mark attributes, `contenteditable` on an atom) and the root's
|
|
200
|
+
ref. The component's own props win, except `className`, joined with Wordgard's, and `style`,
|
|
201
|
+
merged with it (the component's keys winning). Its `ref` gets the root too. Without a
|
|
202
|
+
`contentDOMRef`, the content goes in it. One per component, at its root.
|
|
203
|
+
- Or the component draws its root by hand, for more control over the decoration's attributes:
|
|
204
|
+
below.
|
|
205
|
+
- A root with another tag than the decoration's logs an error, once per tag.
|
|
206
|
+
- The chrome is made uneditable unless it sets `contentEditable` itself.
|
|
207
|
+
|
|
208
|
+
#### A root drawn by hand
|
|
209
|
+
|
|
210
|
+
`DecorationRoot` applies all of Wordgard's attributes. To decide what reaches the root (drop one,
|
|
211
|
+
change one, join Wordgard's classes your way) or to type the root as its element, the component
|
|
212
|
+
draws the root itself. It gets Wordgard's attributes as props and a `ref` for the root:
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import { forwardRef } from "react";
|
|
216
|
+
import { type ShapeComponentProps, createShape } from "@stesura/react-wordgard";
|
|
217
|
+
|
|
218
|
+
// An image whose root drops the `title` a decoration gives it and adds a class of its own.
|
|
219
|
+
const ImageShape = createShape(
|
|
220
|
+
forwardRef<HTMLImageElement, ShapeComponentProps<HTMLImageElement>>(function ImageShape(
|
|
221
|
+
{ nodeProps, children, className, title, ...props },
|
|
222
|
+
ref
|
|
223
|
+
) {
|
|
224
|
+
return <img {...props} ref={ref} className={["picture", className].join(" ").trim()} />;
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
- Draw the element the decoration says (the node type's for a shape, `element` for a wrapper or
|
|
230
|
+
mark), spread the props, and put `ref` on it. On React 19 `ref` is a prop too, so `forwardRef`
|
|
231
|
+
isn't needed: `createShape<HTMLImageElement>(function ImageShape({ ref, ...props }) {…})`.
|
|
232
|
+
- `className` holds Wordgard's classes (the selection's among them): join yours with them, or
|
|
233
|
+
they're lost.
|
|
234
|
+
- `style` holds Wordgard's style as an object: spread yours over it,
|
|
235
|
+
`style={{ ...style, color: "red" }}`, or it replaces Wordgard's.
|
|
236
|
+
- One way or the other: props spread onto `DecorationRoot` would apply twice.
|
|
237
|
+
- `createWrapper` and `createMarkShape` components work the same way (`WrapperComponentProps`,
|
|
238
|
+
`MarkComponentProps`).
|
|
239
|
+
|
|
240
|
+
#### React widgets
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
import { Decoration } from "wordgard/editor";
|
|
244
|
+
import { Paragraph } from "wordgard/types";
|
|
245
|
+
import {
|
|
246
|
+
DecorationRoot,
|
|
247
|
+
type WidgetComponentProps,
|
|
248
|
+
createWidgetType,
|
|
249
|
+
} from "@stesura/react-wordgard";
|
|
250
|
+
|
|
251
|
+
// A pilcrow at the start of every paragraph.
|
|
252
|
+
const Pilcrow = createWidgetType({
|
|
253
|
+
component: function Pilcrow({ value }: WidgetComponentProps<string>) {
|
|
254
|
+
return <DecorationRoot aria-hidden="true">{value}</DecorationRoot>;
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const config = [Decoration.Tag.widget(Paragraph, "start", Pilcrow.of("¶"))];
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- `createWidgetType(spec)`, for a widget: at a position (`Decoration.Point.widget`), beside a node
|
|
262
|
+
(`Decoration.Tag.widget`), inside an `Elt` shape. It takes `Widget.define`'s spec, `component`
|
|
263
|
+
in place of `render` (`eq`, `editable`, `inFlow`, `propagateEvent`, `connect`, `disconnect` keep
|
|
264
|
+
their meaning), and returns a real `Widget.Type`: its widgets are `type.of(value)`.
|
|
265
|
+
- The component gets the `value`. Its root is a `DecorationRoot`, a `span` unless the spec gives
|
|
266
|
+
another `element` (`createWidgetType({ component, element: "div" })`): Wordgard doesn't look at
|
|
267
|
+
a widget's element, so it's the widget's choice. Or the component draws its root by hand, with
|
|
268
|
+
the `ref` it gets (`forwardRef` on React 18), as a shape's can.
|
|
269
|
+
- Widgets are uneditable unless their spec sets `editable`, as in Wordgard.
|
|
270
|
+
- A widget can also draw a node, as Wordgard allows:
|
|
271
|
+
`Decoration.Tag.shape(nodeType, (tag) => type.of(tag.param))`. `DecorationRoot` puts the node's
|
|
272
|
+
attributes on its root (a root drawn by hand gets them as props). A React shape is the usual
|
|
273
|
+
way.
|
|
274
|
+
|
|
275
|
+
#### Hooks inside React decorations
|
|
276
|
+
|
|
277
|
+
From a React shape's, wrapper's, mark's or widget's component:
|
|
278
|
+
|
|
279
|
+
- `useNodePos()`: a function returning the position of what it draws, to call in handlers and
|
|
280
|
+
effects.
|
|
281
|
+
- `useStopEvent((wg, event) => stop)`: asked for events in the decoration's chrome, on its own
|
|
282
|
+
elements (its root and the elements around its content element, unless the root is the content
|
|
283
|
+
element) and in React widgets; never for those in its content. For an event it stops, Wordgard's
|
|
284
|
+
handlers and keymaps are skipped and the browser's default goes ahead.
|
|
285
|
+
- `useIgnoreMutation((wg, record) => ignore)`: Wordgard doesn't read the DOM mutations it claims,
|
|
286
|
+
typing aside (Wordgard takes it from `beforeinput`).
|
|
287
|
+
- `useIsNodeSelected()`, `useSelectNode(select, deselect?)`: the node selection.
|
|
288
|
+
- `useMergedDOMRefs(...refs)`: one ref setting several, as for a root that's also the content
|
|
289
|
+
element.
|
|
290
|
+
|
|
291
|
+
### A shape, a wrapper, or both
|
|
292
|
+
|
|
293
|
+
- A node type's shape decides where attributes go: a mark's or an attribute decoration's `target`
|
|
294
|
+
or `preferTarget` picks an element in it. `ImageAlt` and `ImageSize` go on a figure's `img`
|
|
295
|
+
because the figure's shape has one. A React shape for the figure draws its `img` itself, so they
|
|
296
|
+
land on its root instead: to keep them on the `img`, keep the type's shape and add React around
|
|
297
|
+
it with a wrapper.
|
|
298
|
+
- React chrome around a node, or around an element in its shape, is a React wrapper:
|
|
299
|
+
`Decoration.Tag.wrapper(Figure, createWrapper(Frame), { target: "img" })` puts `Frame` around
|
|
300
|
+
the figure's `img`, which keeps its `src`, `alt` and width.
|
|
301
|
+
- A shape and wrappers register on the same node type and apply to all its nodes.
|
|
302
|
+
- For example: an image is the type's `img`, with a React frame around it for a resize handle. A
|
|
303
|
+
table is a React shape drawing `table > tbody > {children}`, with a wrapper for what goes outside
|
|
304
|
+
the table (a scroll container, column handles). A node taller than a page is clipped by a wrapper
|
|
305
|
+
whose component reads a store.
|
|
306
|
+
|
|
307
|
+
That's a step more than a react-prosemirror node view, which draws all of its node's DOM. Wordgard
|
|
308
|
+
applies decorations to the node type's shape before React draws, so an element decorations aim at
|
|
309
|
+
must be in that shape. A wrapper adds React around it without taking it over.
|
|
310
|
+
|
|
311
|
+
### Node data that changes
|
|
312
|
+
|
|
313
|
+
A component keeps its state while its node changes in place or moves within its parent, and
|
|
314
|
+
remounts, with everything inside it, when its node is replaced. So how an editing action changes a
|
|
315
|
+
node matters:
|
|
316
|
+
|
|
317
|
+
- Data an action changes on a node (an image's size, a block's alignment, a callout's kind, a
|
|
318
|
+
table's column widths) is a mark on its tag: a type made with `Mark.Type.define(name, { target })`,
|
|
319
|
+
read with `node.tag.mark(Type)`, changed with `{ from: pos, add: Type.of(value) }` and
|
|
320
|
+
`{ from: pos, remove: Type.of(value) }`. That's Wordgard's model: a mark change merges in
|
|
321
|
+
collaboration and inverts in history, and the component re-renders. Wordgard's `ImageSize` is
|
|
322
|
+
one; the demo's callout keeps its kind and title so (`demo/schema.ts`).
|
|
323
|
+
- What identifies the node (an image's `src`, a heading's level) is its parameter. Change it by
|
|
324
|
+
retagging, replacing the node's opening token by one of the same type, as Wordgard's commands
|
|
325
|
+
do: `{ from: pos, to: pos + 1, insert: [Heading.of(2)] }`. The component re-renders, and its
|
|
326
|
+
content keeps its components.
|
|
327
|
+
- Replacing the node whole (`{ from, to, insert: [node] }`) or changing its type remounts it.
|
|
328
|
+
- A move keeps the component, and the components inside, when the node stays in its parent: in one
|
|
329
|
+
transaction, it's deleted and the same node object is inserted elsewhere in that parent. A copy
|
|
330
|
+
(a new node, or the same one inserted while the original stays) mounts anew, and so does a node
|
|
331
|
+
moved to another parent. React point widgets inside a moved node are drawn anew, as Wordgard
|
|
332
|
+
redraws them. React moves the node's DOM, and a browser reloads an `<iframe>` on any
|
|
333
|
+
DOM move. Wordgard's drag-and-drop rebuilds what it drops from HTML, so its moves remount.
|
|
334
|
+
|
|
335
|
+
ProseMirror's `setNodeAttribute` and `setNodeMarkup` port to one of the first two.
|
|
336
|
+
|
|
337
|
+
### Accessibility
|
|
338
|
+
|
|
339
|
+
- Wordgard gives the editable element `role="textbox"` and `aria-multiline`. Name it with
|
|
340
|
+
`Wordgard.contentAttributes.of({ "aria-label": "…" })`: in Wordgard 0.6.2, `Wordgard.label`
|
|
341
|
+
puts the label on the editor's outer element, which leaves the textbox without a name.
|
|
342
|
+
- A React decoration's chrome is read inline, with the document's text. Hide what's decorative
|
|
343
|
+
(`aria-hidden` on icons and counters), and give every control a name (its text or
|
|
344
|
+
`aria-label`).
|
|
345
|
+
- Chrome is uneditable: a control in it needs to be focusable to be reached by keyboard, and
|
|
346
|
+
`useStopEvent` for its events, so that Wordgard leaves its keys alone.
|
|
347
|
+
- `DecorationRoot` puts the node's attributes on its root, an image's `alt` among them, in a React
|
|
348
|
+
shape or a widget drawing a node. A root drawn by hand gets them as props: spread them onto it.
|
|
349
|
+
- The tests run axe-core over editors with React shapes, marks and widgets
|
|
350
|
+
(`src/__tests__/accessibility.browser.test.tsx`): nothing breaks a rule plain Wordgard keeps.
|
|
351
|
+
|
|
352
|
+
### Limits
|
|
353
|
+
|
|
354
|
+
- A React decoration must keep its root element. Moving `{children}` under another element
|
|
355
|
+
remounts the content: the browser loses a selection or an IME composition in it.
|
|
356
|
+
- In a textblock or a mark, the content element holds only the content: chrome there would be text
|
|
357
|
+
that can't be told from the content's. Chrome next to a textblock's text (line numbers, a
|
|
358
|
+
marker) is a tag widget at `"start"` or `"end"`, a React widget that may read the shape's
|
|
359
|
+
context: `Decoration.Tag.widget(Paragraph, "start", LineNumber.of(null))`. Wordgard places the
|
|
360
|
+
caret around it. In a node holding blocks, chrome may sit before or after
|
|
361
|
+
the content in the same element, not between its nodes.
|
|
362
|
+
- A React wrapper can't wrap a node a native widget draws (draw it with a React shape or widget),
|
|
363
|
+
nor text.
|
|
364
|
+
- An attribute code sets by hand (a resize observer's, a layout engine's) on a React decoration's
|
|
365
|
+
root or structure stays through Wordgard's updates and the component's renders, until React sets
|
|
366
|
+
that attribute itself: a `classList.add` goes when React sets `className` again (the node gets
|
|
367
|
+
selected, a mark adds a class). On an element Wordgard draws, Wordgard's next update undoes it.
|
|
368
|
+
Draw such state from decorations or the component's state instead; in a decoration's content,
|
|
369
|
+
`useIgnoreMutation` keeps Wordgard from undoing a change it claims, until Wordgard redraws there.
|
|
370
|
+
- `useEditorEventListener` takes these event types: keyboard (`keydown`, `keyup`, `keypress`),
|
|
371
|
+
input (`beforeinput`, `input`, composition events), mouse (`mousedown`, `mouseup`, `mouseover`,
|
|
372
|
+
`mouseout`, `click`, `dblclick`, `contextmenu`), `focus`, `blur`, clipboard (`copy`, `cut`,
|
|
373
|
+
`paste`) and drag-and-drop events. Wordgard adds a handler per type when the editor is created;
|
|
374
|
+
scroll-blocking and the most frequent events (touch, wheel, `mousemove`, pointer events) are left
|
|
375
|
+
out.
|
|
376
|
+
- A node drawn by a React widget has no element around it for its attributes, unlike plain
|
|
377
|
+
Wordgard's: its component gets them as props, so a selection doesn't remount it.
|
|
378
|
+
- Typing with marks before there's text draws a mark from its own type, not a
|
|
379
|
+
`Decoration.Mark.shape` override; plain Wordgard does the same.
|
|
380
|
+
- Wordgard 0.6.2's own typings need `skipLibCheck`.
|
|
381
|
+
|
|
382
|
+
## Migrating from react-prosemirror
|
|
383
|
+
|
|
384
|
+
react-wordgard isn't a compatibility layer: it follows Wordgard's API, and most of a migration is
|
|
385
|
+
moving from ProseMirror's concepts to Wordgard's (see Wordgard's
|
|
386
|
+
[ProseMirror migration guide](https://wordgard.net/docs/prosemirror/)). What changes on the React
|
|
387
|
+
side:
|
|
388
|
+
|
|
389
|
+
| react-prosemirror | react-wordgard |
|
|
390
|
+
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
391
|
+
| `<ProseMirror state={…} dispatchTransaction={…}>` | `<WordgardEditor doc={…} config={…}>`, uncontrolled: the editor owns its state, changes go through `wg.dispatch` |
|
|
392
|
+
| `<ProseMirrorDoc />` | `<WordgardDoc />` |
|
|
393
|
+
| `plugins` prop | Wordgard extensions in `config`; to change them later, compartments or `GardState.appendConfig` |
|
|
394
|
+
| `nodeViews` prop and node view components | Tag decorations: `Decoration.Tag.shape(NodeType, createShape(component))` for any node, atoms included; `createWrapper` with `Decoration.Tag.wrapper` |
|
|
395
|
+
| `markViews` prop and mark view components | `createMarkShape` with `Decoration.Mark.shape` |
|
|
396
|
+
| `widget()` decorations with a component | `Decoration.Point.widget(MyReactWidget.of(value))` |
|
|
397
|
+
| `getPos` prop, `useNodePos` | `nodeProps.getPos`, `useNodePos()` (a function, not the position) |
|
|
398
|
+
| `useStopEvent` | the same name, for events in a React decoration's chrome, on its own elements or in a React widget, not in its content |
|
|
399
|
+
| `useIgnoreMutation` | the same name; chrome's and React widgets' mutations never reach Wordgard anyway |
|
|
400
|
+
| `useEditorState`, `useEditorStateSelector` | the same names, with the state of the last flush |
|
|
401
|
+
| `useEditorEffect` | the same name: runs after the editor's update; what it sets shows a frame later (below) |
|
|
402
|
+
| `useIsNodeSelected`, `useSelectNode` | the same names |
|
|
403
|
+
| `useMergedDOMRefs` | the same name |
|
|
404
|
+
| `useEditorEventCallback` | the same name, getting the editor |
|
|
405
|
+
| `useEditorEventListener` | the same name, before Wordgard's handlers and keymaps, for a fixed set of event types |
|
|
406
|
+
| `useLayoutGroupEffect` | `useEditorEffect`; `useEditorLayoutEffect` to measure, then write on the same paint |
|
|
407
|
+
| `reactKeys()` plugin | not needed: keys come from the changes Wordgard gives the view |
|
|
408
|
+
|
|
409
|
+
Not offered: `useIsComposingIn`, `useIsEditorStatic`.
|
|
410
|
+
|
|
411
|
+
### Migrating a node view
|
|
412
|
+
|
|
413
|
+
A react-prosemirror node view keeps its body: the props keep their names (`nodeProps`,
|
|
414
|
+
`contentDOMRef`, `children`, `ref`, the attributes). What changes:
|
|
415
|
+
|
|
416
|
+
- The props type: `ShapeComponentProps` (any node, atoms included), `WrapperComponentProps`,
|
|
417
|
+
`MarkComponentProps`.
|
|
418
|
+
- The root's tag isn't the component's choice: it's the node type's own element, what `toDOM`'s
|
|
419
|
+
outer element was (see "React decorations" for why).
|
|
420
|
+
- The root element, with its `{...props}` and `ref`, becomes `<DecorationRoot>`.
|
|
421
|
+
- `node.attrs` becomes the node's parameter (`node.tag.param`, `node.param` for a leaf) for what
|
|
422
|
+
identifies it, and marks on its tag (`node.tag.mark(Type)`) for data changed in place: see "Node
|
|
423
|
+
data that changes". `view` becomes `wg`, Wordgard's editor.
|
|
424
|
+
- Registration: `Decoration.Tag.shape(NodeType, createShape(component))` in the editor's `config`
|
|
425
|
+
instead of `nodeViews`.
|
|
426
|
+
- Class names: `ProseMirror-selectednode` is `wg-selected-node`, a selected cell's class
|
|
427
|
+
`wg-selected-cell`.
|
|
428
|
+
- `useNodePos()` returns a function giving the position, not the position.
|
|
429
|
+
- `innerDecorations` has no equivalent.
|
|
430
|
+
|
|
431
|
+
### Effects and timing
|
|
432
|
+
|
|
433
|
+
In react-prosemirror React drives: `useEditorEffect` is a React layout effect that runs after the
|
|
434
|
+
view's update, inside React's commit, so it can read layout, dispatch, and what it sets shows on the
|
|
435
|
+
same paint. In react-wordgard Wordgard drives: React commits inside Wordgard's update, and those
|
|
436
|
+
properties split between two hooks.
|
|
437
|
+
|
|
438
|
+
| | react-prosemirror `useEditorEffect` | `useEditorEffect` | `useEditorLayoutEffect` |
|
|
439
|
+
| ----------------------------------------- | ----------------------------------- | ---------------------------------- | ---------------------------------------------------- |
|
|
440
|
+
| Reads layout after the update | yes | yes | yes, in Wordgard's read phase |
|
|
441
|
+
| May dispatch | yes | yes | no |
|
|
442
|
+
| What it sets shows on the same paint | yes | no, a frame later | yes |
|
|
443
|
+
| Runs on every update without re-rendering | no | no | yes |
|
|
444
|
+
| For | everything | menus, focus, follow-up dispatches | what moves with the text: margin markers, pagination |
|
|
445
|
+
|
|
446
|
+
## Licence
|
|
447
|
+
|
|
448
|
+
MIT, © 2026 Edoardo Caratti, see [LICENSE.txt](LICENSE.txt).
|
|
449
|
+
|
|
450
|
+
Includes code from react-prosemirror (© Handle with Care Collective and The New
|
|
451
|
+
York Times Company) and Wordgard (© Marijn Haverbeke); their notices are in [THIRD_PARTY_LICENSES.txt](THIRD_PARTY_LICENSES.txt).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
react-wordgard includes code from the projects below. Their copyright and
|
|
2
|
+
license notices are reproduced here, as their licenses require.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
================================================================================
|
|
6
|
+
react-prosemirror
|
|
7
|
+
https://github.com/handlewithcarecollective/react-prosemirror
|
|
8
|
+
================================================================================
|
|
9
|
+
|
|
10
|
+
react-wordgard is derived from react-prosemirror, maintained by Handle with
|
|
11
|
+
Care Collective and originally created at The New York Times Company. Files
|
|
12
|
+
that came from it keep their "Copyright (c) The New York Times Company"
|
|
13
|
+
headers.
|
|
14
|
+
|
|
15
|
+
react-prosemirror was released under the Apache License 2.0 until 2026-10-02
|
|
16
|
+
(commit 1827d3a) and under the MIT License since then. Both notices follow.
|
|
17
|
+
|
|
18
|
+
--- MIT License (since 2026-10-02) ---------------------------------------------
|
|
19
|
+
|
|
20
|
+
MIT License
|
|
21
|
+
|
|
22
|
+
Copyright (c) 2026 Handle with Care Collective
|
|
23
|
+
|
|
24
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
25
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
26
|
+
in the Software without restriction, including without limitation the rights
|
|
27
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
28
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
29
|
+
furnished to do so, subject to the following conditions:
|
|
30
|
+
|
|
31
|
+
The above copyright notice and this permission notice shall be included in all
|
|
32
|
+
copies or substantial portions of the Software.
|
|
33
|
+
|
|
34
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
35
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
36
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
37
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
38
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
39
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
40
|
+
SOFTWARE.
|
|
41
|
+
|
|
42
|
+
--- Apache License 2.0 (until 2026-10-02) --------------------------------------
|
|
43
|
+
|
|
44
|
+
Copyright (c) 2022 The New York Times Company
|
|
45
|
+
Copyright (c) 2025 Handle with Care Collective
|
|
46
|
+
|
|
47
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
48
|
+
this library except in compliance with the License. You may obtain a copy of the
|
|
49
|
+
License at
|
|
50
|
+
|
|
51
|
+
www.apache.org/licenses/LICENSE-2.0
|
|
52
|
+
|
|
53
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
54
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
55
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
56
|
+
specific language governing permissions and limitations under the License.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
================================================================================
|
|
60
|
+
Wordgard
|
|
61
|
+
https://wordgard.net
|
|
62
|
+
================================================================================
|
|
63
|
+
|
|
64
|
+
test/wordgard/ holds copies of Wordgard's browser tests and their helpers;
|
|
65
|
+
demo/main.tsx is adapted from Wordgard's demo (demo/demo.ts), and the
|
|
66
|
+
IME emulation in src/__tests__/tile.browser.test.ts from its `compose` test
|
|
67
|
+
helper (test/webtest-composition.ts). src/decorations/DecoIterator.ts copies
|
|
68
|
+
Wordgard's decoration walker (src/editor/decoration.ts), with changes noted in
|
|
69
|
+
the file. src/tile.ts copies `setUneditable` from Wordgard's tile tree
|
|
70
|
+
(src/editor/tile.ts) and follows its tile builder there.
|
|
71
|
+
|
|
72
|
+
MIT License
|
|
73
|
+
|
|
74
|
+
Copyright (C) 2026 by Marijn Haverbeke <marijn@haverbeke.berlin>.
|
|
75
|
+
|
|
76
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
77
|
+
a copy of this software and associated documentation files (the
|
|
78
|
+
"Software"), to deal in the Software without restriction, including
|
|
79
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
80
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
81
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
82
|
+
the following conditions:
|
|
83
|
+
|
|
84
|
+
The above copyright notice and this permission notice shall be
|
|
85
|
+
included in all copies or substantial portions of the Software.
|
|
86
|
+
|
|
87
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
88
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
89
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
90
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
91
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
92
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
93
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Wordgard } from "wordgard/editor";
|
|
2
|
+
import { ReactDocTile } from "./tile.js";
|
|
3
|
+
import { type DOMObserver, DocTile } from "./wordgardInternals.js";
|
|
4
|
+
export declare class ReactWordgard extends Wordgard {
|
|
5
|
+
docTile: DocTile;
|
|
6
|
+
observer: DOMObserver;
|
|
7
|
+
scheduleFlush: () => void;
|
|
8
|
+
static create: (spec: Wordgard.Spec) => ReactWordgard;
|
|
9
|
+
static host: (spec: Wordgard.Spec) => ReactWordgard;
|
|
10
|
+
static DocTile: typeof ReactDocTile;
|
|
11
|
+
constructor(spec: Wordgard.Spec, hosted?: boolean);
|
|
12
|
+
flush(): void;
|
|
13
|
+
}
|