@handlewithcare/react-prosemirror 3.1.0-tiptap.51 → 3.1.0-tiptap.52
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/dist/cjs/ReactEditorView.js +0 -2
- package/dist/cjs/components/ChildNodeViews.js +9 -14
- package/dist/cjs/components/CursorWrapper.js +9 -6
- package/dist/cjs/components/TextNodeView.js +38 -109
- package/dist/cjs/components/TrailingHackView.js +0 -29
- package/dist/cjs/components/WidgetView.js +1 -0
- package/dist/cjs/hooks/useComponentEventListeners.js +6 -14
- package/dist/cjs/hooks/useNodeViewDescription.js +16 -37
- package/dist/cjs/plugins/beforeInputPlugin.js +43 -41
- package/dist/cjs/plugins/componentEventListeners.js +2 -9
- package/dist/cjs/tiptap/utils/ssrJSDOMPatch.js +59 -0
- package/dist/cjs/viewdesc.js +3 -10
- package/dist/esm/ReactEditorView.js +0 -2
- package/dist/esm/components/ChildNodeViews.js +9 -14
- package/dist/esm/components/CursorWrapper.js +10 -7
- package/dist/esm/components/TextNodeView.js +38 -109
- package/dist/esm/components/TrailingHackView.js +1 -30
- package/dist/esm/components/WidgetView.js +1 -0
- package/dist/esm/hooks/useComponentEventListeners.js +6 -14
- package/dist/esm/hooks/useNodeViewDescription.js +17 -38
- package/dist/esm/plugins/beforeInputPlugin.js +43 -41
- package/dist/esm/plugins/componentEventListeners.js +2 -9
- package/dist/esm/tiptap/hooks/useTiptapEditor.js +7 -1
- package/dist/esm/tiptap/utils/ssrJSDOMPatch.js +56 -0
- package/dist/esm/viewdesc.js +3 -10
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ReactEditorView.d.ts +0 -4
- package/dist/types/components/CursorWrapper.d.ts +2 -4
- package/dist/types/components/TextNodeView.d.ts +4 -14
- package/dist/types/components/TrailingHackView.d.ts +1 -1
- package/dist/types/components/WidgetViewComponentProps.d.ts +4 -3
- package/dist/types/constants.d.ts +1 -1
- package/dist/types/hooks/useComponentEventListeners.d.ts +1 -1
- package/dist/types/plugins/componentEventListeners.d.ts +2 -3
- package/dist/types/props.d.ts +26 -26
- package/dist/types/tiptap/hooks/useTiptapEditor.d.ts +7 -0
- package/dist/types/tiptap/utils/ssrJSDOMPatch.d.ts +1 -0
- package/dist/types/viewdesc.d.ts +2 -2
- package/package.json +1 -2
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is used to patch global DOM variables in a NodeJS environment.
|
|
3
|
+
* This is needed for ProseMirror to work in a NodeJS environment.
|
|
4
|
+
*/ "use strict";
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
if (typeof window === "undefined") {
|
|
9
|
+
// Make sure to import JSDOM only in a NodeJS environment.
|
|
10
|
+
// The magic comments prevent bundlers from statically analyzing this require:
|
|
11
|
+
// - webpackIgnore: true → webpack / Next.js
|
|
12
|
+
// - @vite-ignore → Vite
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
14
|
+
const jsdom = require(/* webpackIgnore: true */ /* @vite-ignore */ "jsdom");
|
|
15
|
+
const html = `
|
|
16
|
+
<!DOCTYPE html>
|
|
17
|
+
<html>
|
|
18
|
+
<head>
|
|
19
|
+
<title>Testing</title>
|
|
20
|
+
</head>
|
|
21
|
+
<body></body>
|
|
22
|
+
</html>
|
|
23
|
+
`;
|
|
24
|
+
const { window: window1 } = new jsdom.JSDOM(html);
|
|
25
|
+
global.window = window1;
|
|
26
|
+
global.document = window1.document;
|
|
27
|
+
// Use Object.defineProperty for navigator since it's read-only in Node.js 22+
|
|
28
|
+
Object.defineProperty(global, "navigator", {
|
|
29
|
+
value: window1.navigator,
|
|
30
|
+
writable: true,
|
|
31
|
+
configurable: true
|
|
32
|
+
});
|
|
33
|
+
global.innerHeight = 0;
|
|
34
|
+
global.SVGElement = window1.SVGElement;
|
|
35
|
+
// @ts-expect-error stub getSelection for SSR
|
|
36
|
+
document.getSelection = ()=>({});
|
|
37
|
+
document.createRange = ()=>({
|
|
38
|
+
setStart () {},
|
|
39
|
+
setEnd () {},
|
|
40
|
+
// @ts-expect-error stub getBoundingClientRect for SSR
|
|
41
|
+
getClientRects () {
|
|
42
|
+
return {
|
|
43
|
+
left: 0,
|
|
44
|
+
top: 0,
|
|
45
|
+
right: 0,
|
|
46
|
+
bottom: 0
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
// @ts-expect-error stub getBoundingClientRect for SSR
|
|
50
|
+
getBoundingClientRect () {
|
|
51
|
+
return {
|
|
52
|
+
left: 0,
|
|
53
|
+
top: 0,
|
|
54
|
+
right: 0,
|
|
55
|
+
bottom: 0
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
package/dist/cjs/viewdesc.js
CHANGED
|
@@ -266,9 +266,7 @@ let ViewDesc = class ViewDesc {
|
|
|
266
266
|
prev = i ? this.children[i - 1] : null;
|
|
267
267
|
if (!prev || prev.dom.parentNode == this.contentDOM) break;
|
|
268
268
|
}
|
|
269
|
-
if (prev && side && enter && !prev.border && !prev.domAtom)
|
|
270
|
-
return prev.domFromPos(prev.size, side);
|
|
271
|
-
}
|
|
269
|
+
if (prev && side && enter && !prev.border && !prev.domAtom) return prev.domFromPos(prev.size, side);
|
|
272
270
|
return {
|
|
273
271
|
node: this.contentDOM,
|
|
274
272
|
offset: prev ? (0, _dom.domIndex)(prev.dom) + 1 : 0
|
|
@@ -403,9 +401,7 @@ let ViewDesc = class ViewDesc {
|
|
|
403
401
|
const after = selRange.focusNode.childNodes[selRange.focusOffset];
|
|
404
402
|
if (after && after.contentEditable == "false") force = true;
|
|
405
403
|
}
|
|
406
|
-
if (!(force || brKludge && _browser.browser.safari) && (0, _dom.isEquivalentPosition)(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) && (0, _dom.isEquivalentPosition)(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset))
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
404
|
+
if (!(force || brKludge && _browser.browser.safari) && (0, _dom.isEquivalentPosition)(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) && (0, _dom.isEquivalentPosition)(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset)) return;
|
|
409
405
|
// Selection.extend can be used to create an 'inverted' selection
|
|
410
406
|
// (one where the focus is before the anchor), but not all
|
|
411
407
|
// browsers support it yet.
|
|
@@ -670,10 +666,7 @@ let TextViewDesc = class TextViewDesc extends NodeViewDesc {
|
|
|
670
666
|
skip: skip || true
|
|
671
667
|
};
|
|
672
668
|
}
|
|
673
|
-
update(
|
|
674
|
-
if (this.dirty == NODE_DIRTY || this.dirty != NOT_DIRTY && !this.inParent() || !node.sameMarkup(this.node)) return false;
|
|
675
|
-
this.updateOuterDeco(outerDeco);
|
|
676
|
-
this.node = node;
|
|
669
|
+
update(_node, _outerDeco, _innerDeco, _view) {
|
|
677
670
|
this.dirty = NOT_DIRTY;
|
|
678
671
|
return true;
|
|
679
672
|
}
|
|
@@ -33,7 +33,6 @@ function changedNodeViews(a, b) {
|
|
|
33
33
|
nextProps;
|
|
34
34
|
prevState;
|
|
35
35
|
_destroyed;
|
|
36
|
-
deferPendingEffects;
|
|
37
36
|
constructor(place, props){
|
|
38
37
|
// Prevent the base class from destroying the React-managed nodes.
|
|
39
38
|
// Restore them below after invoking the base class constructor.
|
|
@@ -86,7 +85,6 @@ function changedNodeViews(a, b) {
|
|
|
86
85
|
// @ts-expect-error this violates the typing but class does it, too.
|
|
87
86
|
this.docView = null;
|
|
88
87
|
this._destroyed = false;
|
|
89
|
-
this.deferPendingEffects = false;
|
|
90
88
|
}
|
|
91
89
|
get props() {
|
|
92
90
|
return this.nextProps;
|
|
@@ -51,18 +51,13 @@ const ChildView = /*#__PURE__*/ memo(function ChildView(param) {
|
|
|
51
51
|
key: child.key
|
|
52
52
|
}, (param)=>{
|
|
53
53
|
let { siblingsRef, parentRef } = param;
|
|
54
|
-
return /*#__PURE__*/ React.createElement(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
parentRef: parentRef,
|
|
62
|
-
decorations: child.outerDeco,
|
|
63
|
-
registerEventListener: registerEventListener,
|
|
64
|
-
unregisterEventListener: unregisterEventListener
|
|
65
|
-
});
|
|
54
|
+
return /*#__PURE__*/ React.createElement(TextNodeView, {
|
|
55
|
+
view: view,
|
|
56
|
+
node: child.node,
|
|
57
|
+
getPos: getPos,
|
|
58
|
+
siblingsRef: siblingsRef,
|
|
59
|
+
parentRef: parentRef,
|
|
60
|
+
decorations: child.outerDeco
|
|
66
61
|
});
|
|
67
62
|
}) : /*#__PURE__*/ React.createElement(NodeView, {
|
|
68
63
|
key: child.key,
|
|
@@ -345,14 +340,14 @@ export const ChildNodeViews = /*#__PURE__*/ memo(function ChildNodeViews(param)
|
|
|
345
340
|
component: SeparatorHackView,
|
|
346
341
|
marks: [],
|
|
347
342
|
offset: lastChild?.offset ?? 0,
|
|
348
|
-
index: (lastChild?.index ?? 0) +
|
|
343
|
+
index: (lastChild?.index ?? 0) + 2,
|
|
349
344
|
key: "trailing-hack-img"
|
|
350
345
|
}, {
|
|
351
346
|
type: "hack",
|
|
352
347
|
component: TrailingHackView,
|
|
353
348
|
marks: [],
|
|
354
349
|
offset: lastChild?.offset ?? 0,
|
|
355
|
-
index: (lastChild?.index ?? 0) +
|
|
350
|
+
index: (lastChild?.index ?? 0) + 1,
|
|
356
351
|
key: "trailing-hack-br"
|
|
357
352
|
});
|
|
358
353
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import React, { forwardRef, useImperativeHandle, useRef
|
|
1
|
+
import React, { forwardRef, useImperativeHandle, useRef } from "react";
|
|
2
2
|
import { domIndex } from "../dom.js";
|
|
3
3
|
import { useEditorEffect } from "../hooks/useEditorEffect.js";
|
|
4
4
|
export const CursorWrapper = /*#__PURE__*/ forwardRef(function CursorWrapper(param, ref) {
|
|
5
5
|
let { widget, getPos, ...props } = param;
|
|
6
|
-
const [shouldRender, setShouldRender] = useState(true);
|
|
7
6
|
const innerRef = useRef(null);
|
|
8
7
|
useImperativeHandle(ref, ()=>{
|
|
9
8
|
return innerRef.current;
|
|
@@ -15,18 +14,22 @@ export const CursorWrapper = /*#__PURE__*/ forwardRef(function CursorWrapper(par
|
|
|
15
14
|
// @ts-expect-error Internal property - domSelection
|
|
16
15
|
const domSel = view.domSelection();
|
|
17
16
|
const node = innerRef.current;
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const img = node.nodeName == "IMG";
|
|
18
|
+
if (img) {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20
|
+
domSel.collapse(node.parentNode, domIndex(node) + 1);
|
|
21
|
+
} else {
|
|
22
|
+
domSel.collapse(node, 0);
|
|
23
|
+
}
|
|
20
24
|
// @ts-expect-error Internal property - domObserver
|
|
21
25
|
view.domObserver.connectSelection();
|
|
22
|
-
setShouldRender(false);
|
|
23
26
|
}, []);
|
|
24
|
-
return
|
|
27
|
+
return /*#__PURE__*/ React.createElement("img", {
|
|
25
28
|
ref: innerRef,
|
|
26
29
|
className: "ProseMirror-separator",
|
|
27
30
|
// eslint-disable-next-line react/no-unknown-property
|
|
28
31
|
"mark-placeholder": "true",
|
|
29
32
|
alt: "",
|
|
30
33
|
...props
|
|
31
|
-
})
|
|
34
|
+
});
|
|
32
35
|
});
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { TextSelection } from "prosemirror-state";
|
|
2
1
|
import { DecorationSet } from "prosemirror-view";
|
|
3
2
|
import { Component } from "react";
|
|
4
|
-
import { ReactEditorView } from "../ReactEditorView.js";
|
|
5
3
|
import { findDOMNode } from "../findDOMNode.js";
|
|
6
4
|
import { CompositionViewDesc, TextViewDesc, sortViewDescs } from "../viewdesc.js";
|
|
7
5
|
import { wrapInDeco } from "./ChildNodeViews.js";
|
|
@@ -30,141 +28,72 @@ function shallowEqual(objA, objB) {
|
|
|
30
28
|
export class TextNodeView extends Component {
|
|
31
29
|
viewDescRef = null;
|
|
32
30
|
renderRef = null;
|
|
33
|
-
|
|
34
|
-
containsCompositionNodeText = true;
|
|
35
|
-
// This is basically NodeViewDesc.localCompositionInfo
|
|
36
|
-
// from prosemirror-view. It's been slightly adjusted so that
|
|
37
|
-
// it can be used accurately during render, before we've
|
|
38
|
-
// necessarily found (or even let the browser create)
|
|
39
|
-
// view.input.compositionNode
|
|
40
|
-
shouldProtect(props) {
|
|
41
|
-
const { view, getPos, node } = props;
|
|
42
|
-
if (!(view instanceof ReactEditorView)) return false;
|
|
43
|
-
if (!view.composing) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
const pos = getPos();
|
|
47
|
-
const { from, to } = view.state.selection;
|
|
48
|
-
if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + node.nodeSize) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
return this.containsCompositionNodeText;
|
|
52
|
-
}
|
|
53
|
-
handleCompositionEnd = ()=>{
|
|
54
|
-
if (!this.wasProtecting) return;
|
|
55
|
-
this.forceUpdate();
|
|
56
|
-
return;
|
|
57
|
-
};
|
|
58
|
-
create() {
|
|
31
|
+
updateEffect() {
|
|
59
32
|
const { view, decorations, siblingsRef, parentRef, getPos, node } = this.props;
|
|
33
|
+
// There simply is no other way to ref a text node
|
|
34
|
+
// eslint-disable-next-line react/no-find-dom-node
|
|
60
35
|
const dom = findDOMNode(this);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let viewDesc;
|
|
70
|
-
if (this.shouldProtect(this.props)) {
|
|
71
|
-
viewDesc = new CompositionViewDesc(parentRef.current, getPos, // If we can't
|
|
72
|
-
// actually find the correct DOM nodes from here (
|
|
73
|
-
// which is the case in a composition in a newly
|
|
74
|
-
// created text node), we let our parent do it.
|
|
36
|
+
// We only need to explicitly create a CompositionViewDesc
|
|
37
|
+
// when a composition was started that produces a new text node.
|
|
38
|
+
// Otherwise we just rely on re-rendering the renderRef
|
|
39
|
+
if (!dom) {
|
|
40
|
+
if (!view.composing) return;
|
|
41
|
+
this.viewDescRef = new CompositionViewDesc(parentRef.current, getPos, // These are just placeholders/dummies. We can't
|
|
42
|
+
// actually find the correct DOM nodes from here,
|
|
43
|
+
// so we let our parent do it.
|
|
75
44
|
// Passing a valid element here just so that the
|
|
76
45
|
// ViewDesc constructor doesn't blow up.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!dom || !textNode) return null;
|
|
80
|
-
viewDesc = new TextViewDesc(parentRef.current, [], getPos, node, decorations, DecorationSet.empty, dom, textNode);
|
|
81
|
-
}
|
|
82
|
-
siblingsRef.current.push(viewDesc);
|
|
83
|
-
siblingsRef.current.sort(sortViewDescs);
|
|
84
|
-
return viewDesc;
|
|
85
|
-
}
|
|
86
|
-
update() {
|
|
87
|
-
const { view, node, decorations } = this.props;
|
|
88
|
-
if (!(view instanceof ReactEditorView)) return false;
|
|
89
|
-
const viewDesc = this.viewDescRef;
|
|
90
|
-
if (!viewDesc) return false;
|
|
91
|
-
if (this.shouldProtect(this.props) !== viewDesc instanceof CompositionViewDesc) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
if (viewDesc instanceof CompositionViewDesc) return false;
|
|
95
|
-
const dom = findDOMNode(this);
|
|
96
|
-
if (!dom || dom !== viewDesc.dom) return false;
|
|
97
|
-
if (!dom.contains(viewDesc.nodeDOM)) return false;
|
|
98
|
-
return viewDesc.matchesNode(node, decorations, DecorationSet.empty) || viewDesc.update(node, decorations, DecorationSet.empty, view);
|
|
99
|
-
}
|
|
100
|
-
destroy() {
|
|
101
|
-
const viewDesc = this.viewDescRef;
|
|
102
|
-
if (!viewDesc) return;
|
|
103
|
-
viewDesc.destroy();
|
|
104
|
-
const siblings = this.props.siblingsRef.current;
|
|
105
|
-
if (siblings.includes(viewDesc)) {
|
|
106
|
-
const index = siblings.indexOf(viewDesc);
|
|
107
|
-
siblings.splice(index, 1);
|
|
46
|
+
document.createElement("div"), document.createTextNode(node.text ?? ""), node.text ?? "");
|
|
47
|
+
return;
|
|
108
48
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.destroy();
|
|
113
|
-
this.viewDescRef = this.create();
|
|
49
|
+
let textNode = dom;
|
|
50
|
+
while(textNode.firstChild){
|
|
51
|
+
textNode = textNode.firstChild;
|
|
114
52
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
53
|
+
if (!this.viewDescRef || this.viewDescRef instanceof CompositionViewDesc) {
|
|
54
|
+
this.viewDescRef = new TextViewDesc(undefined, [], getPos, node, decorations, DecorationSet.empty, dom, textNode);
|
|
55
|
+
} else {
|
|
56
|
+
this.viewDescRef.parent = parentRef.current;
|
|
57
|
+
this.viewDescRef.children = [];
|
|
58
|
+
this.viewDescRef.node = node;
|
|
59
|
+
this.viewDescRef.outerDeco = decorations;
|
|
60
|
+
this.viewDescRef.innerDeco = DecorationSet.empty;
|
|
61
|
+
this.viewDescRef.dom = dom;
|
|
62
|
+
this.viewDescRef.dom.pmViewDesc = this.viewDescRef;
|
|
63
|
+
this.viewDescRef.nodeDOM = textNode;
|
|
119
64
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.containsCompositionNodeText = true;
|
|
123
|
-
return;
|
|
65
|
+
if (!siblingsRef.current.includes(this.viewDescRef)) {
|
|
66
|
+
siblingsRef.current.push(this.viewDescRef);
|
|
124
67
|
}
|
|
125
|
-
|
|
126
|
-
const text = textNode.nodeValue;
|
|
127
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
128
|
-
this.containsCompositionNodeText = node.text === text;
|
|
68
|
+
siblingsRef.current.sort(sortViewDescs);
|
|
129
69
|
}
|
|
130
70
|
shouldComponentUpdate(nextProps) {
|
|
131
|
-
// When leaving the protected state, force a re-render so React's
|
|
132
|
-
// virtual DOM resyncs with whatever the IME wrote into the real DOM
|
|
133
|
-
// while we were returning a stale renderRef.
|
|
134
|
-
if (this.wasProtecting && !this.shouldProtect(nextProps)) {
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
71
|
return !shallowEqual(this.props, nextProps);
|
|
138
72
|
}
|
|
139
73
|
componentDidMount() {
|
|
140
|
-
this.viewDescRef = null;
|
|
141
|
-
// After a composition, force an update so that we re-check whether we need
|
|
142
|
-
// to be protecting our rendered content and allow React to re-sync with the
|
|
143
|
-
// DOM.
|
|
144
|
-
const { registerEventListener } = this.props;
|
|
145
|
-
registerEventListener("compositionend", this.handleCompositionEnd);
|
|
146
74
|
this.updateEffect();
|
|
147
75
|
}
|
|
148
76
|
componentDidUpdate() {
|
|
149
77
|
this.updateEffect();
|
|
150
78
|
}
|
|
151
79
|
componentWillUnmount() {
|
|
152
|
-
const {
|
|
153
|
-
|
|
154
|
-
this.
|
|
80
|
+
const { siblingsRef } = this.props;
|
|
81
|
+
if (!this.viewDescRef) return;
|
|
82
|
+
if (siblingsRef.current.includes(this.viewDescRef)) {
|
|
83
|
+
const index = siblingsRef.current.indexOf(this.viewDescRef);
|
|
84
|
+
siblingsRef.current.splice(index, 1);
|
|
85
|
+
}
|
|
155
86
|
}
|
|
156
87
|
render() {
|
|
157
|
-
const { node, decorations } = this.props;
|
|
88
|
+
const { view, getPos, node, decorations } = this.props;
|
|
158
89
|
// During a composition, it's crucial that we don't try to
|
|
159
90
|
// update the DOM that the user is working in. If there's
|
|
160
91
|
// an active composition and the selection is in this node,
|
|
161
92
|
// we freeze the DOM of this element so that it doesn't
|
|
162
93
|
// interrupt the composition
|
|
163
|
-
if (
|
|
164
|
-
this.wasProtecting = true;
|
|
94
|
+
if (view.composing && view.state.selection.from >= getPos() && view.state.selection.from <= getPos() + node.nodeSize) {
|
|
165
95
|
return this.renderRef;
|
|
166
96
|
}
|
|
167
|
-
this.wasProtecting = false;
|
|
168
97
|
this.renderRef = decorations.reduce(wrapInDeco, node.text);
|
|
169
98
|
return this.renderRef;
|
|
170
99
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import React, { useContext, useRef
|
|
1
|
+
import React, { useContext, useRef } from "react";
|
|
2
2
|
import { ChildDescriptionsContext } from "../contexts/ChildDescriptionsContext.js";
|
|
3
3
|
import { useClientLayoutEffect } from "../hooks/useClientLayoutEffect.js";
|
|
4
|
-
import { useEditorEffect } from "../hooks/useEditorEffect.js";
|
|
5
|
-
import { useEditorEventListener } from "../hooks/useEditorEventListener.js";
|
|
6
4
|
import { TrailingHackViewDesc, sortViewDescs } from "../viewdesc.js";
|
|
7
5
|
export function TrailingHackView(param) {
|
|
8
6
|
let { getPos } = param;
|
|
9
|
-
const [shouldRender, setShouldRender] = useState(true);
|
|
10
7
|
const { siblingsRef, parentRef } = useContext(ChildDescriptionsContext);
|
|
11
8
|
const viewDescRef = useRef(null);
|
|
12
9
|
const ref = useRef(null);
|
|
@@ -35,32 +32,6 @@ export function TrailingHackView(param) {
|
|
|
35
32
|
}
|
|
36
33
|
siblingsRef.current.sort(sortViewDescs);
|
|
37
34
|
});
|
|
38
|
-
// At the start of a composition, the browser will automatically delete
|
|
39
|
-
// the trailing hack br element. We need to unmount ourselves _before_
|
|
40
|
-
// that happens, so that React doesn't try to remove the already-removed
|
|
41
|
-
// br node when this component gets unmounted
|
|
42
|
-
useEditorEventListener("compositionstart", (view)=>{
|
|
43
|
-
const { from } = view.state.selection;
|
|
44
|
-
if (from === getPos()) {
|
|
45
|
-
setShouldRender(false);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
// We need to run the same composition check when we first get mounted,
|
|
49
|
-
// in case we got mounted in the same render batch as the beginning of
|
|
50
|
-
// a composition
|
|
51
|
-
useEditorEffect((view)=>{
|
|
52
|
-
if (!view.composing) return;
|
|
53
|
-
const { from } = view.state.selection;
|
|
54
|
-
if (from === getPos()) {
|
|
55
|
-
setShouldRender(false);
|
|
56
|
-
}
|
|
57
|
-
}, [
|
|
58
|
-
getPos
|
|
59
|
-
]);
|
|
60
|
-
useEditorEventListener("compositionend", ()=>{
|
|
61
|
-
setShouldRender(true);
|
|
62
|
-
});
|
|
63
|
-
if (!shouldRender) return null;
|
|
64
35
|
return /*#__PURE__*/ React.createElement("br", {
|
|
65
36
|
ref: ref,
|
|
66
37
|
className: "ProseMirror-trailingBreak"
|
|
@@ -27,6 +27,7 @@ export function WidgetView(param) {
|
|
|
27
27
|
viewDescRef.current.parent = parentRef.current;
|
|
28
28
|
viewDescRef.current.widget = widget;
|
|
29
29
|
viewDescRef.current.dom = domRef.current;
|
|
30
|
+
viewDescRef.current.dom.pmViewDesc = viewDescRef.current;
|
|
30
31
|
}
|
|
31
32
|
if (!siblingsRef.current.includes(viewDescRef.current)) {
|
|
32
33
|
siblingsRef.current.push(viewDescRef.current);
|
|
@@ -25,16 +25,8 @@ import { unstable_batchedUpdates as batch } from "react-dom";
|
|
|
25
25
|
*
|
|
26
26
|
* To accomplish this, we shallowly clone the registry whenever a new event
|
|
27
27
|
* type is registered.
|
|
28
|
-
*/ export function useComponentEventListeners(
|
|
29
|
-
const [registry, setRegistry] = useState(new Map(
|
|
30
|
-
let [eventName, handler] = param;
|
|
31
|
-
return [
|
|
32
|
-
eventName,
|
|
33
|
-
handler ? [
|
|
34
|
-
handler
|
|
35
|
-
] : []
|
|
36
|
-
];
|
|
37
|
-
})));
|
|
28
|
+
*/ export function useComponentEventListeners(handleDOMEventsProp) {
|
|
29
|
+
const [registry, setRegistry] = useState(new Map());
|
|
38
30
|
const registerEventListener = useCallback((eventType, handler)=>{
|
|
39
31
|
const handlers = registry.get(eventType) ?? [];
|
|
40
32
|
handlers.unshift(handler);
|
|
@@ -52,19 +44,19 @@ import { unstable_batchedUpdates as batch } from "react-dom";
|
|
|
52
44
|
registry
|
|
53
45
|
]);
|
|
54
46
|
useLayoutEffect(()=>{
|
|
55
|
-
if (!
|
|
56
|
-
for (const [eventType, handler] of Object.entries(
|
|
47
|
+
if (!handleDOMEventsProp) return;
|
|
48
|
+
for (const [eventType, handler] of Object.entries(handleDOMEventsProp)){
|
|
57
49
|
if (!handler) return;
|
|
58
50
|
registerEventListener(eventType, handler);
|
|
59
51
|
}
|
|
60
52
|
return ()=>{
|
|
61
|
-
for (const [eventType, handler] of Object.entries(
|
|
53
|
+
for (const [eventType, handler] of Object.entries(handleDOMEventsProp)){
|
|
62
54
|
if (!handler) return;
|
|
63
55
|
unregisterEventListener(eventType, handler);
|
|
64
56
|
}
|
|
65
57
|
};
|
|
66
58
|
}, [
|
|
67
|
-
|
|
59
|
+
handleDOMEventsProp,
|
|
68
60
|
registerEventListener,
|
|
69
61
|
unregisterEventListener
|
|
70
62
|
]);
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { useCallback, useContext, useMemo, useRef } from "react";
|
|
2
2
|
import { ReactEditorView } from "../ReactEditorView.js";
|
|
3
|
-
import { CursorWrapper } from "../components/CursorWrapper.js";
|
|
4
3
|
import { ChildDescriptionsContext } from "../contexts/ChildDescriptionsContext.js";
|
|
5
4
|
import { EditorContext } from "../contexts/EditorContext.js";
|
|
6
|
-
import {
|
|
7
|
-
import { CompositionViewDesc, MarkViewDesc, ReactNodeViewDesc, WidgetViewDesc, sortViewDescs } from "../viewdesc.js";
|
|
5
|
+
import { CompositionViewDesc, ReactNodeViewDesc, sortViewDescs } from "../viewdesc.js";
|
|
8
6
|
import { useClientLayoutEffect } from "./useClientLayoutEffect.js";
|
|
9
7
|
import { useEffectEvent } from "./useEffectEvent.js";
|
|
10
8
|
export function useNodeViewDescription(getDOM, getContentDOM, constructor, props) {
|
|
@@ -133,44 +131,25 @@ export function useNodeViewDescription(getDOM, getContentDOM, constructor, props
|
|
|
133
131
|
children.sort(sortViewDescs);
|
|
134
132
|
for (const child of children){
|
|
135
133
|
child.parent = viewDesc;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!(compositionViewDesc instanceof CompositionViewDesc)) return;
|
|
145
|
-
let compositionTopDOM = null;
|
|
146
|
-
let search = children[compositionChildIndex - 1];
|
|
147
|
-
while(search instanceof MarkViewDesc){
|
|
148
|
-
search = search.children[0];
|
|
149
|
-
}
|
|
150
|
-
if (search instanceof WidgetViewDesc && search.widget.type instanceof ReactWidgetType && search.widget.type.Component === CursorWrapper) {
|
|
151
|
-
compositionTopDOM = search.dom.nextSibling;
|
|
152
|
-
} else {
|
|
153
|
-
for (const childNode of viewDescRef.current.contentDOM.childNodes){
|
|
154
|
-
if (children.every((child)=>child.dom !== childNode)) {
|
|
155
|
-
compositionTopDOM = childNode;
|
|
156
|
-
break;
|
|
134
|
+
// Because TextNodeViews can't locate the DOM nodes
|
|
135
|
+
// for compositions, we need to override them here
|
|
136
|
+
if (child instanceof CompositionViewDesc) {
|
|
137
|
+
const compositionTopDOM = viewDesc?.contentDOM?.firstChild;
|
|
138
|
+
if (!compositionTopDOM) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
|
|
139
|
+
let textDOM = compositionTopDOM;
|
|
140
|
+
while(textDOM.firstChild){
|
|
141
|
+
textDOM = textDOM.firstChild;
|
|
157
142
|
}
|
|
143
|
+
if (!textDOM || !(textDOM instanceof Text)) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
|
|
144
|
+
child.dom = compositionTopDOM;
|
|
145
|
+
child.textDOM = textDOM;
|
|
146
|
+
child.text = textDOM.data;
|
|
147
|
+
child.textDOM.pmViewDesc = child;
|
|
148
|
+
// It should not be possible to be in a composition because one could
|
|
149
|
+
// not start between the renders that switch the view type.
|
|
150
|
+
view.input.compositionNodes.push(child);
|
|
158
151
|
}
|
|
159
152
|
}
|
|
160
|
-
if (!compositionTopDOM) return;
|
|
161
|
-
let textDOM = compositionTopDOM;
|
|
162
|
-
while(textDOM.firstChild){
|
|
163
|
-
textDOM = textDOM.firstChild;
|
|
164
|
-
}
|
|
165
|
-
if (!textDOM || !(textDOM instanceof Text)) {
|
|
166
|
-
console.error(compositionTopDOM, textDOM);
|
|
167
|
-
throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
|
|
168
|
-
}
|
|
169
|
-
compositionViewDesc.dom = compositionTopDOM;
|
|
170
|
-
compositionViewDesc.textDOM = textDOM;
|
|
171
|
-
compositionViewDesc.text = textDOM.data;
|
|
172
|
-
compositionViewDesc.textDOM.pmViewDesc = compositionViewDesc;
|
|
173
|
-
view.input.compositionNodes.push(compositionViewDesc);
|
|
174
153
|
});
|
|
175
154
|
const childContextValue = useMemo(()=>({
|
|
176
155
|
parentRef: viewDescRef,
|