@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.
Files changed (111) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE.txt +21 -0
  3. package/README.md +912 -2
  4. package/THIRD_PARTY_LICENSES.txt +93 -0
  5. package/dist/ReactWordgard.d.ts +13 -0
  6. package/dist/ReactWordgard.js +94 -0
  7. package/dist/changes.d.ts +14 -0
  8. package/dist/changes.js +163 -0
  9. package/dist/components/ChildNodeViews.d.ts +8 -0
  10. package/dist/components/ChildNodeViews.js +36 -0
  11. package/dist/components/DecorationRoot.d.ts +13 -0
  12. package/dist/components/DecorationRoot.js +31 -0
  13. package/dist/components/ErrorBoundary.d.ts +18 -0
  14. package/dist/components/ErrorBoundary.js +14 -0
  15. package/dist/components/SeparatorHackView.d.ts +2 -0
  16. package/dist/components/SeparatorHackView.js +4 -0
  17. package/dist/components/ShapeView.d.ts +12 -0
  18. package/dist/components/ShapeView.js +24 -0
  19. package/dist/components/TextNodeView.d.ts +5 -0
  20. package/dist/components/TextNodeView.js +4 -0
  21. package/dist/components/TrailingHackView.d.ts +2 -0
  22. package/dist/components/TrailingHackView.js +4 -0
  23. package/dist/components/WidgetView.d.ts +10 -0
  24. package/dist/components/WidgetView.js +32 -0
  25. package/dist/components/WordgardDoc.d.ts +9 -0
  26. package/dist/components/WordgardDoc.js +36 -0
  27. package/dist/components/WordgardEditor.d.ts +15 -0
  28. package/dist/components/WordgardEditor.js +17 -0
  29. package/dist/components/WordgardScope.d.ts +14 -0
  30. package/dist/components/WordgardScope.js +14 -0
  31. package/dist/components/WrapperView.d.ts +12 -0
  32. package/dist/components/WrapperView.js +23 -0
  33. package/dist/components/marks/MarkView.d.ts +11 -0
  34. package/dist/components/marks/MarkView.js +10 -0
  35. package/dist/components/marks/ReactMarkView.d.ts +10 -0
  36. package/dist/components/marks/ReactMarkView.js +30 -0
  37. package/dist/components/nodes/DefaultNodeView.d.ts +16 -0
  38. package/dist/components/nodes/DefaultNodeView.js +112 -0
  39. package/dist/components/nodes/DocNodeView.d.ts +7 -0
  40. package/dist/components/nodes/DocNodeView.js +5 -0
  41. package/dist/composition.d.ts +23 -0
  42. package/dist/composition.js +95 -0
  43. package/dist/contexts/DecorationContext.d.ts +15 -0
  44. package/dist/contexts/DecorationContext.js +35 -0
  45. package/dist/contexts/DecorationRootContext.d.ts +6 -0
  46. package/dist/contexts/DecorationRootContext.js +4 -0
  47. package/dist/contexts/EditorContext.d.ts +27 -0
  48. package/dist/contexts/EditorContext.js +87 -0
  49. package/dist/contexts/RenderStoreContext.d.ts +15 -0
  50. package/dist/contexts/RenderStoreContext.js +41 -0
  51. package/dist/contexts/editorEvents.d.ts +12 -0
  52. package/dist/contexts/editorEvents.js +92 -0
  53. package/dist/decorations/DecoIterator.d.ts +43 -0
  54. package/dist/decorations/DecoIterator.js +462 -0
  55. package/dist/decorations/ReactMarkType.d.ts +41 -0
  56. package/dist/decorations/ReactMarkType.js +47 -0
  57. package/dist/decorations/ReactShapeType.d.ts +65 -0
  58. package/dist/decorations/ReactShapeType.js +202 -0
  59. package/dist/decorations/ReactWidgetType.d.ts +29 -0
  60. package/dist/decorations/ReactWidgetType.js +67 -0
  61. package/dist/decorations/ReactWrapperType.d.ts +31 -0
  62. package/dist/decorations/ReactWrapperType.js +41 -0
  63. package/dist/decorations/iterDeco.d.ts +82 -0
  64. package/dist/decorations/iterDeco.js +406 -0
  65. package/dist/helpers/attributes.d.ts +2 -0
  66. package/dist/helpers/attributes.js +11 -0
  67. package/dist/helpers/index.d.ts +2 -0
  68. package/dist/helpers/index.js +2 -0
  69. package/dist/helpers/props.d.ts +8 -0
  70. package/dist/helpers/props.js +248 -0
  71. package/dist/helpers/refs.d.ts +6 -0
  72. package/dist/helpers/refs.js +20 -0
  73. package/dist/hooks/useEditorEffect.d.ts +10 -0
  74. package/dist/hooks/useEditorEffect.js +28 -0
  75. package/dist/hooks/useEditorEventCallback.d.ts +8 -0
  76. package/dist/hooks/useEditorEventCallback.js +17 -0
  77. package/dist/hooks/useEditorEventListener.d.ts +10 -0
  78. package/dist/hooks/useEditorEventListener.js +26 -0
  79. package/dist/hooks/useEditorLayoutEffect.d.ts +15 -0
  80. package/dist/hooks/useEditorLayoutEffect.js +34 -0
  81. package/dist/hooks/useEditorState.d.ts +6 -0
  82. package/dist/hooks/useEditorState.js +12 -0
  83. package/dist/hooks/useEditorStateSelector.d.ts +7 -0
  84. package/dist/hooks/useEditorStateSelector.js +11 -0
  85. package/dist/hooks/useIgnoreMutation.d.ts +6 -0
  86. package/dist/hooks/useIgnoreMutation.js +19 -0
  87. package/dist/hooks/useIsNodeSelected.d.ts +2 -0
  88. package/dist/hooks/useIsNodeSelected.js +5 -0
  89. package/dist/hooks/useMergedDOMRefs.d.ts +6 -0
  90. package/dist/hooks/useMergedDOMRefs.js +18 -0
  91. package/dist/hooks/useNodePos.d.ts +4 -0
  92. package/dist/hooks/useNodePos.js +7 -0
  93. package/dist/hooks/useSelectNode.d.ts +6 -0
  94. package/dist/hooks/useSelectNode.js +25 -0
  95. package/dist/hooks/useStopEvent.d.ts +8 -0
  96. package/dist/hooks/useStopEvent.js +21 -0
  97. package/dist/hooks/useWordgard.d.ts +2 -0
  98. package/dist/hooks/useWordgard.js +3 -0
  99. package/dist/index.d.ts +21 -0
  100. package/dist/index.js +21 -0
  101. package/dist/reactKeys.d.ts +39 -0
  102. package/dist/reactKeys.js +188 -0
  103. package/dist/tile.d.ts +32 -0
  104. package/dist/tile.js +1319 -0
  105. package/dist/viewRoots.d.ts +2 -0
  106. package/dist/viewRoots.js +7 -0
  107. package/dist/wordgardInternals.d.ts +211 -0
  108. package/dist/wordgardInternals.js +82 -0
  109. package/package.json +119 -4
  110. package/skills/react-wordgard/SKILL.md +525 -0
  111. package/skills/react-wordgard/references/wordgard.md +189 -0
@@ -0,0 +1,202 @@
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).
6
+ import { createElement, } from "react";
7
+ import { Attributes, Elt } from "wordgard/doc";
8
+ import { ignoresMutation } from "../contexts/DecorationContext.js";
9
+ import { takesRef } from "../helpers/refs.js";
10
+ export const reserved = "data-wg-react";
11
+ const draws = new Map();
12
+ // By component: calling createShape again with one gives the same shape, so
13
+ // Wordgard sees no configuration change and the registry doesn't grow.
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
+ */
19
+ export const createShape = (component) => {
20
+ const found = shapes.get(component);
21
+ if (found)
22
+ return found;
23
+ const id = String(draws.size + 1);
24
+ draws.set(id, ({ attrs, nodeProps, children, ref }) => createElement(component, {
25
+ ...attrs,
26
+ ref: takesRef(component) ? ref : undefined,
27
+ nodeProps,
28
+ children,
29
+ }));
30
+ const shapeElt = (element, attrs, atom) => Elt.create(element, Attributes.merge(attrs, [reserved, id]), atom ? [] : [0]);
31
+ const forTag = (tag) => {
32
+ const { shape } = tag.type;
33
+ const outer = shape.create(tag.param);
34
+ return shapeElt(outer.tagName, outer.attrs, shape.atom);
35
+ };
36
+ const shape = Object.assign(forTag, {
37
+ of: (element, { atom = false } = {}) => shapeElt(element, [], atom),
38
+ });
39
+ shapes.set(component, shape);
40
+ return shape;
41
+ };
42
+ export const idOf = (elt) => Attributes.get(elt.attrs, reserved);
43
+ export const isReactShape = (elt) => {
44
+ const id = idOf(elt);
45
+ return id != null && draws.has(id);
46
+ };
47
+ // The attributes Wordgard merged onto the shape, without the reserved one.
48
+ export const shapeAttributes = (elt) => {
49
+ const attrs = [];
50
+ for (let i = 0; i + 1 < elt.attrs.length; i += 2) {
51
+ const name = elt.attrs[i];
52
+ const value = elt.attrs[i + 1];
53
+ if (name !== undefined && value !== undefined && name != reserved)
54
+ attrs.push(name, value);
55
+ }
56
+ return attrs;
57
+ };
58
+ export const drawShape = (elt, props) => {
59
+ const draw = draws.get(idOf(elt) ?? "");
60
+ if (!draw)
61
+ throw new Error("Not a React shape");
62
+ return draw(props);
63
+ };
64
+ const roots = new WeakMap();
65
+ const contents = new WeakMap();
66
+ const wrapped = new WeakSet();
67
+ // Every element that was a shape's content element: typing never adds or
68
+ // removes one, React moving a shape's content does.
69
+ const everContent = new WeakSet();
70
+ export const shapeOfRoot = (dom) => roots.get(dom);
71
+ export const contentOf = (handle) => handle.content ?? (handle.wrapper || handle.atom ? null : handle.root);
72
+ const rootRef = (handle) => (dom) => {
73
+ if (handle.root)
74
+ roots.delete(handle.root);
75
+ handle.root = dom;
76
+ if (dom)
77
+ roots.set(dom, handle);
78
+ };
79
+ // Whether the shape a root draws is an atom, recorded when React commits it.
80
+ export const atomRef = (handle, atom) => () => {
81
+ handle.atom = atom;
82
+ };
83
+ export const createShapeHandle = () => {
84
+ const handle = { root: null, content: null, wrapper: false, atom: false };
85
+ const contentDOMRef = (dom) => {
86
+ if (handle.content)
87
+ contents.delete(handle.content);
88
+ handle.content = dom;
89
+ if (dom) {
90
+ contents.set(dom, handle);
91
+ everContent.add(dom);
92
+ }
93
+ };
94
+ return { handle, ref: rootRef(handle), contentDOMRef };
95
+ };
96
+ export const createWrapperHandle = () => {
97
+ const handle = { root: null, content: null, wrapper: true, atom: false };
98
+ const wrappedRef = (dom) => {
99
+ if (handle.content)
100
+ wrapped.delete(handle.content);
101
+ handle.content = dom;
102
+ if (dom)
103
+ wrapped.add(dom);
104
+ };
105
+ return { handle, ref: rootRef(handle), wrappedRef };
106
+ };
107
+ // The position of the node a React shape or wrapper draws, from its tile: the
108
+ // root's, or the one around it when wrapped.
109
+ export const nodePos = (handle) => () => {
110
+ let tile = handle.root?.isConnected ? handle.root.wgTile : undefined;
111
+ while (tile && !tile.tag)
112
+ tile = tile.parent ?? undefined;
113
+ if (!tile)
114
+ throw new Error("This React shape or wrapper isn't drawn");
115
+ return tile.posBefore;
116
+ };
117
+ // The React shape or wrapper whose chrome or structure `node` is in: the
118
+ // first root found going up, unless a shape's content element or a wrapped
119
+ // element comes first.
120
+ const shapeAround = (node) => {
121
+ for (let cur = node; cur; cur = cur.parentNode) {
122
+ if (!(cur instanceof Element))
123
+ continue;
124
+ if (contents.has(cur))
125
+ return null;
126
+ const root = roots.get(cur);
127
+ // A shape's root without a content element holds the content itself,
128
+ // unless it's an atom's.
129
+ if (root)
130
+ return root.content || root.atom ? root : null;
131
+ if (wrapped.has(cur))
132
+ return null;
133
+ }
134
+ return null;
135
+ };
136
+ // The React shape whose content element `dom` is.
137
+ const contentShape = (dom) => {
138
+ const handle = contents.get(dom) ?? roots.get(dom);
139
+ return handle && contentOf(handle) == dom ? handle : null;
140
+ };
141
+ // The React shape or wrapper whose root, or an element inside it, `node` is.
142
+ const shapeOf = (node) => {
143
+ for (let cur = node; cur; cur = cur.parentNode) {
144
+ const handle = cur instanceof Element ? roots.get(cur) : undefined;
145
+ if (handle)
146
+ return handle;
147
+ }
148
+ return null;
149
+ };
150
+ const movesContent = (record) => [...record.addedNodes, ...record.removedNodes].some((node) => node instanceof Element && everContent.has(node));
151
+ // Sorts DOM mutation records: the ones a React shape's own commit made outside
152
+ // its content go (Wordgard would take them for edits), and the shapes whose
153
+ // structure changed (an element added or removed between the root and the
154
+ // content, or beside the content in a container's content element) are
155
+ // collected, for their tiles to be rebuilt. The same for a React wrapper, what
156
+ // it wraps standing for the content. In a container (blocks, no text),
157
+ // the browser's typing never adds or removes children of the content element
158
+ // itself, so those records are React's too; `inline` says whether a shape's
159
+ // content is text. Typing in an empty textblock in the same task as a commit
160
+ // moving its content would be taken for React's (a limit).
161
+ export const sortRecords = (records, changed, inline) => {
162
+ // Shapes whose content React moved to another element in these records (one
163
+ // commit's: typing comes in a task of its own): what else changed in their
164
+ // content element is React's too.
165
+ const moved = new Set();
166
+ for (const record of records)
167
+ if (record.type == "childList" && movesContent(record)) {
168
+ const handle = shapeOf(record.target);
169
+ if (handle)
170
+ moved.add(handle);
171
+ }
172
+ const keeps = (record) => {
173
+ const { target } = record;
174
+ if (record.type == "attributes") {
175
+ if (!(target instanceof Element))
176
+ return true;
177
+ return !contents.has(target) && !roots.has(target) && !shapeAround(target);
178
+ }
179
+ const own = record.type == "childList" && target instanceof Element && contentShape(target);
180
+ if (own) {
181
+ if (own.root && inline(own.root) && !moved.has(own))
182
+ return true;
183
+ if (own.root)
184
+ changed.add(own.root);
185
+ return false;
186
+ }
187
+ const handle = shapeAround(target);
188
+ if (!handle)
189
+ return true;
190
+ // An atom's structure is its root's children: its tiles are chrome over them.
191
+ const content = contentOf(handle) ?? (handle.atom ? handle.root : null);
192
+ if (record.type == "childList" &&
193
+ handle.root &&
194
+ content &&
195
+ target instanceof Element &&
196
+ target.contains(content))
197
+ changed.add(handle.root);
198
+ return false;
199
+ };
200
+ // What's left is Wordgard's, unless a decoration around it claims it.
201
+ return records.filter((record) => keeps(record) && !ignoresMutation(record));
202
+ };
@@ -0,0 +1,29 @@
1
+ import { type AllHTMLAttributes, type ComponentType, type HTMLProps, type ReactElement, type RefAttributes, type RefCallback } from "react";
2
+ import { Attributes, Elt } from "wordgard/doc";
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
+ */
8
+ export type WidgetComponentProps<Param, E extends Element = HTMLElement> = Omit<AllHTMLAttributes<HTMLElement>, "value"> & {
9
+ value: Param;
10
+ } & RefAttributes<E>;
11
+ /** `createWidgetType`'s spec: Wordgard's widget spec, with a component in place of `render`. */
12
+ export type WidgetTypeSpec<Param, E extends Element = HTMLElement> = Omit<Widget.Spec<Param>, "render"> & {
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
+ */
18
+ element?: string;
19
+ };
20
+ /** A widget type whose widgets, `type.of(value)`, the component draws. */
21
+ export declare const createWidgetType: <Param, E extends Element = HTMLElement>(spec: WidgetTypeSpec<Param, E>) => Widget.Type<Param>;
22
+ export declare const createWidgetTypeWithRender: <Param, E extends Element = HTMLElement>(spec: WidgetTypeSpec<Param, E> & Pick<Widget.Spec<Param>, "render">) => Widget.Type<Param>;
23
+ export declare const isReactWidget: (widget: Widget) => boolean;
24
+ export declare const drawWidget: (widget: Widget, ref: RefCallback<Element>, attrs?: HTMLProps<HTMLElement>) => ReactElement<any, string | import("react").JSXElementConstructor<any>> | null;
25
+ export declare const widgetElement: (widget: Widget) => string;
26
+ export declare const attributeWidget: (widget: Widget, attrs: Attributes, tagName: string) => Elt<Widget<unknown>>;
27
+ export declare const attributedWidget: (elt: Elt<unknown>) => Widget<any> | null;
28
+ export declare const widgetRoots: WeakSet<Element>;
29
+ export declare const widgetRoot: (dom: Element | null) => void;
@@ -0,0 +1,67 @@
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.
6
+ import { createElement, } from "react";
7
+ import { Attributes, Elt } from "wordgard/doc";
8
+ import { Widget } from "wordgard/editor";
9
+ import { stopsEvent } from "../contexts/DecorationContext.js";
10
+ import { takesRef } from "../helpers/refs.js";
11
+ import { reserved } from "./ReactShapeType.js";
12
+ const components = new WeakMap();
13
+ // Widget.define needs a `render`, which ReactWordgard never calls: only plain
14
+ // Wordgard, given a React widget, draws this.
15
+ const placeholder = () => document.createElement("span");
16
+ /** A widget type whose widgets, `type.of(value)`, the component draws. */
17
+ export const createWidgetType = (spec) => createWidgetTypeWithRender({ ...spec, render: placeholder });
18
+ // For the tests comparing ReactWordgard with plain Wordgard, which draws
19
+ // `render`.
20
+ export const createWidgetTypeWithRender = (spec) => {
21
+ const { component, element = "span", propagateEvent, ...widgetSpec } = spec;
22
+ // Its components' useStopEvent first, then the spec's.
23
+ const type = Widget.define({
24
+ ...widgetSpec,
25
+ propagateEvent: (event) => !stopsEvent(event) &&
26
+ (typeof propagateEvent == "function" ? propagateEvent(event) : propagateEvent !== false),
27
+ });
28
+ // A type guard, not checked by the compiler: a widget of `type` was made by
29
+ // `type.of`, so its value is a Param.
30
+ const own = (widget) => widget.type == type;
31
+ const draw = (widget, ref, attrs) => own(widget)
32
+ ? createElement(component, {
33
+ ...attrs,
34
+ value: widget.value,
35
+ ref: takesRef(component) ? ref : undefined,
36
+ })
37
+ : null;
38
+ components.set(type, { draw, element });
39
+ return type;
40
+ };
41
+ export const isReactWidget = (widget) => components.has(widget.type);
42
+ // The widget's component, its ref on the root element.
43
+ export const drawWidget = (widget, ref, attrs = {}) => components.get(widget.type)?.draw(widget, ref, attrs) ?? null;
44
+ // The root a React widget's DecorationRoot draws.
45
+ export const widgetElement = (widget) => components.get(widget.type)?.element ?? "span";
46
+ // Wordgard adds attributes to a widget-drawn node by wrapping the widget in an
47
+ // element. Around a React widget, that element is marked: the view draws the
48
+ // widget alone, the attributes as props, so a selection (the class it adds)
49
+ // doesn't move the component under a new parent, which would remount it. The
50
+ // marked element still merges later attributes and takes wrappers, as
51
+ // Wordgard's.
52
+ const attributed = "widget";
53
+ export const attributeWidget = (widget, attrs, tagName) => Elt.create(tagName, isReactWidget(widget) ? Attributes.merge(attrs, [reserved, attributed]) : attrs, [widget]);
54
+ // The React widget a marked element holds, its attributes being the widget's.
55
+ export const attributedWidget = (elt) => {
56
+ const [widget] = elt.children;
57
+ return Attributes.get(elt.attrs, reserved) == attributed && widget instanceof Widget
58
+ ? widget
59
+ : null;
60
+ };
61
+ // The root elements React widgets forwarded their ref to, for ReactDocTile to
62
+ // check it got the widget's DOM.
63
+ export const widgetRoots = new WeakSet();
64
+ export const widgetRoot = (dom) => {
65
+ if (dom)
66
+ widgetRoots.add(dom);
67
+ };
@@ -0,0 +1,31 @@
1
+ import { type AllHTMLAttributes, type ComponentType, type HTMLProps, type ReactElement, type ReactNode, type RefAttributes, type RefCallback } from "react";
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
+ */
6
+ export type WrapperComponentProps<E extends Element = HTMLElement> = AllHTMLAttributes<HTMLElement> & {
7
+ nodeProps: {
8
+ node: DocNode;
9
+ getPos: () => number;
10
+ };
11
+ children: ReactNode;
12
+ } & RefAttributes<E>;
13
+ /** `createWrapper`'s options. */
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
+ */
19
+ element?: string;
20
+ };
21
+ type DrawProps = {
22
+ attrs: HTMLProps<HTMLElement>;
23
+ nodeProps: WrapperComponentProps["nodeProps"];
24
+ children: ReactNode;
25
+ ref: RefCallback<Element>;
26
+ };
27
+ /** A wrapper, for `Decoration.Tag.wrapper` and `Decoration.Point.wrapper`. */
28
+ export declare const createWrapper: <E extends Element = HTMLElement>(component: ComponentType<WrapperComponentProps<E>>, { element }?: WrapperOptions) => Elt;
29
+ export declare const isReactWrapper: (elt: Elt<unknown>) => boolean;
30
+ export declare const drawWrapper: (elt: Elt<unknown>, props: DrawProps) => ReactElement<any, string | import("react").JSXElementConstructor<any>>;
31
+ export {};
@@ -0,0 +1,41 @@
1
+ // Named after ReactWidgetType. A React wrapper is a real Wordgard `Elt` with a hole, registered as
2
+ // React shapes are (the reserved attribute).
3
+ import { createElement, } from "react";
4
+ import { Elt } from "wordgard/doc";
5
+ import { takesRef } from "../helpers/refs.js";
6
+ import { idOf, reserved } from "./ReactShapeType.js";
7
+ // Keyed apart from React shapes' ids, which share the attribute.
8
+ const draws = new Map();
9
+ // By component, then root element, as React shapes are.
10
+ const wrappers = new WeakMap();
11
+ /** A wrapper, for `Decoration.Tag.wrapper` and `Decoration.Point.wrapper`. */
12
+ export const createWrapper = (component, { element = "div" } = {}) => {
13
+ let registered = wrappers.get(component);
14
+ if (!registered) {
15
+ const id = `w${draws.size + 1}`;
16
+ draws.set(id, ({ attrs, nodeProps, children, ref }) => createElement(component, {
17
+ ...attrs,
18
+ ref: takesRef(component) ? ref : undefined,
19
+ nodeProps,
20
+ children,
21
+ }));
22
+ registered = { id, elts: new Map() };
23
+ wrappers.set(component, registered);
24
+ }
25
+ let elt = registered.elts.get(element);
26
+ if (!elt) {
27
+ elt = Elt.mk(element, { [reserved]: registered.id }, [0]);
28
+ registered.elts.set(element, elt);
29
+ }
30
+ return elt;
31
+ };
32
+ export const isReactWrapper = (elt) => {
33
+ const id = idOf(elt);
34
+ return id != null && draws.has(id);
35
+ };
36
+ export const drawWrapper = (elt, props) => {
37
+ const draw = draws.get(idOf(elt) ?? "");
38
+ if (!draw)
39
+ throw new Error("Not a React wrapper");
40
+ return draw(props);
41
+ };
@@ -0,0 +1,82 @@
1
+ import { Mark, type Node, type Plot } from "wordgard/doc";
2
+ import { type Decoration, Widget } from "wordgard/editor";
3
+ import type { CompositionView } from "../composition.js";
4
+ import { type RenderStore } from "../contexts/RenderStoreContext.js";
5
+ import type { DecoElt } from "../wordgardInternals.js";
6
+ import { type DecoIterator } from "./DecoIterator.js";
7
+ export type NodeView = {
8
+ type: "node";
9
+ node: Node;
10
+ key: string;
11
+ shape: Decoration.Shape;
12
+ children: readonly ChildView[] | null;
13
+ };
14
+ type WrapperChildView = {
15
+ type: "wrapper";
16
+ elt: DecoElt;
17
+ spanning: boolean;
18
+ children: ChildView[];
19
+ key?: string;
20
+ mark?: Mark;
21
+ };
22
+ type WidgetChildView = {
23
+ type: "widget";
24
+ widget: Widget;
25
+ side: number;
26
+ key?: string;
27
+ offset?: number;
28
+ };
29
+ export type ChildView = NodeView | {
30
+ type: "text";
31
+ text: string;
32
+ } | WrapperChildView | {
33
+ type: "buffer";
34
+ side: -1 | 1;
35
+ } | WidgetChildView | {
36
+ type: "composition";
37
+ key: string;
38
+ rendered: string | null;
39
+ } | {
40
+ type: "trailingHack";
41
+ };
42
+ export type WrapperView = {
43
+ readonly elt: DecoElt;
44
+ readonly spanning: boolean;
45
+ readonly key?: string;
46
+ readonly mark?: Mark;
47
+ };
48
+ export type CompositionLayout = {
49
+ readonly from: number;
50
+ readonly to: number;
51
+ readonly wrappers: readonly WrapperView[];
52
+ readonly cursor: boolean;
53
+ };
54
+ export declare const cursorWrappers: (marks: Mark.Set) => WrapperView[];
55
+ export declare const uneditable: (shape: DecoElt, inEditable: boolean) => {
56
+ shape: DecoElt;
57
+ inEditable: boolean;
58
+ };
59
+ export declare const sameNodeView: (a: NodeView, b: NodeView) => boolean;
60
+ export declare const shapeOf: (views: readonly ChildView[]) => object;
61
+ export type LayoutContext = {
62
+ deco: DecoIterator;
63
+ store: RenderStore;
64
+ changes: readonly number[] | null;
65
+ layouts: WeakMap<Plot, Layout>;
66
+ composition: CompositionView | null;
67
+ composing: ReadonlySet<Plot>;
68
+ replaced: WeakMap<Plot, Plot>;
69
+ pass: object;
70
+ };
71
+ export type Layout = {
72
+ offset: number;
73
+ pass: object;
74
+ readonly views: readonly ChildView[];
75
+ readonly flat: boolean;
76
+ };
77
+ type ParentStart = {
78
+ readonly start: number;
79
+ readonly oldStart: number | null;
80
+ };
81
+ export declare const iterDeco: (context: LayoutContext, plot: Plot, start: number, parent?: ParentStart) => readonly ChildView[];
82
+ export {};