@handlewithcare/react-prosemirror 3.1.6 → 3.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 +12 -0
- package/dist/cjs/ReactEditorView.js +2 -2
- package/dist/cjs/components/EditorStateSelectorsProvider.js +90 -0
- package/dist/cjs/components/ProseMirror.js +3 -12
- package/dist/cjs/components/nodes/NodeView.js +3 -7
- package/dist/cjs/contexts/EditorStateStoreContext.js +45 -0
- package/dist/cjs/hooks/useEditorStateSelector.js +18 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/plugins/beforeInputPlugin.js +35 -7
- package/dist/cjs/viewdesc.js +1 -0
- package/dist/esm/ReactEditorView.js +2 -2
- package/dist/esm/components/EditorStateSelectorsProvider.js +31 -0
- package/dist/esm/components/ProseMirror.js +3 -12
- package/dist/esm/components/nodes/NodeView.js +3 -7
- package/dist/esm/contexts/EditorStateStoreContext.js +27 -0
- package/dist/esm/hooks/useEditorStateSelector.js +15 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/plugins/beforeInputPlugin.js +35 -7
- package/dist/esm/viewdesc.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/components/EditorStateSelectorsProvider.d.ts +10 -0
- package/dist/types/contexts/EditorStateStoreContext.d.ts +9 -0
- package/dist/types/hooks/useEditorStateSelector.d.ts +10 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
- package/dist/cjs/contexts/CompositionContext.js +0 -14
- package/dist/esm/contexts/CompositionContext.js +0 -4
- package/dist/types/contexts/CompositionContext.d.ts +0 -4
package/README.md
CHANGED
|
@@ -85,6 +85,7 @@ import "prosemirror-view/style/prosemirror.css";
|
|
|
85
85
|
- [`useEditorEventListener`](#useeditoreventlistener-1)
|
|
86
86
|
- [`useEditorEffect`](#useeditoreffect-1)
|
|
87
87
|
- [`NodeViewComponentProps`](#nodeviewcomponentprops)
|
|
88
|
+
- [`useEditorStateSelector`](#useeditorstateselector)
|
|
88
89
|
- [`useNodePos`](#usenodepos)
|
|
89
90
|
- [`useStopEvent`](#usestopevent)
|
|
90
91
|
- [`useIgnoreMutation`](#useignoremutation)
|
|
@@ -701,6 +702,17 @@ ref to their top-level DOM element. All node view components that render their
|
|
|
701
702
|
|
|
702
703
|
[See the above example](#building-node-views-with-react) for more details.
|
|
703
704
|
|
|
705
|
+
### `useEditorStateSelector`
|
|
706
|
+
|
|
707
|
+
```ts
|
|
708
|
+
type useEditorStateSelector<Result> = (selector: (state: EditorState) => Result): Result
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
Select a piece of the EditorState, a la Redux’s `useSelector`.
|
|
712
|
+
|
|
713
|
+
This hook will only trigger a re-render of the consuming component if the return
|
|
714
|
+
value of the selector changes.
|
|
715
|
+
|
|
704
716
|
### `useNodePos`
|
|
705
717
|
|
|
706
718
|
```ts
|
|
@@ -190,10 +190,10 @@ let ReactEditorView = class ReactEditorView extends _prosemirrorview.EditorView
|
|
|
190
190
|
const nextSelection = this.nextProps.state.selection;
|
|
191
191
|
const currentSelection = this.prevState.selection;
|
|
192
192
|
const selectionChanged = !nextSelection.eq(currentSelection);
|
|
193
|
-
if (selectionChanged
|
|
193
|
+
if (selectionChanged) {
|
|
194
194
|
super.update(this.nextProps);
|
|
195
195
|
} else {
|
|
196
|
-
// If the selection hasn't changed between renders
|
|
196
|
+
// If the selection hasn't changed between renders, force
|
|
197
197
|
// prosemirror-view to skip the selectionToDOM call. If a render happens after a DOM
|
|
198
198
|
// selection change but before the "selectionchange" event fired, calling
|
|
199
199
|
// selectionToDOM will cause the selection to be reset to its previous position.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: all[name]
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
EditorStateSelectorsProvider: function() {
|
|
13
|
+
return EditorStateSelectorsProvider;
|
|
14
|
+
},
|
|
15
|
+
EditorStateSelectorsRegistrar: function() {
|
|
16
|
+
return EditorStateSelectorsRegistrar;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
|
|
20
|
+
const _EditorStateStoreContext = require("../contexts/EditorStateStoreContext.js");
|
|
21
|
+
const _useEditorState = require("../hooks/useEditorState.js");
|
|
22
|
+
function _getRequireWildcardCache(nodeInterop) {
|
|
23
|
+
if (typeof WeakMap !== "function") return null;
|
|
24
|
+
var cacheBabelInterop = new WeakMap();
|
|
25
|
+
var cacheNodeInterop = new WeakMap();
|
|
26
|
+
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
27
|
+
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
28
|
+
})(nodeInterop);
|
|
29
|
+
}
|
|
30
|
+
function _interop_require_wildcard(obj, nodeInterop) {
|
|
31
|
+
if (!nodeInterop && obj && obj.__esModule) {
|
|
32
|
+
return obj;
|
|
33
|
+
}
|
|
34
|
+
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
35
|
+
return {
|
|
36
|
+
default: obj
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
var cache = _getRequireWildcardCache(nodeInterop);
|
|
40
|
+
if (cache && cache.has(obj)) {
|
|
41
|
+
return cache.get(obj);
|
|
42
|
+
}
|
|
43
|
+
var newObj = {
|
|
44
|
+
__proto__: null
|
|
45
|
+
};
|
|
46
|
+
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
47
|
+
for(var key in obj){
|
|
48
|
+
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
49
|
+
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
50
|
+
if (desc && (desc.get || desc.set)) {
|
|
51
|
+
Object.defineProperty(newObj, key, desc);
|
|
52
|
+
} else {
|
|
53
|
+
newObj[key] = obj[key];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
newObj.default = obj;
|
|
58
|
+
if (cache) {
|
|
59
|
+
cache.set(obj, newObj);
|
|
60
|
+
}
|
|
61
|
+
return newObj;
|
|
62
|
+
}
|
|
63
|
+
function EditorStateSelectorsRegistrar(param) {
|
|
64
|
+
let { children } = param;
|
|
65
|
+
const store = (0, _react.useMemo)(()=>(0, _EditorStateStoreContext.createEditorStateStore)(), []);
|
|
66
|
+
return /*#__PURE__*/ _react.default.createElement(_EditorStateStoreContext.EditorStateStoreContext.Provider, {
|
|
67
|
+
value: store
|
|
68
|
+
}, children);
|
|
69
|
+
}
|
|
70
|
+
function EditorStateSelectorsProvider(param) {
|
|
71
|
+
let { children } = param;
|
|
72
|
+
const editorState = (0, _useEditorState.useEditorState)();
|
|
73
|
+
const store = (0, _react.useContext)(_EditorStateStoreContext.EditorStateStoreContext);
|
|
74
|
+
// This _must_ be set during render so that child components
|
|
75
|
+
// get the latest values from their selectors during render,
|
|
76
|
+
// if they happen to render before store.notifyListeners()
|
|
77
|
+
// is called
|
|
78
|
+
store.setState(editorState);
|
|
79
|
+
// This means that we get a double-render whenever the selectors update,
|
|
80
|
+
// but everything is consistent during both renders.
|
|
81
|
+
// Only components with new selector values will render on
|
|
82
|
+
// the second render cycle
|
|
83
|
+
(0, _react.useLayoutEffect)(()=>{
|
|
84
|
+
store.notifyListeners();
|
|
85
|
+
}, [
|
|
86
|
+
editorState,
|
|
87
|
+
store
|
|
88
|
+
]);
|
|
89
|
+
return children;
|
|
90
|
+
}
|
|
@@ -10,14 +10,13 @@ Object.defineProperty(exports, "ProseMirror", {
|
|
|
10
10
|
});
|
|
11
11
|
const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
|
|
12
12
|
const _ChildDescriptionsContext = require("../contexts/ChildDescriptionsContext.js");
|
|
13
|
-
const _CompositionContext = require("../contexts/CompositionContext.js");
|
|
14
13
|
const _EditorContext = require("../contexts/EditorContext.js");
|
|
15
14
|
const _EditorStateContext = require("../contexts/EditorStateContext.js");
|
|
16
15
|
const _NodeViewContext = require("../contexts/NodeViewContext.js");
|
|
17
16
|
const _computeDocDeco = require("../decorations/computeDocDeco.js");
|
|
18
17
|
const _viewDecorations = require("../decorations/viewDecorations.js");
|
|
19
18
|
const _useEditor = require("../hooks/useEditor.js");
|
|
20
|
-
const
|
|
19
|
+
const _EditorStateSelectorsProvider = require("./EditorStateSelectorsProvider.js");
|
|
21
20
|
const _LayoutGroup = require("./LayoutGroup.js");
|
|
22
21
|
const _ProseMirrorDoc = require("./ProseMirrorDoc.js");
|
|
23
22
|
function _getRequireWildcardCache(nodeInterop) {
|
|
@@ -104,26 +103,18 @@ function ProseMirrorInner(param) {
|
|
|
104
103
|
decorations,
|
|
105
104
|
innerDecorations
|
|
106
105
|
]);
|
|
107
|
-
const freezeFrom = _reactKeys.reactKeysPluginKey.getState(state)?.freezeFrom ?? null;
|
|
108
|
-
const compositionContextValue = (0, _react.useMemo)(()=>({
|
|
109
|
-
freezeFrom
|
|
110
|
-
}), [
|
|
111
|
-
freezeFrom
|
|
112
|
-
]);
|
|
113
106
|
return /*#__PURE__*/ _react.default.createElement(_EditorContext.EditorContext.Provider, {
|
|
114
107
|
value: editor
|
|
115
108
|
}, /*#__PURE__*/ _react.default.createElement(_EditorStateContext.EditorStateContext.Provider, {
|
|
116
109
|
value: state
|
|
117
|
-
}, /*#__PURE__*/ _react.default.createElement(_NodeViewContext.NodeViewContext.Provider, {
|
|
110
|
+
}, /*#__PURE__*/ _react.default.createElement(_EditorStateSelectorsProvider.EditorStateSelectorsProvider, null, /*#__PURE__*/ _react.default.createElement(_NodeViewContext.NodeViewContext.Provider, {
|
|
118
111
|
value: nodeViewContextValue
|
|
119
112
|
}, /*#__PURE__*/ _react.default.createElement(_ChildDescriptionsContext.ChildDescriptionsContext.Provider, {
|
|
120
113
|
value: rootChildDescriptionsContextValue
|
|
121
|
-
}, /*#__PURE__*/ _react.default.createElement(_CompositionContext.CompositionContext.Provider, {
|
|
122
|
-
value: compositionContextValue
|
|
123
114
|
}, /*#__PURE__*/ _react.default.createElement(_ProseMirrorDoc.DocNodeViewContext.Provider, {
|
|
124
115
|
value: docNodeViewContextValue
|
|
125
116
|
}, children))))));
|
|
126
117
|
}
|
|
127
118
|
function ProseMirror(props) {
|
|
128
|
-
return /*#__PURE__*/ _react.default.createElement(_LayoutGroup.LayoutGroup, null, /*#__PURE__*/ _react.default.createElement(ProseMirrorInner, props));
|
|
119
|
+
return /*#__PURE__*/ _react.default.createElement(_LayoutGroup.LayoutGroup, null, /*#__PURE__*/ _react.default.createElement(_EditorStateSelectorsProvider.EditorStateSelectorsRegistrar, null, /*#__PURE__*/ _react.default.createElement(ProseMirrorInner, props)));
|
|
129
120
|
}
|
|
@@ -20,8 +20,9 @@ _export(exports, {
|
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
22
|
const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
|
|
23
|
-
const _CompositionContext = require("../../contexts/CompositionContext.js");
|
|
24
23
|
const _NodeViewContext = require("../../contexts/NodeViewContext.js");
|
|
24
|
+
const _useEditorStateSelector = require("../../hooks/useEditorStateSelector.js");
|
|
25
|
+
const _reactKeys = require("../../plugins/reactKeys.js");
|
|
25
26
|
const _DefaultNodeView = require("./DefaultNodeView.js");
|
|
26
27
|
const _NodeViewConstructorView = require("./NodeViewConstructorView.js");
|
|
27
28
|
const _ReactNodeView = require("./ReactNodeView.js");
|
|
@@ -69,7 +70,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
69
70
|
const NodeView = /*#__PURE__*/ (0, _react.memo)(function NodeView(param) {
|
|
70
71
|
let { forceRemount, ...props } = param;
|
|
71
72
|
const renderRef = (0, _react.useRef)(null);
|
|
72
|
-
const
|
|
73
|
+
const frozen = (0, _useEditorStateSelector.useEditorStateSelector)((state)=>_reactKeys.reactKeysPluginKey.getState(state)?.freezeFrom === props.getPos());
|
|
73
74
|
const { components, constructors } = (0, _react.useContext)(_NodeViewContext.NodeViewContext);
|
|
74
75
|
const committedFrozenRef = (0, _react.useRef)(false);
|
|
75
76
|
const component = components[props.node.type.name] ?? _DefaultNodeView.DefaultNodeView;
|
|
@@ -98,11 +99,6 @@ const NodeView = /*#__PURE__*/ (0, _react.memo)(function NodeView(param) {
|
|
|
98
99
|
constructor,
|
|
99
100
|
component
|
|
100
101
|
]);
|
|
101
|
-
// It's not generally safe to access getPos during render, because the
|
|
102
|
-
// component may not re-render when its return value would change. Here it's
|
|
103
|
-
// safe because we only use it to _suppress_ commits that would otherwise
|
|
104
|
-
// have happened.
|
|
105
|
-
const frozen = props.getPos() === freezeFrom;
|
|
106
102
|
// Protect content while frozen, and also through the single render where we
|
|
107
103
|
// leave the frozen state: `committedFrozenRef` still reflects the previous
|
|
108
104
|
// commit, so we keep returning the exact same cached element reference.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: all[name]
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
EditorStateStoreContext: function() {
|
|
13
|
+
return EditorStateStoreContext;
|
|
14
|
+
},
|
|
15
|
+
createEditorStateStore: function() {
|
|
16
|
+
return createEditorStateStore;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const _react = require("react");
|
|
20
|
+
function createEditorStateStore() {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
let state = null;
|
|
23
|
+
let pendingNotify = false;
|
|
24
|
+
const listeners = new Set();
|
|
25
|
+
return {
|
|
26
|
+
getState: ()=>state,
|
|
27
|
+
subscribe: (listener)=>{
|
|
28
|
+
listeners.add(listener);
|
|
29
|
+
return ()=>listeners.delete(listener);
|
|
30
|
+
},
|
|
31
|
+
setState: (newState)=>{
|
|
32
|
+
if (state !== newState) {
|
|
33
|
+
state = newState;
|
|
34
|
+
pendingNotify = true;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
notifyListeners: ()=>{
|
|
38
|
+
if (pendingNotify) {
|
|
39
|
+
pendingNotify = false;
|
|
40
|
+
listeners.forEach((l)=>l());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const EditorStateStoreContext = (0, _react.createContext)(null);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "useEditorStateSelector", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return useEditorStateSelector;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _react = require("react");
|
|
12
|
+
const _EditorStateStoreContext = require("../contexts/EditorStateStoreContext.js");
|
|
13
|
+
function useEditorStateSelector(selector) {
|
|
14
|
+
const store = (0, _react.useContext)(_EditorStateStoreContext.EditorStateStoreContext);
|
|
15
|
+
const selectorRef = (0, _react.useRef)(selector);
|
|
16
|
+
selectorRef.current = selector;
|
|
17
|
+
return (0, _react.useSyncExternalStore)(store.subscribe, ()=>selectorRef.current(store.getState()));
|
|
18
|
+
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -34,6 +34,9 @@ _export(exports, {
|
|
|
34
34
|
useEditorState: function() {
|
|
35
35
|
return _useEditorState.useEditorState;
|
|
36
36
|
},
|
|
37
|
+
useEditorStateSelector: function() {
|
|
38
|
+
return _useEditorStateSelector.useEditorStateSelector;
|
|
39
|
+
},
|
|
37
40
|
useIgnoreMutation: function() {
|
|
38
41
|
return _useIgnoreMutation.useIgnoreMutation;
|
|
39
42
|
},
|
|
@@ -71,6 +74,7 @@ const _useEditorEventCallback = require("./hooks/useEditorEventCallback.js");
|
|
|
71
74
|
const _useEditorEventListener = require("./hooks/useEditorEventListener.js");
|
|
72
75
|
const _useEditorState = require("./hooks/useEditorState.js");
|
|
73
76
|
const _useNodePos = require("./hooks/useNodePos.js");
|
|
77
|
+
const _useEditorStateSelector = require("./hooks/useEditorStateSelector.js");
|
|
74
78
|
const _useStopEvent = require("./hooks/useStopEvent.js");
|
|
75
79
|
const _useSelectNode = require("./hooks/useSelectNode.js");
|
|
76
80
|
const _useIgnoreMutation = require("./hooks/useIgnoreMutation.js");
|
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "beforeInputPlugin", {
|
|
|
11
11
|
const _prosemirrormodel = require("prosemirror-model");
|
|
12
12
|
const _prosemirrorstate = require("prosemirror-state");
|
|
13
13
|
const _ReactEditorView = require("../ReactEditorView.js");
|
|
14
|
+
const _browser = require("../browser.js");
|
|
14
15
|
const _CursorWrapper = require("../components/CursorWrapper.js");
|
|
15
16
|
const _ReactWidgetType = require("../decorations/ReactWidgetType.js");
|
|
16
17
|
const _viewdesc = require("../viewdesc.js");
|
|
@@ -93,6 +94,13 @@ function beforeInputPlugin() {
|
|
|
93
94
|
view.input.compositionNodes = [];
|
|
94
95
|
view.input.compositionID++;
|
|
95
96
|
}
|
|
97
|
+
function teardownCompositionAndUnfreeze(view, event) {
|
|
98
|
+
teardownComposition(view, event.timeStamp);
|
|
99
|
+
view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
100
|
+
cursorWrapper: null,
|
|
101
|
+
freezeFrom: null
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
96
104
|
return new _prosemirrorstate.Plugin({
|
|
97
105
|
view () {
|
|
98
106
|
return {
|
|
@@ -112,11 +120,17 @@ function beforeInputPlugin() {
|
|
|
112
120
|
const storedMarks = view.state.selection.empty ? view.state.storedMarks : view.state.storedMarks ?? (view.state.selection instanceof _prosemirrorstate.TextSelection ? view.state.selection.$from.marksAcross(view.state.selection.$to) : null);
|
|
113
121
|
view.dispatch(view.state.tr.deleteSelection().setStoredMarks(storedMarks));
|
|
114
122
|
handleGapCursorComposition(view);
|
|
115
|
-
|
|
123
|
+
const { selection } = view.state;
|
|
124
|
+
const tr = view.state.tr.delete(selection.from, selection.to);
|
|
125
|
+
const $from = tr.doc.resolve(tr.mapping.map(selection.from));
|
|
126
|
+
const isEmptyTextblock = $from.parent.isTextblock && $from.parent.childCount === 0;
|
|
127
|
+
if (storedMarks != null || _browser.browser.safari && isEmptyTextblock) {
|
|
116
128
|
view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
117
129
|
cursorWrapper: (0, _ReactWidgetType.widget)(view.state.selection.from, _CursorWrapper.CursorWrapper, {
|
|
118
130
|
key: "cursor-wrapper",
|
|
119
|
-
|
|
131
|
+
...storedMarks !== null && {
|
|
132
|
+
marks: storedMarks
|
|
133
|
+
},
|
|
120
134
|
side: 0,
|
|
121
135
|
raw: true
|
|
122
136
|
})
|
|
@@ -167,13 +181,27 @@ function beforeInputPlugin() {
|
|
|
167
181
|
compositionend (view, event) {
|
|
168
182
|
if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
|
|
169
183
|
if (!view.composing) return false;
|
|
170
|
-
|
|
171
|
-
view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
|
|
172
|
-
cursorWrapper: null,
|
|
173
|
-
freezeFrom: null
|
|
174
|
-
}));
|
|
184
|
+
teardownCompositionAndUnfreeze(view, event);
|
|
175
185
|
return true;
|
|
176
186
|
},
|
|
187
|
+
contextmenu (view, event) {
|
|
188
|
+
if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
|
|
189
|
+
if (!view.composing) return false;
|
|
190
|
+
teardownCompositionAndUnfreeze(view, event);
|
|
191
|
+
return false;
|
|
192
|
+
},
|
|
193
|
+
mousedown (view, event) {
|
|
194
|
+
if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
|
|
195
|
+
if (!view.composing) return false;
|
|
196
|
+
teardownCompositionAndUnfreeze(view, event);
|
|
197
|
+
return false;
|
|
198
|
+
},
|
|
199
|
+
touchstart (view, event) {
|
|
200
|
+
if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
|
|
201
|
+
if (!view.composing) return false;
|
|
202
|
+
teardownCompositionAndUnfreeze(view, event);
|
|
203
|
+
return false;
|
|
204
|
+
},
|
|
177
205
|
beforeinput (view, event) {
|
|
178
206
|
if (event.inputType !== "insertFromComposition") {
|
|
179
207
|
event.preventDefault();
|
package/dist/cjs/viewdesc.js
CHANGED
|
@@ -370,6 +370,7 @@ let ViewDesc = class ViewDesc {
|
|
|
370
370
|
// case we just use whatever domFromPos produces as a best effort.
|
|
371
371
|
setSelection(anchor, head, view) {
|
|
372
372
|
let force = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : false;
|
|
373
|
+
if (view.composing) return;
|
|
373
374
|
// If the selection falls entirely in a child, give it to that child
|
|
374
375
|
const from = Math.min(anchor, head), to = Math.max(anchor, head);
|
|
375
376
|
for(let i = 0, offset = 0; i < this.children.length; i++){
|
|
@@ -190,10 +190,10 @@ function changedNodeViews(a, b) {
|
|
|
190
190
|
const nextSelection = this.nextProps.state.selection;
|
|
191
191
|
const currentSelection = this.prevState.selection;
|
|
192
192
|
const selectionChanged = !nextSelection.eq(currentSelection);
|
|
193
|
-
if (selectionChanged
|
|
193
|
+
if (selectionChanged) {
|
|
194
194
|
super.update(this.nextProps);
|
|
195
195
|
} else {
|
|
196
|
-
// If the selection hasn't changed between renders
|
|
196
|
+
// If the selection hasn't changed between renders, force
|
|
197
197
|
// prosemirror-view to skip the selectionToDOM call. If a render happens after a DOM
|
|
198
198
|
// selection change but before the "selectionchange" event fired, calling
|
|
199
199
|
// selectionToDOM will cause the selection to be reset to its previous position.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { useContext, useLayoutEffect, useMemo } from "react";
|
|
2
|
+
import { EditorStateStoreContext, createEditorStateStore } from "../contexts/EditorStateStoreContext.js";
|
|
3
|
+
import { useEditorState } from "../hooks/useEditorState.js";
|
|
4
|
+
export function EditorStateSelectorsRegistrar(param) {
|
|
5
|
+
let { children } = param;
|
|
6
|
+
const store = useMemo(()=>createEditorStateStore(), []);
|
|
7
|
+
return /*#__PURE__*/ React.createElement(EditorStateStoreContext.Provider, {
|
|
8
|
+
value: store
|
|
9
|
+
}, children);
|
|
10
|
+
}
|
|
11
|
+
export function EditorStateSelectorsProvider(param) {
|
|
12
|
+
let { children } = param;
|
|
13
|
+
const editorState = useEditorState();
|
|
14
|
+
const store = useContext(EditorStateStoreContext);
|
|
15
|
+
// This _must_ be set during render so that child components
|
|
16
|
+
// get the latest values from their selectors during render,
|
|
17
|
+
// if they happen to render before store.notifyListeners()
|
|
18
|
+
// is called
|
|
19
|
+
store.setState(editorState);
|
|
20
|
+
// This means that we get a double-render whenever the selectors update,
|
|
21
|
+
// but everything is consistent during both renders.
|
|
22
|
+
// Only components with new selector values will render on
|
|
23
|
+
// the second render cycle
|
|
24
|
+
useLayoutEffect(()=>{
|
|
25
|
+
store.notifyListeners();
|
|
26
|
+
}, [
|
|
27
|
+
editorState,
|
|
28
|
+
store
|
|
29
|
+
]);
|
|
30
|
+
return children;
|
|
31
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import React, { useMemo, useState } from "react";
|
|
2
2
|
import { ChildDescriptionsContext } from "../contexts/ChildDescriptionsContext.js";
|
|
3
|
-
import { CompositionContext } from "../contexts/CompositionContext.js";
|
|
4
3
|
import { EditorContext } from "../contexts/EditorContext.js";
|
|
5
4
|
import { EditorStateContext } from "../contexts/EditorStateContext.js";
|
|
6
5
|
import { NodeViewContext } from "../contexts/NodeViewContext.js";
|
|
7
6
|
import { computeDocDeco } from "../decorations/computeDocDeco.js";
|
|
8
7
|
import { viewDecorations } from "../decorations/viewDecorations.js";
|
|
9
8
|
import { useEditor } from "../hooks/useEditor.js";
|
|
10
|
-
import {
|
|
9
|
+
import { EditorStateSelectorsProvider, EditorStateSelectorsRegistrar } from "./EditorStateSelectorsProvider.js";
|
|
11
10
|
import { LayoutGroup } from "./LayoutGroup.js";
|
|
12
11
|
import { DocNodeViewContext } from "./ProseMirrorDoc.js";
|
|
13
12
|
function getPos() {
|
|
@@ -53,26 +52,18 @@ function ProseMirrorInner(param) {
|
|
|
53
52
|
decorations,
|
|
54
53
|
innerDecorations
|
|
55
54
|
]);
|
|
56
|
-
const freezeFrom = reactKeysPluginKey.getState(state)?.freezeFrom ?? null;
|
|
57
|
-
const compositionContextValue = useMemo(()=>({
|
|
58
|
-
freezeFrom
|
|
59
|
-
}), [
|
|
60
|
-
freezeFrom
|
|
61
|
-
]);
|
|
62
55
|
return /*#__PURE__*/ React.createElement(EditorContext.Provider, {
|
|
63
56
|
value: editor
|
|
64
57
|
}, /*#__PURE__*/ React.createElement(EditorStateContext.Provider, {
|
|
65
58
|
value: state
|
|
66
|
-
}, /*#__PURE__*/ React.createElement(NodeViewContext.Provider, {
|
|
59
|
+
}, /*#__PURE__*/ React.createElement(EditorStateSelectorsProvider, null, /*#__PURE__*/ React.createElement(NodeViewContext.Provider, {
|
|
67
60
|
value: nodeViewContextValue
|
|
68
61
|
}, /*#__PURE__*/ React.createElement(ChildDescriptionsContext.Provider, {
|
|
69
62
|
value: rootChildDescriptionsContextValue
|
|
70
|
-
}, /*#__PURE__*/ React.createElement(CompositionContext.Provider, {
|
|
71
|
-
value: compositionContextValue
|
|
72
63
|
}, /*#__PURE__*/ React.createElement(DocNodeViewContext.Provider, {
|
|
73
64
|
value: docNodeViewContextValue
|
|
74
65
|
}, children))))));
|
|
75
66
|
}
|
|
76
67
|
export function ProseMirror(props) {
|
|
77
|
-
return /*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(ProseMirrorInner, props));
|
|
68
|
+
return /*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorStateSelectorsRegistrar, null, /*#__PURE__*/ React.createElement(ProseMirrorInner, props)));
|
|
78
69
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import React, { createContext, memo, useContext, useLayoutEffect, useMemo, useReducer, useRef } from "react";
|
|
2
|
-
import { CompositionContext } from "../../contexts/CompositionContext.js";
|
|
3
2
|
import { NodeViewContext } from "../../contexts/NodeViewContext.js";
|
|
3
|
+
import { useEditorStateSelector } from "../../hooks/useEditorStateSelector.js";
|
|
4
|
+
import { reactKeysPluginKey } from "../../plugins/reactKeys.js";
|
|
4
5
|
import { DefaultNodeView } from "./DefaultNodeView.js";
|
|
5
6
|
import { NodeViewConstructorView } from "./NodeViewConstructorView.js";
|
|
6
7
|
import { ReactNodeView } from "./ReactNodeView.js";
|
|
7
8
|
export const NodeView = /*#__PURE__*/ memo(function NodeView(param) {
|
|
8
9
|
let { forceRemount, ...props } = param;
|
|
9
10
|
const renderRef = useRef(null);
|
|
10
|
-
const
|
|
11
|
+
const frozen = useEditorStateSelector((state)=>reactKeysPluginKey.getState(state)?.freezeFrom === props.getPos());
|
|
11
12
|
const { components, constructors } = useContext(NodeViewContext);
|
|
12
13
|
const committedFrozenRef = useRef(false);
|
|
13
14
|
const component = components[props.node.type.name] ?? DefaultNodeView;
|
|
@@ -36,11 +37,6 @@ export const NodeView = /*#__PURE__*/ memo(function NodeView(param) {
|
|
|
36
37
|
constructor,
|
|
37
38
|
component
|
|
38
39
|
]);
|
|
39
|
-
// It's not generally safe to access getPos during render, because the
|
|
40
|
-
// component may not re-render when its return value would change. Here it's
|
|
41
|
-
// safe because we only use it to _suppress_ commits that would otherwise
|
|
42
|
-
// have happened.
|
|
43
|
-
const frozen = props.getPos() === freezeFrom;
|
|
44
40
|
// Protect content while frozen, and also through the single render where we
|
|
45
41
|
// leave the frozen state: `committedFrozenRef` still reflects the previous
|
|
46
42
|
// commit, so we keep returning the exact same cached element reference.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
export function createEditorStateStore() {
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
let state = null;
|
|
5
|
+
let pendingNotify = false;
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
return {
|
|
8
|
+
getState: ()=>state,
|
|
9
|
+
subscribe: (listener)=>{
|
|
10
|
+
listeners.add(listener);
|
|
11
|
+
return ()=>listeners.delete(listener);
|
|
12
|
+
},
|
|
13
|
+
setState: (newState)=>{
|
|
14
|
+
if (state !== newState) {
|
|
15
|
+
state = newState;
|
|
16
|
+
pendingNotify = true;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
notifyListeners: ()=>{
|
|
20
|
+
if (pendingNotify) {
|
|
21
|
+
pendingNotify = false;
|
|
22
|
+
listeners.forEach((l)=>l());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export const EditorStateStoreContext = createContext(null);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useContext, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
import { EditorStateStoreContext } from "../contexts/EditorStateStoreContext.js";
|
|
3
|
+
/**
|
|
4
|
+
* Select a piece of the EditorState, a la Redux’s
|
|
5
|
+
* `useSelector`.
|
|
6
|
+
*
|
|
7
|
+
* This hook will only trigger a re-render of the
|
|
8
|
+
* consuming component if the return value of the selector
|
|
9
|
+
* changes.
|
|
10
|
+
*/ export function useEditorStateSelector(selector) {
|
|
11
|
+
const store = useContext(EditorStateStoreContext);
|
|
12
|
+
const selectorRef = useRef(selector);
|
|
13
|
+
selectorRef.current = selector;
|
|
14
|
+
return useSyncExternalStore(store.subscribe, ()=>selectorRef.current(store.getState()));
|
|
15
|
+
}
|
package/dist/esm/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { useEditorEventCallback } from "./hooks/useEditorEventCallback.js";
|
|
|
8
8
|
export { useEditorEventListener } from "./hooks/useEditorEventListener.js";
|
|
9
9
|
export { useEditorState } from "./hooks/useEditorState.js";
|
|
10
10
|
export { useNodePos } from "./hooks/useNodePos.js";
|
|
11
|
+
export { useEditorStateSelector } from "./hooks/useEditorStateSelector.js";
|
|
11
12
|
export { useStopEvent } from "./hooks/useStopEvent.js";
|
|
12
13
|
export { useSelectNode } from "./hooks/useSelectNode.js";
|
|
13
14
|
export { useIgnoreMutation } from "./hooks/useIgnoreMutation.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Fragment, Slice } from "prosemirror-model";
|
|
2
2
|
import { Plugin, TextSelection } from "prosemirror-state";
|
|
3
3
|
import { ReactEditorView } from "../ReactEditorView.js";
|
|
4
|
+
import { browser } from "../browser.js";
|
|
4
5
|
import { CursorWrapper } from "../components/CursorWrapper.js";
|
|
5
6
|
import { widget } from "../decorations/ReactWidgetType.js";
|
|
6
7
|
import { CompositionViewDesc, TextViewDesc, findTextInFragment } from "../viewdesc.js";
|
|
@@ -83,6 +84,13 @@ export function beforeInputPlugin() {
|
|
|
83
84
|
view.input.compositionNodes = [];
|
|
84
85
|
view.input.compositionID++;
|
|
85
86
|
}
|
|
87
|
+
function teardownCompositionAndUnfreeze(view, event) {
|
|
88
|
+
teardownComposition(view, event.timeStamp);
|
|
89
|
+
view.dispatch(view.state.tr.setMeta(reactKeysPluginKey, {
|
|
90
|
+
cursorWrapper: null,
|
|
91
|
+
freezeFrom: null
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
86
94
|
return new Plugin({
|
|
87
95
|
view () {
|
|
88
96
|
return {
|
|
@@ -102,11 +110,17 @@ export function beforeInputPlugin() {
|
|
|
102
110
|
const storedMarks = view.state.selection.empty ? view.state.storedMarks : view.state.storedMarks ?? (view.state.selection instanceof TextSelection ? view.state.selection.$from.marksAcross(view.state.selection.$to) : null);
|
|
103
111
|
view.dispatch(view.state.tr.deleteSelection().setStoredMarks(storedMarks));
|
|
104
112
|
handleGapCursorComposition(view);
|
|
105
|
-
|
|
113
|
+
const { selection } = view.state;
|
|
114
|
+
const tr = view.state.tr.delete(selection.from, selection.to);
|
|
115
|
+
const $from = tr.doc.resolve(tr.mapping.map(selection.from));
|
|
116
|
+
const isEmptyTextblock = $from.parent.isTextblock && $from.parent.childCount === 0;
|
|
117
|
+
if (storedMarks != null || browser.safari && isEmptyTextblock) {
|
|
106
118
|
view.dispatch(view.state.tr.setMeta(reactKeysPluginKey, {
|
|
107
119
|
cursorWrapper: widget(view.state.selection.from, CursorWrapper, {
|
|
108
120
|
key: "cursor-wrapper",
|
|
109
|
-
|
|
121
|
+
...storedMarks !== null && {
|
|
122
|
+
marks: storedMarks
|
|
123
|
+
},
|
|
110
124
|
side: 0,
|
|
111
125
|
raw: true
|
|
112
126
|
})
|
|
@@ -157,13 +171,27 @@ export function beforeInputPlugin() {
|
|
|
157
171
|
compositionend (view, event) {
|
|
158
172
|
if (!(view instanceof ReactEditorView)) return false;
|
|
159
173
|
if (!view.composing) return false;
|
|
160
|
-
|
|
161
|
-
view.dispatch(view.state.tr.setMeta(reactKeysPluginKey, {
|
|
162
|
-
cursorWrapper: null,
|
|
163
|
-
freezeFrom: null
|
|
164
|
-
}));
|
|
174
|
+
teardownCompositionAndUnfreeze(view, event);
|
|
165
175
|
return true;
|
|
166
176
|
},
|
|
177
|
+
contextmenu (view, event) {
|
|
178
|
+
if (!(view instanceof ReactEditorView)) return false;
|
|
179
|
+
if (!view.composing) return false;
|
|
180
|
+
teardownCompositionAndUnfreeze(view, event);
|
|
181
|
+
return false;
|
|
182
|
+
},
|
|
183
|
+
mousedown (view, event) {
|
|
184
|
+
if (!(view instanceof ReactEditorView)) return false;
|
|
185
|
+
if (!view.composing) return false;
|
|
186
|
+
teardownCompositionAndUnfreeze(view, event);
|
|
187
|
+
return false;
|
|
188
|
+
},
|
|
189
|
+
touchstart (view, event) {
|
|
190
|
+
if (!(view instanceof ReactEditorView)) return false;
|
|
191
|
+
if (!view.composing) return false;
|
|
192
|
+
teardownCompositionAndUnfreeze(view, event);
|
|
193
|
+
return false;
|
|
194
|
+
},
|
|
167
195
|
beforeinput (view, event) {
|
|
168
196
|
if (event.inputType !== "insertFromComposition") {
|
|
169
197
|
event.preventDefault();
|
package/dist/esm/viewdesc.js
CHANGED
|
@@ -336,6 +336,7 @@ export class ViewDesc {
|
|
|
336
336
|
// case we just use whatever domFromPos produces as a best effort.
|
|
337
337
|
setSelection(anchor, head, view) {
|
|
338
338
|
let force = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : false;
|
|
339
|
+
if (view.composing) return;
|
|
339
340
|
// If the selection falls entirely in a child, give it to that child
|
|
340
341
|
const from = Math.min(anchor, head), to = Math.max(anchor, head);
|
|
341
342
|
for(let i = 0, offset = 0; i < this.children.length; i++){
|