@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,188 @@
|
|
|
1
|
+
import { mapNodePos, unchangedEnds } from "./changes.js";
|
|
2
|
+
export const createNodeKey = () => Math.floor(Math.random() * 0xffffffffffff).toString(16);
|
|
3
|
+
// Gives keys to `plot` and everything in it that has none yet.
|
|
4
|
+
export const addFreshKeys = (keys, plot) => {
|
|
5
|
+
if (keys.has(plot))
|
|
6
|
+
return;
|
|
7
|
+
keys.set(plot, plot.content.map((child) => (child.isText ? "" : createNodeKey())));
|
|
8
|
+
for (const child of plot.content)
|
|
9
|
+
if (child.isPlot)
|
|
10
|
+
addFreshKeys(keys, child);
|
|
11
|
+
};
|
|
12
|
+
// Carries keys from `oldDoc` to `newDoc` through `changes` (the sections
|
|
13
|
+
// DocTile.update receives), as react-prosemirror maps them through a
|
|
14
|
+
// transaction's steps: a node whose start survives keeps its key, as does a
|
|
15
|
+
// node moved within its parent (the same object); others get fresh ones. Only
|
|
16
|
+
// plots that changed identity are visited. Returns, for each
|
|
17
|
+
// of those, the plot it replaces.
|
|
18
|
+
export const mapKeys = (keys, oldDoc, newDoc, changes) => {
|
|
19
|
+
const replaced = new WeakMap();
|
|
20
|
+
const mapChildren = (oldPlot, oldStart, newPlot, newStart) => {
|
|
21
|
+
if (keys.has(newPlot))
|
|
22
|
+
return;
|
|
23
|
+
replaced.set(newPlot, oldPlot);
|
|
24
|
+
const oldKeys = keys.get(oldPlot) ?? [];
|
|
25
|
+
// The children at both ends are the same nodes at the same places: they
|
|
26
|
+
// keep their keys, and only the middle is mapped.
|
|
27
|
+
const { head, tail } = unchangedEnds(changes, oldPlot, oldStart, newPlot);
|
|
28
|
+
const before = oldPlot.content;
|
|
29
|
+
const after = newPlot.content;
|
|
30
|
+
const oldTail = before.length - tail;
|
|
31
|
+
const newTail = after.length - tail;
|
|
32
|
+
// Only the middle is walked node by node: typing in one of 5,000 blocks
|
|
33
|
+
// mustn't allocate per block.
|
|
34
|
+
let headSize = 0;
|
|
35
|
+
for (let i = 0; i < head; i++)
|
|
36
|
+
headSize += before[i]?.length ?? 0;
|
|
37
|
+
const found = new Map();
|
|
38
|
+
const byNode = new Map();
|
|
39
|
+
const retagged = new Map();
|
|
40
|
+
for (let i = head, pos = oldStart + headSize; i < oldTail; i++) {
|
|
41
|
+
const node = before[i];
|
|
42
|
+
if (!node)
|
|
43
|
+
continue;
|
|
44
|
+
const key = oldKeys[i];
|
|
45
|
+
if (key && !node.isText) {
|
|
46
|
+
const old = { key, node, start: pos };
|
|
47
|
+
const mapped = mapNodePos(changes, pos, node.isLeaf);
|
|
48
|
+
if (mapped && !mapped.retagged)
|
|
49
|
+
found.set(mapped.pos, old);
|
|
50
|
+
else {
|
|
51
|
+
byNode.set(node, [...(byNode.get(node) ?? []), old]);
|
|
52
|
+
if (mapped)
|
|
53
|
+
retagged.set(mapped.pos, old);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
pos += node.length;
|
|
57
|
+
}
|
|
58
|
+
// Each old key goes once: to its node where it's still there (moved),
|
|
59
|
+
// else to what a retag put in its place, if of its type.
|
|
60
|
+
const claimed = new Set();
|
|
61
|
+
const moved = [];
|
|
62
|
+
for (let i = head; i < newTail; i++) {
|
|
63
|
+
const child = after[i];
|
|
64
|
+
const old = child && !child.isText ? byNode.get(child)?.find((old) => !claimed.has(old)) : undefined;
|
|
65
|
+
if (old)
|
|
66
|
+
claimed.add(old);
|
|
67
|
+
moved.push(old);
|
|
68
|
+
}
|
|
69
|
+
const same = (child, key) => {
|
|
70
|
+
if (key !== undefined)
|
|
71
|
+
return key;
|
|
72
|
+
if (child.isPlot)
|
|
73
|
+
addFreshKeys(keys, child);
|
|
74
|
+
return child.isText ? "" : createNodeKey();
|
|
75
|
+
};
|
|
76
|
+
const newKeys = [];
|
|
77
|
+
for (let i = 0; i < head; i++) {
|
|
78
|
+
const child = after[i];
|
|
79
|
+
if (child)
|
|
80
|
+
newKeys.push(same(child, oldKeys[i]));
|
|
81
|
+
}
|
|
82
|
+
for (let i = head, pos = newStart + headSize; i < newTail; i++) {
|
|
83
|
+
const child = after[i];
|
|
84
|
+
if (!child)
|
|
85
|
+
continue;
|
|
86
|
+
const start = pos;
|
|
87
|
+
pos += child.length;
|
|
88
|
+
if (child.isText) {
|
|
89
|
+
newKeys.push("");
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
let kept = found.get(start) ?? moved[i - head];
|
|
93
|
+
const retag = kept ? undefined : retagged.get(start);
|
|
94
|
+
if (retag && !claimed.has(retag) && retag.node.type == child.type) {
|
|
95
|
+
claimed.add(retag);
|
|
96
|
+
kept = retag;
|
|
97
|
+
}
|
|
98
|
+
// A moved plot is the same object: its content keeps its keys.
|
|
99
|
+
if (child.isPlot && kept?.node.isPlot && kept.node != child)
|
|
100
|
+
mapChildren(kept.node, kept.start + 1, child, start + 1);
|
|
101
|
+
else if (child.isPlot)
|
|
102
|
+
addFreshKeys(keys, child);
|
|
103
|
+
newKeys.push(kept ? kept.key : createNodeKey());
|
|
104
|
+
}
|
|
105
|
+
for (let i = newTail; i < after.length; i++) {
|
|
106
|
+
const child = after[i];
|
|
107
|
+
if (child)
|
|
108
|
+
newKeys.push(same(child, oldKeys[i - newTail + oldTail]));
|
|
109
|
+
}
|
|
110
|
+
keys.set(newPlot, newKeys);
|
|
111
|
+
};
|
|
112
|
+
mapChildren(oldDoc, 0, newDoc, 0);
|
|
113
|
+
return replaced;
|
|
114
|
+
};
|
|
115
|
+
const typeIds = new WeakMap();
|
|
116
|
+
let lastTypeId = 0;
|
|
117
|
+
const typeId = (type) => {
|
|
118
|
+
let id = typeIds.get(type);
|
|
119
|
+
if (id === undefined)
|
|
120
|
+
typeIds.set(type, (id = ++lastTypeId));
|
|
121
|
+
return id;
|
|
122
|
+
};
|
|
123
|
+
// The keys of a list of sibling views (ChildNodeViews renders them, ReactDocTile
|
|
124
|
+
// records them), paired with them. A node's key comes from ReactKeys, through
|
|
125
|
+
// its view (iterDeco); other
|
|
126
|
+
// views are keyed after their previous sibling, so a text run keeps its text
|
|
127
|
+
// node while its start changes. Widgets don't count as that sibling: one
|
|
128
|
+
// coming or going leaves the next text node alone. React point widgets come
|
|
129
|
+
// with keys (iterDeco); other widgets are keyed after the sibling on their
|
|
130
|
+
// side (the next one for a positive side, as a `before` widget sticks to its
|
|
131
|
+
// node) and their type, so a React tag widget keeps its instance while its
|
|
132
|
+
// node does.
|
|
133
|
+
// A composition, and the wrappers around it, keep the keys of the nodes React
|
|
134
|
+
// committed there, and nothing else in their list may take those.
|
|
135
|
+
export const childViewKeys = (views) => {
|
|
136
|
+
const fixed = (view) => view.type == "composition" || view.type == "wrapper" ? view.key : undefined;
|
|
137
|
+
const taken = new Set();
|
|
138
|
+
for (const view of views) {
|
|
139
|
+
const key = fixed(view);
|
|
140
|
+
if (key !== undefined)
|
|
141
|
+
taken.add(key);
|
|
142
|
+
}
|
|
143
|
+
let previous = "";
|
|
144
|
+
let widgets = false;
|
|
145
|
+
// Widgets first get the key before them, and their own key below.
|
|
146
|
+
const keyed = views.map((view) => {
|
|
147
|
+
if (view.type == "widget") {
|
|
148
|
+
if (view.key !== undefined)
|
|
149
|
+
return { view, key: view.key };
|
|
150
|
+
widgets = true;
|
|
151
|
+
return { view, key: previous };
|
|
152
|
+
}
|
|
153
|
+
let key = fixed(view);
|
|
154
|
+
if (key === undefined) {
|
|
155
|
+
key =
|
|
156
|
+
view.type == "node"
|
|
157
|
+
? view.key
|
|
158
|
+
: `${previous}>${view.type == "buffer" ? `buffer${view.side}` : view.type}`;
|
|
159
|
+
while (taken.has(key))
|
|
160
|
+
key += "+";
|
|
161
|
+
}
|
|
162
|
+
previous = key;
|
|
163
|
+
return { view, key };
|
|
164
|
+
});
|
|
165
|
+
if (!widgets)
|
|
166
|
+
return keyed;
|
|
167
|
+
let next = "";
|
|
168
|
+
const used = new Set();
|
|
169
|
+
for (let i = keyed.length - 1; i >= 0; i--) {
|
|
170
|
+
const entry = keyed[i];
|
|
171
|
+
if (!entry)
|
|
172
|
+
continue;
|
|
173
|
+
const { view } = entry;
|
|
174
|
+
if (view.type != "widget") {
|
|
175
|
+
next = entry.key;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (view.key !== undefined)
|
|
179
|
+
continue;
|
|
180
|
+
const id = typeId(view.widget.type);
|
|
181
|
+
let key = view.side > 0 ? `${next}<w${id}` : `${entry.key}>w${id}`;
|
|
182
|
+
while (used.has(key))
|
|
183
|
+
key += "+";
|
|
184
|
+
used.add(key);
|
|
185
|
+
entry.key = key;
|
|
186
|
+
}
|
|
187
|
+
return keyed;
|
|
188
|
+
};
|
package/dist/tile.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
import { type Wordgard } from "wordgard/editor";
|
|
3
|
+
import type { GardState } from "wordgard/state";
|
|
4
|
+
import { type NodeView } from "./decorations/iterDeco.js";
|
|
5
|
+
import { type CompositionInfo, type DecoSet, DocTile, type Tile } from "./wordgardInternals.js";
|
|
6
|
+
export type DocPortal = {
|
|
7
|
+
readonly get: () => ReactElement;
|
|
8
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
9
|
+
readonly committed: (element: ReactElement) => void;
|
|
10
|
+
readonly claim: () => () => void;
|
|
11
|
+
readonly mount: () => void;
|
|
12
|
+
};
|
|
13
|
+
export declare const nodeViewOf: (tile: Tile) => NodeView | undefined;
|
|
14
|
+
export declare class RefusedUpdate extends Error {
|
|
15
|
+
}
|
|
16
|
+
export declare const docPortal: (dom: Element) => DocPortal;
|
|
17
|
+
export declare class ReactDocTile extends DocTile {
|
|
18
|
+
widgets: boolean;
|
|
19
|
+
static create: (state: GardState, dom: Element, wg: Wordgard) => ReactDocTile;
|
|
20
|
+
static host: (state: GardState, dom: Element, wg: Wordgard, mount: () => void) => ReactDocTile;
|
|
21
|
+
static mount: (dom: Element, wg: Wordgard, old: DocTile) => ReactDocTile;
|
|
22
|
+
private static render;
|
|
23
|
+
private static build;
|
|
24
|
+
updateRanges(state: GardState, decoSet: DecoSet, changes: readonly number[], wg: Wordgard, info?: CompositionInfo | null): ReactDocTile;
|
|
25
|
+
}
|
|
26
|
+
export declare const observeShapes: (wg: Wordgard, queue: (records: MutationRecord[]) => void) => {
|
|
27
|
+
takeRecords(): MutationRecord[];
|
|
28
|
+
discard(): void;
|
|
29
|
+
disconnect(): void;
|
|
30
|
+
observe(target: Node, options?: MutationObserverInit): void;
|
|
31
|
+
};
|
|
32
|
+
export declare const rebuildShapes: (wg: Wordgard) => void;
|