@handlewithcare/react-prosemirror 2.1.0 → 2.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/README.md +22 -3
- package/dist/cjs/components/CustomNodeView.js +80 -4
- package/dist/cjs/components/NodeView.js +13 -133
- package/dist/cjs/components/OutputSpec.js +1 -1
- package/dist/cjs/components/ReactNodeView.js +136 -0
- package/dist/cjs/components/TextNodeView.js +2 -2
- package/dist/cjs/findDOMNode.js +46 -0
- package/dist/cjs/hooks/useEditor.js +14 -3
- package/dist/cjs/hooks/useEditorEffect.js +5 -2
- package/dist/cjs/hooks/useIsNodeSelected.js +21 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/plugins/componentEventListeners.js +1 -1
- package/dist/cjs/plugins/reactKeys.js +1 -1
- package/dist/esm/components/CustomNodeView.js +82 -6
- package/dist/esm/components/NodeView.js +14 -134
- package/dist/esm/components/OutputSpec.js +1 -1
- package/dist/esm/components/ReactNodeView.js +85 -0
- package/dist/esm/components/TextNodeView.js +1 -1
- package/dist/esm/findDOMNode.js +31 -0
- package/dist/esm/hooks/useEditor.js +14 -3
- package/dist/esm/hooks/useEditorEffect.js +5 -2
- package/dist/esm/hooks/useIsNodeSelected.js +11 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/plugins/componentEventListeners.js +1 -1
- package/dist/esm/plugins/reactKeys.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/components/CustomNodeView.d.ts +3 -12
- package/dist/types/components/ReactNodeView.d.ts +11 -0
- package/dist/types/contexts/EditorContext.d.ts +2 -0
- package/dist/types/findDOMNode.d.ts +3 -0
- package/dist/types/hooks/useEditor.d.ts +1 -0
- package/dist/types/hooks/useIsNodeSelected.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +17 -4
|
@@ -1,12 +1,79 @@
|
|
|
1
|
-
import React, { createElement, useContext } from "react";
|
|
1
|
+
import React, { cloneElement, createElement, memo, useContext, useLayoutEffect, useMemo, useRef } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
|
+
import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
|
|
3
4
|
import { EditorContext } from "../contexts/EditorContext.js";
|
|
4
5
|
import { useClientOnly } from "../hooks/useClientOnly.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
|
|
7
|
+
import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";
|
|
8
|
+
export const CustomNodeView = /*#__PURE__*/ memo(function CustomNodeView(param) {
|
|
9
|
+
let { customNodeView, node, getPos, innerDeco, outerDeco } = param;
|
|
8
10
|
const { view } = useContext(EditorContext);
|
|
11
|
+
const domRef = useRef(null);
|
|
12
|
+
const nodeDomRef = useRef(null);
|
|
13
|
+
const contentDomRef = useRef(null);
|
|
14
|
+
const getPosFunc = useRef(()=>getPos.current()).current;
|
|
15
|
+
// this is ill-conceived; should revisit
|
|
16
|
+
const initialNode = useRef(node);
|
|
17
|
+
const initialOuterDeco = useRef(outerDeco);
|
|
18
|
+
const initialInnerDeco = useRef(innerDeco);
|
|
19
|
+
const customNodeViewRootRef = useRef(null);
|
|
20
|
+
const customNodeViewRef = useRef(null);
|
|
9
21
|
const shouldRender = useClientOnly();
|
|
22
|
+
useLayoutEffect(()=>{
|
|
23
|
+
if (!customNodeViewRef.current || !customNodeViewRootRef.current || !shouldRender) return;
|
|
24
|
+
const { dom } = customNodeViewRef.current;
|
|
25
|
+
nodeDomRef.current = customNodeViewRootRef.current;
|
|
26
|
+
customNodeViewRootRef.current.appendChild(dom);
|
|
27
|
+
return ()=>{
|
|
28
|
+
customNodeViewRef.current?.destroy?.();
|
|
29
|
+
};
|
|
30
|
+
}, [
|
|
31
|
+
customNodeViewRef,
|
|
32
|
+
customNodeViewRootRef,
|
|
33
|
+
nodeDomRef,
|
|
34
|
+
shouldRender
|
|
35
|
+
]);
|
|
36
|
+
useLayoutEffect(()=>{
|
|
37
|
+
if (!customNodeView || !customNodeViewRef.current || !shouldRender) return;
|
|
38
|
+
const { destroy, update } = customNodeViewRef.current;
|
|
39
|
+
const updated = update?.call(customNodeViewRef.current, node, outerDeco, innerDeco) ?? true;
|
|
40
|
+
if (updated) return;
|
|
41
|
+
destroy?.call(customNodeViewRef.current);
|
|
42
|
+
if (!customNodeViewRootRef.current) return;
|
|
43
|
+
initialNode.current = node;
|
|
44
|
+
initialOuterDeco.current = outerDeco;
|
|
45
|
+
initialInnerDeco.current = innerDeco;
|
|
46
|
+
customNodeViewRef.current = customNodeView(initialNode.current, // customNodeView will only be set if view is set, and we can only reach
|
|
47
|
+
// this line if customNodeView is set
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
49
|
+
view, getPosFunc, initialOuterDeco.current, initialInnerDeco.current);
|
|
50
|
+
const { dom } = customNodeViewRef.current;
|
|
51
|
+
nodeDomRef.current = customNodeViewRootRef.current;
|
|
52
|
+
customNodeViewRootRef.current.appendChild(dom);
|
|
53
|
+
}, [
|
|
54
|
+
customNodeView,
|
|
55
|
+
view,
|
|
56
|
+
innerDeco,
|
|
57
|
+
node,
|
|
58
|
+
outerDeco,
|
|
59
|
+
getPos,
|
|
60
|
+
customNodeViewRef,
|
|
61
|
+
customNodeViewRootRef,
|
|
62
|
+
initialNode,
|
|
63
|
+
initialOuterDeco,
|
|
64
|
+
initialInnerDeco,
|
|
65
|
+
nodeDomRef,
|
|
66
|
+
shouldRender,
|
|
67
|
+
getPosFunc
|
|
68
|
+
]);
|
|
69
|
+
const { childDescriptors, nodeViewDescRef } = useNodeViewDescriptor(node, ()=>getPos.current(), domRef, nodeDomRef, innerDeco, outerDeco, undefined, contentDomRef);
|
|
70
|
+
const childContextValue = useMemo(()=>({
|
|
71
|
+
parentRef: nodeViewDescRef,
|
|
72
|
+
siblingsRef: childDescriptors
|
|
73
|
+
}), [
|
|
74
|
+
childDescriptors,
|
|
75
|
+
nodeViewDescRef
|
|
76
|
+
]);
|
|
10
77
|
if (!shouldRender) return null;
|
|
11
78
|
if (!customNodeViewRef.current) {
|
|
12
79
|
customNodeViewRef.current = customNodeView(initialNode.current, // customNodeView will only be set if view is set, and we can only reach
|
|
@@ -16,7 +83,7 @@ export function CustomNodeView(param) {
|
|
|
16
83
|
}
|
|
17
84
|
const { contentDOM } = customNodeViewRef.current;
|
|
18
85
|
contentDomRef.current = contentDOM ?? null;
|
|
19
|
-
|
|
86
|
+
const element = /*#__PURE__*/ createElement(node.isInline ? "span" : "div", {
|
|
20
87
|
ref: customNodeViewRootRef,
|
|
21
88
|
contentEditable: !!contentDOM,
|
|
22
89
|
suppressContentEditableWarning: true
|
|
@@ -25,4 +92,13 @@ export function CustomNodeView(param) {
|
|
|
25
92
|
node: node,
|
|
26
93
|
innerDecorations: innerDeco
|
|
27
94
|
}), contentDOM));
|
|
28
|
-
|
|
95
|
+
const decoratedElement = /*#__PURE__*/ cloneElement(outerDeco.reduce(wrapInDeco, element), // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
96
|
+
outerDeco.some((d)=>d.type.attrs.nodeName) ? {
|
|
97
|
+
ref: domRef
|
|
98
|
+
} : // we've already passed the domRef to the NodeView component
|
|
99
|
+
// as a prop
|
|
100
|
+
undefined);
|
|
101
|
+
return /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
|
|
102
|
+
value: childContextValue
|
|
103
|
+
}, decoratedElement);
|
|
104
|
+
});
|
|
@@ -1,145 +1,25 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
|
|
1
|
+
import React, { memo, useContext } from "react";
|
|
3
2
|
import { EditorContext } from "../contexts/EditorContext.js";
|
|
4
|
-
import { NodeViewContext } from "../contexts/NodeViewContext.js";
|
|
5
|
-
import { SelectNodeContext } from "../contexts/SelectNodeContext.js";
|
|
6
|
-
import { StopEventContext } from "../contexts/StopEventContext.js";
|
|
7
|
-
import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
|
|
8
|
-
import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";
|
|
9
3
|
import { CustomNodeView } from "./CustomNodeView.js";
|
|
10
|
-
import {
|
|
4
|
+
import { ReactNodeView } from "./ReactNodeView.js";
|
|
11
5
|
export const NodeView = /*#__PURE__*/ memo(function NodeView(param) {
|
|
12
6
|
let { outerDeco, getPos, node, innerDeco, ...props } = param;
|
|
13
|
-
const domRef = useRef(null);
|
|
14
|
-
const nodeDomRef = useRef(null);
|
|
15
|
-
const contentDomRef = useRef(null);
|
|
16
|
-
const getPosFunc = useRef(()=>getPos.current()).current;
|
|
17
|
-
// this is ill-conceived; should revisit
|
|
18
|
-
const initialNode = useRef(node);
|
|
19
|
-
const initialOuterDeco = useRef(outerDeco);
|
|
20
|
-
const initialInnerDeco = useRef(innerDeco);
|
|
21
|
-
const customNodeViewRootRef = useRef(null);
|
|
22
|
-
const customNodeViewRef = useRef(null);
|
|
23
|
-
// const state = useEditorState();
|
|
24
|
-
const { nodeViews } = useContext(NodeViewContext);
|
|
25
7
|
const { view } = useContext(EditorContext);
|
|
26
|
-
let element = null;
|
|
27
|
-
const Component = nodeViews[node.type.name];
|
|
28
|
-
const outputSpec = useMemo(()=>node.type.spec.toDOM?.(node), [
|
|
29
|
-
node
|
|
30
|
-
]);
|
|
31
|
-
// TODO: Would be great to pull all of the custom node view stuff into
|
|
32
|
-
// a hook
|
|
33
8
|
const customNodeView = view?.someProp("nodeViews", (nodeViews)=>nodeViews?.[node.type.name]);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const { dom } = customNodeViewRef.current;
|
|
37
|
-
nodeDomRef.current = customNodeViewRootRef.current;
|
|
38
|
-
customNodeViewRootRef.current.appendChild(dom);
|
|
39
|
-
return ()=>{
|
|
40
|
-
customNodeViewRef.current?.destroy?.();
|
|
41
|
-
};
|
|
42
|
-
}, []);
|
|
43
|
-
useLayoutEffect(()=>{
|
|
44
|
-
if (!customNodeView || !customNodeViewRef.current) return;
|
|
45
|
-
const { destroy, update } = customNodeViewRef.current;
|
|
46
|
-
const updated = update?.call(customNodeViewRef.current, node, outerDeco, innerDeco) ?? true;
|
|
47
|
-
if (updated) return;
|
|
48
|
-
destroy?.call(customNodeViewRef.current);
|
|
49
|
-
if (!customNodeViewRootRef.current) return;
|
|
50
|
-
initialNode.current = node;
|
|
51
|
-
initialOuterDeco.current = outerDeco;
|
|
52
|
-
initialInnerDeco.current = innerDeco;
|
|
53
|
-
customNodeViewRef.current = customNodeView(initialNode.current, // customNodeView will only be set if view is set, and we can only reach
|
|
54
|
-
// this line if customNodeView is set
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
56
|
-
view, ()=>getPos.current(), initialOuterDeco.current, initialInnerDeco.current);
|
|
57
|
-
const { dom } = customNodeViewRef.current;
|
|
58
|
-
nodeDomRef.current = customNodeViewRootRef.current;
|
|
59
|
-
customNodeViewRootRef.current.appendChild(dom);
|
|
60
|
-
}, [
|
|
61
|
-
customNodeView,
|
|
62
|
-
view,
|
|
63
|
-
innerDeco,
|
|
64
|
-
node,
|
|
65
|
-
outerDeco,
|
|
66
|
-
getPos
|
|
67
|
-
]);
|
|
68
|
-
const { hasContentDOM, childDescriptors, setStopEvent, setSelectNode, nodeViewDescRef } = useNodeViewDescriptor(node, ()=>getPos.current(), domRef, nodeDomRef, innerDeco, outerDeco, undefined, contentDomRef);
|
|
69
|
-
const finalProps = {
|
|
70
|
-
...props,
|
|
71
|
-
...!hasContentDOM && {
|
|
72
|
-
contentEditable: false
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
const nodeProps = useMemo(()=>({
|
|
76
|
-
node: node,
|
|
77
|
-
getPos: getPosFunc,
|
|
78
|
-
decorations: outerDeco,
|
|
79
|
-
innerDecorations: innerDeco
|
|
80
|
-
}), [
|
|
81
|
-
getPosFunc,
|
|
82
|
-
innerDeco,
|
|
83
|
-
node,
|
|
84
|
-
outerDeco
|
|
85
|
-
]);
|
|
86
|
-
if (Component) {
|
|
87
|
-
element = /*#__PURE__*/ React.createElement(Component, {
|
|
88
|
-
...finalProps,
|
|
89
|
-
ref: nodeDomRef,
|
|
90
|
-
nodeProps: nodeProps
|
|
91
|
-
}, /*#__PURE__*/ React.createElement(ChildNodeViews, {
|
|
92
|
-
getPos: getPos,
|
|
93
|
-
node: node,
|
|
94
|
-
innerDecorations: innerDeco
|
|
95
|
-
}));
|
|
96
|
-
} else if (customNodeView) {
|
|
97
|
-
element = /*#__PURE__*/ React.createElement(CustomNodeView, {
|
|
98
|
-
contentDomRef: contentDomRef,
|
|
9
|
+
if (customNodeView) {
|
|
10
|
+
return /*#__PURE__*/ React.createElement(CustomNodeView, {
|
|
99
11
|
customNodeView: customNodeView,
|
|
100
|
-
customNodeViewRef: customNodeViewRef,
|
|
101
|
-
customNodeViewRootRef: customNodeViewRootRef,
|
|
102
|
-
initialInnerDeco: initialInnerDeco,
|
|
103
|
-
initialNode: initialNode,
|
|
104
|
-
initialOuterDeco: initialOuterDeco,
|
|
105
12
|
node: node,
|
|
106
|
-
|
|
107
|
-
|
|
13
|
+
innerDeco: innerDeco,
|
|
14
|
+
outerDeco: outerDeco,
|
|
15
|
+
getPos: getPos
|
|
108
16
|
});
|
|
109
|
-
} else {
|
|
110
|
-
if (outputSpec) {
|
|
111
|
-
element = /*#__PURE__*/ React.createElement(OutputSpec, {
|
|
112
|
-
...finalProps,
|
|
113
|
-
ref: nodeDomRef,
|
|
114
|
-
outputSpec: outputSpec
|
|
115
|
-
}, /*#__PURE__*/ React.createElement(ChildNodeViews, {
|
|
116
|
-
getPos: getPos,
|
|
117
|
-
node: node,
|
|
118
|
-
innerDecorations: innerDeco
|
|
119
|
-
}));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (!element) {
|
|
123
|
-
throw new Error(`Node spec for ${node.type.name} is missing toDOM`);
|
|
124
17
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
parentRef: nodeViewDescRef,
|
|
133
|
-
siblingsRef: childDescriptors
|
|
134
|
-
}), [
|
|
135
|
-
childDescriptors,
|
|
136
|
-
nodeViewDescRef
|
|
137
|
-
]);
|
|
138
|
-
return /*#__PURE__*/ React.createElement(SelectNodeContext.Provider, {
|
|
139
|
-
value: setSelectNode
|
|
140
|
-
}, /*#__PURE__*/ React.createElement(StopEventContext.Provider, {
|
|
141
|
-
value: setStopEvent
|
|
142
|
-
}, /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
|
|
143
|
-
value: childContextValue
|
|
144
|
-
}, decoratedElement)));
|
|
18
|
+
return /*#__PURE__*/ React.createElement(ReactNodeView, {
|
|
19
|
+
node: node,
|
|
20
|
+
innerDeco: innerDeco,
|
|
21
|
+
outerDeco: outerDeco,
|
|
22
|
+
getPos: getPos,
|
|
23
|
+
...props
|
|
24
|
+
});
|
|
145
25
|
});
|
|
@@ -6,7 +6,7 @@ const ForwardedOutputSpec = /*#__PURE__*/ memo(/*#__PURE__*/ forwardRef(function
|
|
|
6
6
|
return /*#__PURE__*/ React.createElement(React.Fragment, null, outputSpec);
|
|
7
7
|
}
|
|
8
8
|
if (!Array.isArray(outputSpec)) {
|
|
9
|
-
throw new Error("@
|
|
9
|
+
throw new Error("@handlewithcare/react-prosemirror only supports strings and arrays in toDOM");
|
|
10
10
|
}
|
|
11
11
|
const tagSpec = outputSpec[0];
|
|
12
12
|
const tagName = tagSpec.replace(" ", ":");
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { cloneElement, memo, useContext, useMemo, useRef } from "react";
|
|
2
|
+
import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
|
|
3
|
+
import { NodeViewContext } from "../contexts/NodeViewContext.js";
|
|
4
|
+
import { SelectNodeContext } from "../contexts/SelectNodeContext.js";
|
|
5
|
+
import { StopEventContext } from "../contexts/StopEventContext.js";
|
|
6
|
+
import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
|
|
7
|
+
import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";
|
|
8
|
+
import { OutputSpec } from "./OutputSpec.js";
|
|
9
|
+
export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
|
|
10
|
+
let { outerDeco, getPos, node, innerDeco, ...props } = param;
|
|
11
|
+
const domRef = useRef(null);
|
|
12
|
+
const nodeDomRef = useRef(null);
|
|
13
|
+
const contentDomRef = useRef(null);
|
|
14
|
+
const getPosFunc = useRef(()=>getPos.current()).current;
|
|
15
|
+
const { nodeViews } = useContext(NodeViewContext);
|
|
16
|
+
let element = null;
|
|
17
|
+
const Component = nodeViews[node.type.name];
|
|
18
|
+
const outputSpec = useMemo(()=>node.type.spec.toDOM?.(node), [
|
|
19
|
+
node
|
|
20
|
+
]);
|
|
21
|
+
const { hasContentDOM, childDescriptors, setStopEvent, setSelectNode, nodeViewDescRef } = useNodeViewDescriptor(node, ()=>getPos.current(), domRef, nodeDomRef, innerDeco, outerDeco, undefined, contentDomRef);
|
|
22
|
+
const finalProps = {
|
|
23
|
+
...props,
|
|
24
|
+
...!hasContentDOM && {
|
|
25
|
+
contentEditable: false
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const nodeProps = useMemo(()=>({
|
|
29
|
+
node: node,
|
|
30
|
+
getPos: getPosFunc,
|
|
31
|
+
decorations: outerDeco,
|
|
32
|
+
innerDecorations: innerDeco
|
|
33
|
+
}), [
|
|
34
|
+
getPosFunc,
|
|
35
|
+
innerDeco,
|
|
36
|
+
node,
|
|
37
|
+
outerDeco
|
|
38
|
+
]);
|
|
39
|
+
if (Component) {
|
|
40
|
+
element = /*#__PURE__*/ React.createElement(Component, {
|
|
41
|
+
...finalProps,
|
|
42
|
+
ref: nodeDomRef,
|
|
43
|
+
nodeProps: nodeProps
|
|
44
|
+
}, /*#__PURE__*/ React.createElement(ChildNodeViews, {
|
|
45
|
+
getPos: getPos,
|
|
46
|
+
node: node,
|
|
47
|
+
innerDecorations: innerDeco
|
|
48
|
+
}));
|
|
49
|
+
} else {
|
|
50
|
+
if (outputSpec) {
|
|
51
|
+
element = /*#__PURE__*/ React.createElement(OutputSpec, {
|
|
52
|
+
...finalProps,
|
|
53
|
+
ref: nodeDomRef,
|
|
54
|
+
outputSpec: outputSpec
|
|
55
|
+
}, /*#__PURE__*/ React.createElement(ChildNodeViews, {
|
|
56
|
+
getPos: getPos,
|
|
57
|
+
node: node,
|
|
58
|
+
innerDecorations: innerDeco
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!element) {
|
|
63
|
+
throw new Error(`Node spec for ${node.type.name} is missing toDOM`);
|
|
64
|
+
}
|
|
65
|
+
const decoratedElement = /*#__PURE__*/ cloneElement(outerDeco.reduce(wrapInDeco, element), // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
outerDeco.some((d)=>d.type.attrs.nodeName) ? {
|
|
67
|
+
ref: domRef
|
|
68
|
+
} : // we've already passed the domRef to the NodeView component
|
|
69
|
+
// as a prop
|
|
70
|
+
undefined);
|
|
71
|
+
const childContextValue = useMemo(()=>({
|
|
72
|
+
parentRef: nodeViewDescRef,
|
|
73
|
+
siblingsRef: childDescriptors
|
|
74
|
+
}), [
|
|
75
|
+
childDescriptors,
|
|
76
|
+
nodeViewDescRef
|
|
77
|
+
]);
|
|
78
|
+
return /*#__PURE__*/ React.createElement(SelectNodeContext.Provider, {
|
|
79
|
+
value: setSelectNode
|
|
80
|
+
}, /*#__PURE__*/ React.createElement(StopEventContext.Provider, {
|
|
81
|
+
value: setStopEvent
|
|
82
|
+
}, /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
|
|
83
|
+
value: childContextValue
|
|
84
|
+
}, decoratedElement)));
|
|
85
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DecorationSet } from "prosemirror-view";
|
|
2
2
|
import { Component } from "react";
|
|
3
|
-
import { findDOMNode } from "
|
|
3
|
+
import { findDOMNode } from "../findDOMNode.js";
|
|
4
4
|
import { CompositionViewDesc, TextViewDesc, sortViewDescs } from "../viewdesc.js";
|
|
5
5
|
import { wrapInDeco } from "./ChildNodeViews.js";
|
|
6
6
|
function shallowEqual(objA, objB) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//@ts-expect-error This module isn't typed
|
|
2
|
+
import { findCurrentHostFiber } from "react-reconciler/reflection";
|
|
3
|
+
// https://github.com/facebook/react/blob/main/packages/shared/ReactInstanceMap.js#L18
|
|
4
|
+
export function getInstance(key) {
|
|
5
|
+
return key._reactInternals;
|
|
6
|
+
}
|
|
7
|
+
// https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js#L322
|
|
8
|
+
function getPublicInstance(instance) {
|
|
9
|
+
return instance;
|
|
10
|
+
}
|
|
11
|
+
// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberReconciler.js#L153
|
|
12
|
+
function findHostInstance(component) {
|
|
13
|
+
const fiber = getInstance(component);
|
|
14
|
+
if (fiber === undefined) {
|
|
15
|
+
if (typeof component.render === "function") {
|
|
16
|
+
throw new Error("Unable to find node on an unmounted component.");
|
|
17
|
+
} else {
|
|
18
|
+
const keys = Object.keys(component).join(",");
|
|
19
|
+
throw new Error(`Argument appears to not be a ReactComponent. Keys: ${keys}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const hostFiber = findCurrentHostFiber(fiber);
|
|
23
|
+
if (hostFiber === null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return getPublicInstance(hostFiber.stateNode);
|
|
27
|
+
}
|
|
28
|
+
// https://github.com/facebook/react/blob/main/packages/react-dom/src/client/ReactDOMClient.js#L43
|
|
29
|
+
export function findDOMNode(componentOrElement) {
|
|
30
|
+
return findHostInstance(componentOrElement);
|
|
31
|
+
}
|
|
@@ -171,6 +171,7 @@ let didWarnValueDefaultValue = false;
|
|
|
171
171
|
didWarnValueDefaultValue = true;
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
const flushSyncRef = useRef(true);
|
|
174
175
|
const [cursorWrapper, _setCursorWrapper] = useState(null);
|
|
175
176
|
const forceUpdate = useForceUpdate();
|
|
176
177
|
const defaultState = options.defaultState ?? EMPTY_STATE;
|
|
@@ -192,14 +193,23 @@ let didWarnValueDefaultValue = false;
|
|
|
192
193
|
setCursorWrapper
|
|
193
194
|
]);
|
|
194
195
|
const dispatchTransaction = useCallback(function dispatchTransaction(tr) {
|
|
195
|
-
|
|
196
|
+
if (flushSyncRef.current) {
|
|
197
|
+
flushSync(()=>{
|
|
198
|
+
if (!options.state) {
|
|
199
|
+
setState((s)=>s.apply(tr));
|
|
200
|
+
}
|
|
201
|
+
if (options.dispatchTransaction) {
|
|
202
|
+
options.dispatchTransaction.call(this, tr);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
} else {
|
|
196
206
|
if (!options.state) {
|
|
197
207
|
setState((s)=>s.apply(tr));
|
|
198
208
|
}
|
|
199
209
|
if (options.dispatchTransaction) {
|
|
200
210
|
options.dispatchTransaction.call(this, tr);
|
|
201
211
|
}
|
|
202
|
-
}
|
|
212
|
+
}
|
|
203
213
|
}, [
|
|
204
214
|
options.dispatchTransaction,
|
|
205
215
|
options.state
|
|
@@ -264,7 +274,8 @@ let didWarnValueDefaultValue = false;
|
|
|
264
274
|
registerEventListener,
|
|
265
275
|
unregisterEventListener,
|
|
266
276
|
cursorWrapper,
|
|
267
|
-
docViewDescRef
|
|
277
|
+
docViewDescRef,
|
|
278
|
+
flushSyncRef
|
|
268
279
|
}), [
|
|
269
280
|
view,
|
|
270
281
|
registerEventListener,
|
|
@@ -15,7 +15,7 @@ import { useLayoutGroupEffect } from "./useLayoutGroupEffect.js";
|
|
|
15
15
|
* _after_ the EditorView has been updated, even when the
|
|
16
16
|
* EditorView lives in an ancestor component.
|
|
17
17
|
*/ export function useEditorEffect(effect, dependencies) {
|
|
18
|
-
const { view } = useContext(EditorContext);
|
|
18
|
+
const { view, flushSyncRef } = useContext(EditorContext);
|
|
19
19
|
// The rules of hooks want `effect` to be included in the
|
|
20
20
|
// dependency list, but dependency issues for `effect` will
|
|
21
21
|
// be caught by the linter at the call-site for
|
|
@@ -25,7 +25,10 @@ import { useLayoutGroupEffect } from "./useLayoutGroupEffect.js";
|
|
|
25
25
|
// be defined inline and run on every re-render.
|
|
26
26
|
useLayoutGroupEffect(()=>{
|
|
27
27
|
if (view) {
|
|
28
|
-
|
|
28
|
+
flushSyncRef.current = false;
|
|
29
|
+
const result = effect(view);
|
|
30
|
+
flushSyncRef.current = true;
|
|
31
|
+
return result;
|
|
29
32
|
}
|
|
30
33
|
}, // The rules of hooks want to be able to statically
|
|
31
34
|
// verify the dependencies for the effect, but this will
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useSelectNode } from "./useSelectNode.js";
|
|
3
|
+
export function useIsNodeSelected() {
|
|
4
|
+
const [isSelected, setIsSelected] = useState(false);
|
|
5
|
+
useSelectNode(()=>{
|
|
6
|
+
setIsSelected(true);
|
|
7
|
+
}, ()=>{
|
|
8
|
+
setIsSelected(false);
|
|
9
|
+
});
|
|
10
|
+
return isSelected;
|
|
11
|
+
}
|
package/dist/esm/index.js
CHANGED
|
@@ -7,5 +7,6 @@ export { useEditorEventListener } from "./hooks/useEditorEventListener.js";
|
|
|
7
7
|
export { useEditorState } from "./hooks/useEditorState.js";
|
|
8
8
|
export { useStopEvent } from "./hooks/useStopEvent.js";
|
|
9
9
|
export { useSelectNode } from "./hooks/useSelectNode.js";
|
|
10
|
+
export { useIsNodeSelected } from "./hooks/useIsNodeSelected.js";
|
|
10
11
|
export { reactKeys } from "./plugins/reactKeys.js";
|
|
11
12
|
export { widget } from "./decorations/ReactWidgetType.js";
|
|
@@ -16,7 +16,7 @@ export function componentEventListeners(eventHandlerRegistry) {
|
|
|
16
16
|
domEventHandlers[eventType] = handleEvent;
|
|
17
17
|
}
|
|
18
18
|
const plugin = new Plugin({
|
|
19
|
-
key: new PluginKey("@
|
|
19
|
+
key: new PluginKey("@handlewithcare/react-prosemirror/componentEventListeners"),
|
|
20
20
|
props: {
|
|
21
21
|
handleDOMEvents: domEventHandlers
|
|
22
22
|
}
|
|
@@ -3,7 +3,7 @@ export function createNodeKey() {
|
|
|
3
3
|
const key = Math.floor(Math.random() * 0xffffffffffff).toString(16);
|
|
4
4
|
return key;
|
|
5
5
|
}
|
|
6
|
-
export const reactKeysPluginKey = new PluginKey("@
|
|
6
|
+
export const reactKeysPluginKey = new PluginKey("@handlewithcare/react-prosemirror/reactKeys");
|
|
7
7
|
/**
|
|
8
8
|
* Tracks a unique key for each (non-text) node in the
|
|
9
9
|
* document, identified by its current position. Keys are
|