@handlewithcare/react-prosemirror 2.5.4 → 2.6.0-tiptap.2
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 -24
- package/dist/cjs/components/__tests__/ProseMirror.composition.test.js +398 -0
- package/dist/cjs/components/__tests__/ProseMirror.domchange.test.js +270 -0
- package/dist/cjs/components/__tests__/ProseMirror.draw-decoration.test.js +1010 -0
- package/dist/cjs/components/__tests__/ProseMirror.draw.test.js +337 -0
- package/dist/cjs/components/__tests__/ProseMirror.node-view.test.js +315 -0
- package/dist/cjs/components/__tests__/ProseMirror.selection.test.js +444 -0
- package/dist/cjs/components/__tests__/ProseMirror.test.js +382 -0
- package/dist/cjs/contexts/__tests__/DeferredLayoutEffects.test.js +141 -0
- package/dist/cjs/hooks/__tests__/useEditorViewLayoutEffect.test.js +108 -0
- package/dist/cjs/hooks/useClientOnly.js +19 -0
- package/dist/cjs/hooks/useEditor.js +17 -19
- package/dist/cjs/hooks/useNodeViewDescriptor.js +24 -16
- package/dist/cjs/plugins/__tests__/reactKeys.test.js +81 -0
- package/dist/cjs/selection/SelectionDOMObserver.js +171 -0
- package/dist/cjs/selection/hasFocusAndSelection.js +35 -0
- package/dist/cjs/selection/selectionFromDOM.js +77 -0
- package/dist/cjs/selection/selectionToDOM.js +226 -0
- package/dist/cjs/ssr.js +85 -0
- package/dist/cjs/tiptap/TiptapEditorContent.js +144 -0
- package/dist/cjs/tiptap/TiptapEditorView.js +91 -0
- package/dist/cjs/tiptap/hooks/useTiptapEditor.js +17 -0
- package/dist/cjs/tiptap/hooks/useTiptapEditorEffect.js +27 -0
- package/dist/cjs/tiptap/hooks/useTiptapEditorEventCallback.js +26 -0
- package/dist/cjs/tiptap/index.js +36 -0
- package/dist/cjs/tiptap/tiptapNodeView.js +180 -0
- package/dist/esm/ReactEditorView.js +0 -24
- package/dist/esm/components/__tests__/ProseMirror.composition.test.js +395 -0
- package/dist/esm/components/__tests__/ProseMirror.domchange.test.js +266 -0
- package/dist/esm/components/__tests__/ProseMirror.draw-decoration.test.js +967 -0
- package/dist/esm/components/__tests__/ProseMirror.draw.test.js +294 -0
- package/dist/esm/components/__tests__/ProseMirror.node-view.test.js +272 -0
- package/dist/esm/components/__tests__/ProseMirror.selection.test.js +440 -0
- package/dist/esm/components/__tests__/ProseMirror.test.js +339 -0
- package/dist/esm/contexts/__tests__/DeferredLayoutEffects.test.js +98 -0
- package/dist/esm/hooks/__tests__/useEditorViewLayoutEffect.test.js +99 -0
- package/dist/esm/hooks/useClientOnly.js +9 -0
- package/dist/esm/hooks/useEditor.js +17 -19
- package/dist/esm/hooks/useEditorEffect.js +4 -0
- package/dist/esm/hooks/useEditorEventCallback.js +3 -5
- package/dist/esm/hooks/useNodeViewDescriptor.js +25 -17
- package/dist/esm/plugins/__tests__/reactKeys.test.js +77 -0
- package/dist/esm/selection/SelectionDOMObserver.js +161 -0
- package/dist/esm/selection/hasFocusAndSelection.js +17 -0
- package/dist/esm/selection/selectionFromDOM.js +59 -0
- package/dist/esm/selection/selectionToDOM.js +196 -0
- package/dist/esm/ssr.js +82 -0
- package/dist/esm/tiptap/TiptapEditorContent.js +93 -0
- package/dist/esm/tiptap/TiptapEditorView.js +42 -0
- package/dist/esm/tiptap/hooks/useTiptapEditor.js +7 -0
- package/dist/esm/tiptap/hooks/useTiptapEditorEffect.js +34 -0
- package/dist/esm/tiptap/hooks/useTiptapEditorEventCallback.js +26 -0
- package/dist/esm/tiptap/index.js +6 -0
- package/dist/esm/tiptap/tiptapNodeView.js +148 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ReactEditorView.d.ts +0 -12
- package/dist/types/constants.d.ts +1 -1
- package/dist/types/hooks/__tests__/useEditorViewLayoutEffect.test.d.ts +1 -0
- package/dist/types/hooks/useClientOnly.d.ts +1 -0
- package/dist/types/hooks/useEditorEffect.d.ts +4 -0
- package/dist/types/hooks/useEditorEventCallback.d.ts +3 -5
- package/dist/types/props.d.ts +24 -24
- package/dist/types/selection/SelectionDOMObserver.d.ts +33 -0
- package/dist/types/selection/hasFocusAndSelection.d.ts +3 -0
- package/dist/types/selection/selectionFromDOM.d.ts +4 -0
- package/dist/types/selection/selectionToDOM.d.ts +9 -0
- package/dist/types/ssr.d.ts +19 -0
- package/dist/types/tiptap/TiptapEditorContent.d.ts +19 -0
- package/dist/types/tiptap/TiptapEditorView.d.ts +13 -0
- package/dist/types/tiptap/hooks/useTiptapEditor.d.ts +4 -0
- package/dist/types/tiptap/hooks/useTiptapEditorEffect.d.ts +21 -0
- package/dist/types/tiptap/hooks/useTiptapEditorEventCallback.d.ts +13 -0
- package/dist/types/tiptap/index.d.ts +6 -0
- package/dist/types/tiptap/tiptapNodeView.d.ts +48 -0
- package/package.json +8 -1
- package/dist/cjs/hooks/useEffectEvent.js +0 -34
- package/dist/esm/hooks/useEffectEvent.js +0 -24
- package/dist/types/hooks/useEffectEvent.d.ts +0 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "tiptapNodeView", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return tiptapNodeView;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _core = require("@tiptap/core");
|
|
12
|
+
const _react = require("@tiptap/react");
|
|
13
|
+
const _classnames = /*#__PURE__*/ _interop_require_default(require("classnames"));
|
|
14
|
+
const _react1 = /*#__PURE__*/ _interop_require_wildcard(require("react"));
|
|
15
|
+
const _useEditorEventCallback = require("../hooks/useEditorEventCallback.js");
|
|
16
|
+
const _useIgnoreMutation = require("../hooks/useIgnoreMutation.js");
|
|
17
|
+
const _useIsNodeSelected = require("../hooks/useIsNodeSelected.js");
|
|
18
|
+
const _useStopEvent = require("../hooks/useStopEvent.js");
|
|
19
|
+
const _props = require("../props.js");
|
|
20
|
+
function _interop_require_default(obj) {
|
|
21
|
+
return obj && obj.__esModule ? obj : {
|
|
22
|
+
default: obj
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function _getRequireWildcardCache(nodeInterop) {
|
|
26
|
+
if (typeof WeakMap !== "function") return null;
|
|
27
|
+
var cacheBabelInterop = new WeakMap();
|
|
28
|
+
var cacheNodeInterop = new WeakMap();
|
|
29
|
+
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
30
|
+
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
31
|
+
})(nodeInterop);
|
|
32
|
+
}
|
|
33
|
+
function _interop_require_wildcard(obj, nodeInterop) {
|
|
34
|
+
if (!nodeInterop && obj && obj.__esModule) {
|
|
35
|
+
return obj;
|
|
36
|
+
}
|
|
37
|
+
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
38
|
+
return {
|
|
39
|
+
default: obj
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
var cache = _getRequireWildcardCache(nodeInterop);
|
|
43
|
+
if (cache && cache.has(obj)) {
|
|
44
|
+
return cache.get(obj);
|
|
45
|
+
}
|
|
46
|
+
var newObj = {
|
|
47
|
+
__proto__: null
|
|
48
|
+
};
|
|
49
|
+
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
50
|
+
for(var key in obj){
|
|
51
|
+
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
52
|
+
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
53
|
+
if (desc && (desc.get || desc.set)) {
|
|
54
|
+
Object.defineProperty(newObj, key, desc);
|
|
55
|
+
} else {
|
|
56
|
+
newObj[key] = obj[key];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
newObj.default = obj;
|
|
61
|
+
if (cache) {
|
|
62
|
+
cache.set(obj, newObj);
|
|
63
|
+
}
|
|
64
|
+
return newObj;
|
|
65
|
+
}
|
|
66
|
+
function tiptapNodeView(param) {
|
|
67
|
+
let { component: WrappedComponent, extension, as, className = "", attrs, contentDOMElementTag: InnerTag = "div", stopEvent, ignoreMutation } = param;
|
|
68
|
+
const TiptapNodeView = /*#__PURE__*/ (0, _react1.memo)(/*#__PURE__*/ (0, _react1.forwardRef)(function TiptapNodeView(param, ref) {
|
|
69
|
+
let { children, nodeProps, ...props } = param;
|
|
70
|
+
const { node, getPos, decorations, innerDecorations } = nodeProps;
|
|
71
|
+
const OuterTag = as ?? node.type.isInline ? "span" : "div";
|
|
72
|
+
const { editor } = (0, _react.useCurrentEditor)();
|
|
73
|
+
const extensionManager = editor?.extensionManager ?? null;
|
|
74
|
+
const extensions = extensionManager?.extensions ?? null;
|
|
75
|
+
const selected = (0, _useIsNodeSelected.useIsNodeSelected)();
|
|
76
|
+
(0, _useStopEvent.useStopEvent)((_, event)=>{
|
|
77
|
+
if (stopEvent) {
|
|
78
|
+
return stopEvent({
|
|
79
|
+
event
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
});
|
|
84
|
+
(0, _useIgnoreMutation.useIgnoreMutation)((_, mutation)=>{
|
|
85
|
+
if (ignoreMutation) {
|
|
86
|
+
return ignoreMutation({
|
|
87
|
+
mutation
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
});
|
|
92
|
+
// This is just a dummy ref to satisfy Tiptap's types
|
|
93
|
+
const innerRef = (0, _react1.useRef)(null);
|
|
94
|
+
const htmlAttributes = (0, _react1.useMemo)(()=>{
|
|
95
|
+
if (!extensions) return {};
|
|
96
|
+
const attributes = (0, _core.getAttributesFromExtensions)(extensions);
|
|
97
|
+
const extensionAttributes = attributes.filter((attribute)=>attribute.type === extension.name);
|
|
98
|
+
return (0, _core.getRenderedAttributes)(node, extensionAttributes);
|
|
99
|
+
}, [
|
|
100
|
+
extensions,
|
|
101
|
+
node
|
|
102
|
+
]);
|
|
103
|
+
const { extraClassName, htmlProps } = (0, _react1.useMemo)(()=>{
|
|
104
|
+
if (!attrs) return {};
|
|
105
|
+
const resolvedAttrs = typeof attrs === "function" ? attrs({
|
|
106
|
+
node,
|
|
107
|
+
HTMLAttributes: htmlAttributes
|
|
108
|
+
}) : attrs;
|
|
109
|
+
const { className: extraClassName, ...htmlProps } = (0, _props.htmlAttrsToReactProps)(resolvedAttrs);
|
|
110
|
+
return {
|
|
111
|
+
extraClassName,
|
|
112
|
+
htmlProps
|
|
113
|
+
};
|
|
114
|
+
}, [
|
|
115
|
+
htmlAttributes,
|
|
116
|
+
node
|
|
117
|
+
]);
|
|
118
|
+
const finalClassName = (0, _classnames.default)("react-renderer", `node-${node.type.name}`, className, extraClassName, {
|
|
119
|
+
"ProseMirror-selectednode": selected
|
|
120
|
+
});
|
|
121
|
+
const updateAttributes = (0, _useEditorEventCallback.useEditorEventCallback)((_, attributes)=>{
|
|
122
|
+
if (!editor) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
editor.commands.command((param)=>{
|
|
126
|
+
let { tr } = param;
|
|
127
|
+
const pos = getPos();
|
|
128
|
+
tr.setNodeMarkup(pos, undefined, {
|
|
129
|
+
...node.attrs,
|
|
130
|
+
...attributes
|
|
131
|
+
});
|
|
132
|
+
return true;
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
const deleteNode = (0, _useEditorEventCallback.useEditorEventCallback)(()=>{
|
|
136
|
+
if (!editor) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const from = getPos();
|
|
140
|
+
const to = from + node.nodeSize;
|
|
141
|
+
editor.commands.deleteRange({
|
|
142
|
+
from,
|
|
143
|
+
to
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
const nodeViewContent = (0, _react1.useMemo)(()=>/*#__PURE__*/ _react1.default.createElement(InnerTag, {
|
|
147
|
+
"data-node-view-content-inner": node.type.name,
|
|
148
|
+
style: {
|
|
149
|
+
whitespace: "inherit"
|
|
150
|
+
}
|
|
151
|
+
}, children), [
|
|
152
|
+
children,
|
|
153
|
+
node.type.name
|
|
154
|
+
]);
|
|
155
|
+
if (!editor) return null;
|
|
156
|
+
return /*#__PURE__*/ _react1.default.createElement(_react.ReactNodeViewContentProvider, {
|
|
157
|
+
content: nodeViewContent
|
|
158
|
+
}, /*#__PURE__*/ _react1.default.createElement(OuterTag, {
|
|
159
|
+
ref: ref,
|
|
160
|
+
className: finalClassName,
|
|
161
|
+
...props,
|
|
162
|
+
...htmlProps
|
|
163
|
+
}, /*#__PURE__*/ _react1.default.createElement(WrappedComponent, {
|
|
164
|
+
ref: innerRef,
|
|
165
|
+
node: node,
|
|
166
|
+
getPos: getPos,
|
|
167
|
+
view: editor.view,
|
|
168
|
+
editor: editor,
|
|
169
|
+
decorations: decorations,
|
|
170
|
+
innerDecorations: innerDecorations,
|
|
171
|
+
extension: extension,
|
|
172
|
+
HTMLAttributes: htmlAttributes,
|
|
173
|
+
selected: selected,
|
|
174
|
+
updateAttributes: updateAttributes,
|
|
175
|
+
deleteNode: deleteNode
|
|
176
|
+
})));
|
|
177
|
+
}));
|
|
178
|
+
TiptapNodeView.displayName = `TiptapNodeView(${WrappedComponent.displayName ?? "Anonymous"})`;
|
|
179
|
+
return TiptapNodeView;
|
|
180
|
+
}
|
|
@@ -32,7 +32,6 @@ function changedNodeViews(a, b) {
|
|
|
32
32
|
*/ export class ReactEditorView extends EditorView {
|
|
33
33
|
nextProps;
|
|
34
34
|
prevState;
|
|
35
|
-
_destroyed;
|
|
36
35
|
constructor(place, props){
|
|
37
36
|
// Prevent the base class from destroying the React-managed nodes.
|
|
38
37
|
// Restore them below after invoking the base class constructor.
|
|
@@ -56,16 +55,6 @@ function changedNodeViews(a, b) {
|
|
|
56
55
|
this.domObserver.stop();
|
|
57
56
|
this.domObserver.observer = null;
|
|
58
57
|
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
|
-
};
|
|
69
58
|
} finally{
|
|
70
59
|
place.mount.replaceChildren(...reactContent);
|
|
71
60
|
for (const attr of place.mount.attributes){
|
|
@@ -84,22 +73,10 @@ function changedNodeViews(a, b) {
|
|
|
84
73
|
this.docView.destroy();
|
|
85
74
|
// @ts-expect-error this violates the typing but class does it, too.
|
|
86
75
|
this.docView = null;
|
|
87
|
-
this._destroyed = false;
|
|
88
76
|
}
|
|
89
77
|
get props() {
|
|
90
78
|
return this.nextProps;
|
|
91
79
|
}
|
|
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
|
-
}
|
|
103
80
|
setProps(props) {
|
|
104
81
|
this.update({
|
|
105
82
|
...this.props,
|
|
@@ -165,7 +142,6 @@ function changedNodeViews(a, b) {
|
|
|
165
142
|
super.destroy();
|
|
166
143
|
} finally{
|
|
167
144
|
this.dom.replaceChildren(...reactContent);
|
|
168
|
-
this._destroyed = true;
|
|
169
145
|
}
|
|
170
146
|
}
|
|
171
147
|
/**
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { doc, em, p, strong } from "prosemirror-test-builder";
|
|
2
|
+
// @ts-expect-error This is an internal export
|
|
3
|
+
import { __endComposition } from "prosemirror-view";
|
|
4
|
+
import { findTextNode, tempEditor } from "../../testing/editorViewTestHelpers.js";
|
|
5
|
+
function endComposition(view, forceUpdate) {
|
|
6
|
+
__endComposition(view, forceUpdate);
|
|
7
|
+
}
|
|
8
|
+
function event(pm, type) {
|
|
9
|
+
pm.dom.dispatchEvent(new CompositionEvent(type));
|
|
10
|
+
}
|
|
11
|
+
function edit(node) {
|
|
12
|
+
let text = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "", from = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : node.nodeValue.length, to = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : from;
|
|
13
|
+
const val = node.nodeValue;
|
|
14
|
+
node.nodeValue = val.slice(0, from) + text + val.slice(to);
|
|
15
|
+
document.getSelection().collapse(node, from + text.length);
|
|
16
|
+
return node;
|
|
17
|
+
}
|
|
18
|
+
function hasCompositionNode(_pm) {
|
|
19
|
+
let { focusNode } = document.getSelection();
|
|
20
|
+
while(focusNode && !focusNode.pmViewDesc)focusNode = focusNode.parentNode;
|
|
21
|
+
return focusNode && focusNode.pmViewDesc.constructor.name == "CompositionViewDesc";
|
|
22
|
+
}
|
|
23
|
+
function compose(pm, start, update) {
|
|
24
|
+
let options = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : {};
|
|
25
|
+
event(pm, "compositionstart");
|
|
26
|
+
expect(pm.composing).toBeTruthy();
|
|
27
|
+
let node;
|
|
28
|
+
const sel = document.getSelection();
|
|
29
|
+
for(let i = -1; i < update.length; i++){
|
|
30
|
+
if (i < 0) node = start();
|
|
31
|
+
else update[i](node);
|
|
32
|
+
const { focusNode , focusOffset } = sel;
|
|
33
|
+
// @ts-expect-error Internal property
|
|
34
|
+
pm.domObserver.flush();
|
|
35
|
+
if (options.cancel && i == update.length - 1) {
|
|
36
|
+
expect(hasCompositionNode(pm)).toBeFalsy();
|
|
37
|
+
} else {
|
|
38
|
+
expect(node.parentNode && pm.dom.contains(node.parentNode)).toBeTruthy();
|
|
39
|
+
expect(sel.focusNode === focusNode).toBeTruthy();
|
|
40
|
+
expect(sel.focusOffset === focusOffset).toBeTruthy();
|
|
41
|
+
if (options.node) expect(hasCompositionNode(pm)).toBeTruthy();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
event(pm, "compositionend");
|
|
45
|
+
if (options.end) {
|
|
46
|
+
options.end(node);
|
|
47
|
+
// @ts-expect-error Internal property
|
|
48
|
+
pm.domObserver.flush();
|
|
49
|
+
}
|
|
50
|
+
endComposition(pm);
|
|
51
|
+
expect(pm.composing).toBeFalsy();
|
|
52
|
+
expect(hasCompositionNode(pm)).toBeFalsy();
|
|
53
|
+
}
|
|
54
|
+
// function wordDeco(state: EditorState) {
|
|
55
|
+
// const re = /\w+/g,
|
|
56
|
+
// deco: Decoration[] = [];
|
|
57
|
+
// state.doc.descendants((node, pos) => {
|
|
58
|
+
// if (node.isText)
|
|
59
|
+
// for (let m; (m = re.exec(node.text!)); )
|
|
60
|
+
// deco.push(
|
|
61
|
+
// Decoration.inline(pos + m.index, pos + m.index + m[0].length, {
|
|
62
|
+
// class: "word",
|
|
63
|
+
// })
|
|
64
|
+
// );
|
|
65
|
+
// });
|
|
66
|
+
// return DecorationSet.create(state.doc, deco);
|
|
67
|
+
// }
|
|
68
|
+
// const wordHighlighter = new Plugin({
|
|
69
|
+
// props: { decorations: wordDeco },
|
|
70
|
+
// });
|
|
71
|
+
// const Widget = forwardRef(function Widget(
|
|
72
|
+
// { widget, pos, ...props }: WidgetViewComponentProps,
|
|
73
|
+
// ref: Ref<HTMLElement>
|
|
74
|
+
// ) {
|
|
75
|
+
// return (
|
|
76
|
+
// <var ref={ref} {...props}>
|
|
77
|
+
// ×
|
|
78
|
+
// </var>
|
|
79
|
+
// );
|
|
80
|
+
// });
|
|
81
|
+
// function widgets(positions: number[], sides: number[]) {
|
|
82
|
+
// return new Plugin({
|
|
83
|
+
// state: {
|
|
84
|
+
// init(state) {
|
|
85
|
+
// const deco = positions.map((p, i) =>
|
|
86
|
+
// widget(p, Widget, { side: sides[i] })
|
|
87
|
+
// );
|
|
88
|
+
// return DecorationSet.create(state.doc!, deco);
|
|
89
|
+
// },
|
|
90
|
+
// apply(tr, deco) {
|
|
91
|
+
// return deco.map(tr.mapping, tr.doc);
|
|
92
|
+
// },
|
|
93
|
+
// },
|
|
94
|
+
// props: {
|
|
95
|
+
// decorations(this: Plugin, state) {
|
|
96
|
+
// return this.getState(state);
|
|
97
|
+
// },
|
|
98
|
+
// },
|
|
99
|
+
// });
|
|
100
|
+
// }
|
|
101
|
+
// These unfortunately aren't working at the moment, though
|
|
102
|
+
// composition seems to be working generally.
|
|
103
|
+
describe.skip("EditorView composition", ()=>{
|
|
104
|
+
it("supports composition in an empty block", ()=>{
|
|
105
|
+
const { view: pm } = tempEditor({
|
|
106
|
+
doc: doc(p("<a>"))
|
|
107
|
+
});
|
|
108
|
+
compose(pm, ()=>edit(pm.dom.firstChild.appendChild(document.createTextNode("a"))), [
|
|
109
|
+
(n)=>edit(n, "b"),
|
|
110
|
+
(n)=>edit(n, "c")
|
|
111
|
+
], {
|
|
112
|
+
node: true
|
|
113
|
+
});
|
|
114
|
+
expect(pm.state.doc).toEqualNode(doc(p("abc")));
|
|
115
|
+
});
|
|
116
|
+
it("supports composition at end of block", ()=>{
|
|
117
|
+
const { view: pm } = tempEditor({
|
|
118
|
+
doc: doc(p("foo"))
|
|
119
|
+
});
|
|
120
|
+
compose(pm, ()=>edit(findTextNode(pm.dom, "foo")), [
|
|
121
|
+
(n)=>edit(n, "!"),
|
|
122
|
+
(n)=>edit(n, "?")
|
|
123
|
+
]);
|
|
124
|
+
expect(pm.state.doc).toEqualNode(doc(p("foo!?")));
|
|
125
|
+
});
|
|
126
|
+
it("supports composition at end of block in a new node", ()=>{
|
|
127
|
+
const { view: pm } = tempEditor({
|
|
128
|
+
doc: doc(p("foo"))
|
|
129
|
+
});
|
|
130
|
+
compose(pm, ()=>edit(pm.dom.firstChild.appendChild(document.createTextNode("!"))), [
|
|
131
|
+
(n)=>edit(n, "?")
|
|
132
|
+
], // $$FORK: We don't use composition view descriptors except for in initially empty nodes
|
|
133
|
+
{
|
|
134
|
+
node: false
|
|
135
|
+
});
|
|
136
|
+
expect(pm.state.doc).toEqualNode(doc(p("foo!?")));
|
|
137
|
+
});
|
|
138
|
+
it("supports composition at start of block in a new node", ()=>{
|
|
139
|
+
const { view: pm } = tempEditor({
|
|
140
|
+
doc: doc(p("foo"))
|
|
141
|
+
});
|
|
142
|
+
compose(pm, ()=>{
|
|
143
|
+
const p = pm.dom.firstChild;
|
|
144
|
+
return edit(p.insertBefore(document.createTextNode("!"), p.firstChild));
|
|
145
|
+
}, [
|
|
146
|
+
(n)=>edit(n, "?")
|
|
147
|
+
], // $$FORK: We don't use composition view descriptors except for in initially empty nodes
|
|
148
|
+
{
|
|
149
|
+
node: false
|
|
150
|
+
});
|
|
151
|
+
expect(pm.state.doc).toEqualNode(doc(p("!?foo")));
|
|
152
|
+
});
|
|
153
|
+
it("supports composition inside existing text", ()=>{
|
|
154
|
+
const { view: pm } = tempEditor({
|
|
155
|
+
doc: doc(p("foo"))
|
|
156
|
+
});
|
|
157
|
+
compose(pm, ()=>edit(findTextNode(pm.dom, "foo")), [
|
|
158
|
+
(n)=>edit(n, "x", 1),
|
|
159
|
+
(n)=>edit(n, "y", 2),
|
|
160
|
+
(n)=>edit(n, "z", 3)
|
|
161
|
+
]);
|
|
162
|
+
expect(pm.state.doc).toEqualNode(doc(p("fxyzoo")));
|
|
163
|
+
});
|
|
164
|
+
// TODO: Offset out of bound
|
|
165
|
+
// eslint-disable-next-line jest/no-disabled-tests
|
|
166
|
+
it.skip("can deal with Android-style newline-after-composition", ()=>{
|
|
167
|
+
const { view: pm } = tempEditor({
|
|
168
|
+
doc: doc(p("abcdef"))
|
|
169
|
+
});
|
|
170
|
+
compose(pm, ()=>edit(findTextNode(pm.dom, "abcdef")), [
|
|
171
|
+
(n)=>edit(n, "x", 3),
|
|
172
|
+
(n)=>edit(n, "y", 4)
|
|
173
|
+
], {
|
|
174
|
+
end: (n)=>{
|
|
175
|
+
const line = pm.dom.appendChild(document.createElement("div"));
|
|
176
|
+
line.textContent = "def";
|
|
177
|
+
n.nodeValue = "abcxy";
|
|
178
|
+
document.getSelection().collapse(line, 0);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
expect(pm.state.doc).toEqualNode(doc(p("abcxy"), p("def")));
|
|
182
|
+
});
|
|
183
|
+
it("handles replacement of existing words", ()=>{
|
|
184
|
+
const { view: pm } = tempEditor({
|
|
185
|
+
doc: doc(p("one two three"))
|
|
186
|
+
});
|
|
187
|
+
compose(pm, ()=>edit(findTextNode(pm.dom, "one two three"), "five", 4, 7), [
|
|
188
|
+
(n)=>edit(n, "seven", 4, 8),
|
|
189
|
+
(n)=>edit(n, "zero", 4, 9)
|
|
190
|
+
]);
|
|
191
|
+
expect(pm.state.doc).toEqualNode(doc(p("one zero three")));
|
|
192
|
+
});
|
|
193
|
+
it("handles composition inside marks", ()=>{
|
|
194
|
+
const { view: pm } = tempEditor({
|
|
195
|
+
doc: doc(p("one ", em("two")))
|
|
196
|
+
});
|
|
197
|
+
compose(pm, ()=>edit(findTextNode(pm.dom, "two"), "o"), [
|
|
198
|
+
(n)=>edit(n, "o"),
|
|
199
|
+
(n)=>edit(n, "w")
|
|
200
|
+
]);
|
|
201
|
+
expect(pm.state.doc).toEqualNode(doc(p("one ", em("twooow"))));
|
|
202
|
+
});
|
|
203
|
+
it.skip("handles composition in a mark that has multiple children", ()=>{
|
|
204
|
+
const { view: pm } = tempEditor({
|
|
205
|
+
doc: doc(p("one ", em("two", strong(" three"))))
|
|
206
|
+
});
|
|
207
|
+
compose(pm, ()=>edit(findTextNode(pm.dom, "two"), "o"), [
|
|
208
|
+
(n)=>edit(n, "o"),
|
|
209
|
+
(n)=>edit(n, "w")
|
|
210
|
+
]);
|
|
211
|
+
expect(pm.state.doc).toEqualNode(doc(p("one ", em("twooow", strong(" three")))));
|
|
212
|
+
});
|
|
213
|
+
// it("supports composition in a cursor wrapper", () => {
|
|
214
|
+
// const { view: pm } = tempEditor({ doc: doc(p("<a>")) });
|
|
215
|
+
// pm.dispatch(pm.state.tr.addStoredMark(schema.marks.em.create()));
|
|
216
|
+
// compose(
|
|
217
|
+
// pm,
|
|
218
|
+
// () =>
|
|
219
|
+
// edit(pm.dom.firstChild!.appendChild(document.createTextNode("")), "a"),
|
|
220
|
+
// [(n) => edit(n, "b"), (n) => edit(n, "c")],
|
|
221
|
+
// { node: true }
|
|
222
|
+
// );
|
|
223
|
+
// ist(pm.state.doc, doc(p(em("abc"))), eq);
|
|
224
|
+
// });
|
|
225
|
+
// it("handles composition in a multi-child mark with a cursor wrapper", () => {
|
|
226
|
+
// const { view: pm } = requireFocus(
|
|
227
|
+
// tempEditor({ doc: doc(p("one ", em("two<a>", strong(" three")))) })
|
|
228
|
+
// );
|
|
229
|
+
// pm.dispatch(pm.state.tr.addStoredMark(schema.marks.code.create()));
|
|
230
|
+
// const emNode = pm.dom.querySelector("em")!;
|
|
231
|
+
// compose(
|
|
232
|
+
// pm,
|
|
233
|
+
// () =>
|
|
234
|
+
// edit(
|
|
235
|
+
// emNode.insertBefore(
|
|
236
|
+
// document.createTextNode(""),
|
|
237
|
+
// emNode.querySelector("strong")
|
|
238
|
+
// ),
|
|
239
|
+
// "o"
|
|
240
|
+
// ),
|
|
241
|
+
// [(n) => edit(n, "o"), (n) => edit(n, "w")],
|
|
242
|
+
// { node: true }
|
|
243
|
+
// );
|
|
244
|
+
// ist(
|
|
245
|
+
// pm.state.doc,
|
|
246
|
+
// doc(p("one ", em("two", code("oow"), strong(" three")))),
|
|
247
|
+
// eq
|
|
248
|
+
// );
|
|
249
|
+
// });
|
|
250
|
+
// it("doesn't get interrupted by changes in decorations", () => {
|
|
251
|
+
// const { view: pm } = requireFocus(
|
|
252
|
+
// tempEditor({ doc: doc(p("foo ...")), plugins: [wordHighlighter] })
|
|
253
|
+
// );
|
|
254
|
+
// compose(pm, () => edit(findTextNode(pm.dom, " ...")!), [
|
|
255
|
+
// (n) => edit(n, "hi", 1, 4),
|
|
256
|
+
// ]);
|
|
257
|
+
// ist(pm.state.doc, doc(p("foo hi")), eq);
|
|
258
|
+
// });
|
|
259
|
+
// it("works inside highlighted text", () => {
|
|
260
|
+
// const { view: pm } = requireFocus(
|
|
261
|
+
// tempEditor({ doc: doc(p("one two")), plugins: [wordHighlighter] })
|
|
262
|
+
// );
|
|
263
|
+
// compose(pm, () => edit(findTextNode(pm.dom, "one")!, "x"), [
|
|
264
|
+
// (n) => edit(n, "y"),
|
|
265
|
+
// (n) => edit(n, "."),
|
|
266
|
+
// ]);
|
|
267
|
+
// ist(pm.state.doc, doc(p("onexy. two")), eq);
|
|
268
|
+
// });
|
|
269
|
+
// it("can handle compositions spanning multiple nodes", () => {
|
|
270
|
+
// const { view: pm } = requireFocus(
|
|
271
|
+
// tempEditor({ doc: doc(p("one two")), plugins: [wordHighlighter] })
|
|
272
|
+
// );
|
|
273
|
+
// compose(
|
|
274
|
+
// pm,
|
|
275
|
+
// () => edit(findTextNode(pm.dom, "two")!, "a"),
|
|
276
|
+
// [(n) => edit(n, "b"), (n) => edit(n, "c")],
|
|
277
|
+
// {
|
|
278
|
+
// end: (n: Text) => {
|
|
279
|
+
// n.parentNode!.previousSibling!.remove();
|
|
280
|
+
// n.parentNode!.previousSibling!.remove();
|
|
281
|
+
// return edit(n, "xyzone ", 0);
|
|
282
|
+
// },
|
|
283
|
+
// }
|
|
284
|
+
// );
|
|
285
|
+
// ist(pm.state.doc, doc(p("xyzone twoabc")), eq);
|
|
286
|
+
// });
|
|
287
|
+
// it("doesn't overwrite widgets next to the composition", () => {
|
|
288
|
+
// const { view: pm } = requireFocus(
|
|
289
|
+
// tempEditor({ doc: doc(p("")), plugins: [widgets([1, 1], [-1, 1])] })
|
|
290
|
+
// );
|
|
291
|
+
// compose(
|
|
292
|
+
// pm,
|
|
293
|
+
// () => {
|
|
294
|
+
// const p = pm.dom.firstChild!;
|
|
295
|
+
// return edit(p.insertBefore(document.createTextNode("a"), p.lastChild));
|
|
296
|
+
// },
|
|
297
|
+
// [(n) => edit(n, "b", 0, 1)],
|
|
298
|
+
// {
|
|
299
|
+
// end: () => {
|
|
300
|
+
// ist(pm.dom.querySelectorAll("var").length, 2);
|
|
301
|
+
// },
|
|
302
|
+
// }
|
|
303
|
+
// );
|
|
304
|
+
// ist(pm.state.doc, doc(p("b")), eq);
|
|
305
|
+
// });
|
|
306
|
+
// it("cancels composition when a change fully overlaps with it", () => {
|
|
307
|
+
// const { view: pm } = requireFocus(
|
|
308
|
+
// tempEditor({ doc: doc(p("one"), p("two"), p("three")) })
|
|
309
|
+
// );
|
|
310
|
+
// compose(
|
|
311
|
+
// pm,
|
|
312
|
+
// () => edit(findTextNode(pm.dom, "two")!, "x"),
|
|
313
|
+
// [() => pm.dispatch(pm.state.tr.insertText("---", 3, 13))],
|
|
314
|
+
// { cancel: true }
|
|
315
|
+
// );
|
|
316
|
+
// ist(pm.state.doc, doc(p("on---hree")), eq);
|
|
317
|
+
// });
|
|
318
|
+
// it("cancels composition when a change partially overlaps with it", () => {
|
|
319
|
+
// const { view: pm } = requireFocus(
|
|
320
|
+
// tempEditor({ doc: doc(p("one"), p("two"), p("three")) })
|
|
321
|
+
// );
|
|
322
|
+
// compose(
|
|
323
|
+
// pm,
|
|
324
|
+
// () => edit(findTextNode(pm.dom, "two")!, "x", 0),
|
|
325
|
+
// [() => pm.dispatch(pm.state.tr.insertText("---", 7, 15))],
|
|
326
|
+
// { cancel: true }
|
|
327
|
+
// );
|
|
328
|
+
// ist(pm.state.doc, doc(p("one"), p("x---ee")), eq);
|
|
329
|
+
// });
|
|
330
|
+
// it("cancels composition when a change happens inside of it", () => {
|
|
331
|
+
// const { view: pm } = requireFocus(
|
|
332
|
+
// tempEditor({ doc: doc(p("one"), p("two"), p("three")) })
|
|
333
|
+
// );
|
|
334
|
+
// compose(
|
|
335
|
+
// pm,
|
|
336
|
+
// () => edit(findTextNode(pm.dom, "two")!, "x", 0),
|
|
337
|
+
// [() => pm.dispatch(pm.state.tr.insertText("!", 7, 8))],
|
|
338
|
+
// { cancel: true }
|
|
339
|
+
// );
|
|
340
|
+
// ist(pm.state.doc, doc(p("one"), p("x!wo"), p("three")), eq);
|
|
341
|
+
// });
|
|
342
|
+
// it("doesn't cancel composition when a change happens elsewhere", () => {
|
|
343
|
+
// const { view: pm } = requireFocus(
|
|
344
|
+
// tempEditor({ doc: doc(p("one"), p("two"), p("three")) })
|
|
345
|
+
// );
|
|
346
|
+
// compose(pm, () => edit(findTextNode(pm.dom, "two")!, "x", 0), [
|
|
347
|
+
// (n) => edit(n, "y", 1),
|
|
348
|
+
// () => pm.dispatch(pm.state.tr.insertText("!", 2, 3)),
|
|
349
|
+
// (n) => edit(n, "z", 2),
|
|
350
|
+
// ]);
|
|
351
|
+
// ist(pm.state.doc, doc(p("o!e"), p("xyztwo"), p("three")), eq);
|
|
352
|
+
// });
|
|
353
|
+
// it("handles compositions rapidly following each other", () => {
|
|
354
|
+
// const { view: pm } = tempEditor({ doc: doc(p("one"), p("two")) });
|
|
355
|
+
// event(pm, "compositionstart");
|
|
356
|
+
// const one = findTextNode(pm.dom, "one")!;
|
|
357
|
+
// edit(one, "!");
|
|
358
|
+
// pm.domObserver.flush();
|
|
359
|
+
// event(pm, "compositionend");
|
|
360
|
+
// one.nodeValue = "one!!";
|
|
361
|
+
// const L2 = pm.dom.lastChild;
|
|
362
|
+
// event(pm, "compositionstart");
|
|
363
|
+
// const two = findTextNode(pm.dom, "two")!;
|
|
364
|
+
// ist(pm.dom.lastChild, L2);
|
|
365
|
+
// edit(two, ".");
|
|
366
|
+
// pm.domObserver.flush();
|
|
367
|
+
// ist(document.getSelection()!.focusNode, two);
|
|
368
|
+
// ist(document.getSelection()!.focusOffset, 4);
|
|
369
|
+
// ist(pm.composing);
|
|
370
|
+
// event(pm, "compositionend");
|
|
371
|
+
// pm.domObserver.flush();
|
|
372
|
+
// ist(pm.state.doc, doc(p("one!!"), p("two.")), eq);
|
|
373
|
+
// });
|
|
374
|
+
// function crossParagraph(first = false) {
|
|
375
|
+
// const { view: pm } = requireFocus(
|
|
376
|
+
// tempEditor({ doc: doc(p("one <a>two"), p("three"), p("four<b> five")) })
|
|
377
|
+
// );
|
|
378
|
+
// compose(
|
|
379
|
+
// pm,
|
|
380
|
+
// () => {
|
|
381
|
+
// for (let i = 0; i < 2; i++)
|
|
382
|
+
// pm.dom.removeChild(first ? pm.dom.lastChild! : pm.dom.firstChild!);
|
|
383
|
+
// const target = pm.dom.firstChild!.firstChild as Text;
|
|
384
|
+
// target.nodeValue = "one A five";
|
|
385
|
+
// document.getSelection()!.collapse(target, 4);
|
|
386
|
+
// return target;
|
|
387
|
+
// },
|
|
388
|
+
// [(n) => edit(n, "B", 4, 5), (n) => edit(n, "C", 4, 5)]
|
|
389
|
+
// );
|
|
390
|
+
// ist(pm.state.doc, doc(p("one C five")), eq);
|
|
391
|
+
// }
|
|
392
|
+
// it("can handle cross-paragraph compositions", () => crossParagraph(true));
|
|
393
|
+
// it("can handle cross-paragraph compositions (keeping the last paragraph)", () =>
|
|
394
|
+
// crossParagraph(false));
|
|
395
|
+
});
|