@stesura/react-wordgard 0.0.0-stage → 0.2.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 +41 -0
- package/LICENSE.txt +21 -0
- package/README.md +912 -2
- package/THIRD_PARTY_LICENSES.txt +93 -0
- package/dist/ReactWordgard.d.ts +13 -0
- package/dist/ReactWordgard.js +94 -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 +36 -0
- package/dist/components/DecorationRoot.d.ts +13 -0
- package/dist/components/DecorationRoot.js +31 -0
- package/dist/components/ErrorBoundary.d.ts +18 -0
- package/dist/components/ErrorBoundary.js +14 -0
- package/dist/components/SeparatorHackView.d.ts +2 -0
- package/dist/components/SeparatorHackView.js +4 -0
- package/dist/components/ShapeView.d.ts +12 -0
- package/dist/components/ShapeView.js +24 -0
- package/dist/components/TextNodeView.d.ts +5 -0
- package/dist/components/TextNodeView.js +4 -0
- package/dist/components/TrailingHackView.d.ts +2 -0
- package/dist/components/TrailingHackView.js +4 -0
- package/dist/components/WidgetView.d.ts +10 -0
- package/dist/components/WidgetView.js +32 -0
- package/dist/components/WordgardDoc.d.ts +9 -0
- package/dist/components/WordgardDoc.js +36 -0
- package/dist/components/WordgardEditor.d.ts +15 -0
- package/dist/components/WordgardEditor.js +17 -0
- package/dist/components/WordgardScope.d.ts +14 -0
- package/dist/components/WordgardScope.js +14 -0
- package/dist/components/WrapperView.d.ts +12 -0
- package/dist/components/WrapperView.js +23 -0
- package/dist/components/marks/MarkView.d.ts +11 -0
- package/dist/components/marks/MarkView.js +10 -0
- package/dist/components/marks/ReactMarkView.d.ts +10 -0
- package/dist/components/marks/ReactMarkView.js +30 -0
- package/dist/components/nodes/DefaultNodeView.d.ts +16 -0
- package/dist/components/nodes/DefaultNodeView.js +112 -0
- package/dist/components/nodes/DocNodeView.d.ts +7 -0
- package/dist/components/nodes/DocNodeView.js +5 -0
- package/dist/composition.d.ts +23 -0
- package/dist/composition.js +95 -0
- package/dist/contexts/DecorationContext.d.ts +15 -0
- package/dist/contexts/DecorationContext.js +35 -0
- package/dist/contexts/DecorationRootContext.d.ts +6 -0
- package/dist/contexts/DecorationRootContext.js +4 -0
- package/dist/contexts/EditorContext.d.ts +27 -0
- package/dist/contexts/EditorContext.js +87 -0
- package/dist/contexts/RenderStoreContext.d.ts +15 -0
- package/dist/contexts/RenderStoreContext.js +41 -0
- package/dist/contexts/editorEvents.d.ts +12 -0
- package/dist/contexts/editorEvents.js +92 -0
- package/dist/decorations/DecoIterator.d.ts +43 -0
- package/dist/decorations/DecoIterator.js +462 -0
- package/dist/decorations/ReactMarkType.d.ts +41 -0
- package/dist/decorations/ReactMarkType.js +47 -0
- package/dist/decorations/ReactShapeType.d.ts +65 -0
- package/dist/decorations/ReactShapeType.js +202 -0
- package/dist/decorations/ReactWidgetType.d.ts +29 -0
- package/dist/decorations/ReactWidgetType.js +67 -0
- package/dist/decorations/ReactWrapperType.d.ts +31 -0
- package/dist/decorations/ReactWrapperType.js +41 -0
- package/dist/decorations/iterDeco.d.ts +82 -0
- package/dist/decorations/iterDeco.js +406 -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 +10 -0
- package/dist/hooks/useEditorEffect.js +28 -0
- package/dist/hooks/useEditorEventCallback.d.ts +8 -0
- package/dist/hooks/useEditorEventCallback.js +17 -0
- package/dist/hooks/useEditorEventListener.d.ts +10 -0
- package/dist/hooks/useEditorEventListener.js +26 -0
- package/dist/hooks/useEditorLayoutEffect.d.ts +15 -0
- package/dist/hooks/useEditorLayoutEffect.js +34 -0
- package/dist/hooks/useEditorState.d.ts +6 -0
- package/dist/hooks/useEditorState.js +12 -0
- package/dist/hooks/useEditorStateSelector.d.ts +7 -0
- package/dist/hooks/useEditorStateSelector.js +11 -0
- package/dist/hooks/useIgnoreMutation.d.ts +6 -0
- package/dist/hooks/useIgnoreMutation.js +19 -0
- package/dist/hooks/useIsNodeSelected.d.ts +2 -0
- package/dist/hooks/useIsNodeSelected.js +5 -0
- package/dist/hooks/useMergedDOMRefs.d.ts +6 -0
- package/dist/hooks/useMergedDOMRefs.js +18 -0
- package/dist/hooks/useNodePos.d.ts +4 -0
- package/dist/hooks/useNodePos.js +7 -0
- package/dist/hooks/useSelectNode.d.ts +6 -0
- package/dist/hooks/useSelectNode.js +25 -0
- package/dist/hooks/useStopEvent.d.ts +8 -0
- package/dist/hooks/useStopEvent.js +21 -0
- package/dist/hooks/useWordgard.d.ts +2 -0
- package/dist/hooks/useWordgard.js +3 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -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 +1319 -0
- package/dist/viewRoots.d.ts +2 -0
- package/dist/viewRoots.js +7 -0
- package/dist/wordgardInternals.d.ts +211 -0
- package/dist/wordgardInternals.js +82 -0
- package/package.json +119 -4
- package/skills/react-wordgard/SKILL.md +525 -0
- package/skills/react-wordgard/references/wordgard.md +189 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ForwardedRef, type RefCallback } from "react";
|
|
2
|
+
type DOMRef = (dom: Element | null) => void;
|
|
3
|
+
export declare const mergeRefs: (...refs: (DOMRef | undefined)[]) => DOMRef;
|
|
4
|
+
export declare const toCallbackRef: <T>(ref: ForwardedRef<T>) => RefCallback<T> | undefined;
|
|
5
|
+
export declare const takesRef: (component: unknown) => boolean;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { version } from "react";
|
|
2
|
+
// A React decoration's root ref, with what else must know its root: the
|
|
3
|
+
// React wrapper around it, its hooks' handlers.
|
|
4
|
+
export const mergeRefs = (...refs) => (dom) => {
|
|
5
|
+
for (const ref of refs)
|
|
6
|
+
ref?.(dom);
|
|
7
|
+
};
|
|
8
|
+
// A forwarded ref as a callback, for mergeRefs.
|
|
9
|
+
export const toCallbackRef = (ref) => typeof ref == "function"
|
|
10
|
+
? ref
|
|
11
|
+
: ref
|
|
12
|
+
? (value) => {
|
|
13
|
+
ref.current = value;
|
|
14
|
+
}
|
|
15
|
+
: undefined;
|
|
16
|
+
// Whether React gives `component` a ref: React 19 passes it as a prop, React
|
|
17
|
+
// 18 only to a forwardRef (an object) and warns for a function component,
|
|
18
|
+
// which needs none with DecorationRoot.
|
|
19
|
+
const refAsProp = Number(version.split(".")[0]) >= 19;
|
|
20
|
+
export const takesRef = (component) => refAsProp || typeof component != "function";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type DependencyList } from "react";
|
|
2
|
+
import type { Wordgard } from "wordgard/editor";
|
|
3
|
+
/**
|
|
4
|
+
* Runs `effect` once the editor's DOM is up to date: after the flush under way or pending, else in
|
|
5
|
+
* a microtask, never inside React's commit. It may read layout and dispatch; what it sets shows a
|
|
6
|
+
* frame later. For focus, follow-up dispatches and menus. The dependencies work as React's. Don't
|
|
7
|
+
* read the editor's layout from a plain React effect: it can run inside Wordgard's update, before
|
|
8
|
+
* the editor's view of the new DOM exists.
|
|
9
|
+
*/
|
|
10
|
+
export declare const useEditorEffect: (effect: (wg: Wordgard) => void | (() => void), dependencies?: DependencyList) => void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Named after react-prosemirror's useEditorEffect, rewritten for Wordgard.
|
|
2
|
+
import { useLayoutEffect } from "react";
|
|
3
|
+
import { useEditorContext } from "../contexts/EditorContext.js";
|
|
4
|
+
/**
|
|
5
|
+
* Runs `effect` once the editor's DOM is up to date: after the flush under way or pending, else in
|
|
6
|
+
* a microtask, never inside React's commit. It may read layout and dispatch; what it sets shows a
|
|
7
|
+
* frame later. For focus, follow-up dispatches and menus. The dependencies work as React's. Don't
|
|
8
|
+
* read the editor's layout from a plain React effect: it can run inside Wordgard's update, before
|
|
9
|
+
* the editor's view of the new DOM exists.
|
|
10
|
+
*/
|
|
11
|
+
export const useEditorEffect = (effect, dependencies) => {
|
|
12
|
+
const { wg, afterFlush, cancel } = useEditorContext();
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
let cleanup;
|
|
15
|
+
const run = () => {
|
|
16
|
+
cleanup = effect(wg);
|
|
17
|
+
};
|
|
18
|
+
afterFlush(run);
|
|
19
|
+
return () => {
|
|
20
|
+
cancel(run);
|
|
21
|
+
if (typeof cleanup == "function")
|
|
22
|
+
cleanup();
|
|
23
|
+
};
|
|
24
|
+
// The caller's dependencies, as with React's own effects, and the editor:
|
|
25
|
+
// a WordgardScope can change it.
|
|
26
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
27
|
+
}, dependencies && [wg, ...dependencies]);
|
|
28
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Wordgard } from "wordgard/editor";
|
|
2
|
+
/**
|
|
3
|
+
* A stable function calling the latest `callback` with the editor first, for event handlers. There,
|
|
4
|
+
* `wg.state` is the latest state, dispatches not yet drawn included. See the `SaveButton` in
|
|
5
|
+
* [Accessing the state outside the
|
|
6
|
+
* editor](https://github.com/stesura/react-wordgard#accessing-the-state-outside-the-editor).
|
|
7
|
+
*/
|
|
8
|
+
export declare const useEditorEventCallback: <Args extends unknown[], Result>(callback: (wg: Wordgard, ...args: Args) => Result) => (...args: Args) => Result;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Named after react-prosemirror's useEditorEventCallback, rewritten for Wordgard.
|
|
2
|
+
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
3
|
+
import { useEditorContext } from "../contexts/EditorContext.js";
|
|
4
|
+
/**
|
|
5
|
+
* A stable function calling the latest `callback` with the editor first, for event handlers. There,
|
|
6
|
+
* `wg.state` is the latest state, dispatches not yet drawn included. See the `SaveButton` in
|
|
7
|
+
* [Accessing the state outside the
|
|
8
|
+
* editor](https://github.com/stesura/react-wordgard#accessing-the-state-outside-the-editor).
|
|
9
|
+
*/
|
|
10
|
+
export const useEditorEventCallback = (callback) => {
|
|
11
|
+
const { wg } = useEditorContext();
|
|
12
|
+
const latest = useRef(callback);
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
latest.current = callback;
|
|
15
|
+
});
|
|
16
|
+
return useCallback((...args) => latest.current(wg, ...args), [wg]);
|
|
17
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Wordgard } from "wordgard/editor";
|
|
2
|
+
import { type EditorEvent } from "../contexts/editorEvents.js";
|
|
3
|
+
/**
|
|
4
|
+
* Listens to the editor's DOM events of `type`, before Wordgard's handlers and keymaps. Returning
|
|
5
|
+
* `true` handles the event: Wordgard calls `preventDefault` and skips its own handling. The event
|
|
6
|
+
* types are listed under [Limits](https://github.com/stesura/react-wordgard#limits). Key events
|
|
7
|
+
* during a composition don't come; handling `input`, `beforeinput` or a composition event skips
|
|
8
|
+
* Wordgard's own handling of it, so leave typing to Wordgard.
|
|
9
|
+
*/
|
|
10
|
+
export declare const useEditorEventListener: <Type extends EditorEvent>(type: Type, handler: (wg: Wordgard, event: HTMLElementEventMap[Type]) => boolean | void) => void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Named after react-prosemirror's useEditorEventListener, rewritten for Wordgard: ReactWordgard
|
|
2
|
+
// installs a `prec.highest` Wordgard.domEventHandler per type, first in an editor made from
|
|
3
|
+
// `config`, after the state's own in one made from a `state`. The latest registered runs first, as
|
|
4
|
+
// in react-prosemirror.
|
|
5
|
+
import { useLayoutEffect } from "react";
|
|
6
|
+
import { useEditorContext } from "../contexts/EditorContext.js";
|
|
7
|
+
import { listenersOf } from "../contexts/editorEvents.js";
|
|
8
|
+
import { useEditorEventCallback } from "./useEditorEventCallback.js";
|
|
9
|
+
/**
|
|
10
|
+
* Listens to the editor's DOM events of `type`, before Wordgard's handlers and keymaps. Returning
|
|
11
|
+
* `true` handles the event: Wordgard calls `preventDefault` and skips its own handling. The event
|
|
12
|
+
* types are listed under [Limits](https://github.com/stesura/react-wordgard#limits). Key events
|
|
13
|
+
* during a composition don't come; handling `input`, `beforeinput` or a composition event skips
|
|
14
|
+
* Wordgard's own handling of it, so leave typing to Wordgard.
|
|
15
|
+
*/
|
|
16
|
+
export const useEditorEventListener = (type, handler) => {
|
|
17
|
+
const { wg } = useEditorContext();
|
|
18
|
+
const listener = useEditorEventCallback(handler);
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
|
+
const listeners = listenersOf(wg, type);
|
|
21
|
+
listeners.unshift(listener);
|
|
22
|
+
return () => {
|
|
23
|
+
listeners.splice(listeners.indexOf(listener), 1);
|
|
24
|
+
};
|
|
25
|
+
}, [wg, type, listener]);
|
|
26
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Wordgard } from "wordgard/editor";
|
|
2
|
+
/** The `read` and `write` pair `useEditorLayoutEffect` takes. */
|
|
3
|
+
export type EditorLayoutEffect<T> = {
|
|
4
|
+
read: (wg: Wordgard) => T | undefined;
|
|
5
|
+
write: (value: T, wg: Wordgard) => void;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Runs inside every editor update, without re-rendering: `read` in Wordgard's read phase, then
|
|
9
|
+
* `write` with its value in the write phase, on the same paint as the document. For what moves with
|
|
10
|
+
* the text (markers in the margin, pagination). It can't dispatch: Wordgard throws during its
|
|
11
|
+
* update. A `read` returning `undefined` skips its write, and when no read asks for a write, none
|
|
12
|
+
* is scheduled, which spares Wordgard measuring again. Reads run in the order the effects were
|
|
13
|
+
* registered.
|
|
14
|
+
*/
|
|
15
|
+
export declare const useEditorLayoutEffect: <T>(measure: EditorLayoutEffect<T>) => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The other half of react-prosemirror's useEditorEffect, which ran inside React's commit. Here
|
|
2
|
+
// React commits inside Wordgard's flush: useEditorEffect runs after it, this hook in it, through
|
|
3
|
+
// Wordgard's scheduleDOMRead and scheduleDOMWrite. A write resizing the content makes Wordgard
|
|
4
|
+
// measure again within the flush, so pagination settles there. Writes into the document's own DOM
|
|
5
|
+
// are foreign edits to Wordgard: write inside widgets, outside the editor, or through decorations.
|
|
6
|
+
import { useLayoutEffect, useRef } from "react";
|
|
7
|
+
import { useEditorContext } from "../contexts/EditorContext.js";
|
|
8
|
+
/**
|
|
9
|
+
* Runs inside every editor update, without re-rendering: `read` in Wordgard's read phase, then
|
|
10
|
+
* `write` with its value in the write phase, on the same paint as the document. For what moves with
|
|
11
|
+
* the text (markers in the margin, pagination). It can't dispatch: Wordgard throws during its
|
|
12
|
+
* update. A `read` returning `undefined` skips its write, and when no read asks for a write, none
|
|
13
|
+
* is scheduled, which spares Wordgard measuring again. Reads run in the order the effects were
|
|
14
|
+
* registered.
|
|
15
|
+
*/
|
|
16
|
+
export const useEditorLayoutEffect = (measure) => {
|
|
17
|
+
const { wg, measures, requestMeasure } = useEditorContext();
|
|
18
|
+
const latest = useRef(measure);
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
|
+
latest.current = measure;
|
|
21
|
+
});
|
|
22
|
+
useLayoutEffect(() => {
|
|
23
|
+
const entry = () => {
|
|
24
|
+
const { read, write } = latest.current;
|
|
25
|
+
const value = read(wg);
|
|
26
|
+
return value === undefined ? null : () => write(value, wg);
|
|
27
|
+
};
|
|
28
|
+
measures.add(entry);
|
|
29
|
+
requestMeasure();
|
|
30
|
+
return () => {
|
|
31
|
+
measures.delete(entry);
|
|
32
|
+
};
|
|
33
|
+
}, [wg, measures, requestMeasure]);
|
|
34
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The state as of the editor's last flush, rendered in the same React commit as the document. The
|
|
3
|
+
* component re-renders on every flush: for a part of the state, use `useEditorStateSelector`; in a
|
|
4
|
+
* handler, `wg.state` is the latest.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useEditorState: () => import("wordgard/state").GardState;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Named after react-prosemirror's useEditorState, rewritten for Wordgard.
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { useEditorContext } from "../contexts/EditorContext.js";
|
|
4
|
+
/**
|
|
5
|
+
* The state as of the editor's last flush, rendered in the same React commit as the document. The
|
|
6
|
+
* component re-renders on every flush: for a part of the state, use `useEditorStateSelector`; in a
|
|
7
|
+
* handler, `wg.state` is the latest.
|
|
8
|
+
*/
|
|
9
|
+
export const useEditorState = () => {
|
|
10
|
+
const { store } = useEditorContext();
|
|
11
|
+
return useSyncExternalStore(store.subscribe, store.getState);
|
|
12
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { GardState } from "wordgard/state";
|
|
2
|
+
/**
|
|
3
|
+
* A part of the last flush's state, like Redux's `useSelector`: the component re-renders only when
|
|
4
|
+
* the result changes, by `Object.is` or by `isEqual`. A selector building a new object or array
|
|
5
|
+
* each time re-renders on every flush unless you pass `isEqual`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const useEditorStateSelector: <Result>(selector: (state: GardState) => Result, isEqual?: (a: Result, b: Result) => boolean) => Result;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
|
|
2
|
+
import { useEditorContext } from "../contexts/EditorContext.js";
|
|
3
|
+
/**
|
|
4
|
+
* A part of the last flush's state, like Redux's `useSelector`: the component re-renders only when
|
|
5
|
+
* the result changes, by `Object.is` or by `isEqual`. A selector building a new object or array
|
|
6
|
+
* each time re-renders on every flush unless you pass `isEqual`.
|
|
7
|
+
*/
|
|
8
|
+
export const useEditorStateSelector = (selector, isEqual) => {
|
|
9
|
+
const { store } = useEditorContext();
|
|
10
|
+
return useSyncExternalStoreWithSelector(store.subscribe, store.getState, store.getState, selector, isEqual);
|
|
11
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Wordgard } from "wordgard/editor";
|
|
2
|
+
/**
|
|
3
|
+
* Wordgard doesn't read the DOM mutations `ignore` returns `true` for, typing aside (Wordgard takes
|
|
4
|
+
* it from `beforeinput`). Mutations in chrome and React widgets never reach Wordgard anyway.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useIgnoreMutation: (ignore: (wg: Wordgard, mutation: MutationRecord) => boolean) => void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Named after react-prosemirror's useIgnoreMutation, rewritten for Wordgard. Chrome mutations are
|
|
2
|
+
// dropped before, by `sortRecords`.
|
|
3
|
+
import { useLayoutEffect } from "react";
|
|
4
|
+
import { useDecorationContext } from "../contexts/DecorationContext.js";
|
|
5
|
+
import { useEditorEventCallback } from "./useEditorEventCallback.js";
|
|
6
|
+
/**
|
|
7
|
+
* Wordgard doesn't read the DOM mutations `ignore` returns `true` for, typing aside (Wordgard takes
|
|
8
|
+
* it from `beforeinput`). Mutations in chrome and React widgets never reach Wordgard anyway.
|
|
9
|
+
*/
|
|
10
|
+
export const useIgnoreMutation = (ignore) => {
|
|
11
|
+
const { handlers } = useDecorationContext("useIgnoreMutation");
|
|
12
|
+
const callback = useEditorEventCallback(ignore);
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
handlers.ignoreMutation.add(callback);
|
|
15
|
+
return () => {
|
|
16
|
+
handlers.ignoreMutation.delete(callback);
|
|
17
|
+
};
|
|
18
|
+
}, [handlers, callback]);
|
|
19
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Named after react-prosemirror's useIsNodeSelected, rewritten for Wordgard: read from the
|
|
2
|
+
// `wg-selected-node` class Wordgard draws, so the component renders with it, without an effect.
|
|
3
|
+
import { useDecorationContext } from "../contexts/DecorationContext.js";
|
|
4
|
+
/** Whether the node the decoration draws is the editor's node selection. */
|
|
5
|
+
export const useIsNodeSelected = () => useDecorationContext("useIsNodeSelected").selected;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Ref, type RefCallback } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* One ref setting several, as for a root that's also the content element (`ref` and
|
|
4
|
+
* `contentDOMRef`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const useMergedDOMRefs: <Value>(...refs: (Ref<Value> | undefined)[]) => RefCallback<Value>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Adapted from react-prosemirror's useMergedDOMRefs (src/refs.ts there), without its typecast.
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* One ref setting several, as for a root that's also the content element (`ref` and
|
|
5
|
+
* `contentDOMRef`).
|
|
6
|
+
*/
|
|
7
|
+
export const useMergedDOMRefs = (...refs) => useCallback((value) => {
|
|
8
|
+
for (const ref of refs) {
|
|
9
|
+
if (typeof ref == "function")
|
|
10
|
+
ref(value);
|
|
11
|
+
else if (ref)
|
|
12
|
+
Object.assign(ref, { current: value });
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
// The refs themselves, as react-prosemirror's: a new callback when one
|
|
16
|
+
// changes, so React gives it the element.
|
|
17
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps, react-hooks/use-memo
|
|
18
|
+
refs);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Named after react-prosemirror's useNodePos, rewritten for Wordgard: read from the decoration's
|
|
2
|
+
// tile. A function rather than the position: during render, the tile may not be built yet.
|
|
3
|
+
import { useDecorationContext } from "../contexts/DecorationContext.js";
|
|
4
|
+
/**
|
|
5
|
+
* A function giving the position of what the decoration draws, to call in handlers and effects.
|
|
6
|
+
*/
|
|
7
|
+
export const useNodePos = () => useDecorationContext("useNodePos").handlers.getPos;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Wordgard } from "wordgard/editor";
|
|
2
|
+
/**
|
|
3
|
+
* Calls `selectNode` when the node becomes the node selection, and `deselectNode` when it stops
|
|
4
|
+
* being it, once the editor's DOM is up to date (as `useEditorEffect`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const useSelectNode: (selectNode: (wg: Wordgard) => void, deselectNode?: (wg: Wordgard) => void) => void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Named after react-prosemirror's useSelectNode, rewritten for Wordgard.
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { useEditorEffect } from "./useEditorEffect.js";
|
|
4
|
+
import { useEditorEventCallback } from "./useEditorEventCallback.js";
|
|
5
|
+
import { useIsNodeSelected } from "./useIsNodeSelected.js";
|
|
6
|
+
const noop = () => { };
|
|
7
|
+
/**
|
|
8
|
+
* Calls `selectNode` when the node becomes the node selection, and `deselectNode` when it stops
|
|
9
|
+
* being it, once the editor's DOM is up to date (as `useEditorEffect`).
|
|
10
|
+
*/
|
|
11
|
+
export const useSelectNode = (selectNode, deselectNode = noop) => {
|
|
12
|
+
const selected = useIsNodeSelected();
|
|
13
|
+
const select = useEditorEventCallback(selectNode);
|
|
14
|
+
const deselect = useEditorEventCallback(deselectNode);
|
|
15
|
+
const was = useRef(false);
|
|
16
|
+
useEditorEffect(() => {
|
|
17
|
+
if (selected == was.current)
|
|
18
|
+
return;
|
|
19
|
+
was.current = selected;
|
|
20
|
+
if (selected)
|
|
21
|
+
select();
|
|
22
|
+
else
|
|
23
|
+
deselect();
|
|
24
|
+
}, [selected, select, deselect]);
|
|
25
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Wordgard } from "wordgard/editor";
|
|
2
|
+
/**
|
|
3
|
+
* Asked for events in the decoration's chrome (its root and the elements around its content
|
|
4
|
+
* element, unless the root is the content element) and in React widgets; never for those in its
|
|
5
|
+
* content. For an event it stops, Wordgard's handlers and keymaps are skipped and the browser's
|
|
6
|
+
* default goes ahead: for a control in the chrome, such as an input.
|
|
7
|
+
*/
|
|
8
|
+
export declare const useStopEvent: (stopEvent: (wg: Wordgard, event: Event) => boolean) => void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Named after react-prosemirror's useStopEvent, rewritten for Wordgard: consulted through widget
|
|
2
|
+
// types' `propagateEvent`, and through the decoration's own elements' tiles (tile.ts `ownTile`).
|
|
3
|
+
import { useLayoutEffect } from "react";
|
|
4
|
+
import { useDecorationContext } from "../contexts/DecorationContext.js";
|
|
5
|
+
import { useEditorEventCallback } from "./useEditorEventCallback.js";
|
|
6
|
+
/**
|
|
7
|
+
* Asked for events in the decoration's chrome (its root and the elements around its content
|
|
8
|
+
* element, unless the root is the content element) and in React widgets; never for those in its
|
|
9
|
+
* content. For an event it stops, Wordgard's handlers and keymaps are skipped and the browser's
|
|
10
|
+
* default goes ahead: for a control in the chrome, such as an input.
|
|
11
|
+
*/
|
|
12
|
+
export const useStopEvent = (stopEvent) => {
|
|
13
|
+
const { handlers } = useDecorationContext("useStopEvent");
|
|
14
|
+
const stop = useEditorEventCallback(stopEvent);
|
|
15
|
+
useLayoutEffect(() => {
|
|
16
|
+
handlers.stopEvent.add(stop);
|
|
17
|
+
return () => {
|
|
18
|
+
handlers.stopEvent.delete(stop);
|
|
19
|
+
};
|
|
20
|
+
}, [handlers, stop]);
|
|
21
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { DecorationRoot, type DecorationRootProps } from "./components/DecorationRoot.js";
|
|
2
|
+
export { WordgardDoc } from "./components/WordgardDoc.js";
|
|
3
|
+
export { WordgardEditor } from "./components/WordgardEditor.js";
|
|
4
|
+
export { WordgardScope } from "./components/WordgardScope.js";
|
|
5
|
+
export { type MarkComponentProps, type MarkShapeOptions, createMarkShape, } from "./decorations/ReactMarkType.js";
|
|
6
|
+
export { type ReactShape, type ShapeComponentProps, createShape, } from "./decorations/ReactShapeType.js";
|
|
7
|
+
export { type WidgetTypeSpec, type WidgetComponentProps, createWidgetType, } from "./decorations/ReactWidgetType.js";
|
|
8
|
+
export { type WrapperOptions, type WrapperComponentProps, createWrapper, } from "./decorations/ReactWrapperType.js";
|
|
9
|
+
export { useEditorEffect } from "./hooks/useEditorEffect.js";
|
|
10
|
+
export { useEditorEventCallback } from "./hooks/useEditorEventCallback.js";
|
|
11
|
+
export { useEditorEventListener } from "./hooks/useEditorEventListener.js";
|
|
12
|
+
export { type EditorLayoutEffect, useEditorLayoutEffect } from "./hooks/useEditorLayoutEffect.js";
|
|
13
|
+
export { useEditorState } from "./hooks/useEditorState.js";
|
|
14
|
+
export { useEditorStateSelector } from "./hooks/useEditorStateSelector.js";
|
|
15
|
+
export { useIgnoreMutation } from "./hooks/useIgnoreMutation.js";
|
|
16
|
+
export { useIsNodeSelected } from "./hooks/useIsNodeSelected.js";
|
|
17
|
+
export { useMergedDOMRefs } from "./hooks/useMergedDOMRefs.js";
|
|
18
|
+
export { useNodePos } from "./hooks/useNodePos.js";
|
|
19
|
+
export { useSelectNode } from "./hooks/useSelectNode.js";
|
|
20
|
+
export { useStopEvent } from "./hooks/useStopEvent.js";
|
|
21
|
+
export { useWordgard } from "./hooks/useWordgard.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { DecorationRoot } from "./components/DecorationRoot.js";
|
|
2
|
+
export { WordgardDoc } from "./components/WordgardDoc.js";
|
|
3
|
+
export { WordgardEditor } from "./components/WordgardEditor.js";
|
|
4
|
+
export { WordgardScope } from "./components/WordgardScope.js";
|
|
5
|
+
export { createMarkShape, } from "./decorations/ReactMarkType.js";
|
|
6
|
+
export { createShape, } from "./decorations/ReactShapeType.js";
|
|
7
|
+
export { createWidgetType, } from "./decorations/ReactWidgetType.js";
|
|
8
|
+
export { createWrapper, } from "./decorations/ReactWrapperType.js";
|
|
9
|
+
export { useEditorEffect } from "./hooks/useEditorEffect.js";
|
|
10
|
+
export { useEditorEventCallback } from "./hooks/useEditorEventCallback.js";
|
|
11
|
+
export { useEditorEventListener } from "./hooks/useEditorEventListener.js";
|
|
12
|
+
export { useEditorLayoutEffect } from "./hooks/useEditorLayoutEffect.js";
|
|
13
|
+
export { useEditorState } from "./hooks/useEditorState.js";
|
|
14
|
+
export { useEditorStateSelector } from "./hooks/useEditorStateSelector.js";
|
|
15
|
+
export { useIgnoreMutation } from "./hooks/useIgnoreMutation.js";
|
|
16
|
+
export { useIsNodeSelected } from "./hooks/useIsNodeSelected.js";
|
|
17
|
+
export { useMergedDOMRefs } from "./hooks/useMergedDOMRefs.js";
|
|
18
|
+
export { useNodePos } from "./hooks/useNodePos.js";
|
|
19
|
+
export { useSelectNode } from "./hooks/useSelectNode.js";
|
|
20
|
+
export { useStopEvent } from "./hooks/useStopEvent.js";
|
|
21
|
+
export { useWordgard } from "./hooks/useWordgard.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Plot } from "wordgard/doc";
|
|
2
|
+
import type { Widget } from "wordgard/editor";
|
|
3
|
+
import type { ChildView } from "./decorations/iterDeco.js";
|
|
4
|
+
export type ReactKeys = WeakMap<Plot, readonly string[]>;
|
|
5
|
+
export declare const createNodeKey: () => string;
|
|
6
|
+
export declare const addFreshKeys: (keys: ReactKeys, plot: Plot) => void;
|
|
7
|
+
export declare const mapKeys: (keys: ReactKeys, oldDoc: Plot, newDoc: Plot, changes: readonly number[]) => WeakMap<Plot, Plot>;
|
|
8
|
+
export declare const childViewKeys: (views: readonly ChildView[]) => ({
|
|
9
|
+
view: {
|
|
10
|
+
type: "widget";
|
|
11
|
+
widget: Widget;
|
|
12
|
+
side: number;
|
|
13
|
+
key?: string;
|
|
14
|
+
offset?: number;
|
|
15
|
+
};
|
|
16
|
+
key: string;
|
|
17
|
+
} | {
|
|
18
|
+
view: import("./decorations/iterDeco.js").NodeView | {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "wrapper";
|
|
23
|
+
elt: import("./wordgardInternals.js").DecoElt;
|
|
24
|
+
spanning: boolean;
|
|
25
|
+
children: ChildView[];
|
|
26
|
+
key?: string;
|
|
27
|
+
mark?: import("wordgard/doc").Mark;
|
|
28
|
+
} | {
|
|
29
|
+
type: "buffer";
|
|
30
|
+
side: -1 | 1;
|
|
31
|
+
} | {
|
|
32
|
+
type: "composition";
|
|
33
|
+
key: string;
|
|
34
|
+
rendered: string | null;
|
|
35
|
+
} | {
|
|
36
|
+
type: "trailingHack";
|
|
37
|
+
};
|
|
38
|
+
key: string;
|
|
39
|
+
})[];
|