@handlewithcare/react-prosemirror 3.1.0-tiptap.52 → 3.1.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.
Files changed (75) hide show
  1. package/README.md +3 -0
  2. package/dist/cjs/ReactEditorView.js +18 -1
  3. package/dist/cjs/components/ChildNodeViews.js +4 -4
  4. package/dist/cjs/components/CursorWrapper.js +9 -11
  5. package/dist/cjs/components/ProseMirror.js +13 -3
  6. package/dist/cjs/components/TextNodeView.js +54 -50
  7. package/dist/cjs/components/WidgetView.js +3 -1
  8. package/dist/cjs/components/nodes/NodeView.js +40 -4
  9. package/dist/cjs/contexts/CompositionContext.js +14 -0
  10. package/dist/cjs/decorations/viewDecorations.js +1 -6
  11. package/dist/cjs/hooks/useEditor.js +2 -10
  12. package/dist/cjs/hooks/useMarkViewDescription.js +1 -4
  13. package/dist/cjs/hooks/useNodeViewDescription.js +1 -22
  14. package/dist/cjs/plugins/beforeInputPlugin.js +162 -50
  15. package/dist/cjs/plugins/reactKeys.js +34 -15
  16. package/dist/cjs/tiptap/tiptapNodeView.js +10 -9
  17. package/dist/cjs/viewdesc.js +55 -4
  18. package/dist/esm/ReactEditorView.js +18 -1
  19. package/dist/esm/components/ChildNodeViews.js +5 -5
  20. package/dist/esm/components/CursorWrapper.js +9 -11
  21. package/dist/esm/components/ProseMirror.js +13 -3
  22. package/dist/esm/components/TextNodeView.js +56 -52
  23. package/dist/esm/components/WidgetView.js +3 -1
  24. package/dist/esm/components/nodes/NodeView.js +38 -5
  25. package/dist/esm/contexts/CompositionContext.js +4 -0
  26. package/dist/esm/decorations/viewDecorations.js +1 -6
  27. package/dist/esm/hooks/useEditor.js +2 -10
  28. package/dist/esm/hooks/useIsEditorStatic.js +4 -1
  29. package/dist/esm/hooks/useMarkViewDescription.js +1 -4
  30. package/dist/esm/hooks/useNodeViewDescription.js +2 -23
  31. package/dist/esm/plugins/beforeInputPlugin.js +162 -50
  32. package/dist/esm/plugins/reactKeys.js +34 -15
  33. package/dist/esm/tiptap/ReactProseMirrorNodeView.js +1 -1
  34. package/dist/esm/tiptap/TiptapEditorContent.js +8 -1
  35. package/dist/esm/tiptap/hooks/useIsInReactProseMirror.js +5 -1
  36. package/dist/esm/tiptap/tiptapNodeView.js +13 -14
  37. package/dist/esm/viewdesc.js +54 -4
  38. package/dist/tsconfig.tsbuildinfo +1 -1
  39. package/dist/types/ReactEditorView.d.ts +10 -1
  40. package/dist/types/components/ChildNodeViews.d.ts +2 -2
  41. package/dist/types/components/CursorWrapper.d.ts +1 -1
  42. package/dist/types/components/TextNodeView.d.ts +7 -4
  43. package/dist/types/components/__tests__/ProseMirror.composition.test.d.ts +17 -1
  44. package/dist/types/components/marks/DefaultMarkView.d.ts +1 -1
  45. package/dist/types/components/marks/MarkView.d.ts +1 -1
  46. package/dist/types/components/marks/MarkViewConstructorView.d.ts +1 -1
  47. package/dist/types/components/marks/ReactMarkView.d.ts +1 -1
  48. package/dist/types/components/nodes/DefaultNodeView.d.ts +1 -1
  49. package/dist/types/components/nodes/NodeView.d.ts +3 -1
  50. package/dist/types/components/nodes/NodeViewConstructorView.d.ts +1 -1
  51. package/dist/types/components/nodes/ReactNodeView.d.ts +1 -1
  52. package/dist/types/contexts/ChildDescriptionsContext.d.ts +3 -2
  53. package/dist/types/contexts/CompositionContext.d.ts +4 -0
  54. package/dist/types/decorations/viewDecorations.d.ts +2 -2
  55. package/dist/types/hooks/useEditor.d.ts +3 -4
  56. package/dist/types/hooks/useIsEditorStatic.d.ts +4 -0
  57. package/dist/types/hooks/useReactKeys.d.ts +2 -5
  58. package/dist/types/plugins/beforeInputPlugin.d.ts +1 -2
  59. package/dist/types/plugins/reactKeys.d.ts +10 -9
  60. package/dist/types/props.d.ts +225 -225
  61. package/dist/types/tiptap/ReactProseMirrorNodeView.d.ts +1 -1
  62. package/dist/types/tiptap/TiptapEditorContent.d.ts +10 -1
  63. package/dist/types/tiptap/hooks/useIsInReactProseMirror.d.ts +5 -0
  64. package/dist/types/tiptap/tiptapNodeView.d.ts +5 -6
  65. package/dist/types/viewdesc.d.ts +5 -3
  66. package/package.json +22 -6
  67. package/dist/cjs/plugins/componentEventListeners.js +0 -28
  68. package/dist/cjs/plugins/componentEventListenersPlugin.js +0 -35
  69. package/dist/cjs/tiptap/utils/ssrJSDOMPatch.js +0 -59
  70. package/dist/esm/plugins/componentEventListeners.js +0 -18
  71. package/dist/esm/plugins/componentEventListenersPlugin.js +0 -25
  72. package/dist/esm/tiptap/utils/ssrJSDOMPatch.js +0 -56
  73. package/dist/types/plugins/componentEventListeners.d.ts +0 -3
  74. package/dist/types/plugins/componentEventListenersPlugin.d.ts +0 -4
  75. package/dist/types/tiptap/utils/ssrJSDOMPatch.d.ts +0 -1
@@ -10,8 +10,11 @@ Object.defineProperty(exports, "beforeInputPlugin", {
10
10
  });
11
11
  const _prosemirrormodel = require("prosemirror-model");
12
12
  const _prosemirrorstate = require("prosemirror-state");
13
+ const _ReactEditorView = require("../ReactEditorView.js");
13
14
  const _CursorWrapper = require("../components/CursorWrapper.js");
14
15
  const _ReactWidgetType = require("../decorations/ReactWidgetType.js");
16
+ const _viewdesc = require("../viewdesc.js");
17
+ const _reactKeys = require("./reactKeys.js");
15
18
  function insertText(view, eventData) {
16
19
  let options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
17
20
  if (eventData === null) return false;
@@ -54,74 +57,127 @@ function handleGapCursorComposition(view) {
54
57
  tr.setSelection(_prosemirrorstate.TextSelection.near(tr.doc.resolve($from.pos + 1)));
55
58
  view.dispatch(tr);
56
59
  }
57
- function beforeInputPlugin(setCursorWrapper) {
58
- let compositionMarks = null;
59
- let precompositionSnapshot = null;
60
+ const observeOptions = {
61
+ childList: true,
62
+ characterData: true,
63
+ characterDataOldValue: true,
64
+ attributes: true,
65
+ attributeOldValue: true,
66
+ subtree: true
67
+ };
68
+ function beforeInputPlugin() {
69
+ let observer = null;
70
+ let preCompositionSnapshot = null;
71
+ function teardownComposition(view, endedAt) {
72
+ view.input.composing = false;
73
+ if (observer) {
74
+ if (view.input.compositionNode && view.dom.contains(view.input.compositionNode)) {
75
+ view.domObserver.queue.push(...observer.takeRecords());
76
+ view.domObserver.flush();
77
+ } else {
78
+ const freezeFrom = _reactKeys.reactKeysPluginKey.getState(view.state)?.freezeFrom;
79
+ const frozenNode = freezeFrom == null ? null : view.state.doc.nodeAt(freezeFrom);
80
+ if (freezeFrom != null && frozenNode != null && preCompositionSnapshot) {
81
+ // This is a little hacky — it only works because we always abort
82
+ // compositions if the node after freezeFrom changes, so we can
83
+ // be sure that if a composition was canceled by the user/browser,
84
+ // the content hasn't changed since the composition started
85
+ view.dispatch(view.state.tr.replaceWith(freezeFrom + 1, freezeFrom + 1 + frozenNode.content.size, preCompositionSnapshot));
86
+ }
87
+ }
88
+ observer.disconnect();
89
+ observer = null;
90
+ }
91
+ view.input.compositionEndedAt = endedAt;
92
+ view.input.compositionNode = null;
93
+ view.input.compositionNodes = [];
94
+ view.input.compositionID++;
95
+ }
60
96
  return new _prosemirrorstate.Plugin({
97
+ view () {
98
+ return {
99
+ update (view) {
100
+ if (!(view instanceof _ReactEditorView.ReactEditorView)) return;
101
+ const frozen = _reactKeys.reactKeysPluginKey.getState(view.state)?.freezeFrom != null;
102
+ if (observer && view.composing && !frozen) {
103
+ teardownComposition(view, Date.now());
104
+ }
105
+ }
106
+ };
107
+ },
61
108
  props: {
62
109
  handleDOMEvents: {
63
110
  compositionstart (view) {
64
- compositionMarks = view.state.storedMarks ?? view.state.selection.$from.marks();
65
- view.dispatch(view.state.tr.deleteSelection());
111
+ if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
112
+ 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
+ view.dispatch(view.state.tr.deleteSelection().setStoredMarks(storedMarks));
66
114
  handleGapCursorComposition(view);
67
- const { state } = view;
68
- const $pos = state.selection.$from;
69
- if (compositionMarks) {
70
- setCursorWrapper((0, _ReactWidgetType.widget)(state.selection.from, _CursorWrapper.CursorWrapper, {
71
- key: "cursor-wrapper",
72
- marks: compositionMarks
115
+ if (storedMarks) {
116
+ view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
117
+ cursorWrapper: (0, _ReactWidgetType.widget)(view.state.selection.from, _CursorWrapper.CursorWrapper, {
118
+ key: "cursor-wrapper",
119
+ marks: storedMarks,
120
+ side: 0,
121
+ raw: true
122
+ })
73
123
  }));
124
+ // Pin the DOM cursor to PM's canonical position before the IME
125
+ // captures wherever the browser happened to leave it. Without this,
126
+ // a cursor at a mark boundary lands in either the left or right text
127
+ // node depending on the user's last navigation direction, and the
128
+ // IME composes into whichever one it found.
129
+ } else if (view.state.selection.empty) {
130
+ view.domObserver.disconnectSelection();
131
+ try {
132
+ view.docView.setSelection(view.state.selection.anchor, view.state.selection.head, view, true // force — skip the isEquivalentPosition early-return
133
+ );
134
+ } finally{
135
+ view.domObserver.setCurSelection();
136
+ view.domObserver.connectSelection();
137
+ }
74
138
  }
75
- // Snapshot the siblings of the node that contains the
76
- // current cursor. We'll restore this later, so that React
77
- // doesn't panic about unknown DOM nodes.
78
- const { node: parent } = view.domAtPos($pos.pos);
79
- precompositionSnapshot = [];
80
- for (const node of parent.childNodes){
81
- precompositionSnapshot.push(node);
139
+ const freezeFrom = view.state.selection.$from.before();
140
+ view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
141
+ freezeFrom
142
+ }));
143
+ const frozenDom = view.nodeDOM(freezeFrom);
144
+ if (!frozenDom) {
145
+ view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
146
+ cursorWrapper: null,
147
+ freezeFrom: null
148
+ }));
149
+ return false;
82
150
  }
83
- // @ts-expect-error Internal property - input
151
+ preCompositionSnapshot = view.state.doc.nodeAt(freezeFrom)?.content ?? null;
84
152
  view.input.composing = true;
153
+ observer = new MutationObserver((records)=>{
154
+ if (_reactKeys.reactKeysPluginKey.getState(view.state)?.freezeFrom == null) {
155
+ return;
156
+ }
157
+ view.domObserver.queue.push(...records);
158
+ view.domObserver.flush();
159
+ syncCompositionViewDescs(view);
160
+ });
161
+ observer.observe(frozenDom, observeOptions);
85
162
  return true;
86
163
  },
87
164
  compositionupdate () {
88
165
  return true;
89
166
  },
90
167
  compositionend (view, event) {
91
- // @ts-expect-error Internal property - input
92
- view.input.composing = false;
93
- const { state } = view;
94
- const { node: parent } = view.domAtPos(state.selection.from);
95
- if (precompositionSnapshot) {
96
- // Restore the snapshot of the parent node's children
97
- // from before the composition started. This gives us a
98
- // clean slate from which to dispatch our transaction
99
- // and trigger a React update.
100
- precompositionSnapshot.forEach((prevNode, i)=>{
101
- if (parent.childNodes.length <= i) {
102
- parent.appendChild(prevNode);
103
- return;
104
- }
105
- parent.replaceChild(prevNode, parent.childNodes.item(i));
106
- });
107
- if (parent.childNodes.length > precompositionSnapshot.length) {
108
- for(let i = precompositionSnapshot.length; i < parent.childNodes.length; i++){
109
- parent.removeChild(parent.childNodes.item(i));
110
- }
111
- }
112
- }
113
- if (event.data) {
114
- insertText(view, event.data, {
115
- marks: compositionMarks
116
- });
117
- }
118
- compositionMarks = null;
119
- precompositionSnapshot = null;
120
- setCursorWrapper(null);
168
+ if (!(view instanceof _ReactEditorView.ReactEditorView)) return false;
169
+ if (!view.composing) return false;
170
+ teardownComposition(view, event.timeStamp);
171
+ view.dispatch(view.state.tr.setMeta(_reactKeys.reactKeysPluginKey, {
172
+ cursorWrapper: null,
173
+ freezeFrom: null
174
+ }));
121
175
  return true;
122
176
  },
123
177
  beforeinput (view, event) {
124
- event.preventDefault();
178
+ if (event.inputType !== "insertFromComposition") {
179
+ event.preventDefault();
180
+ }
125
181
  switch(event.inputType){
126
182
  case "insertParagraph":
127
183
  case "insertLineBreak":
@@ -199,3 +255,59 @@ function beforeInputPlugin(setCursorWrapper) {
199
255
  }
200
256
  });
201
257
  }
258
+ function syncCompositionViewDescs(view) {
259
+ const compositionNode = view.domObserver.lastChangedTextNode;
260
+ if (!compositionNode) return;
261
+ const freezeFrom = _reactKeys.reactKeysPluginKey.getState(view.state)?.freezeFrom;
262
+ if (freezeFrom == null) return;
263
+ const compositionBlock = view.state.doc.nodeAt(freezeFrom);
264
+ if (!compositionBlock) return;
265
+ const compositionBlockDesc = view.docView.descAt(freezeFrom);
266
+ if (!compositionBlockDesc) return;
267
+ const desc = view.docView.nearestDesc(compositionNode);
268
+ compositionBlockDesc.node = compositionBlock;
269
+ if (desc instanceof _viewdesc.TextViewDesc) {
270
+ if (compositionNode.nodeValue && desc.node.text !== compositionNode.nodeValue) {
271
+ desc.node = view.state.schema.text(compositionNode.nodeValue, desc.node.marks);
272
+ desc.nodeDOM = compositionNode;
273
+ compositionNode.pmViewDesc = desc;
274
+ }
275
+ return;
276
+ }
277
+ if (desc instanceof _viewdesc.CompositionViewDesc) {
278
+ if (compositionNode.nodeValue != null && desc.text !== compositionNode.nodeValue) {
279
+ desc.dom = compositionNode;
280
+ desc.textDOM = compositionNode;
281
+ desc.text = compositionNode.nodeValue;
282
+ compositionNode.pmViewDesc = desc;
283
+ }
284
+ return;
285
+ }
286
+ const parentDesc = desc?.contentDOM ? desc : compositionBlockDesc;
287
+ const children = parentDesc.children;
288
+ // Drop any text or composition desc in this container whose DOM the
289
+ // IME has detached. This covers two cases: a TextViewDesc the IME subsumed
290
+ // into the composition node, and (on Safari, which replaces the whole text
291
+ // node on each composition update) any orphaned composition view
292
+ // desc(s) left over from the previous composition steps.
293
+ for(let i = children.length - 1; i >= 0; i--){
294
+ const c = children[i];
295
+ if (!(c instanceof _viewdesc.TextViewDesc) && !(c instanceof _viewdesc.CompositionViewDesc)) {
296
+ continue;
297
+ }
298
+ const dom = c.dom;
299
+ if (view.dom.contains(dom)) continue;
300
+ children.splice(i, 1);
301
+ }
302
+ const contentStart = freezeFrom + 1;
303
+ const { from, to } = view.state.selection;
304
+ const textPos = (0, _viewdesc.findTextInFragment)(compositionBlock.content, compositionNode.nodeValue ?? "", from - contentStart, to - contentStart);
305
+ if (textPos < 0) return;
306
+ const startPos = contentStart + textPos;
307
+ let topDOM = compositionNode;
308
+ while(topDOM.parentNode && topDOM.parentNode !== parentDesc.contentDOM){
309
+ topDOM = topDOM.parentNode;
310
+ }
311
+ const insertIndex = children.findLastIndex((c)=>c.posBefore <= startPos) + 1;
312
+ children.splice(insertIndex, 0, new _viewdesc.CompositionViewDesc(parentDesc, ()=>startPos, topDOM, compositionNode, compositionNode.nodeValue ?? ""));
313
+ }
@@ -20,20 +20,23 @@ _export(exports, {
20
20
  }
21
21
  });
22
22
  const _prosemirrorstate = require("prosemirror-state");
23
+ const _prosemirrorview = require("prosemirror-view");
24
+ const _ReactWidgetType = require("../decorations/ReactWidgetType.js");
23
25
  function createNodeKey() {
24
26
  const key = Math.floor(Math.random() * 0xffffffffffff).toString(16);
25
27
  return key;
26
28
  }
27
29
  const reactKeysPluginKey = new _prosemirrorstate.PluginKey("@handlewithcare/react-prosemirror/reactKeys");
28
30
  function reactKeys() {
29
- let composing = false;
30
31
  return new _prosemirrorstate.Plugin({
31
32
  key: reactKeysPluginKey,
32
33
  state: {
33
34
  init (_, state) {
34
35
  const next = {
35
36
  posToKey: new Map(),
36
- keyToPos: new Map()
37
+ keyToPos: new Map(),
38
+ cursorWrapper: null,
39
+ freezeFrom: null
37
40
  };
38
41
  state.doc.descendants((_, pos)=>{
39
42
  const key = createNodeKey();
@@ -50,15 +53,32 @@ function reactKeys() {
50
53
  * through the transaction to identify its current position,
51
54
  * and assign its key to that new position, dropping it if the
52
55
  * node was deleted.
53
- */ apply (tr, value, _, newState) {
54
- if (!tr.docChanged || composing) {
55
- return value;
56
- }
57
- const overrides = tr.getMeta(reactKeysPluginKey)?.overrides;
56
+ */ apply (tr, value, oldState, newState) {
57
+ const meta = tr.getMeta(reactKeysPluginKey);
58
+ const overrides = meta && "overrides" in meta ? meta.overrides : {};
59
+ const cursorWrapper = meta && "cursorWrapper" in meta ? meta.cursorWrapper : undefined;
60
+ const freezeFrom = meta && "freezeFrom" in meta ? meta.freezeFrom : undefined;
58
61
  const next = {
59
62
  posToKey: new Map(),
60
- keyToPos: new Map()
63
+ keyToPos: new Map(),
64
+ cursorWrapper: cursorWrapper === undefined ? value.cursorWrapper ? (0, _ReactWidgetType.widget)(tr.mapping.map(value.cursorWrapper.from, -1), value.cursorWrapper.type.Component, value.cursorWrapper.spec) : null : cursorWrapper,
65
+ freezeFrom: freezeFrom === undefined ? value.freezeFrom !== null ? tr.mapping.map(value.freezeFrom, -1) : null : freezeFrom
61
66
  };
67
+ if (value.freezeFrom !== null && next.freezeFrom !== null && tr.getMeta("composition") == null) {
68
+ const oldBlock = oldState.doc.nodeAt(value.freezeFrom);
69
+ const newBlock = newState.doc.nodeAt(next.freezeFrom);
70
+ if (newBlock && !oldBlock?.eq(newBlock)) {
71
+ next.freezeFrom = null;
72
+ next.cursorWrapper = null;
73
+ }
74
+ }
75
+ if (!tr.docChanged) {
76
+ return {
77
+ ...value,
78
+ cursorWrapper: next.cursorWrapper,
79
+ freezeFrom: next.freezeFrom
80
+ };
81
+ }
62
82
  const posToKeyEntries = Array.from(value.posToKey.entries()).sort((param, param1)=>{
63
83
  let [a] = param, [b] = param1;
64
84
  return a - b;
@@ -84,13 +104,12 @@ function reactKeys() {
84
104
  }
85
105
  },
86
106
  props: {
87
- handleDOMEvents: {
88
- compositionstart: ()=>{
89
- composing = true;
90
- },
91
- compositionend: ()=>{
92
- composing = false;
93
- }
107
+ decorations (state) {
108
+ const deco = reactKeysPluginKey.getState(state)?.cursorWrapper;
109
+ if (!deco) return _prosemirrorview.DecorationSet.empty;
110
+ return _prosemirrorview.DecorationSet.create(state.doc, [
111
+ deco
112
+ ]);
94
113
  }
95
114
  }
96
115
  });
@@ -116,15 +116,6 @@ function tiptapNodeView(param) {
116
116
  return result;
117
117
  });
118
118
  (0, _useIgnoreMutation.useIgnoreMutation)(function(_, mutation) {
119
- if (ignoreMutation) {
120
- return ignoreMutation.call({
121
- name: extension.name,
122
- editor,
123
- type: node.type
124
- }, {
125
- mutation
126
- });
127
- }
128
119
  if (!editor || !(this.dom instanceof HTMLElement)) return false;
129
120
  const nodeView = new _ReactProseMirrorNodeView.ReactProseMirrorNodeView(WrappedComponent, {
130
121
  extension,
@@ -136,6 +127,16 @@ function tiptapNodeView(param) {
136
127
  node,
137
128
  view: editor.view
138
129
  }, this.dom, this.contentDOM);
130
+ if (ignoreMutation) {
131
+ return ignoreMutation.call({
132
+ name: extension.name,
133
+ editor,
134
+ type: node.type
135
+ }, {
136
+ mutation,
137
+ defaultIgnoreMutation: nodeView.ignoreMutation.bind(nodeView)
138
+ });
139
+ }
139
140
  return nodeView.ignoreMutation(mutation) ?? false;
140
141
  });
141
142
  const { extraClassName, htmlProps } = (0, _react1.useMemo)(()=>{
@@ -36,6 +36,9 @@ _export(exports, {
36
36
  WidgetViewDesc: function() {
37
37
  return WidgetViewDesc;
38
38
  },
39
+ findTextInFragment: function() {
40
+ return findTextInFragment;
41
+ },
39
42
  sameOuterDeco: function() {
40
43
  return sameOuterDeco;
41
44
  },
@@ -49,7 +52,17 @@ const _dom = require("./dom.js");
49
52
  function sortViewDescs(a, b) {
50
53
  if (a instanceof TrailingHackViewDesc) return 1;
51
54
  if (b instanceof TrailingHackViewDesc) return -1;
52
- return a.getPos() - b.getPos();
55
+ const posDiff = a.getPos() - b.getPos();
56
+ if (posDiff !== 0) return posDiff;
57
+ // When two descs share the same PM position (e.g. a zero-width widget
58
+ // and a text node that starts at the same position), fall back to DOM
59
+ // order so that the viewdesc children match the actual DOM layout.
60
+ // Without this, position computations like `posBeforeChild` can return
61
+ // the wrong PM position for the widget's container.
62
+ const cmp = a.dom.compareDocumentPosition(b.dom);
63
+ if (cmp & 4 /* DOCUMENT_POSITION_FOLLOWING */ ) return -1;
64
+ if (cmp & 2 /* DOCUMENT_POSITION_PRECEDING */ ) return 1;
65
+ return 0;
53
66
  }
54
67
  const NOT_DIRTY = 0, CHILD_DIRTY = 1, CONTENT_DIRTY = 2, NODE_DIRTY = 3;
55
68
  let ViewDesc = class ViewDesc {
@@ -266,7 +279,9 @@ let ViewDesc = class ViewDesc {
266
279
  prev = i ? this.children[i - 1] : null;
267
280
  if (!prev || prev.dom.parentNode == this.contentDOM) break;
268
281
  }
269
- if (prev && side && enter && !prev.border && !prev.domAtom) return prev.domFromPos(prev.size, side);
282
+ if (prev && side && enter && !prev.border && !prev.domAtom) {
283
+ return prev.domFromPos(prev.size, side);
284
+ }
270
285
  return {
271
286
  node: this.contentDOM,
272
287
  offset: prev ? (0, _dom.domIndex)(prev.dom) + 1 : 0
@@ -401,7 +416,9 @@ let ViewDesc = class ViewDesc {
401
416
  const after = selRange.focusNode.childNodes[selRange.focusOffset];
402
417
  if (after && after.contentEditable == "false") force = true;
403
418
  }
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;
419
+ if (view.cursorWrapped || !(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)) {
420
+ return;
421
+ }
405
422
  // Selection.extend can be used to create an 'inverted' selection
406
423
  // (one where the focus is before the anchor), but not all
407
424
  // browsers support it yet.
@@ -666,7 +683,10 @@ let TextViewDesc = class TextViewDesc extends NodeViewDesc {
666
683
  skip: skip || true
667
684
  };
668
685
  }
669
- update(_node, _outerDeco, _innerDeco, _view) {
686
+ update(node, outerDeco, _innerDeco, _view) {
687
+ if (this.dirty == NODE_DIRTY || this.dirty != NOT_DIRTY && !this.inParent() || !node.sameMarkup(this.node)) return false;
688
+ this.updateOuterDeco(outerDeco);
689
+ this.node = node;
670
690
  this.dirty = NOT_DIRTY;
671
691
  return true;
672
692
  }
@@ -695,6 +715,9 @@ let TextViewDesc = class TextViewDesc extends NodeViewDesc {
695
715
  isText(text) {
696
716
  return this.node.text == text;
697
717
  }
718
+ ignoreMutation(mutation) {
719
+ return mutation.type !== "characterData" && mutation.type !== "selection";
720
+ }
698
721
  };
699
722
  let TrailingHackViewDesc = class TrailingHackViewDesc extends ViewDesc {
700
723
  parseRule() {
@@ -782,3 +805,31 @@ function sameOuterDeco(a, b) {
782
805
  for(let i = 0; i < a.length; i++)if (!a[i].type.eq(b[i].type)) return false;
783
806
  return true;
784
807
  }
808
+ function findTextInFragment(frag, text, from, to) {
809
+ for(let i = 0, pos = 0; i < frag.childCount && pos <= to;){
810
+ const child = frag.child(i++);
811
+ const childStart = pos;
812
+ pos += child.nodeSize;
813
+ if (!child.isText) continue;
814
+ let str = child.text;
815
+ while(i < frag.childCount){
816
+ const next = frag.child(i++);
817
+ pos += next.nodeSize;
818
+ if (!next.isText) break;
819
+ str += next.text;
820
+ }
821
+ if (pos >= from) {
822
+ if (pos >= to && str.slice(to - text.length - childStart, to - childStart) === text) {
823
+ return to - text.length;
824
+ }
825
+ const found = childStart < to ? str.lastIndexOf(text, to - childStart - 1) : -1;
826
+ if (found >= 0 && found + text.length + childStart >= from) {
827
+ return childStart + found;
828
+ }
829
+ if (from === to && str.length >= to + text.length - childStart && str.slice(to - childStart, to - childStart + text.length) === text) {
830
+ return to;
831
+ }
832
+ }
833
+ }
834
+ return -1;
835
+ }
@@ -33,6 +33,7 @@ function changedNodeViews(a, b) {
33
33
  nextProps;
34
34
  prevState;
35
35
  _destroyed;
36
+ cursorWrapped;
36
37
  constructor(place, props){
37
38
  // Prevent the base class from destroying the React-managed nodes.
38
39
  // Restore them below after invoking the base class constructor.
@@ -85,6 +86,7 @@ function changedNodeViews(a, b) {
85
86
  // @ts-expect-error this violates the typing but class does it, too.
86
87
  this.docView = null;
87
88
  this._destroyed = false;
89
+ this.cursorWrapped = false;
88
90
  }
89
91
  get props() {
90
92
  return this.nextProps;
@@ -185,7 +187,22 @@ function changedNodeViews(a, b) {
185
187
  // this ensures that the base class validates the DOM selection and invokes
186
188
  // node view selection callbacks.
187
189
  this.docView.markDirty(-1, -1);
188
- super.update(this.nextProps);
190
+ const selectionChanged = !this.state.selection.eq(this.prevState.selection);
191
+ if (selectionChanged) {
192
+ super.update(this.nextProps);
193
+ } else {
194
+ // If the selection hasn't changed between renders, force prosemirror-view to
195
+ // skip the selectionToDOM call. If a render happens after a DOM selection change
196
+ // but before the "selectionchange" event fired, calling selectionToDOM will cause
197
+ // the selection to be reset its the previous position.
198
+ this.domObserver.setCurSelection();
199
+ this.input.mouseDown = {
200
+ allowDefault: false,
201
+ delayedSelectionSync: false
202
+ };
203
+ super.update(this.nextProps);
204
+ this.input.mouseDown = null;
205
+ }
189
206
  // Store the new previous state.
190
207
  this.prevState = this.state;
191
208
  }
@@ -11,7 +11,7 @@ import { TextNodeView } from "./TextNodeView.js";
11
11
  import { TrailingHackView } from "./TrailingHackView.js";
12
12
  import { WidgetView } from "./WidgetView.js";
13
13
  import { MarkView } from "./marks/MarkView.js";
14
- import { NodeView } from "./nodes/NodeView.js";
14
+ import { RemountableNodeView } from "./nodes/NodeView.js";
15
15
  export function wrapInDeco(reactNode, deco) {
16
16
  const { nodeName, ...attrs } = deco.type.attrs;
17
17
  const props = htmlAttrsToReactProps(attrs);
@@ -59,7 +59,7 @@ const ChildView = /*#__PURE__*/ memo(function ChildView(param) {
59
59
  parentRef: parentRef,
60
60
  decorations: child.outerDeco
61
61
  });
62
- }) : /*#__PURE__*/ React.createElement(NodeView, {
62
+ }) : /*#__PURE__*/ React.createElement(RemountableNodeView, {
63
63
  key: child.key,
64
64
  node: child.node,
65
65
  getPos: getPos,
@@ -207,7 +207,7 @@ const ChildElement = /*#__PURE__*/ memo(function ChildElement(param) {
207
207
  mark: mark,
208
208
  getPos: getPos,
209
209
  inline: false
210
- }, element), /*#__PURE__*/ React.createElement(NodeView, {
210
+ }, element), /*#__PURE__*/ React.createElement(RemountableNodeView, {
211
211
  key: child.key,
212
212
  outerDeco: child.outerDeco,
213
213
  node: child.node,
@@ -340,14 +340,14 @@ export const ChildNodeViews = /*#__PURE__*/ memo(function ChildNodeViews(param)
340
340
  component: SeparatorHackView,
341
341
  marks: [],
342
342
  offset: lastChild?.offset ?? 0,
343
- index: (lastChild?.index ?? 0) + 2,
343
+ index: (lastChild?.index ?? 0) + 1,
344
344
  key: "trailing-hack-img"
345
345
  }, {
346
346
  type: "hack",
347
347
  component: TrailingHackView,
348
348
  marks: [],
349
349
  offset: lastChild?.offset ?? 0,
350
- index: (lastChild?.index ?? 0) + 1,
350
+ index: (lastChild?.index ?? 0) + 2,
351
351
  key: "trailing-hack-br"
352
352
  });
353
353
  }
@@ -1,4 +1,5 @@
1
1
  import React, { forwardRef, useImperativeHandle, useRef } from "react";
2
+ import { ReactEditorView } from "../ReactEditorView.js";
2
3
  import { domIndex } from "../dom.js";
3
4
  import { useEditorEffect } from "../hooks/useEditorEffect.js";
4
5
  export const CursorWrapper = /*#__PURE__*/ forwardRef(function CursorWrapper(param, ref) {
@@ -8,21 +9,18 @@ export const CursorWrapper = /*#__PURE__*/ forwardRef(function CursorWrapper(par
8
9
  return innerRef.current;
9
10
  }, []);
10
11
  useEditorEffect((view)=>{
11
- if (!view || !innerRef.current) return;
12
- // @ts-expect-error Internal property - domObserver
12
+ if (!(view instanceof ReactEditorView) || !innerRef.current) return;
13
13
  view.domObserver.disconnectSelection();
14
- // @ts-expect-error Internal property - domSelection
15
14
  const domSel = view.domSelection();
15
+ if (!domSel.isCollapsed) return;
16
16
  const node = innerRef.current;
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
- }
24
- // @ts-expect-error Internal property - domObserver
17
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
18
+ domSel.collapse(node.parentNode, domIndex(node) + 1);
19
+ view.cursorWrapped = true;
25
20
  view.domObserver.connectSelection();
21
+ return ()=>{
22
+ view.cursorWrapped = false;
23
+ };
26
24
  }, []);
27
25
  return /*#__PURE__*/ React.createElement("img", {
28
26
  ref: innerRef,
@@ -1,11 +1,13 @@
1
1
  import React, { useMemo, useState } from "react";
2
2
  import { ChildDescriptionsContext } from "../contexts/ChildDescriptionsContext.js";
3
+ import { CompositionContext } from "../contexts/CompositionContext.js";
3
4
  import { EditorContext } from "../contexts/EditorContext.js";
4
5
  import { EditorStateContext } from "../contexts/EditorStateContext.js";
5
6
  import { NodeViewContext } from "../contexts/NodeViewContext.js";
6
7
  import { computeDocDeco } from "../decorations/computeDocDeco.js";
7
8
  import { viewDecorations } from "../decorations/viewDecorations.js";
8
9
  import { useEditor } from "../hooks/useEditor.js";
10
+ import { reactKeysPluginKey } from "../plugins/reactKeys.js";
9
11
  import { LayoutGroup } from "./LayoutGroup.js";
10
12
  import { DocNodeViewContext } from "./ProseMirrorDoc.js";
11
13
  function getPos() {
@@ -22,7 +24,7 @@ const rootChildDescriptionsContextValue = {
22
24
  function ProseMirrorInner(param) {
23
25
  let { children, nodeViewComponents, markViewComponents, ...props } = param;
24
26
  const [mount, setMount] = useState(null);
25
- const { editor, cursorWrapper, state } = useEditor(mount, props);
27
+ const { editor, state } = useEditor(mount, props);
26
28
  const nodeViewConstructors = editor.view.nodeViews;
27
29
  const nodeViewContextValue = useMemo(()=>{
28
30
  return {
@@ -39,7 +41,7 @@ function ProseMirrorInner(param) {
39
41
  ]);
40
42
  const node = state.doc;
41
43
  const decorations = computeDocDeco(editor.view);
42
- const innerDecorations = viewDecorations(editor.view, cursorWrapper);
44
+ const innerDecorations = viewDecorations(editor.view);
43
45
  const docNodeViewContextValue = useMemo(()=>({
44
46
  setMount,
45
47
  node,
@@ -51,6 +53,12 @@ function ProseMirrorInner(param) {
51
53
  decorations,
52
54
  innerDecorations
53
55
  ]);
56
+ const freezeFrom = reactKeysPluginKey.getState(state)?.freezeFrom ?? null;
57
+ const compositionContextValue = useMemo(()=>({
58
+ freezeFrom
59
+ }), [
60
+ freezeFrom
61
+ ]);
54
62
  return /*#__PURE__*/ React.createElement(EditorContext.Provider, {
55
63
  value: editor
56
64
  }, /*#__PURE__*/ React.createElement(EditorStateContext.Provider, {
@@ -59,9 +67,11 @@ function ProseMirrorInner(param) {
59
67
  value: nodeViewContextValue
60
68
  }, /*#__PURE__*/ React.createElement(ChildDescriptionsContext.Provider, {
61
69
  value: rootChildDescriptionsContextValue
70
+ }, /*#__PURE__*/ React.createElement(CompositionContext.Provider, {
71
+ value: compositionContextValue
62
72
  }, /*#__PURE__*/ React.createElement(DocNodeViewContext.Provider, {
63
73
  value: docNodeViewContextValue
64
- }, children)))));
74
+ }, children))))));
65
75
  }
66
76
  export function ProseMirror(props) {
67
77
  return /*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(ProseMirrorInner, props));