@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,87 @@
|
|
|
1
|
+
// Named after react-prosemirror's EditorContext, rewritten for Wordgard. One value per editor,
|
|
2
|
+
// which never changes, so memoised components aren't scanned for it.
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
import { flushSync } from "react-dom";
|
|
5
|
+
const editors = new WeakMap();
|
|
6
|
+
export const editorOf = (wg) => {
|
|
7
|
+
const found = editors.get(wg);
|
|
8
|
+
if (found)
|
|
9
|
+
return found;
|
|
10
|
+
let state = wg.state;
|
|
11
|
+
const listeners = new Set();
|
|
12
|
+
const effects = new Set();
|
|
13
|
+
const measures = new Set();
|
|
14
|
+
let requested = false;
|
|
15
|
+
// After a flush the DOM shows `state`, even when a dispatch since then
|
|
16
|
+
// (an update listener's) has put `wg.state` ahead.
|
|
17
|
+
const runEffects = () => {
|
|
18
|
+
if (!wg.connected)
|
|
19
|
+
return;
|
|
20
|
+
const pending = [...effects];
|
|
21
|
+
effects.clear();
|
|
22
|
+
for (const effect of pending)
|
|
23
|
+
effect();
|
|
24
|
+
};
|
|
25
|
+
// Without a flush under way or pending, the DOM is up to date.
|
|
26
|
+
const requestEffects = () => {
|
|
27
|
+
if (requested)
|
|
28
|
+
return;
|
|
29
|
+
requested = true;
|
|
30
|
+
queueMicrotask(() => {
|
|
31
|
+
requested = false;
|
|
32
|
+
if (!editor.flushing && wg.state == state)
|
|
33
|
+
runEffects();
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const readMeasures = (wg) => {
|
|
37
|
+
const writes = [...measures].flatMap((measure) => measure() ?? []);
|
|
38
|
+
// A scheduled write makes Wordgard measure again.
|
|
39
|
+
if (!writes.length)
|
|
40
|
+
return;
|
|
41
|
+
// React state set by a write commits now, on the same paint.
|
|
42
|
+
wg.scheduleDOMWrite(() => flushSync(() => {
|
|
43
|
+
for (const write of writes)
|
|
44
|
+
write();
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
const editor = {
|
|
48
|
+
wg,
|
|
49
|
+
store: {
|
|
50
|
+
getState: () => state,
|
|
51
|
+
subscribe: (listener) => {
|
|
52
|
+
listeners.add(listener);
|
|
53
|
+
return () => listeners.delete(listener);
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
afterFlush: (effect) => {
|
|
57
|
+
effects.add(effect);
|
|
58
|
+
if (!editor.flushing)
|
|
59
|
+
requestEffects();
|
|
60
|
+
},
|
|
61
|
+
cancel: (effect) => effects.delete(effect),
|
|
62
|
+
measures,
|
|
63
|
+
requestMeasure: () => wg.scheduleDOMRead(readMeasures),
|
|
64
|
+
publish: (next) => {
|
|
65
|
+
if (next == state)
|
|
66
|
+
return;
|
|
67
|
+
state = next;
|
|
68
|
+
for (const listener of listeners)
|
|
69
|
+
listener();
|
|
70
|
+
},
|
|
71
|
+
readMeasures,
|
|
72
|
+
runEffects,
|
|
73
|
+
requestEffects,
|
|
74
|
+
flushing: false,
|
|
75
|
+
};
|
|
76
|
+
editors.set(wg, editor);
|
|
77
|
+
return editor;
|
|
78
|
+
};
|
|
79
|
+
// The editor's value when react-wordgard drives it (WordgardEditor made it).
|
|
80
|
+
export const findEditor = (wg) => editors.get(wg);
|
|
81
|
+
export const EditorContext = createContext(null);
|
|
82
|
+
export const useEditorContext = () => {
|
|
83
|
+
const context = useContext(EditorContext);
|
|
84
|
+
if (!context)
|
|
85
|
+
throw new Error("Used outside a Wordgard editor");
|
|
86
|
+
return context;
|
|
87
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NodeView } from "../decorations/iterDeco.js";
|
|
2
|
+
import type { ReactKeys } from "../reactKeys.js";
|
|
3
|
+
export type NodeCell = {
|
|
4
|
+
view: NodeView;
|
|
5
|
+
listener: (() => void) | null;
|
|
6
|
+
};
|
|
7
|
+
export type RenderStore = {
|
|
8
|
+
readonly keys: ReactKeys;
|
|
9
|
+
readonly pending: Map<NodeCell, NodeView>;
|
|
10
|
+
};
|
|
11
|
+
export declare const cellOf: (view: NodeView) => NodeCell;
|
|
12
|
+
export declare const moveCell: (store: RenderStore, old: NodeView, view: NodeView) => void;
|
|
13
|
+
export declare const publishCells: (store: RenderStore) => () => void;
|
|
14
|
+
export declare const RenderStoreContext: import("react").Context<RenderStore | null>;
|
|
15
|
+
export declare const useRenderStore: () => RenderStore;
|
|
@@ -0,0 +1,41 @@
|
|
|
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.
|
|
4
|
+
import { createContext, useContext } from "react";
|
|
5
|
+
const cells = new WeakMap();
|
|
6
|
+
export const cellOf = (view) => {
|
|
7
|
+
let cell = cells.get(view);
|
|
8
|
+
if (!cell)
|
|
9
|
+
cells.set(view, (cell = { view, listener: null }));
|
|
10
|
+
return cell;
|
|
11
|
+
};
|
|
12
|
+
// `view` takes the place of `old`, its node's view in the last layout.
|
|
13
|
+
export const moveCell = (store, old, view) => {
|
|
14
|
+
const cell = cellOf(old);
|
|
15
|
+
cells.set(view, cell);
|
|
16
|
+
if (cell.view == view)
|
|
17
|
+
store.pending.delete(cell);
|
|
18
|
+
else
|
|
19
|
+
store.pending.set(cell, view);
|
|
20
|
+
};
|
|
21
|
+
// Gives the cells their views, inside the commit's flushSync. Returns what
|
|
22
|
+
// takes them back, for an update React refused.
|
|
23
|
+
export const publishCells = (store) => {
|
|
24
|
+
const before = [];
|
|
25
|
+
for (const [cell, view] of store.pending) {
|
|
26
|
+
before.push([cell, cell.view]);
|
|
27
|
+
cell.view = view;
|
|
28
|
+
cell.listener?.();
|
|
29
|
+
}
|
|
30
|
+
return () => {
|
|
31
|
+
for (const [cell, view] of before)
|
|
32
|
+
cell.view = view;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export const RenderStoreContext = createContext(null);
|
|
36
|
+
export const useRenderStore = () => {
|
|
37
|
+
const store = useContext(RenderStoreContext);
|
|
38
|
+
if (!store)
|
|
39
|
+
throw new Error("Rendered outside a ReactDocTile");
|
|
40
|
+
return store;
|
|
41
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Wordgard } from "wordgard/editor";
|
|
2
|
+
import { GardState } from "wordgard/state";
|
|
3
|
+
export declare const editorEvents: readonly ["keydown", "keyup", "keypress", "beforeinput", "input", "mousedown", "mouseup", "mouseover", "mouseout", "click", "dblclick", "contextmenu", "focus", "blur", "copy", "cut", "paste", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "drop", "compositionstart", "compositionupdate", "compositionend"];
|
|
4
|
+
export type EditorEvent = (typeof editorEvents)[number];
|
|
5
|
+
type Listener<Type extends EditorEvent> = (event: HTMLElementEventMap[Type]) => boolean | void;
|
|
6
|
+
type Listeners = {
|
|
7
|
+
[Type in EditorEvent]: Listener<Type>[];
|
|
8
|
+
};
|
|
9
|
+
export declare const listenersOf: <Type extends EditorEvent>(wg: Wordgard, type: Type) => Listeners[Type];
|
|
10
|
+
export declare const hasEditorEventHandlers: (state: GardState) => boolean;
|
|
11
|
+
export declare const editorEventHandlers: GardState.Extension;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
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.
|
|
4
|
+
import { Wordgard } from "wordgard/editor";
|
|
5
|
+
import { GardState } from "wordgard/state";
|
|
6
|
+
export const editorEvents = [
|
|
7
|
+
"keydown",
|
|
8
|
+
"keyup",
|
|
9
|
+
"keypress",
|
|
10
|
+
"beforeinput",
|
|
11
|
+
"input",
|
|
12
|
+
"mousedown",
|
|
13
|
+
"mouseup",
|
|
14
|
+
"mouseover",
|
|
15
|
+
"mouseout",
|
|
16
|
+
"click",
|
|
17
|
+
"dblclick",
|
|
18
|
+
"contextmenu",
|
|
19
|
+
"focus",
|
|
20
|
+
"blur",
|
|
21
|
+
"copy",
|
|
22
|
+
"cut",
|
|
23
|
+
"paste",
|
|
24
|
+
"dragstart",
|
|
25
|
+
"dragend",
|
|
26
|
+
"dragenter",
|
|
27
|
+
"dragover",
|
|
28
|
+
"dragleave",
|
|
29
|
+
"drop",
|
|
30
|
+
"compositionstart",
|
|
31
|
+
"compositionupdate",
|
|
32
|
+
"compositionend",
|
|
33
|
+
];
|
|
34
|
+
// Every type's list made at once: the type checks none is missing, and they're
|
|
35
|
+
// then only read.
|
|
36
|
+
const createListeners = () => ({
|
|
37
|
+
keydown: [],
|
|
38
|
+
keyup: [],
|
|
39
|
+
keypress: [],
|
|
40
|
+
beforeinput: [],
|
|
41
|
+
input: [],
|
|
42
|
+
mousedown: [],
|
|
43
|
+
mouseup: [],
|
|
44
|
+
mouseover: [],
|
|
45
|
+
mouseout: [],
|
|
46
|
+
click: [],
|
|
47
|
+
dblclick: [],
|
|
48
|
+
contextmenu: [],
|
|
49
|
+
focus: [],
|
|
50
|
+
blur: [],
|
|
51
|
+
copy: [],
|
|
52
|
+
cut: [],
|
|
53
|
+
paste: [],
|
|
54
|
+
dragstart: [],
|
|
55
|
+
dragend: [],
|
|
56
|
+
dragenter: [],
|
|
57
|
+
dragover: [],
|
|
58
|
+
dragleave: [],
|
|
59
|
+
drop: [],
|
|
60
|
+
compositionstart: [],
|
|
61
|
+
compositionupdate: [],
|
|
62
|
+
compositionend: [],
|
|
63
|
+
});
|
|
64
|
+
const registries = new WeakMap();
|
|
65
|
+
export const listenersOf = (wg, type) => {
|
|
66
|
+
let registry = registries.get(wg);
|
|
67
|
+
if (!registry) {
|
|
68
|
+
registry = createListeners();
|
|
69
|
+
registries.set(wg, registry);
|
|
70
|
+
}
|
|
71
|
+
return registry[type];
|
|
72
|
+
};
|
|
73
|
+
const handler = (type) => Wordgard.domEventHandler(type, (event, wg) => {
|
|
74
|
+
// A copy: a listener may unregister one.
|
|
75
|
+
for (const listener of [...listenersOf(wg, type)])
|
|
76
|
+
if (listener(event) || event.defaultPrevented)
|
|
77
|
+
return true;
|
|
78
|
+
return false;
|
|
79
|
+
});
|
|
80
|
+
// Whether a state has the handlers: `GardState.reconfigure` drops them.
|
|
81
|
+
const installed = GardState.Facet.define({
|
|
82
|
+
combine: (values) => values.length > 0,
|
|
83
|
+
});
|
|
84
|
+
export const hasEditorEventHandlers = (state) => state.facet(installed);
|
|
85
|
+
// Wordgard redoes its handlers only when its plugins change: this one makes
|
|
86
|
+
// adding or dropping the handlers such a change.
|
|
87
|
+
const refresh = Wordgard.Plugin.define(() => ({}));
|
|
88
|
+
export const editorEventHandlers = GardState.prec.highest([
|
|
89
|
+
editorEvents.map(handler),
|
|
90
|
+
installed.of(true),
|
|
91
|
+
refresh,
|
|
92
|
+
]);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Mark, type Node, type Plot, type Pos } from "wordgard/doc";
|
|
2
|
+
import { Decoration, Widget } from "wordgard/editor";
|
|
3
|
+
import { type Set } from "wordgard/set";
|
|
4
|
+
import type { GardState } from "wordgard/state";
|
|
5
|
+
import { type DecoElt, type DecoSet, type TagAttribute, type TagWidget, type TagWrapper, type WrapperRangeDecoration } from "../wordgardInternals.js";
|
|
6
|
+
declare const enum WidgetPlace {
|
|
7
|
+
Before = 0,
|
|
8
|
+
After = 1,
|
|
9
|
+
Start = 2,
|
|
10
|
+
End = 3
|
|
11
|
+
}
|
|
12
|
+
export type WrapperSource = Mark | WrapperRangeDecoration;
|
|
13
|
+
export interface DecoWalker {
|
|
14
|
+
enter(node: Plot, shape: DecoElt, wrappers: readonly WrapperSource[], pos: number): boolean;
|
|
15
|
+
leave(): void;
|
|
16
|
+
node(node: Node, shape: Decoration.Shape, wrappers: readonly WrapperSource[], partial: number | undefined, pos: number): void;
|
|
17
|
+
nodePart(node: Node, length: number, done: boolean): void;
|
|
18
|
+
widget(widget: Widget, side: number, point: boolean): void;
|
|
19
|
+
}
|
|
20
|
+
export declare const getDecoSet: (state: GardState) => DecoSet;
|
|
21
|
+
export declare const renderMarkWrapper: (arg: Mark<unknown>) => DecoElt;
|
|
22
|
+
export declare const documentMark: (mark: Mark) => Mark<unknown>;
|
|
23
|
+
export declare const renderWrapper: (src: WrapperSource) => DecoElt;
|
|
24
|
+
export declare class DecoIterator {
|
|
25
|
+
readonly state: GardState;
|
|
26
|
+
readonly decoSet: DecoSet;
|
|
27
|
+
tagShapes: Map<Node.Type, (tag: Node.Tag) => Decoration.Shape>;
|
|
28
|
+
globalWidgets: readonly TagWidget[];
|
|
29
|
+
globalWrappers: readonly TagWrapper[];
|
|
30
|
+
globalAttrs: readonly TagAttribute[];
|
|
31
|
+
markShapes: Map<Mark.Type, Mark.Type>;
|
|
32
|
+
pos: Pos;
|
|
33
|
+
rangeCursor: Set.Cursor<Decoration.Range>;
|
|
34
|
+
pointSets: readonly Decoration.Point.Set[];
|
|
35
|
+
pointCursor: Set.Cursor<Decoration.Point>;
|
|
36
|
+
constructor(state: GardState, decoSet: DecoSet);
|
|
37
|
+
widgets(tag: Node.Tag, place: WidgetPlace, walker: DecoWalker): void;
|
|
38
|
+
nodeWrappers(tag: Node.Tag, active: readonly Decoration.Range[], atom: boolean): readonly WrapperSource[];
|
|
39
|
+
walk(from: number, inclusiveStart: boolean, to: number, walker: DecoWalker): void;
|
|
40
|
+
tagShape(tag: Node.Tag, active: readonly Decoration.Range[]): Decoration.Shape;
|
|
41
|
+
}
|
|
42
|
+
export declare const spanningOf: (src: WrapperSource) => boolean;
|
|
43
|
+
export {};
|