@handlewithcare/react-prosemirror 2.5.2 → 2.5.4
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 +2 -3
- package/dist/cjs/ReactEditorView.js +36 -0
- package/dist/cjs/components/CustomNodeView.js +11 -23
- package/dist/cjs/components/ReactNodeView.js +16 -18
- package/dist/cjs/hooks/useEditor.js +19 -17
- package/dist/cjs/hooks/useEffectEvent.js +34 -0
- package/dist/cjs/hooks/useNodeViewDescriptor.js +16 -24
- package/dist/esm/ReactEditorView.js +36 -0
- package/dist/esm/components/CustomNodeView.js +12 -24
- package/dist/esm/components/ReactNodeView.js +16 -18
- package/dist/esm/hooks/useEditor.js +19 -17
- package/dist/esm/hooks/useEffectEvent.js +24 -0
- package/dist/esm/hooks/useNodeViewDescriptor.js +17 -25
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ReactEditorView.d.ts +13 -0
- package/dist/types/hooks/useEffectEvent.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -693,15 +693,14 @@ export function SelectionWidget() {
|
|
|
693
693
|
### `NodeViewComponentProps`
|
|
694
694
|
|
|
695
695
|
```tsx
|
|
696
|
-
|
|
696
|
+
interface NodeViewComponentProps extends AllHTMLAttributes<HTMLElement> = {
|
|
697
697
|
nodeProps: {
|
|
698
698
|
decorations: readonly Decoration[];
|
|
699
699
|
innerDecorations: DecorationSource;
|
|
700
700
|
node: Node;
|
|
701
|
-
children?: ReactNode | ReactNode[];
|
|
702
701
|
getPos: () => number;
|
|
703
702
|
};
|
|
704
|
-
}
|
|
703
|
+
};
|
|
705
704
|
```
|
|
706
705
|
|
|
707
706
|
The props that will be passed to all node view components. These props map
|
|
@@ -32,6 +32,7 @@ function changedNodeViews(a, b) {
|
|
|
32
32
|
let ReactEditorView = class ReactEditorView extends _prosemirrorview.EditorView {
|
|
33
33
|
nextProps;
|
|
34
34
|
prevState;
|
|
35
|
+
_destroyed;
|
|
35
36
|
constructor(place, props){
|
|
36
37
|
// Prevent the base class from destroying the React-managed nodes.
|
|
37
38
|
// Restore them below after invoking the base class constructor.
|
|
@@ -55,6 +56,16 @@ let ReactEditorView = class ReactEditorView extends _prosemirrorview.EditorView
|
|
|
55
56
|
this.domObserver.stop();
|
|
56
57
|
this.domObserver.observer = null;
|
|
57
58
|
this.domObserver.queue = [];
|
|
59
|
+
const originalOnSelectionChange = this.domObserver.onSelectionChange;
|
|
60
|
+
this.domObserver.onSelectionChange = ()=>{
|
|
61
|
+
// During a composition, we completely pause React-driven
|
|
62
|
+
// selection and DOM updates. Compositions are "fragile";
|
|
63
|
+
// in Safari, even updating the selection to the same
|
|
64
|
+
// position it's already set to will end the current
|
|
65
|
+
// composition.
|
|
66
|
+
if (this.composing) return;
|
|
67
|
+
originalOnSelectionChange();
|
|
68
|
+
};
|
|
58
69
|
} finally{
|
|
59
70
|
place.mount.replaceChildren(...reactContent);
|
|
60
71
|
for (const attr of place.mount.attributes){
|
|
@@ -73,10 +84,22 @@ let ReactEditorView = class ReactEditorView extends _prosemirrorview.EditorView
|
|
|
73
84
|
this.docView.destroy();
|
|
74
85
|
// @ts-expect-error this violates the typing but class does it, too.
|
|
75
86
|
this.docView = null;
|
|
87
|
+
this._destroyed = false;
|
|
76
88
|
}
|
|
77
89
|
get props() {
|
|
78
90
|
return this.nextProps;
|
|
79
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* @privateremarks
|
|
94
|
+
*
|
|
95
|
+
* We override this getter because the base implementation
|
|
96
|
+
* relies on checking `docView === null`, but we unconditionally
|
|
97
|
+
* set view.docView in a layout effect in the DocNodeView.
|
|
98
|
+
* This has the effect of "un-destroying" the EditorView,
|
|
99
|
+
* making it impossible to determine whether it's been destroyed.
|
|
100
|
+
*/ get isDestroyed() {
|
|
101
|
+
return this._destroyed;
|
|
102
|
+
}
|
|
80
103
|
setProps(props) {
|
|
81
104
|
this.update({
|
|
82
105
|
...this.props,
|
|
@@ -132,6 +155,19 @@ let ReactEditorView = class ReactEditorView extends _prosemirrorview.EditorView
|
|
|
132
155
|
}
|
|
133
156
|
return undefined;
|
|
134
157
|
}
|
|
158
|
+
destroy() {
|
|
159
|
+
// Prevent the base class from destroying the React-managed nodes.
|
|
160
|
+
// Restore them below after invoking the base class method.
|
|
161
|
+
const reactContent = [
|
|
162
|
+
...this.dom.childNodes
|
|
163
|
+
];
|
|
164
|
+
try {
|
|
165
|
+
super.destroy();
|
|
166
|
+
} finally{
|
|
167
|
+
this.dom.replaceChildren(...reactContent);
|
|
168
|
+
this._destroyed = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
135
171
|
/**
|
|
136
172
|
* Commit effects by appling the pending props and state.
|
|
137
173
|
*
|
|
@@ -59,7 +59,6 @@ const CustomNodeView = /*#__PURE__*/ (0, _react.memo)(function CustomNodeView(pa
|
|
|
59
59
|
let { constructor, node, getPos, innerDeco, outerDeco } = param;
|
|
60
60
|
const ref = (0, _react.useRef)(null);
|
|
61
61
|
const innerRef = (0, _react.useRef)(null);
|
|
62
|
-
const [selected, setSelected] = (0, _react.useState)(false);
|
|
63
62
|
const nodeProps = (0, _react.useMemo)(()=>({
|
|
64
63
|
node,
|
|
65
64
|
getPos,
|
|
@@ -89,7 +88,6 @@ const CustomNodeView = /*#__PURE__*/ (0, _react.memo)(function CustomNodeView(pa
|
|
|
89
88
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
90
89
|
args[_key] = arguments[_key];
|
|
91
90
|
}
|
|
92
|
-
setSelected(false);
|
|
93
91
|
const nodeView = createNodeView(...args);
|
|
94
92
|
const contentDOM = nodeView.contentDOM;
|
|
95
93
|
const nodeDOM = nodeView.dom;
|
|
@@ -99,6 +97,9 @@ const CustomNodeView = /*#__PURE__*/ (0, _react.memo)(function CustomNodeView(pa
|
|
|
99
97
|
if (!nodeDOM.hasAttribute("contenteditable")) {
|
|
100
98
|
nodeDOM.contentEditable = "false";
|
|
101
99
|
}
|
|
100
|
+
if (node.type.spec.draggable) {
|
|
101
|
+
nodeDOM.draggable = true;
|
|
102
|
+
}
|
|
102
103
|
}
|
|
103
104
|
return {
|
|
104
105
|
...nodeView,
|
|
@@ -108,22 +109,16 @@ const CustomNodeView = /*#__PURE__*/ (0, _react.memo)(function CustomNodeView(pa
|
|
|
108
109
|
}
|
|
109
110
|
wrapperDOM.removeChild(nodeDOM);
|
|
110
111
|
},
|
|
111
|
-
selectNode: nodeView.selectNode?.bind(nodeView)
|
|
112
|
-
|
|
113
|
-
nodeDOM.classList.add("ProseMirror-selectednode");
|
|
114
|
-
}
|
|
115
|
-
setSelected(true);
|
|
116
|
-
}),
|
|
117
|
-
deselectNode: nodeView.deselectNode?.bind(nodeView) ?? (()=>{
|
|
118
|
-
if (nodeDOM instanceof HTMLElement) {
|
|
119
|
-
nodeDOM.classList.remove("ProseMirror-selectednode");
|
|
120
|
-
}
|
|
121
|
-
setSelected(false);
|
|
122
|
-
}),
|
|
112
|
+
selectNode: nodeView.selectNode?.bind(nodeView),
|
|
113
|
+
deselectNode: nodeView.deselectNode?.bind(nodeView),
|
|
123
114
|
stopEvent: nodeView.stopEvent?.bind(nodeView),
|
|
124
115
|
ignoreMutation: nodeView.ignoreMutation?.bind(nodeView)
|
|
125
116
|
};
|
|
126
117
|
}, nodeProps);
|
|
118
|
+
const Component = node.isInline ? "span" : "div";
|
|
119
|
+
const props = {
|
|
120
|
+
ref: innerRef
|
|
121
|
+
};
|
|
127
122
|
const children = !node.isLeaf && contentDOM ? /*#__PURE__*/ (0, _reactdom.createPortal)(/*#__PURE__*/ _react.default.createElement(_ChildDescriptorsContext.ChildDescriptorsContext.Provider, {
|
|
128
123
|
value: childContextValue
|
|
129
124
|
}, /*#__PURE__*/ _react.default.createElement(_ChildNodeViews.ChildNodeViews, {
|
|
@@ -131,14 +126,7 @@ const CustomNodeView = /*#__PURE__*/ (0, _react.memo)(function CustomNodeView(pa
|
|
|
131
126
|
node: node,
|
|
132
127
|
innerDecorations: innerDeco
|
|
133
128
|
})), contentDOM) : null;
|
|
134
|
-
|
|
135
|
-
ref: innerRef
|
|
136
|
-
}, children);
|
|
137
|
-
const props = {
|
|
138
|
-
...selected || node.type.spec.draggable ? {
|
|
139
|
-
draggable: true
|
|
140
|
-
} : null,
|
|
129
|
+
return /*#__PURE__*/ (0, _react.cloneElement)(outerDeco.reduce(_ChildNodeViews.wrapInDeco, /*#__PURE__*/ _react.default.createElement(Component, props, children)), {
|
|
141
130
|
ref
|
|
142
|
-
};
|
|
143
|
-
return /*#__PURE__*/ (0, _react.cloneElement)(outerDeco.reduce(_ChildNodeViews.wrapInDeco, innerElement), props);
|
|
131
|
+
});
|
|
144
132
|
});
|
|
@@ -58,7 +58,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
58
58
|
}
|
|
59
59
|
const ReactNodeView = /*#__PURE__*/ (0, _react.memo)(function ReactNodeView(param) {
|
|
60
60
|
let { component: Component, outerDeco, getPos, node, innerDeco } = param;
|
|
61
|
-
const [
|
|
61
|
+
const [hasCustomSelectNode, setHasCustomSelectNode] = (0, _react.useState)(false);
|
|
62
62
|
const [selected, setSelected] = (0, _react.useState)(false);
|
|
63
63
|
const ref = (0, _react.useRef)(null);
|
|
64
64
|
const innerRef = (0, _react.useRef)(null);
|
|
@@ -69,11 +69,11 @@ const ReactNodeView = /*#__PURE__*/ (0, _react.memo)(function ReactNodeView(para
|
|
|
69
69
|
const setSelectNode = (0, _react.useCallback)((selectHandler, deselectHandler)=>{
|
|
70
70
|
selectNodeRef.current = selectHandler;
|
|
71
71
|
deselectNodeRef.current = deselectHandler;
|
|
72
|
-
|
|
72
|
+
setHasCustomSelectNode(true);
|
|
73
73
|
return ()=>{
|
|
74
74
|
selectNodeRef.current = null;
|
|
75
75
|
deselectNodeRef.current = null;
|
|
76
|
-
|
|
76
|
+
setHasCustomSelectNode(false);
|
|
77
77
|
};
|
|
78
78
|
}, []);
|
|
79
79
|
const setStopEvent = (0, _react.useCallback)((handler)=>{
|
|
@@ -140,30 +140,28 @@ const ReactNodeView = /*#__PURE__*/ (0, _react.memo)(function ReactNodeView(para
|
|
|
140
140
|
}
|
|
141
141
|
};
|
|
142
142
|
}, nodeProps);
|
|
143
|
-
const
|
|
144
|
-
getPos: getPos,
|
|
145
|
-
node: node,
|
|
146
|
-
innerDecorations: innerDeco
|
|
147
|
-
}) : null;
|
|
148
|
-
const innerProps = {
|
|
143
|
+
const props = {
|
|
149
144
|
nodeProps,
|
|
150
145
|
...!contentDOM && !nodeProps.node.isText && nodeDOM?.nodeName !== "BR" ? {
|
|
151
146
|
contentEditable: false,
|
|
152
147
|
suppressContentEditableWarning: true
|
|
153
148
|
} : null,
|
|
154
|
-
|
|
149
|
+
...!hasCustomSelectNode && selected ? {
|
|
155
150
|
className: "ProseMirror-selectednode"
|
|
156
151
|
} : null,
|
|
157
|
-
|
|
158
|
-
};
|
|
159
|
-
const innerElement = /*#__PURE__*/ _react.default.createElement(Component, innerProps, children);
|
|
160
|
-
const props = {
|
|
161
|
-
...controlSelected && selected || node.type.spec.draggable ? {
|
|
152
|
+
...!hasCustomSelectNode && selected || node.type.spec.draggable ? {
|
|
162
153
|
draggable: true
|
|
163
154
|
} : null,
|
|
164
|
-
ref
|
|
155
|
+
ref: innerRef
|
|
165
156
|
};
|
|
166
|
-
const
|
|
157
|
+
const children = !node.isLeaf ? /*#__PURE__*/ _react.default.createElement(_ChildNodeViews.ChildNodeViews, {
|
|
158
|
+
getPos: getPos,
|
|
159
|
+
node: node,
|
|
160
|
+
innerDecorations: innerDeco
|
|
161
|
+
}) : null;
|
|
162
|
+
const element = /*#__PURE__*/ (0, _react.cloneElement)(outerDeco.reduce(_ChildNodeViews.wrapInDeco, /*#__PURE__*/ _react.default.createElement(Component, props, children)), {
|
|
163
|
+
ref
|
|
164
|
+
});
|
|
167
165
|
return /*#__PURE__*/ _react.default.createElement(_SelectNodeContext.SelectNodeContext.Provider, {
|
|
168
166
|
value: setSelectNode
|
|
169
167
|
}, /*#__PURE__*/ _react.default.createElement(_StopEventContext.StopEventContext.Provider, {
|
|
@@ -172,5 +170,5 @@ const ReactNodeView = /*#__PURE__*/ (0, _react.memo)(function ReactNodeView(para
|
|
|
172
170
|
value: setIgnoreMutation
|
|
173
171
|
}, /*#__PURE__*/ _react.default.createElement(_ChildDescriptorsContext.ChildDescriptorsContext.Provider, {
|
|
174
172
|
value: childContextValue
|
|
175
|
-
},
|
|
173
|
+
}, element))));
|
|
176
174
|
});
|
|
@@ -16,6 +16,7 @@ const _constants = require("../constants.js");
|
|
|
16
16
|
const _beforeInputPlugin = require("../plugins/beforeInputPlugin.js");
|
|
17
17
|
const _useClientLayoutEffect = require("./useClientLayoutEffect.js");
|
|
18
18
|
const _useComponentEventListeners = require("./useComponentEventListeners.js");
|
|
19
|
+
const _useEffectEvent = require("./useEffectEvent.js");
|
|
19
20
|
const _useForceUpdate = require("./useForceUpdate.js");
|
|
20
21
|
let didWarnValueDefaultValue = false;
|
|
21
22
|
function useEditor(mount, options) {
|
|
@@ -77,30 +78,31 @@ function useEditor(mount, options) {
|
|
|
77
78
|
const [view, setView] = (0, _react.useState)(()=>{
|
|
78
79
|
return new _StaticEditorView.StaticEditorView(directEditorProps);
|
|
79
80
|
});
|
|
81
|
+
const createEditorView = (0, _useEffectEvent.useEffectEvent)((mount)=>{
|
|
82
|
+
if (mount) {
|
|
83
|
+
const view = new _ReactEditorView.ReactEditorView({
|
|
84
|
+
mount
|
|
85
|
+
}, directEditorProps);
|
|
86
|
+
view.dom.addEventListener("compositionend", forceUpdate);
|
|
87
|
+
return view;
|
|
88
|
+
}
|
|
89
|
+
return new _StaticEditorView.StaticEditorView(directEditorProps);
|
|
90
|
+
});
|
|
80
91
|
(0, _useClientLayoutEffect.useClientLayoutEffect)(()=>{
|
|
92
|
+
const view = createEditorView(mount);
|
|
93
|
+
setView(view);
|
|
81
94
|
return ()=>{
|
|
82
95
|
view.destroy();
|
|
83
96
|
};
|
|
84
97
|
}, [
|
|
85
|
-
|
|
98
|
+
createEditorView,
|
|
99
|
+
mount
|
|
86
100
|
]);
|
|
87
|
-
// This rule is concerned about infinite updates due to the
|
|
88
|
-
// call to setView. These calls are deliberately conditional,
|
|
89
|
-
// so this is not a concern.
|
|
90
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
91
101
|
(0, _useClientLayoutEffect.useClientLayoutEffect)(()=>{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}, directEditorProps);
|
|
97
|
-
view.dom.addEventListener("compositionend", forceUpdate);
|
|
98
|
-
setView(view);
|
|
99
|
-
} else {
|
|
100
|
-
const view = new _StaticEditorView.StaticEditorView(directEditorProps);
|
|
101
|
-
setView(view);
|
|
102
|
-
}
|
|
103
|
-
} else if (view instanceof _ReactEditorView.ReactEditorView) {
|
|
102
|
+
// Ensure that the EditorView hasn't been destroyed before
|
|
103
|
+
// running effects. Running effects will reattach selection
|
|
104
|
+
// change listeners if the EditorView has been destroyed.
|
|
105
|
+
if (view instanceof _ReactEditorView.ReactEditorView && !view.isDestroyed) {
|
|
104
106
|
view.commitPendingEffects();
|
|
105
107
|
}
|
|
106
108
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "useEffectEvent", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return useEffectEvent;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _react = require("react");
|
|
12
|
+
function useEffectEvent(fn) {
|
|
13
|
+
const ref = (0, _react.useRef)(fn);
|
|
14
|
+
// Ideally this would be a useInsertionEffect, but
|
|
15
|
+
// that was introduced in React 18 and we still
|
|
16
|
+
// support React 17. useLayoutEffect is safe
|
|
17
|
+
// here as long as the function returned by
|
|
18
|
+
// useEffectEvent isn't called in a layout effect
|
|
19
|
+
// that's defined _before_ the useEffectEvent
|
|
20
|
+
// call, and effect events are never passed
|
|
21
|
+
// to child components.
|
|
22
|
+
(0, _react.useLayoutEffect)(()=>{
|
|
23
|
+
ref.current = fn;
|
|
24
|
+
}, [
|
|
25
|
+
fn
|
|
26
|
+
]);
|
|
27
|
+
return (0, _react.useCallback)(function() {
|
|
28
|
+
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
29
|
+
args[_key] = arguments[_key];
|
|
30
|
+
}
|
|
31
|
+
const f = ref.current;
|
|
32
|
+
return f(...args);
|
|
33
|
+
}, []);
|
|
34
|
+
}
|
|
@@ -14,6 +14,7 @@ const _ChildDescriptorsContext = require("../contexts/ChildDescriptorsContext.js
|
|
|
14
14
|
const _EditorContext = require("../contexts/EditorContext.js");
|
|
15
15
|
const _viewdesc = require("../viewdesc.js");
|
|
16
16
|
const _useClientLayoutEffect = require("./useClientLayoutEffect.js");
|
|
17
|
+
const _useEffectEvent = require("./useEffectEvent.js");
|
|
17
18
|
function findContentDOM(source, children) {
|
|
18
19
|
return source?.contentDOM ?? children[0]?.dom?.parentElement ?? null;
|
|
19
20
|
}
|
|
@@ -25,7 +26,7 @@ function useNodeViewDescriptor(ref, constructor, props) {
|
|
|
25
26
|
const [contentDOM, setContentDOM] = (0, _react.useState)(null);
|
|
26
27
|
const viewDescRef = (0, _react.useRef)();
|
|
27
28
|
const childrenRef = (0, _react.useRef)([]);
|
|
28
|
-
const create = (0,
|
|
29
|
+
const create = (0, _useEffectEvent.useEffectEvent)(()=>{
|
|
29
30
|
if (!(view instanceof _ReactEditorView.ReactEditorView)) {
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
@@ -47,13 +48,8 @@ function useNodeViewDescriptor(ref, constructor, props) {
|
|
|
47
48
|
setContentDOM(contentDOM);
|
|
48
49
|
setNodeDOM(nodeDOM);
|
|
49
50
|
return viewDesc;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
parentRef,
|
|
53
|
-
constructor,
|
|
54
|
-
view
|
|
55
|
-
]);
|
|
56
|
-
const update = (0, _react.useCallback)((props)=>{
|
|
51
|
+
});
|
|
52
|
+
const update = (0, _useEffectEvent.useEffectEvent)(()=>{
|
|
57
53
|
if (!(view instanceof _ReactEditorView.ReactEditorView)) {
|
|
58
54
|
return false;
|
|
59
55
|
}
|
|
@@ -74,11 +70,8 @@ function useNodeViewDescriptor(ref, constructor, props) {
|
|
|
74
70
|
}
|
|
75
71
|
const { node, decorations, innerDecorations } = props;
|
|
76
72
|
return viewDesc.matchesNode(node, decorations, innerDecorations) || viewDesc.update(node, decorations, innerDecorations, view);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
view
|
|
80
|
-
]);
|
|
81
|
-
const destroy = (0, _react.useCallback)(()=>{
|
|
73
|
+
});
|
|
74
|
+
const destroy = (0, _useEffectEvent.useEffectEvent)(()=>{
|
|
82
75
|
const viewDesc = viewDescRef.current;
|
|
83
76
|
if (!viewDesc) {
|
|
84
77
|
return;
|
|
@@ -92,13 +85,20 @@ function useNodeViewDescriptor(ref, constructor, props) {
|
|
|
92
85
|
setDOM(null);
|
|
93
86
|
setContentDOM(null);
|
|
94
87
|
setNodeDOM(null);
|
|
88
|
+
});
|
|
89
|
+
(0, _useClientLayoutEffect.useClientLayoutEffect)(()=>{
|
|
90
|
+
viewDescRef.current = create();
|
|
91
|
+
return ()=>{
|
|
92
|
+
destroy();
|
|
93
|
+
};
|
|
95
94
|
}, [
|
|
96
|
-
|
|
95
|
+
create,
|
|
96
|
+
destroy
|
|
97
97
|
]);
|
|
98
98
|
(0, _useClientLayoutEffect.useClientLayoutEffect)(()=>{
|
|
99
|
-
if (!update(
|
|
99
|
+
if (!update()) {
|
|
100
100
|
destroy();
|
|
101
|
-
viewDescRef.current = create(
|
|
101
|
+
viewDescRef.current = create();
|
|
102
102
|
}
|
|
103
103
|
const viewDesc = viewDescRef.current;
|
|
104
104
|
if (!viewDesc) {
|
|
@@ -137,14 +137,6 @@ function useNodeViewDescriptor(ref, constructor, props) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
});
|
|
140
|
-
(0, _useClientLayoutEffect.useClientLayoutEffect)(()=>{
|
|
141
|
-
return ()=>{
|
|
142
|
-
destroy();
|
|
143
|
-
viewDescRef.current = undefined;
|
|
144
|
-
};
|
|
145
|
-
}, [
|
|
146
|
-
destroy
|
|
147
|
-
]);
|
|
148
140
|
const childContextValue = (0, _react.useMemo)(()=>({
|
|
149
141
|
parentRef: viewDescRef,
|
|
150
142
|
siblingsRef: childrenRef
|
|
@@ -32,6 +32,7 @@ function changedNodeViews(a, b) {
|
|
|
32
32
|
*/ export class ReactEditorView extends EditorView {
|
|
33
33
|
nextProps;
|
|
34
34
|
prevState;
|
|
35
|
+
_destroyed;
|
|
35
36
|
constructor(place, props){
|
|
36
37
|
// Prevent the base class from destroying the React-managed nodes.
|
|
37
38
|
// Restore them below after invoking the base class constructor.
|
|
@@ -55,6 +56,16 @@ function changedNodeViews(a, b) {
|
|
|
55
56
|
this.domObserver.stop();
|
|
56
57
|
this.domObserver.observer = null;
|
|
57
58
|
this.domObserver.queue = [];
|
|
59
|
+
const originalOnSelectionChange = this.domObserver.onSelectionChange;
|
|
60
|
+
this.domObserver.onSelectionChange = ()=>{
|
|
61
|
+
// During a composition, we completely pause React-driven
|
|
62
|
+
// selection and DOM updates. Compositions are "fragile";
|
|
63
|
+
// in Safari, even updating the selection to the same
|
|
64
|
+
// position it's already set to will end the current
|
|
65
|
+
// composition.
|
|
66
|
+
if (this.composing) return;
|
|
67
|
+
originalOnSelectionChange();
|
|
68
|
+
};
|
|
58
69
|
} finally{
|
|
59
70
|
place.mount.replaceChildren(...reactContent);
|
|
60
71
|
for (const attr of place.mount.attributes){
|
|
@@ -73,10 +84,22 @@ function changedNodeViews(a, b) {
|
|
|
73
84
|
this.docView.destroy();
|
|
74
85
|
// @ts-expect-error this violates the typing but class does it, too.
|
|
75
86
|
this.docView = null;
|
|
87
|
+
this._destroyed = false;
|
|
76
88
|
}
|
|
77
89
|
get props() {
|
|
78
90
|
return this.nextProps;
|
|
79
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* @privateremarks
|
|
94
|
+
*
|
|
95
|
+
* We override this getter because the base implementation
|
|
96
|
+
* relies on checking `docView === null`, but we unconditionally
|
|
97
|
+
* set view.docView in a layout effect in the DocNodeView.
|
|
98
|
+
* This has the effect of "un-destroying" the EditorView,
|
|
99
|
+
* making it impossible to determine whether it's been destroyed.
|
|
100
|
+
*/ get isDestroyed() {
|
|
101
|
+
return this._destroyed;
|
|
102
|
+
}
|
|
80
103
|
setProps(props) {
|
|
81
104
|
this.update({
|
|
82
105
|
...this.props,
|
|
@@ -132,6 +155,19 @@ function changedNodeViews(a, b) {
|
|
|
132
155
|
}
|
|
133
156
|
return undefined;
|
|
134
157
|
}
|
|
158
|
+
destroy() {
|
|
159
|
+
// Prevent the base class from destroying the React-managed nodes.
|
|
160
|
+
// Restore them below after invoking the base class method.
|
|
161
|
+
const reactContent = [
|
|
162
|
+
...this.dom.childNodes
|
|
163
|
+
];
|
|
164
|
+
try {
|
|
165
|
+
super.destroy();
|
|
166
|
+
} finally{
|
|
167
|
+
this.dom.replaceChildren(...reactContent);
|
|
168
|
+
this._destroyed = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
135
171
|
/**
|
|
136
172
|
* Commit effects by appling the pending props and state.
|
|
137
173
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DOMSerializer } from "prosemirror-model";
|
|
2
|
-
import React, { cloneElement,
|
|
2
|
+
import React, { cloneElement, memo, useMemo, useRef } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
|
|
5
5
|
import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
|
|
@@ -8,7 +8,6 @@ export const CustomNodeView = /*#__PURE__*/ memo(function CustomNodeView(param)
|
|
|
8
8
|
let { constructor, node, getPos, innerDeco, outerDeco } = param;
|
|
9
9
|
const ref = useRef(null);
|
|
10
10
|
const innerRef = useRef(null);
|
|
11
|
-
const [selected, setSelected] = useState(false);
|
|
12
11
|
const nodeProps = useMemo(()=>({
|
|
13
12
|
node,
|
|
14
13
|
getPos,
|
|
@@ -38,7 +37,6 @@ export const CustomNodeView = /*#__PURE__*/ memo(function CustomNodeView(param)
|
|
|
38
37
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
39
38
|
args[_key] = arguments[_key];
|
|
40
39
|
}
|
|
41
|
-
setSelected(false);
|
|
42
40
|
const nodeView = createNodeView(...args);
|
|
43
41
|
const contentDOM = nodeView.contentDOM;
|
|
44
42
|
const nodeDOM = nodeView.dom;
|
|
@@ -48,6 +46,9 @@ export const CustomNodeView = /*#__PURE__*/ memo(function CustomNodeView(param)
|
|
|
48
46
|
if (!nodeDOM.hasAttribute("contenteditable")) {
|
|
49
47
|
nodeDOM.contentEditable = "false";
|
|
50
48
|
}
|
|
49
|
+
if (node.type.spec.draggable) {
|
|
50
|
+
nodeDOM.draggable = true;
|
|
51
|
+
}
|
|
51
52
|
}
|
|
52
53
|
return {
|
|
53
54
|
...nodeView,
|
|
@@ -57,22 +58,16 @@ export const CustomNodeView = /*#__PURE__*/ memo(function CustomNodeView(param)
|
|
|
57
58
|
}
|
|
58
59
|
wrapperDOM.removeChild(nodeDOM);
|
|
59
60
|
},
|
|
60
|
-
selectNode: nodeView.selectNode?.bind(nodeView)
|
|
61
|
-
|
|
62
|
-
nodeDOM.classList.add("ProseMirror-selectednode");
|
|
63
|
-
}
|
|
64
|
-
setSelected(true);
|
|
65
|
-
}),
|
|
66
|
-
deselectNode: nodeView.deselectNode?.bind(nodeView) ?? (()=>{
|
|
67
|
-
if (nodeDOM instanceof HTMLElement) {
|
|
68
|
-
nodeDOM.classList.remove("ProseMirror-selectednode");
|
|
69
|
-
}
|
|
70
|
-
setSelected(false);
|
|
71
|
-
}),
|
|
61
|
+
selectNode: nodeView.selectNode?.bind(nodeView),
|
|
62
|
+
deselectNode: nodeView.deselectNode?.bind(nodeView),
|
|
72
63
|
stopEvent: nodeView.stopEvent?.bind(nodeView),
|
|
73
64
|
ignoreMutation: nodeView.ignoreMutation?.bind(nodeView)
|
|
74
65
|
};
|
|
75
66
|
}, nodeProps);
|
|
67
|
+
const Component = node.isInline ? "span" : "div";
|
|
68
|
+
const props = {
|
|
69
|
+
ref: innerRef
|
|
70
|
+
};
|
|
76
71
|
const children = !node.isLeaf && contentDOM ? /*#__PURE__*/ createPortal(/*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
|
|
77
72
|
value: childContextValue
|
|
78
73
|
}, /*#__PURE__*/ React.createElement(ChildNodeViews, {
|
|
@@ -80,14 +75,7 @@ export const CustomNodeView = /*#__PURE__*/ memo(function CustomNodeView(param)
|
|
|
80
75
|
node: node,
|
|
81
76
|
innerDecorations: innerDeco
|
|
82
77
|
})), contentDOM) : null;
|
|
83
|
-
|
|
84
|
-
ref: innerRef
|
|
85
|
-
}, children);
|
|
86
|
-
const props = {
|
|
87
|
-
...selected || node.type.spec.draggable ? {
|
|
88
|
-
draggable: true
|
|
89
|
-
} : null,
|
|
78
|
+
return /*#__PURE__*/ cloneElement(outerDeco.reduce(wrapInDeco, /*#__PURE__*/ React.createElement(Component, props, children)), {
|
|
90
79
|
ref
|
|
91
|
-
};
|
|
92
|
-
return /*#__PURE__*/ cloneElement(outerDeco.reduce(wrapInDeco, innerElement), props);
|
|
80
|
+
});
|
|
93
81
|
});
|
|
@@ -7,7 +7,7 @@ import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
|
|
|
7
7
|
import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";
|
|
8
8
|
export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
|
|
9
9
|
let { component: Component, outerDeco, getPos, node, innerDeco } = param;
|
|
10
|
-
const [
|
|
10
|
+
const [hasCustomSelectNode, setHasCustomSelectNode] = useState(false);
|
|
11
11
|
const [selected, setSelected] = useState(false);
|
|
12
12
|
const ref = useRef(null);
|
|
13
13
|
const innerRef = useRef(null);
|
|
@@ -18,11 +18,11 @@ export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
|
|
|
18
18
|
const setSelectNode = useCallback((selectHandler, deselectHandler)=>{
|
|
19
19
|
selectNodeRef.current = selectHandler;
|
|
20
20
|
deselectNodeRef.current = deselectHandler;
|
|
21
|
-
|
|
21
|
+
setHasCustomSelectNode(true);
|
|
22
22
|
return ()=>{
|
|
23
23
|
selectNodeRef.current = null;
|
|
24
24
|
deselectNodeRef.current = null;
|
|
25
|
-
|
|
25
|
+
setHasCustomSelectNode(false);
|
|
26
26
|
};
|
|
27
27
|
}, []);
|
|
28
28
|
const setStopEvent = useCallback((handler)=>{
|
|
@@ -89,30 +89,28 @@ export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
|
|
|
89
89
|
}
|
|
90
90
|
};
|
|
91
91
|
}, nodeProps);
|
|
92
|
-
const
|
|
93
|
-
getPos: getPos,
|
|
94
|
-
node: node,
|
|
95
|
-
innerDecorations: innerDeco
|
|
96
|
-
}) : null;
|
|
97
|
-
const innerProps = {
|
|
92
|
+
const props = {
|
|
98
93
|
nodeProps,
|
|
99
94
|
...!contentDOM && !nodeProps.node.isText && nodeDOM?.nodeName !== "BR" ? {
|
|
100
95
|
contentEditable: false,
|
|
101
96
|
suppressContentEditableWarning: true
|
|
102
97
|
} : null,
|
|
103
|
-
|
|
98
|
+
...!hasCustomSelectNode && selected ? {
|
|
104
99
|
className: "ProseMirror-selectednode"
|
|
105
100
|
} : null,
|
|
106
|
-
|
|
107
|
-
};
|
|
108
|
-
const innerElement = /*#__PURE__*/ React.createElement(Component, innerProps, children);
|
|
109
|
-
const props = {
|
|
110
|
-
...controlSelected && selected || node.type.spec.draggable ? {
|
|
101
|
+
...!hasCustomSelectNode && selected || node.type.spec.draggable ? {
|
|
111
102
|
draggable: true
|
|
112
103
|
} : null,
|
|
113
|
-
ref
|
|
104
|
+
ref: innerRef
|
|
114
105
|
};
|
|
115
|
-
const
|
|
106
|
+
const children = !node.isLeaf ? /*#__PURE__*/ React.createElement(ChildNodeViews, {
|
|
107
|
+
getPos: getPos,
|
|
108
|
+
node: node,
|
|
109
|
+
innerDecorations: innerDeco
|
|
110
|
+
}) : null;
|
|
111
|
+
const element = /*#__PURE__*/ cloneElement(outerDeco.reduce(wrapInDeco, /*#__PURE__*/ React.createElement(Component, props, children)), {
|
|
112
|
+
ref
|
|
113
|
+
});
|
|
116
114
|
return /*#__PURE__*/ React.createElement(SelectNodeContext.Provider, {
|
|
117
115
|
value: setSelectNode
|
|
118
116
|
}, /*#__PURE__*/ React.createElement(StopEventContext.Provider, {
|
|
@@ -121,5 +119,5 @@ export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
|
|
|
121
119
|
value: setIgnoreMutation
|
|
122
120
|
}, /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
|
|
123
121
|
value: childContextValue
|
|
124
|
-
},
|
|
122
|
+
}, element))));
|
|
125
123
|
});
|