@handlewithcare/react-prosemirror 2.0.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 +29 -5
- package/dist/cjs/components/CustomNodeView.js +80 -4
- package/dist/cjs/components/MarkView.js +6 -3
- 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/cjs/selection/SelectionDOMObserver.js +0 -3
- package/dist/cjs/selection/selectionToDOM.js +1 -1
- package/dist/cjs/viewdesc.js +18 -8
- package/dist/esm/components/CustomNodeView.js +82 -6
- package/dist/esm/components/MarkView.js +6 -3
- 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/esm/selection/SelectionDOMObserver.js +0 -3
- package/dist/esm/selection/selectionToDOM.js +1 -1
- package/dist/esm/viewdesc.js +18 -8
- 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/dist/types/selection/SelectionDOMObserver.d.ts +0 -1
- package/dist/types/viewdesc.d.ts +19 -6
- package/package.json +19 -6
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "useIsNodeSelected", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return useIsNodeSelected;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _react = require("react");
|
|
12
|
+
const _useSelectNode = require("./useSelectNode.js");
|
|
13
|
+
function useIsNodeSelected() {
|
|
14
|
+
const [isSelected, setIsSelected] = (0, _react.useState)(false);
|
|
15
|
+
(0, _useSelectNode.useSelectNode)(()=>{
|
|
16
|
+
setIsSelected(true);
|
|
17
|
+
}, ()=>{
|
|
18
|
+
setIsSelected(false);
|
|
19
|
+
});
|
|
20
|
+
return isSelected;
|
|
21
|
+
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -31,6 +31,9 @@ _export(exports, {
|
|
|
31
31
|
useEditorState: function() {
|
|
32
32
|
return _useEditorState.useEditorState;
|
|
33
33
|
},
|
|
34
|
+
useIsNodeSelected: function() {
|
|
35
|
+
return _useIsNodeSelected.useIsNodeSelected;
|
|
36
|
+
},
|
|
34
37
|
useSelectNode: function() {
|
|
35
38
|
return _useSelectNode.useSelectNode;
|
|
36
39
|
},
|
|
@@ -49,5 +52,6 @@ const _useEditorEventListener = require("./hooks/useEditorEventListener.js");
|
|
|
49
52
|
const _useEditorState = require("./hooks/useEditorState.js");
|
|
50
53
|
const _useStopEvent = require("./hooks/useStopEvent.js");
|
|
51
54
|
const _useSelectNode = require("./hooks/useSelectNode.js");
|
|
55
|
+
const _useIsNodeSelected = require("./hooks/useIsNodeSelected.js");
|
|
52
56
|
const _reactKeys = require("./plugins/reactKeys.js");
|
|
53
57
|
const _ReactWidgetType = require("./decorations/ReactWidgetType.js");
|
|
@@ -26,7 +26,7 @@ function componentEventListeners(eventHandlerRegistry) {
|
|
|
26
26
|
domEventHandlers[eventType] = handleEvent;
|
|
27
27
|
}
|
|
28
28
|
const plugin = new _prosemirrorstate.Plugin({
|
|
29
|
-
key: new _prosemirrorstate.PluginKey("@
|
|
29
|
+
key: new _prosemirrorstate.PluginKey("@handlewithcare/react-prosemirror/componentEventListeners"),
|
|
30
30
|
props: {
|
|
31
31
|
handleDOMEvents: domEventHandlers
|
|
32
32
|
}
|
|
@@ -24,7 +24,7 @@ function createNodeKey() {
|
|
|
24
24
|
const key = Math.floor(Math.random() * 0xffffffffffff).toString(16);
|
|
25
25
|
return key;
|
|
26
26
|
}
|
|
27
|
-
const reactKeysPluginKey = new _prosemirrorstate.PluginKey("@
|
|
27
|
+
const reactKeysPluginKey = new _prosemirrorstate.PluginKey("@handlewithcare/react-prosemirror/reactKeys");
|
|
28
28
|
function reactKeys() {
|
|
29
29
|
let composing = false;
|
|
30
30
|
return new _prosemirrorstate.Plugin({
|
|
@@ -145,9 +145,6 @@ let SelectionDOMObserver = class SelectionDOMObserver {
|
|
|
145
145
|
this.currentSelection.set(sel);
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
selectionChanged(sel) {
|
|
149
|
-
return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && (0, _hasFocusAndSelection.hasFocusAndSelection)(this.view) && !this.ignoreSelectionChange(sel);
|
|
150
|
-
}
|
|
151
148
|
forceFlush() {
|
|
152
149
|
if (this.flushingSoon > -1) {
|
|
153
150
|
window.clearTimeout(this.flushingSoon);
|
|
@@ -209,7 +209,7 @@ function selectionToDOM(view) {
|
|
|
209
209
|
if (!sel.$from.parent.inlineContent) resetEditableFrom = temporarilyEditableNear(v, sel.from);
|
|
210
210
|
if (!sel.empty && !sel.$from.parent.inlineContent) resetEditableTo = temporarilyEditableNear(v, sel.to);
|
|
211
211
|
}
|
|
212
|
-
v.docView.setSelection(anchor, head, v
|
|
212
|
+
v.docView.setSelection(anchor, head, v, force);
|
|
213
213
|
if (brokenSelectBetweenUneditable) {
|
|
214
214
|
if (resetEditableFrom) resetEditable(resetEditableFrom);
|
|
215
215
|
if (resetEditableTo) resetEditable(resetEditableTo);
|
package/dist/cjs/viewdesc.js
CHANGED
|
@@ -336,18 +336,20 @@ let ViewDesc = class ViewDesc {
|
|
|
336
336
|
// custom things with the selection. Note that this falls apart when
|
|
337
337
|
// a selection starts in such a node and ends in another, in which
|
|
338
338
|
// case we just use whatever domFromPos produces as a best effort.
|
|
339
|
-
setSelection(anchor, head,
|
|
339
|
+
setSelection(anchor, head, view) {
|
|
340
340
|
let force = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : false;
|
|
341
341
|
// If the selection falls entirely in a child, give it to that child
|
|
342
342
|
const from = Math.min(anchor, head), to = Math.max(anchor, head);
|
|
343
343
|
for(let i = 0, offset = 0; i < this.children.length; i++){
|
|
344
344
|
const child = this.children[i], end = offset + child.size;
|
|
345
|
-
if (from > offset && to < end) return child.setSelection(anchor - offset - child.border, head - offset - child.border,
|
|
345
|
+
if (from > offset && to < end) return child.setSelection(anchor - offset - child.border, head - offset - child.border, view, force);
|
|
346
346
|
offset = end;
|
|
347
347
|
}
|
|
348
348
|
let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1);
|
|
349
349
|
let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1);
|
|
350
|
-
const domSel = root.getSelection();
|
|
350
|
+
const domSel = view.root.getSelection();
|
|
351
|
+
// @ts-expect-error - Internal method domSelectionRange
|
|
352
|
+
const selRange = view.domSelectionRange();
|
|
351
353
|
let brKludge = false;
|
|
352
354
|
// On Firefox, using Selection.collapse to put the cursor after a
|
|
353
355
|
// BR node for some reason doesn't always work (#1073). On Safari,
|
|
@@ -379,11 +381,11 @@ let ViewDesc = class ViewDesc {
|
|
|
379
381
|
}
|
|
380
382
|
// Firefox can act strangely when the selection is in front of an
|
|
381
383
|
// uneditable node. See #1163 and https://bugzilla.mozilla.org/show_bug.cgi?id=1709536
|
|
382
|
-
if (_browser.browser.gecko &&
|
|
383
|
-
const after =
|
|
384
|
+
if (_browser.browser.gecko && selRange.focusNode && selRange.focusNode != headDOM.node && selRange.focusNode.nodeType == 1) {
|
|
385
|
+
const after = selRange.focusNode.childNodes[selRange.focusOffset];
|
|
384
386
|
if (after && after.contentEditable == "false") force = true;
|
|
385
387
|
}
|
|
386
|
-
if (!(force || brKludge && _browser.browser.safari) && (0, _selectionToDOM.isEquivalentPosition)(anchorDOM.node, anchorDOM.offset,
|
|
388
|
+
if (!(force || brKludge && _browser.browser.safari) && (0, _selectionToDOM.isEquivalentPosition)(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) && (0, _selectionToDOM.isEquivalentPosition)(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset)) return;
|
|
387
389
|
// Selection.extend can be used to create an 'inverted' selection
|
|
388
390
|
// (one where the focus is before the anchor), but not all
|
|
389
391
|
// browsers support it yet.
|
|
@@ -508,8 +510,9 @@ let CompositionViewDesc = class CompositionViewDesc extends ViewDesc {
|
|
|
508
510
|
};
|
|
509
511
|
let MarkViewDesc = class MarkViewDesc extends ViewDesc {
|
|
510
512
|
mark;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
+
spec;
|
|
514
|
+
constructor(parent, children, getPos, mark, dom, contentDOM, spec){
|
|
515
|
+
super(parent, children, getPos, dom, contentDOM), this.mark = mark, this.spec = spec;
|
|
513
516
|
}
|
|
514
517
|
parseRule() {
|
|
515
518
|
if (this.dirty & NODE_DIRTY || this.mark.type.spec.reparseInView) return null;
|
|
@@ -532,6 +535,13 @@ let MarkViewDesc = class MarkViewDesc extends ViewDesc {
|
|
|
532
535
|
this.dirty = NOT_DIRTY;
|
|
533
536
|
}
|
|
534
537
|
}
|
|
538
|
+
ignoreMutation(mutation) {
|
|
539
|
+
return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation);
|
|
540
|
+
}
|
|
541
|
+
destroy() {
|
|
542
|
+
if (this.spec.destroy) this.spec.destroy();
|
|
543
|
+
super.destroy();
|
|
544
|
+
}
|
|
535
545
|
};
|
|
536
546
|
let NodeViewDesc = class NodeViewDesc extends ViewDesc {
|
|
537
547
|
node;
|
|
@@ -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
|
+
});
|
|
@@ -31,12 +31,15 @@ export const MarkView = /*#__PURE__*/ memo(/*#__PURE__*/ forwardRef(function Mar
|
|
|
31
31
|
if (!domRef.current) return;
|
|
32
32
|
const firstChildDesc = childDescriptors.current[0];
|
|
33
33
|
if (!viewDescRef.current) {
|
|
34
|
-
viewDescRef.current = new MarkViewDesc(parentRef.current, childDescriptors.current, ()=>getPos.current(), mark, domRef.current, firstChildDesc?.dom.parentElement ?? domRef.current
|
|
34
|
+
viewDescRef.current = new MarkViewDesc(parentRef.current, childDescriptors.current, ()=>getPos.current(), mark, domRef.current, firstChildDesc?.dom.parentElement ?? domRef.current, {
|
|
35
|
+
dom: domRef.current,
|
|
36
|
+
contentDOM: firstChildDesc?.dom.parentElement ?? domRef.current
|
|
37
|
+
});
|
|
35
38
|
} else {
|
|
36
39
|
viewDescRef.current.parent = parentRef.current;
|
|
37
|
-
viewDescRef.current.dom = domRef.current;
|
|
40
|
+
viewDescRef.current.spec.dom = viewDescRef.current.dom = domRef.current;
|
|
38
41
|
viewDescRef.current.children = childDescriptors.current;
|
|
39
|
-
viewDescRef.current.contentDOM = firstChildDesc?.dom.parentElement ?? domRef.current;
|
|
42
|
+
viewDescRef.current.spec.contentDOM = viewDescRef.current.contentDOM = firstChildDesc?.dom.parentElement ?? domRef.current;
|
|
40
43
|
viewDescRef.current.mark = mark;
|
|
41
44
|
viewDescRef.current.getPos = ()=>getPos.current();
|
|
42
45
|
}
|
|
@@ -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";
|