@stesura/react-wordgard 0.1.0 → 0.2.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/CHANGELOG.md +12 -0
- package/README.md +699 -237
- package/dist/ReactWordgard.js +2 -4
- package/dist/components/ChildNodeViews.js +2 -4
- package/dist/components/DecorationRoot.d.ts +6 -0
- package/dist/components/DecorationRoot.js +7 -4
- package/dist/components/ErrorBoundary.js +3 -5
- package/dist/components/SeparatorHackView.js +1 -2
- package/dist/components/ShapeView.js +2 -4
- package/dist/components/TextNodeView.js +2 -3
- package/dist/components/TrailingHackView.js +2 -3
- package/dist/components/WidgetView.js +2 -5
- package/dist/components/WordgardDoc.d.ts +5 -0
- package/dist/components/WordgardDoc.js +8 -6
- package/dist/components/WordgardEditor.d.ts +8 -0
- package/dist/components/WordgardEditor.js +10 -6
- package/dist/components/WordgardScope.d.ts +14 -0
- package/dist/components/WordgardScope.js +14 -0
- package/dist/components/WrapperView.js +1 -4
- package/dist/components/marks/MarkView.js +3 -6
- package/dist/components/marks/ReactMarkView.js +2 -4
- package/dist/components/nodes/DefaultNodeView.js +3 -7
- package/dist/components/nodes/DocNodeView.js +2 -3
- package/dist/composition.js +4 -6
- package/dist/contexts/DecorationContext.js +2 -5
- package/dist/contexts/DecorationRootContext.js +2 -4
- package/dist/contexts/EditorContext.d.ts +1 -0
- package/dist/contexts/EditorContext.js +3 -3
- package/dist/contexts/RenderStoreContext.js +3 -5
- package/dist/contexts/editorEvents.js +3 -6
- package/dist/decorations/ReactMarkType.d.ts +11 -0
- package/dist/decorations/ReactMarkType.js +6 -7
- package/dist/decorations/ReactShapeType.d.ts +16 -0
- package/dist/decorations/ReactShapeType.js +9 -11
- package/dist/decorations/ReactWidgetType.d.ts +10 -0
- package/dist/decorations/ReactWidgetType.js +6 -8
- package/dist/decorations/ReactWrapperType.d.ts +9 -0
- package/dist/decorations/ReactWrapperType.js +3 -6
- package/dist/decorations/iterDeco.js +3 -6
- package/dist/hooks/useEditorEffect.d.ts +7 -0
- package/dist/hooks/useEditorEffect.js +11 -7
- package/dist/hooks/useEditorEventCallback.d.ts +6 -0
- package/dist/hooks/useEditorEventCallback.js +7 -3
- package/dist/hooks/useEditorEventListener.d.ts +7 -0
- package/dist/hooks/useEditorEventListener.js +11 -11
- package/dist/hooks/useEditorLayoutEffect.d.ts +9 -0
- package/dist/hooks/useEditorLayoutEffect.js +13 -17
- package/dist/hooks/useEditorState.d.ts +5 -0
- package/dist/hooks/useEditorState.js +6 -4
- package/dist/hooks/useEditorStateSelector.d.ts +5 -0
- package/dist/hooks/useEditorStateSelector.js +5 -0
- package/dist/hooks/useIgnoreMutation.d.ts +5 -1
- package/dist/hooks/useIgnoreMutation.js +11 -10
- package/dist/hooks/useIsNodeSelected.d.ts +1 -0
- package/dist/hooks/useIsNodeSelected.js +3 -4
- package/dist/hooks/useMergedDOMRefs.d.ts +4 -0
- package/dist/hooks/useMergedDOMRefs.js +5 -4
- package/dist/hooks/useNodePos.d.ts +3 -0
- package/dist/hooks/useNodePos.js +5 -6
- package/dist/hooks/useSelectNode.d.ts +4 -0
- package/dist/hooks/useSelectNode.js +5 -4
- package/dist/hooks/useStopEvent.d.ts +6 -0
- package/dist/hooks/useStopEvent.js +8 -7
- package/dist/hooks/useWordgard.d.ts +1 -0
- package/dist/hooks/useWordgard.js +1 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/tile.js +3 -8
- package/dist/viewRoots.js +2 -3
- package/dist/wordgardInternals.js +2 -5
- package/package.json +1 -1
- package/skills/react-wordgard/SKILL.md +138 -10
package/dist/ReactWordgard.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
// Adapted from react-prosemirror's ReactEditorView, to fit Wordgard: it
|
|
2
|
-
//
|
|
3
|
-
// the docView ProseMirror's constructor builds, has the base constructor build
|
|
4
|
-
// a tile tree React renders.
|
|
1
|
+
// Adapted from react-prosemirror's ReactEditorView, to fit Wordgard: it extends Wordgard, and its
|
|
2
|
+
// base constructor builds a tile tree React renders, where ReactEditorView replaces the docView.
|
|
5
3
|
import { Wordgard } from "wordgard/editor";
|
|
6
4
|
import { GardState } from "wordgard/state";
|
|
7
5
|
import { editorOf } from "./contexts/EditorContext.js";
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
// Named after react-prosemirror's ChildNodeViews, rewritten for Wordgard: draws
|
|
2
|
-
//
|
|
3
|
-
// React widgets. Native widgets aren't React's: ReactDocTile puts their DOM in
|
|
4
|
-
// place after the commit.
|
|
1
|
+
// Named after react-prosemirror's ChildNodeViews, rewritten for Wordgard: draws what iterDeco lays
|
|
2
|
+
// out. Native widgets aren't React's: ReactDocTile puts their DOM in place after the commit.
|
|
5
3
|
import React, { memo } from "react";
|
|
6
4
|
import { cellOf } from "../contexts/RenderStoreContext.js";
|
|
7
5
|
import { isReactWidget } from "../decorations/ReactWidgetType.js";
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { type AllHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
/** `DecorationRoot`'s props: its root's attributes, and its children. */
|
|
2
3
|
export type DecorationRootProps = AllHTMLAttributes<HTMLElement> & {
|
|
3
4
|
children?: ReactNode;
|
|
4
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* The root element of a React shape, wrapper, mark or widget: the tag the decoration says, with
|
|
8
|
+
* Wordgard's attributes merged with the props. See [React
|
|
9
|
+
* decorations](https://github.com/stesura/react-wordgard#react-decorations).
|
|
10
|
+
*/
|
|
5
11
|
export declare const DecorationRoot: import("react").ForwardRefExoticComponent<AllHTMLAttributes<HTMLElement> & {
|
|
6
12
|
children?: ReactNode;
|
|
7
13
|
} & import("react").RefAttributes<Element>>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// can't get them wrong. The component's own props win, but `className` joins
|
|
4
|
-
// Wordgard's and `style` merges, its keys winning. Not in react-prosemirror.
|
|
1
|
+
// Not in react-prosemirror. The component's own props win, but `className` joins Wordgard's and
|
|
2
|
+
// `style` merges, its keys winning.
|
|
5
3
|
import { createElement, forwardRef, useContext, useMemo, } from "react";
|
|
6
4
|
import { DecorationRootContext } from "../contexts/DecorationRootContext.js";
|
|
7
5
|
import { cssToStyle, htmlAttrsToReactProps } from "../helpers/props.js";
|
|
8
6
|
import { mergeRefs, toCallbackRef } from "../helpers/refs.js";
|
|
7
|
+
/**
|
|
8
|
+
* The root element of a React shape, wrapper, mark or widget: the tag the decoration says, with
|
|
9
|
+
* Wordgard's attributes merged with the props. See [React
|
|
10
|
+
* decorations](https://github.com/stesura/react-wordgard#react-decorations).
|
|
11
|
+
*/
|
|
9
12
|
export const DecorationRoot = forwardRef(({ className, style, ...props }, ref) => {
|
|
10
13
|
const root = useContext(DecorationRootContext);
|
|
11
14
|
const rootRef = root?.ref;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
// Not in react-prosemirror: catches
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// tiles can't be built over what didn't commit. A class: React has no hook for
|
|
5
|
-
// this.
|
|
1
|
+
// Not in react-prosemirror: catches a render error for ReactDocTile to throw from the update. React
|
|
2
|
+
// 18 rethrows it from flushSync, React 19 reports it and commits nothing. A class: React has no
|
|
3
|
+
// hook for this.
|
|
6
4
|
import { Component } from "react";
|
|
7
5
|
export class ErrorBoundary extends Component {
|
|
8
6
|
state = { failed: false };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Named after react-prosemirror's SeparatorHackView, rewritten for Wordgard: the
|
|
2
|
-
// `<img class="wg-buffer">`
|
|
3
|
-
// (`Widget.img`), which gives the cursor somewhere to go.
|
|
2
|
+
// `<img class="wg-buffer">` (`Widget.img`) at the ends of an inline plot.
|
|
4
3
|
import React from "react";
|
|
5
4
|
export const SeparatorHackView = () => React.createElement("img", { className: "wg-buffer" });
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
// Named after react-prosemirror's ReactNodeView, for Wordgard: draws a React
|
|
2
|
-
//
|
|
3
|
-
// builds the tiles over what it commits; the component's own commits are
|
|
4
|
-
// sorted out of Wordgard's DOM observer (`sortRecords`).
|
|
1
|
+
// Named after react-prosemirror's ReactNodeView, for Wordgard: draws a React shape's component. Its
|
|
2
|
+
// own commits are sorted out of Wordgard's DOM observer (`sortRecords`).
|
|
5
3
|
import React, { useMemo, useState } from "react";
|
|
6
4
|
import { DecorationContext, createDecorationHandlers, decorationRoot, } from "../contexts/DecorationContext.js";
|
|
7
5
|
import { atomRef, createShapeHandle, drawShape, nodePos, shapeAttributes, } from "../decorations/ReactShapeType.js";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// Named after react-prosemirror's TextNodeView, rewritten for Wordgard: a text
|
|
2
|
-
//
|
|
3
|
-
// (ReactDocTile). Skipped while its text is the same.
|
|
1
|
+
// Named after react-prosemirror's TextNodeView, rewritten for Wordgard: a bare text node, whose
|
|
2
|
+
// tile ReactDocTile builds after the commit.
|
|
4
3
|
import { memo } from "react";
|
|
5
4
|
export const TextNodeView = memo(({ text }) => text);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// Named after react-prosemirror's TrailingHackView, rewritten for Wordgard: the
|
|
2
|
-
//
|
|
3
|
-
// (`Widget.br`).
|
|
1
|
+
// Named after react-prosemirror's TrailingHackView, rewritten for Wordgard: the `<br>` a textblock
|
|
2
|
+
// may need (`Widget.br`).
|
|
4
3
|
import React from "react";
|
|
5
4
|
export const TrailingHackView = () => React.createElement("br", null);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
// Named after react-prosemirror's WidgetView, rewritten for Wordgard
|
|
2
|
-
//
|
|
3
|
-
// ReactDocTile builds its tile after the commit, so there's no view
|
|
4
|
-
// description to register here. Skipped while the widget is `eq`, so the
|
|
5
|
-
// component keeps its DOM, as Wordgard keeps an equal widget's.
|
|
1
|
+
// Named after react-prosemirror's WidgetView, rewritten for Wordgard. Skipped while the widget is
|
|
2
|
+
// `eq`, so the component keeps its DOM, as Wordgard keeps an equal widget's.
|
|
6
3
|
import React, { memo, useMemo, useRef, useState } from "react";
|
|
7
4
|
import { DecorationContext, createDecorationHandlers, decorationRoot, } from "../contexts/DecorationContext.js";
|
|
8
5
|
import { DecorationRootContext } from "../contexts/DecorationRootContext.js";
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import React, { type HTMLAttributes } from "react";
|
|
2
2
|
type Props = Omit<HTMLAttributes<HTMLDivElement>, "children">;
|
|
3
|
+
/**
|
|
4
|
+
* Puts the editor in the page, inside a `div` taking the props, and renders the document through a
|
|
5
|
+
* portal, in your React tree. It goes under its `<WordgardEditor>`, inside any other components,
|
|
6
|
+
* with other children before or after it. One per editor: a second throws.
|
|
7
|
+
*/
|
|
3
8
|
export declare const WordgardDoc: (props: Props) => React.JSX.Element;
|
|
4
9
|
export {};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
// Named after react-prosemirror's ProseMirrorDoc, rewritten for Wordgard
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// the document shares the app's React tree and contexts. The portal commits
|
|
5
|
-
// first and builds the tiles; attaching the element then connects the editor,
|
|
6
|
-
// as Wordgard's own editor is built before it's connected.
|
|
1
|
+
// Named after react-prosemirror's ProseMirrorDoc, rewritten for Wordgard. The portal commits first
|
|
2
|
+
// and builds the tiles; attaching the element then connects the editor, as Wordgard's own editor is
|
|
3
|
+
// built before it's connected.
|
|
7
4
|
import React, { useCallback, useInsertionEffect, useLayoutEffect, useSyncExternalStore, } from "react";
|
|
8
5
|
import { createPortal } from "react-dom";
|
|
9
6
|
import { editorOf, useEditorContext } from "../contexts/EditorContext.js";
|
|
@@ -19,6 +16,11 @@ const DocPortal = ({ wg }) => {
|
|
|
19
16
|
useLayoutEffect(() => portal.mount(), [portal]);
|
|
20
17
|
return createPortal(element, wg.contentDOM);
|
|
21
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* Puts the editor in the page, inside a `div` taking the props, and renders the document through a
|
|
21
|
+
* portal, in your React tree. It goes under its `<WordgardEditor>`, inside any other components,
|
|
22
|
+
* with other children before or after it. One per editor: a second throws.
|
|
23
|
+
*/
|
|
22
24
|
export const WordgardDoc = (props) => {
|
|
23
25
|
const { wg } = useEditorContext();
|
|
24
26
|
const attach = useCallback((dom) => {
|
|
@@ -3,5 +3,13 @@ import type { Wordgard } from "wordgard/editor";
|
|
|
3
3
|
type Props = Omit<Wordgard.Spec, "parent"> & {
|
|
4
4
|
children?: ReactNode;
|
|
5
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Wordgard editor from the spec (`doc`, `config`, `selection`, `state`, `scrollTo`) and
|
|
8
|
+
* provides it to its children. It draws no DOM of its own. The spec is read once, on mount: from
|
|
9
|
+
* then on the editor owns its state (see [Accessing the state outside the
|
|
10
|
+
* editor](https://github.com/stesura/react-wordgard#accessing-the-state-outside-the-editor)). In
|
|
11
|
+
* React's StrictMode (development only), it creates its editor twice and drops one, which is never
|
|
12
|
+
* put in the page.
|
|
13
|
+
*/
|
|
6
14
|
export declare const WordgardEditor: ({ children, ...spec }: Props) => React.JSX.Element;
|
|
7
15
|
export {};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
// Named after react-prosemirror's ProseMirror component, rewritten for
|
|
2
|
-
//
|
|
3
|
-
// and the app's own components (menus, hooks). The spec is read once, as
|
|
4
|
-
// react-prosemirror's `defaultState`: Wordgard's editor owns its state, so
|
|
5
|
-
// changes go through `dispatch`, and configuration through Wordgard's
|
|
6
|
-
// compartments or `GardState.appendConfig`.
|
|
1
|
+
// Named after react-prosemirror's ProseMirror component, rewritten for Wordgard. The spec is read
|
|
2
|
+
// once, as react-prosemirror's `defaultState`: Wordgard's editor owns its state.
|
|
7
3
|
import React, { useState } from "react";
|
|
8
4
|
import { ReactWordgard } from "../ReactWordgard.js";
|
|
9
5
|
import { EditorContext, editorOf } from "../contexts/EditorContext.js";
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Wordgard editor from the spec (`doc`, `config`, `selection`, `state`, `scrollTo`) and
|
|
8
|
+
* provides it to its children. It draws no DOM of its own. The spec is read once, on mount: from
|
|
9
|
+
* then on the editor owns its state (see [Accessing the state outside the
|
|
10
|
+
* editor](https://github.com/stesura/react-wordgard#accessing-the-state-outside-the-editor)). In
|
|
11
|
+
* React's StrictMode (development only), it creates its editor twice and drops one, which is never
|
|
12
|
+
* put in the page.
|
|
13
|
+
*/
|
|
10
14
|
export const WordgardEditor = ({ children, ...spec }) => {
|
|
11
15
|
const [editor] = useState(() => editorOf(ReactWordgard.host(spec)));
|
|
12
16
|
return React.createElement(EditorContext.Provider, { value: editor }, children);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import type { Wordgard } from "wordgard/editor";
|
|
3
|
+
type Props = {
|
|
4
|
+
wg: Wordgard;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Gives the components it holds `wg`, an editor a `WordgardEditor` made elsewhere, in place of the
|
|
9
|
+
* one around them: for a toolbar acting on whichever of a page's editors has focus. The hooks under
|
|
10
|
+
* it follow when `wg` changes: selectors subscribe to the new editor's state, callbacks and
|
|
11
|
+
* listeners get it, effects run again. Any other editor throws.
|
|
12
|
+
*/
|
|
13
|
+
export declare const WordgardScope: ({ wg, children }: Props) => React.JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { EditorContext, findEditor } from "../contexts/EditorContext.js";
|
|
3
|
+
/**
|
|
4
|
+
* Gives the components it holds `wg`, an editor a `WordgardEditor` made elsewhere, in place of the
|
|
5
|
+
* one around them: for a toolbar acting on whichever of a page's editors has focus. The hooks under
|
|
6
|
+
* it follow when `wg` changes: selectors subscribe to the new editor's state, callbacks and
|
|
7
|
+
* listeners get it, effects run again. Any other editor throws.
|
|
8
|
+
*/
|
|
9
|
+
export const WordgardScope = ({ wg, children }) => {
|
|
10
|
+
const editor = findEditor(wg);
|
|
11
|
+
if (!editor)
|
|
12
|
+
throw new Error("WordgardScope takes an editor a WordgardEditor made");
|
|
13
|
+
return React.createElement(EditorContext.Provider, { value: editor }, children);
|
|
14
|
+
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// the element its `target` picks) as its children. Like ShapeView, it has no
|
|
3
|
-
// effects: ReactDocTile builds the tiles over what it commits, and the
|
|
4
|
-
// component's own commits are sorted out of Wordgard's DOM observer.
|
|
1
|
+
// Like ShapeView, no effects: ReactDocTile builds the tiles over what it commits.
|
|
5
2
|
import React, { useMemo, useState } from "react";
|
|
6
3
|
import { DecorationContext, createDecorationHandlers, decorationRoot, } from "../contexts/DecorationContext.js";
|
|
7
4
|
import { createWrapperHandle, nodePos, shapeAttributes } from "../decorations/ReactShapeType.js";
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
// Named after react-prosemirror's MarkView, rewritten for Wordgard:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// from its views, so it can be skipped while they keep their shape (iterDeco
|
|
5
|
-
// keeps a wrapper's unchanged views; a node that changed gets its view from
|
|
6
|
-
// its cell).
|
|
1
|
+
// Named after react-prosemirror's MarkView, rewritten for Wordgard: an element-shaped mark, the
|
|
2
|
+
// span of a text run's attribute-shaped marks, or a React mark. Memoised: iterDeco keeps a
|
|
3
|
+
// wrapper's unchanged views.
|
|
7
4
|
import React, { createElement, memo } from "react";
|
|
8
5
|
import { shapeOf } from "../../decorations/iterDeco.js";
|
|
9
6
|
import { attrsToRecord, htmlAttrsToReactProps } from "../../helpers/index.js";
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
// Named after react-prosemirror's ReactMarkView, rewritten for Wordgard
|
|
2
|
-
//
|
|
3
|
-
// effects: ReactDocTile builds the tiles over what it commits, and the
|
|
4
|
-
// component's own commits are sorted out of Wordgard's DOM observer.
|
|
1
|
+
// Named after react-prosemirror's ReactMarkView, rewritten for Wordgard. Like ShapeView, no
|
|
2
|
+
// effects: ReactDocTile builds the tiles over what it commits.
|
|
5
3
|
import React, { useMemo, useState } from "react";
|
|
6
4
|
import { DecorationContext, createDecorationHandlers, decorationRoot, } from "../../contexts/DecorationContext.js";
|
|
7
5
|
import { drawMark } from "../../decorations/ReactMarkType.js";
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
// Named after react-prosemirror's DefaultNodeView, rewritten for Wordgard: a
|
|
2
|
-
// node
|
|
3
|
-
//
|
|
4
|
-
// type has a shape, so there's no missing `toDOM` to check for. Widgets, as a
|
|
5
|
-
// node's shape or inside it, are drawn by their component when they're React
|
|
6
|
-
// widgets, else draw nothing: ReactDocTile puts their DOM in place. React
|
|
7
|
-
// shapes and wrappers in the shape are drawn by ShapeView and WrapperView.
|
|
1
|
+
// Named after react-prosemirror's DefaultNodeView, rewritten for Wordgard: a node renders as its
|
|
2
|
+
// decorated shape (buildNodeShape). Every Wordgard node type has a shape, so there's no missing
|
|
3
|
+
// `toDOM` to check for. Native widgets draw nothing: ReactDocTile puts their DOM in place.
|
|
8
4
|
import React, { Component, createElement } from "react";
|
|
9
5
|
import { Attributes, Elt } from "wordgard/doc";
|
|
10
6
|
import { isReactShape, shapeAttributes } from "../../decorations/ReactShapeType.js";
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// Named after react-prosemirror's DocNodeView, rewritten for Wordgard:
|
|
2
|
-
//
|
|
3
|
-
// is React's container.
|
|
1
|
+
// Named after react-prosemirror's DocNodeView, rewritten for Wordgard: renders straight into
|
|
2
|
+
// Wordgard's content element, React's container.
|
|
4
3
|
import React from "react";
|
|
5
4
|
import { ChildNodeViews } from "../ChildNodeViews.js";
|
|
6
5
|
export const DocNodeView = ({ views }) => React.createElement(ChildNodeViews, { views: views });
|
package/dist/composition.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// committed there, in the wrappers it committed around it; when the browser
|
|
6
|
-
// made it (or an element around it), React draws nothing in its place.
|
|
1
|
+
// Keeps a composition alive through Wordgard's flush, as Wordgard's ContentUpdate.composition does:
|
|
2
|
+
// the browser's text node stays, in a tile of its own, and React must not write to it. When React
|
|
3
|
+
// drew it, React keeps rendering what it last committed there; when the browser made it, React
|
|
4
|
+
// draws nothing in its place.
|
|
7
5
|
import { Attributes, Elt } from "wordgard/doc";
|
|
8
6
|
import { cursorWrappers, } from "./decorations/iterDeco.js";
|
|
9
7
|
import { markOfTile, markTiles } from "./decorations/ReactMarkType.js";
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
// The React decoration
|
|
2
|
-
//
|
|
3
|
-
// its node is selected, and the handlers its components register. It takes
|
|
4
|
-
// the place of react-prosemirror's GetPosContext, SelectNodeContext,
|
|
5
|
-
// StopEventContext and IgnoreMutationContext.
|
|
1
|
+
// The React decoration a component is drawn in, for the decoration hooks. Replaces
|
|
2
|
+
// react-prosemirror's GetPosContext, SelectNodeContext, StopEventContext and IgnoreMutationContext.
|
|
6
3
|
import { createContext, useContext } from "react";
|
|
7
4
|
export const DecorationContext = createContext(null);
|
|
8
5
|
export const useDecorationContext = (hook) => {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// react-prosemirror, whose node views draw their root themselves: Wordgard
|
|
4
|
-
// fixes the root's tag before React draws.
|
|
1
|
+
// Not in react-prosemirror, whose node views draw their root: Wordgard fixes the root's tag before
|
|
2
|
+
// React draws.
|
|
5
3
|
import { createContext } from "react";
|
|
6
4
|
export const DecorationRootContext = createContext(null);
|
|
@@ -21,6 +21,7 @@ type Editor = EditorContextValue & {
|
|
|
21
21
|
flushing: boolean;
|
|
22
22
|
};
|
|
23
23
|
export declare const editorOf: (wg: Wordgard) => Editor;
|
|
24
|
+
export declare const findEditor: (wg: Wordgard) => EditorContextValue | undefined;
|
|
24
25
|
export declare const EditorContext: import("react").Context<EditorContextValue | null>;
|
|
25
26
|
export declare const useEditorContext: () => EditorContextValue;
|
|
26
27
|
export {};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
// Named after react-prosemirror's EditorContext, rewritten for Wordgard
|
|
2
|
-
// editor, the state as of its last flush (react-prosemirror's state store),
|
|
3
|
-
// and what the hooks register to run around its flush. One value per editor,
|
|
1
|
+
// Named after react-prosemirror's EditorContext, rewritten for Wordgard. One value per editor,
|
|
4
2
|
// which never changes, so memoised components aren't scanned for it.
|
|
5
3
|
import { createContext, useContext } from "react";
|
|
6
4
|
import { flushSync } from "react-dom";
|
|
@@ -78,6 +76,8 @@ export const editorOf = (wg) => {
|
|
|
78
76
|
editors.set(wg, editor);
|
|
79
77
|
return editor;
|
|
80
78
|
};
|
|
79
|
+
// The editor's value when react-wordgard drives it (WordgardEditor made it).
|
|
80
|
+
export const findEditor = (wg) => editors.get(wg);
|
|
81
81
|
export const EditorContext = createContext(null);
|
|
82
82
|
export const useEditorContext = () => {
|
|
83
83
|
const context = useContext(EditorContext);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
// Named after our react-prosemirror fork's RenderStoreContext, rewritten for
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// memoised node views are never scanned for it. ReactDocTile writes it inside
|
|
5
|
-
// Wordgard's flush, before React renders.
|
|
1
|
+
// Named after our react-prosemirror fork's RenderStoreContext, rewritten for Wordgard: one object
|
|
2
|
+
// per content element, which ReactDocTile writes inside the flush. Its value never changes, so
|
|
3
|
+
// memoised node views aren't scanned for it.
|
|
6
4
|
import { createContext, useContext } from "react";
|
|
7
5
|
const cells = new WeakMap();
|
|
8
6
|
export const cellOf = (view) => {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
// The DOM events useEditorEventListener can listen to
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// Wordgard's listener for the type non-passive: the events that block
|
|
5
|
-
// scrolling (touch, wheel) aren't among them, nor the most frequent ones
|
|
6
|
-
// (mousemove, pointer events), each of which would then walk the tiles.
|
|
1
|
+
// The DOM events useEditorEventListener can listen to. Wordgard fixes its handlers' event types at
|
|
2
|
+
// creation, and a handler makes its listener non-passive: scroll-blocking events (touch, wheel) and
|
|
3
|
+
// frequent ones (mousemove, pointer events) are left out.
|
|
7
4
|
import { Wordgard } from "wordgard/editor";
|
|
8
5
|
import { GardState } from "wordgard/state";
|
|
9
6
|
export const editorEvents = [
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type AllHTMLAttributes, type ComponentType, type HTMLProps, type ReactElement, type ReactNode, type RefAttributes, type RefCallback } from "react";
|
|
2
2
|
import type { Elt, Mark } from "wordgard/doc";
|
|
3
3
|
import type { Tile } from "../wordgardInternals.js";
|
|
4
|
+
/**
|
|
5
|
+
* A React mark's props, for each run of an equal mark: `markProps.mark` is the document's mark.
|
|
6
|
+
*/
|
|
4
7
|
export type MarkComponentProps<E extends Element = HTMLElement> = AllHTMLAttributes<HTMLElement> & {
|
|
5
8
|
markProps: {
|
|
6
9
|
mark: Mark;
|
|
@@ -9,7 +12,12 @@ export type MarkComponentProps<E extends Element = HTMLElement> = AllHTMLAttribu
|
|
|
9
12
|
};
|
|
10
13
|
children: ReactNode;
|
|
11
14
|
} & RefAttributes<E>;
|
|
15
|
+
/** `createMarkShape`'s options. */
|
|
12
16
|
export type MarkShapeOptions = {
|
|
17
|
+
/**
|
|
18
|
+
* The component's root element, `span` by default, which plain Wordgard draws. It can't come from
|
|
19
|
+
* the mark type: a mark type's own `shape` is written before the type exists.
|
|
20
|
+
*/
|
|
13
21
|
element?: string;
|
|
14
22
|
};
|
|
15
23
|
type DrawProps = {
|
|
@@ -22,6 +30,9 @@ type MarkShape = {
|
|
|
22
30
|
element: string;
|
|
23
31
|
attributes: Record<string, string>;
|
|
24
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* A mark's shape, for `Decoration.Mark.shape(MarkType, shape)` or a mark type's own `shape`.
|
|
35
|
+
*/
|
|
25
36
|
export declare const createMarkShape: <E extends Element = HTMLElement>(component: ComponentType<MarkComponentProps<E>>, { element }?: MarkShapeOptions) => MarkShape;
|
|
26
37
|
export declare const isReactMark: (elt: Elt<unknown>) => boolean;
|
|
27
38
|
export declare const drawMark: (elt: Elt<unknown>, props: DrawProps) => ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
// Named after ReactWidgetType
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// marked content as `children`. Wordgard's mark shapes are declarative, so the
|
|
5
|
-
// registration is the reserved attribute React shapes use, which the element
|
|
6
|
-
// Wordgard makes from the shape keeps. The props follow react-prosemirror's
|
|
7
|
-
// MarkComponentProps.
|
|
1
|
+
// Named after ReactWidgetType. A React mark is a mark's element shape, registered as drawn by a
|
|
2
|
+
// component through the reserved attribute React shapes use: Wordgard's mark shapes are
|
|
3
|
+
// declarative. The props follow react-prosemirror's MarkComponentProps.
|
|
8
4
|
import { createElement, } from "react";
|
|
9
5
|
import { takesRef } from "../helpers/refs.js";
|
|
10
6
|
import { idOf, reserved } from "./ReactShapeType.js";
|
|
@@ -12,6 +8,9 @@ import { idOf, reserved } from "./ReactShapeType.js";
|
|
|
12
8
|
const draws = new Map();
|
|
13
9
|
// By component, then root element, as React shapes are.
|
|
14
10
|
const marks = new WeakMap();
|
|
11
|
+
/**
|
|
12
|
+
* A mark's shape, for `Decoration.Mark.shape(MarkType, shape)` or a mark type's own `shape`.
|
|
13
|
+
*/
|
|
15
14
|
export const createMarkShape = (component, { element = "span" } = {}) => {
|
|
16
15
|
let registered = marks.get(component);
|
|
17
16
|
if (!registered) {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { type AllHTMLAttributes, type ComponentType, type HTMLProps, type ReactElement, type ReactNode, type RefAttributes, type RefCallback } from "react";
|
|
2
2
|
import { type Node as DocNode, Elt } from "wordgard/doc";
|
|
3
|
+
/**
|
|
4
|
+
* A React shape's props: `nodeProps`, the content as `children`, and Wordgard's attributes and
|
|
5
|
+
* `ref` for its root. The content goes in the `contentDOMRef` element, else in the root; in an
|
|
6
|
+
* atom, `children` is empty and a `contentDOMRef` throws. With `<DecorationRoot>` as the root, the
|
|
7
|
+
* attributes and `ref` are taken care of; a root drawn by hand spreads them onto itself (see [A
|
|
8
|
+
* root drawn by hand](https://github.com/stesura/react-wordgard#a-root-drawn-by-hand)).
|
|
9
|
+
*/
|
|
3
10
|
export type ShapeComponentProps<E extends Element = HTMLElement> = AllHTMLAttributes<HTMLElement> & {
|
|
4
11
|
nodeProps: {
|
|
5
12
|
node: DocNode;
|
|
@@ -8,6 +15,11 @@ export type ShapeComponentProps<E extends Element = HTMLElement> = AllHTMLAttrib
|
|
|
8
15
|
};
|
|
9
16
|
children: ReactNode;
|
|
10
17
|
} & RefAttributes<E>;
|
|
18
|
+
/**
|
|
19
|
+
* What `createShape` returns: for `Decoration.Tag.shape`, a node's shape, its root the node type's
|
|
20
|
+
* own outer element, an atom where the type's shape is. `of` gives the shape with `element` for its
|
|
21
|
+
* root: for `Decoration.Point.shape`, or a root other than the type's.
|
|
22
|
+
*/
|
|
11
23
|
export type ReactShape = ((tag: DocNode.Tag) => Elt) & {
|
|
12
24
|
of: (element: string, options?: {
|
|
13
25
|
atom?: boolean;
|
|
@@ -20,6 +32,10 @@ type DrawProps = {
|
|
|
20
32
|
children: ReactNode;
|
|
21
33
|
ref: RefCallback<Element>;
|
|
22
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* A node's shape, for `Decoration.Tag.shape(NodeType, shape)`. `shape.of(element)` is the shape
|
|
37
|
+
* with another root element, also for `Decoration.Point.shape`.
|
|
38
|
+
*/
|
|
23
39
|
export declare const createShape: <E extends Element = HTMLElement>(component: ComponentType<ShapeComponentProps<E>>) => ReactShape;
|
|
24
40
|
export declare const idOf: (elt: Elt<unknown>) => string | null;
|
|
25
41
|
export declare const isReactShape: (elt: Elt<unknown>) => boolean;
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
// Named after ReactWidgetType
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// takes it from the tag's node type, per node (a heading's level). Without a
|
|
7
|
-
// hole, as for the type's own shape, it draws an atom. Wordgard copies an Elt
|
|
8
|
-
// when it adds attributes or wraps it, so the registration is a reserved
|
|
9
|
-
// attribute, which every copy keeps; React never renders it. The props follow
|
|
10
|
-
// react-prosemirror's node views (`nodeProps`, `contentDOMRef`, a forwarded
|
|
11
|
-
// ref), named after the decoration: Wordgard has no node views.
|
|
1
|
+
// Named after ReactWidgetType. A React shape is a real Wordgard `Elt`, registered as drawn by a
|
|
2
|
+
// component through a reserved attribute, which every copy Wordgard makes of it keeps and React
|
|
3
|
+
// never renders. Its tag is fixed before React renders (`target` selectors match it), so
|
|
4
|
+
// `createShape` gives `Decoration.Tag.shape` a function of the tag. The props follow
|
|
5
|
+
// react-prosemirror's node views (`nodeProps`, `contentDOMRef`, a forwarded ref).
|
|
12
6
|
import { createElement, } from "react";
|
|
13
7
|
import { Attributes, Elt } from "wordgard/doc";
|
|
14
8
|
import { ignoresMutation } from "../contexts/DecorationContext.js";
|
|
@@ -18,6 +12,10 @@ const draws = new Map();
|
|
|
18
12
|
// By component: calling createShape again with one gives the same shape, so
|
|
19
13
|
// Wordgard sees no configuration change and the registry doesn't grow.
|
|
20
14
|
const shapes = new WeakMap();
|
|
15
|
+
/**
|
|
16
|
+
* A node's shape, for `Decoration.Tag.shape(NodeType, shape)`. `shape.of(element)` is the shape
|
|
17
|
+
* with another root element, also for `Decoration.Point.shape`.
|
|
18
|
+
*/
|
|
21
19
|
export const createShape = (component) => {
|
|
22
20
|
const found = shapes.get(component);
|
|
23
21
|
if (found)
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { type AllHTMLAttributes, type ComponentType, type HTMLProps, type ReactElement, type RefAttributes, type RefCallback } from "react";
|
|
2
2
|
import { Attributes, Elt } from "wordgard/doc";
|
|
3
3
|
import { Widget } from "wordgard/editor";
|
|
4
|
+
/**
|
|
5
|
+
* A React widget's props: its `value`, and, for a widget drawing a node, the node's attributes for
|
|
6
|
+
* its root.
|
|
7
|
+
*/
|
|
4
8
|
export type WidgetComponentProps<Param, E extends Element = HTMLElement> = Omit<AllHTMLAttributes<HTMLElement>, "value"> & {
|
|
5
9
|
value: Param;
|
|
6
10
|
} & RefAttributes<E>;
|
|
11
|
+
/** `createWidgetType`'s spec: Wordgard's widget spec, with a component in place of `render`. */
|
|
7
12
|
export type WidgetTypeSpec<Param, E extends Element = HTMLElement> = Omit<Widget.Spec<Param>, "render"> & {
|
|
8
13
|
component: ComponentType<WidgetComponentProps<Param, E>>;
|
|
14
|
+
/**
|
|
15
|
+
* The root `DecorationRoot` draws in the component, `span` by default. Wordgard doesn't look at a
|
|
16
|
+
* widget's element, so it's the component's choice.
|
|
17
|
+
*/
|
|
9
18
|
element?: string;
|
|
10
19
|
};
|
|
20
|
+
/** A widget type whose widgets, `type.of(value)`, the component draws. */
|
|
11
21
|
export declare const createWidgetType: <Param, E extends Element = HTMLElement>(spec: WidgetTypeSpec<Param, E>) => Widget.Type<Param>;
|
|
12
22
|
export declare const createWidgetTypeWithRender: <Param, E extends Element = HTMLElement>(spec: WidgetTypeSpec<Param, E> & Pick<Widget.Spec<Param>, "render">) => Widget.Type<Param>;
|
|
13
23
|
export declare const isReactWidget: (widget: Widget) => boolean;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
// Named after react-prosemirror's ReactWidgetType, rewritten for Wordgard: a
|
|
2
|
-
//
|
|
3
|
-
// the
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// inside that element, so it may re-render on its own, but not swap the
|
|
7
|
-
// element itself: the parent's tile would put the old one back. An editable
|
|
8
|
-
// one the user edits keeps the edit, where Wordgard would redraw it.
|
|
1
|
+
// Named after react-prosemirror's ReactWidgetType, rewritten for Wordgard: a real Wordgard widget
|
|
2
|
+
// type (Widget.define, same spec), drawn by its component in the editor's React tree. Its root,
|
|
3
|
+
// which the component forwards its ref to, is the widget's DOM: the component may re-render inside
|
|
4
|
+
// it, but not swap it, or the parent's tile puts the old one back. An editable one the user edits
|
|
5
|
+
// keeps the edit, where Wordgard would redraw it.
|
|
9
6
|
import { createElement, } from "react";
|
|
10
7
|
import { Attributes, Elt } from "wordgard/doc";
|
|
11
8
|
import { Widget } from "wordgard/editor";
|
|
@@ -16,6 +13,7 @@ const components = new WeakMap();
|
|
|
16
13
|
// Widget.define needs a `render`, which ReactWordgard never calls: only plain
|
|
17
14
|
// Wordgard, given a React widget, draws this.
|
|
18
15
|
const placeholder = () => document.createElement("span");
|
|
16
|
+
/** A widget type whose widgets, `type.of(value)`, the component draws. */
|
|
19
17
|
export const createWidgetType = (spec) => createWidgetTypeWithRender({ ...spec, render: placeholder });
|
|
20
18
|
// For the tests comparing ReactWordgard with plain Wordgard, which draws
|
|
21
19
|
// `render`.
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type AllHTMLAttributes, type ComponentType, type HTMLProps, type ReactElement, type ReactNode, type RefAttributes, type RefCallback } from "react";
|
|
2
2
|
import { type Node as DocNode, Elt } from "wordgard/doc";
|
|
3
|
+
/**
|
|
4
|
+
* A React wrapper's props: as a shape's, without `contentDOMRef`. `children` is what it wraps.
|
|
5
|
+
*/
|
|
3
6
|
export type WrapperComponentProps<E extends Element = HTMLElement> = AllHTMLAttributes<HTMLElement> & {
|
|
4
7
|
nodeProps: {
|
|
5
8
|
node: DocNode;
|
|
@@ -7,7 +10,12 @@ export type WrapperComponentProps<E extends Element = HTMLElement> = AllHTMLAttr
|
|
|
7
10
|
};
|
|
8
11
|
children: ReactNode;
|
|
9
12
|
} & RefAttributes<E>;
|
|
13
|
+
/** `createWrapper`'s options. */
|
|
10
14
|
export type WrapperOptions = {
|
|
15
|
+
/**
|
|
16
|
+
* The component's root element, `div` by default: `target` selectors match it, and plain Wordgard
|
|
17
|
+
* draws it. `span` around an inline node.
|
|
18
|
+
*/
|
|
11
19
|
element?: string;
|
|
12
20
|
};
|
|
13
21
|
type DrawProps = {
|
|
@@ -16,6 +24,7 @@ type DrawProps = {
|
|
|
16
24
|
children: ReactNode;
|
|
17
25
|
ref: RefCallback<Element>;
|
|
18
26
|
};
|
|
27
|
+
/** A wrapper, for `Decoration.Tag.wrapper` and `Decoration.Point.wrapper`. */
|
|
19
28
|
export declare const createWrapper: <E extends Element = HTMLElement>(component: ComponentType<WrapperComponentProps<E>>, { element }?: WrapperOptions) => Elt;
|
|
20
29
|
export declare const isReactWrapper: (elt: Elt<unknown>) => boolean;
|
|
21
30
|
export declare const drawWrapper: (elt: Elt<unknown>, props: DrawProps) => ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|