@handlewithcare/react-prosemirror 2.4.12 → 2.5.1

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 (149) hide show
  1. package/dist/cjs/AbstractEditorView.js +4 -0
  2. package/dist/cjs/ReactEditorView.js +156 -0
  3. package/dist/cjs/StaticEditorView.js +86 -0
  4. package/dist/cjs/components/ChildNodeViews.js +58 -29
  5. package/dist/cjs/components/CustomNodeView.js +77 -137
  6. package/dist/cjs/{hooks/useNodePos.js → components/DefaultNodeView.js} +24 -26
  7. package/dist/cjs/components/DocNodeView.js +33 -41
  8. package/dist/cjs/components/MarkView.js +1 -2
  9. package/dist/cjs/components/NativeWidgetView.js +2 -3
  10. package/dist/cjs/components/NodeView.js +31 -21
  11. package/dist/cjs/components/ProseMirror.js +25 -17
  12. package/dist/cjs/components/ProseMirrorDoc.js +7 -27
  13. package/dist/cjs/components/ReactNodeView.js +98 -61
  14. package/dist/cjs/components/SeparatorHackView.js +1 -2
  15. package/dist/cjs/components/TextNodeView.js +4 -5
  16. package/dist/cjs/components/TrailingHackView.js +1 -2
  17. package/dist/cjs/components/WidgetView.js +2 -4
  18. package/dist/cjs/constants.js +33 -0
  19. package/dist/cjs/hooks/useEditor.js +33 -229
  20. package/dist/cjs/hooks/useEditorEffect.js +2 -2
  21. package/dist/cjs/hooks/useEditorEventCallback.js +8 -5
  22. package/dist/cjs/hooks/useIgnoreMutation.js +1 -1
  23. package/dist/cjs/hooks/useNodeViewDescriptor.js +123 -80
  24. package/dist/cjs/hooks/useReactKeys.js +1 -1
  25. package/dist/cjs/hooks/useSelectNode.js +9 -7
  26. package/dist/cjs/hooks/useStopEvent.js +1 -1
  27. package/dist/cjs/plugins/beforeInputPlugin.js +12 -0
  28. package/dist/cjs/testing/editorViewTestHelpers.js +0 -2
  29. package/dist/cjs/viewdesc.js +104 -25
  30. package/dist/esm/AbstractEditorView.js +1 -0
  31. package/dist/esm/ReactEditorView.js +156 -0
  32. package/dist/esm/StaticEditorView.js +76 -0
  33. package/dist/esm/components/ChildNodeViews.js +59 -31
  34. package/dist/esm/components/CustomNodeView.js +78 -138
  35. package/dist/esm/components/DefaultNodeView.js +16 -0
  36. package/dist/esm/components/DocNodeView.js +33 -41
  37. package/dist/esm/components/MarkView.js +1 -2
  38. package/dist/esm/components/NativeWidgetView.js +2 -3
  39. package/dist/esm/components/NodeView.js +32 -22
  40. package/dist/esm/components/ProseMirror.js +25 -17
  41. package/dist/esm/components/ProseMirrorDoc.js +7 -28
  42. package/dist/esm/components/ReactNodeView.js +99 -62
  43. package/dist/esm/components/SeparatorHackView.js +1 -2
  44. package/dist/esm/components/TextNodeView.js +4 -5
  45. package/dist/esm/components/TrailingHackView.js +1 -2
  46. package/dist/esm/components/WidgetView.js +2 -4
  47. package/dist/esm/constants.js +15 -0
  48. package/dist/esm/hooks/useEditor.js +29 -218
  49. package/dist/esm/hooks/useEditorEffect.js +2 -2
  50. package/dist/esm/hooks/useEditorEventCallback.js +8 -5
  51. package/dist/esm/hooks/useIgnoreMutation.js +1 -1
  52. package/dist/esm/hooks/useNodeViewDescriptor.js +125 -82
  53. package/dist/esm/hooks/useReactKeys.js +1 -1
  54. package/dist/esm/hooks/useSelectNode.js +9 -7
  55. package/dist/esm/hooks/useStopEvent.js +1 -1
  56. package/dist/esm/plugins/beforeInputPlugin.js +12 -0
  57. package/dist/esm/testing/editorViewTestHelpers.js +0 -2
  58. package/dist/esm/viewdesc.js +94 -18
  59. package/dist/tsconfig.tsbuildinfo +1 -1
  60. package/dist/types/AbstractEditorView.d.ts +27 -0
  61. package/dist/types/ReactEditorView.d.ts +80 -0
  62. package/dist/types/StaticEditorView.d.ts +24 -0
  63. package/dist/types/components/ChildNodeViews.d.ts +2 -2
  64. package/dist/types/components/CustomNodeView.d.ts +3 -3
  65. package/dist/types/components/DefaultNodeView.d.ts +3 -0
  66. package/dist/types/components/DocNodeView.d.ts +9 -17
  67. package/dist/types/components/MarkView.d.ts +2 -2
  68. package/dist/types/components/NativeWidgetView.d.ts +2 -2
  69. package/dist/types/components/NodeView.d.ts +5 -5
  70. package/dist/types/components/NodeViewComponentProps.d.ts +3 -4
  71. package/dist/types/components/ProseMirrorDoc.d.ts +14 -8
  72. package/dist/types/components/ReactNodeView.d.ts +4 -2
  73. package/dist/types/components/SeparatorHackView.d.ts +2 -2
  74. package/dist/types/components/TextNodeView.d.ts +4 -3
  75. package/dist/types/components/TrailingHackView.d.ts +2 -2
  76. package/dist/types/components/WidgetView.d.ts +2 -2
  77. package/dist/types/constants.d.ts +4 -0
  78. package/dist/types/contexts/EditorContext.d.ts +6 -4
  79. package/dist/types/contexts/IgnoreMutationContext.d.ts +2 -1
  80. package/dist/types/contexts/NodeViewContext.d.ts +3 -1
  81. package/dist/types/contexts/SelectNodeContext.d.ts +3 -1
  82. package/dist/types/contexts/StopEventContext.d.ts +2 -1
  83. package/dist/types/decorations/computeDocDeco.d.ts +3 -2
  84. package/dist/types/decorations/viewDecorations.d.ts +3 -2
  85. package/dist/types/hooks/useEditor.d.ts +5 -46
  86. package/dist/types/hooks/useNodeViewDescriptor.d.ts +18 -10
  87. package/dist/types/hooks/useReactKeys.d.ts +1 -1
  88. package/dist/types/hooks/useSelectNode.d.ts +2 -1
  89. package/dist/types/props.d.ts +3 -3
  90. package/dist/types/viewdesc.d.ts +29 -11
  91. package/package.json +7 -3
  92. package/dist/cjs/components/Editor.js +0 -28
  93. package/dist/cjs/components/NodeViews.js +0 -73
  94. package/dist/cjs/components/__tests__/LayoutGroup.test.js +0 -141
  95. package/dist/cjs/components/__tests__/ProseMirror.test.js +0 -255
  96. package/dist/cjs/contexts/NodeViewsContext.js +0 -10
  97. package/dist/cjs/hooks/__tests__/useEditorViewLayoutEffect.test.js +0 -107
  98. package/dist/cjs/hooks/__tests__/useNodeViews.test.js +0 -159
  99. package/dist/cjs/hooks/useClientOnly.js +0 -19
  100. package/dist/cjs/hooks/useEditorView.js +0 -100
  101. package/dist/cjs/hooks/useNodeViews.js +0 -100
  102. package/dist/cjs/nodeViews/createReactNodeViewConstructor.js +0 -244
  103. package/dist/cjs/nodeViews/phrasingContentTags.js +0 -57
  104. package/dist/cjs/plugins/__tests__/react.test.js +0 -139
  105. package/dist/cjs/plugins/react.js +0 -71
  106. package/dist/cjs/selection/SelectionDOMObserver.js +0 -171
  107. package/dist/cjs/selection/hasFocusAndSelection.js +0 -35
  108. package/dist/cjs/selection/selectionFromDOM.js +0 -77
  109. package/dist/cjs/selection/selectionToDOM.js +0 -226
  110. package/dist/cjs/ssr.js +0 -85
  111. package/dist/esm/components/Editor.js +0 -15
  112. package/dist/esm/components/NodeViews.js +0 -26
  113. package/dist/esm/components/__tests__/LayoutGroup.test.js +0 -98
  114. package/dist/esm/components/__tests__/ProseMirror.test.js +0 -207
  115. package/dist/esm/contexts/NodeViewsContext.js +0 -9
  116. package/dist/esm/hooks/__tests__/useEditorViewLayoutEffect.test.js +0 -98
  117. package/dist/esm/hooks/__tests__/useNodeViews.test.js +0 -116
  118. package/dist/esm/hooks/useClientOnly.js +0 -9
  119. package/dist/esm/hooks/useEditorView.js +0 -99
  120. package/dist/esm/hooks/useNodePos.js +0 -16
  121. package/dist/esm/hooks/useNodeViews.js +0 -53
  122. package/dist/esm/nodeViews/createReactNodeViewConstructor.js +0 -214
  123. package/dist/esm/nodeViews/phrasingContentTags.js +0 -49
  124. package/dist/esm/plugins/__tests__/react.test.js +0 -135
  125. package/dist/esm/plugins/react.js +0 -64
  126. package/dist/esm/selection/SelectionDOMObserver.js +0 -161
  127. package/dist/esm/selection/hasFocusAndSelection.js +0 -17
  128. package/dist/esm/selection/selectionFromDOM.js +0 -59
  129. package/dist/esm/selection/selectionToDOM.js +0 -196
  130. package/dist/esm/ssr.js +0 -82
  131. package/dist/types/components/Editor.d.ts +0 -7
  132. package/dist/types/components/NodeViews.d.ts +0 -6
  133. package/dist/types/components/__tests__/LayoutGroup.test.d.ts +0 -1
  134. package/dist/types/contexts/NodeViewsContext.d.ts +0 -19
  135. package/dist/types/hooks/__tests__/useEditorViewLayoutEffect.test.d.ts +0 -1
  136. package/dist/types/hooks/__tests__/useNodeViews.test.d.ts +0 -1
  137. package/dist/types/hooks/useClientOnly.d.ts +0 -1
  138. package/dist/types/hooks/useEditorView.d.ts +0 -23
  139. package/dist/types/hooks/useNodePos.d.ts +0 -9
  140. package/dist/types/hooks/useNodeViews.d.ts +0 -5
  141. package/dist/types/nodeViews/createReactNodeViewConstructor.d.ts +0 -48
  142. package/dist/types/nodeViews/phrasingContentTags.d.ts +0 -1
  143. package/dist/types/plugins/__tests__/react.test.d.ts +0 -1
  144. package/dist/types/plugins/react.d.ts +0 -21
  145. package/dist/types/selection/SelectionDOMObserver.d.ts +0 -33
  146. package/dist/types/selection/hasFocusAndSelection.d.ts +0 -3
  147. package/dist/types/selection/selectionFromDOM.d.ts +0 -4
  148. package/dist/types/selection/selectionToDOM.d.ts +0 -9
  149. package/dist/types/ssr.d.ts +0 -19
@@ -1,178 +1,12 @@
1
- import { Schema } from "prosemirror-model";
2
- import { EditorState } from "prosemirror-state";
3
- import { DecorationSet, EditorView } from "prosemirror-view";
4
1
  import { useCallback, useMemo, useRef, useState } from "react";
5
2
  import { flushSync } from "react-dom";
3
+ import { ReactEditorView } from "../ReactEditorView.js";
4
+ import { StaticEditorView } from "../StaticEditorView.js";
5
+ import { EMPTY_STATE } from "../constants.js";
6
6
  import { beforeInputPlugin } from "../plugins/beforeInputPlugin.js";
7
- import { SelectionDOMObserver } from "../selection/SelectionDOMObserver.js";
8
- import { setSsrStubs } from "../ssr.js";
9
- import { NodeViewDesc } from "../viewdesc.js";
10
7
  import { useClientLayoutEffect } from "./useClientLayoutEffect.js";
11
8
  import { useComponentEventListeners } from "./useComponentEventListeners.js";
12
9
  import { useForceUpdate } from "./useForceUpdate.js";
13
- function buildNodeViews(view) {
14
- const result = Object.create(null);
15
- function add(obj) {
16
- for(const prop in obj)if (!Object.prototype.hasOwnProperty.call(result, prop)) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17
- result[prop] = obj[prop];
18
- }
19
- view.someProp("nodeViews", add);
20
- view.someProp("markViews", add);
21
- return result;
22
- }
23
- function changedNodeViews(a, b) {
24
- let nA = 0, nB = 0;
25
- for(const prop in a){
26
- if (a[prop] != b[prop]) return true;
27
- nA++;
28
- }
29
- for(const _ in b)nB++;
30
- return nA != nB;
31
- }
32
- function changedProps(a, b) {
33
- for (const prop of Object.keys(a)){
34
- if (a[prop] !== b[prop]) return true;
35
- }
36
- return false;
37
- }
38
- function getEditable(view) {
39
- return !view.someProp("editable", (value)=>value(view.state) === false);
40
- }
41
- // @ts-expect-error We're making use of knowledge of internal methods here
42
- export class ReactEditorView extends EditorView {
43
- shouldUpdatePluginViews = false;
44
- oldProps;
45
- _props;
46
- ready;
47
- constructor(place, props){
48
- // Call the superclass constructor with an empty
49
- // document and limited props. We'll set everything
50
- // else ourselves.
51
- const cleanup = setSsrStubs();
52
- super(place, {
53
- state: EditorState.create({
54
- schema: props.state.schema
55
- })
56
- });
57
- cleanup();
58
- this.ready = props.ready;
59
- this.shouldUpdatePluginViews = true;
60
- this._props = props;
61
- this.oldProps = {
62
- state: props.state
63
- };
64
- this.state = props.state;
65
- // @ts-expect-error We're making use of knowledge of internal attributes here
66
- this.domObserver.stop();
67
- // @ts-expect-error We're making use of knowledge of internal attributes here
68
- this.domObserver = new SelectionDOMObserver(this);
69
- // @ts-expect-error We're making use of knowledge of internal attributes here
70
- this.domObserver.start();
71
- this.editable = getEditable(this);
72
- // Destroy the DOM created by the default
73
- // ProseMirror ViewDesc implementation; we
74
- // have a NodeViewDesc from React instead.
75
- // @ts-expect-error We're making use of knowledge of internal attributes here
76
- this.docView.dom.replaceChildren();
77
- // @ts-expect-error We're making use of knowledge of internal attributes here
78
- this.nodeViews = buildNodeViews(this);
79
- // @ts-expect-error We're making use of knowledge of internal attributes here
80
- this.docView = props.docView;
81
- }
82
- /**
83
- * Whether the EditorView's updateStateInner method thinks that the
84
- * docView needs to be blown away and redrawn.
85
- *
86
- * @privateremarks
87
- *
88
- * When ProseMirror View detects that the EditorState has been reconfigured
89
- * to provide new custom node views, it calls an internal function that
90
- * we can't override in order to recreate the entire editor DOM.
91
- *
92
- * This property mimics that check, so that we can replace the EditorView
93
- * with another of our own, preventing ProseMirror View from taking over
94
- * DOM management responsibility.
95
- */ get needsRedraw() {
96
- if (this.oldProps.state.plugins === this._props.state.plugins && this._props.plugins === this.oldProps.plugins) {
97
- return false;
98
- }
99
- const newNodeViews = buildNodeViews(this);
100
- // @ts-expect-error Internal property
101
- return changedNodeViews(this.nodeViews, newNodeViews);
102
- }
103
- /**
104
- * Like setProps, but without executing any side effects.
105
- * Safe to use in a component render method.
106
- */ pureSetProps(props) {
107
- // this.oldProps = this.props;
108
- this._props = {
109
- ...this._props,
110
- ...props
111
- };
112
- this.state = this._props.state;
113
- this.editable = getEditable(this);
114
- }
115
- /**
116
- * Triggers any side effects that have been queued by previous
117
- * calls to pureSetProps.
118
- */ runPendingEffects() {
119
- if (changedProps(this.props, this.oldProps)) {
120
- const newProps = this.props;
121
- this._props = this.oldProps;
122
- this.state = this._props.state;
123
- this.update(newProps);
124
- }
125
- }
126
- update(props) {
127
- super.update(props);
128
- // Ensure that side effects aren't re-triggered until
129
- // pureSetProps is called again
130
- this.oldProps = props;
131
- }
132
- updatePluginViews(prevState) {
133
- if (this.shouldUpdatePluginViews) {
134
- // @ts-expect-error We're making use of knowledge of internal methods here
135
- super.updatePluginViews(prevState);
136
- }
137
- }
138
- // We want to trigger the default EditorView cleanup, but without
139
- // the actual view.dom cleanup (which React will have already handled).
140
- // So we give the EditorView a dummy DOM element and ask it to clean up
141
- destroy() {
142
- // If needsRedraw returns true, then we will destroy and recreate
143
- // the EditorView, but will likely leave the ProseMirrorDoc node as-is.
144
- // In this case, this.dom will still be connected when destroy runs, and
145
- // it will be reused for the next EditorView, so we need to manually reset
146
- // it.
147
- if (this.dom.isConnected) {
148
- // We need to manually execute this code from super.destroy(),
149
- // because when super.destroy() runs, it will have been given a dummy
150
- // dom node
151
- // @ts-expect-error Internal property - input
152
- for(const type in this.input.eventHandlers){
153
- // @ts-expect-error Internal property - input
154
- this.dom.removeEventListener(type, this.input.eventHandlers[type]);
155
- }
156
- }
157
- // @ts-expect-error we're intentionally overwriting this property
158
- // to prevent side effects
159
- this.dom = document.createElement("div");
160
- super.destroy();
161
- }
162
- }
163
- const EMPTY_SCHEMA = new Schema({
164
- nodes: {
165
- doc: {
166
- content: "text*"
167
- },
168
- text: {
169
- inline: true
170
- }
171
- }
172
- });
173
- const EMPTY_STATE = EditorState.create({
174
- schema: EMPTY_SCHEMA
175
- });
176
10
  let didWarnValueDefaultValue = false;
177
11
  /**
178
12
  * Creates, mounts, and manages a ProseMirror `EditorView`.
@@ -185,7 +19,7 @@ let didWarnValueDefaultValue = false;
185
19
  */ export function useEditor(mount, options) {
186
20
  if (process.env.NODE_ENV !== "production") {
187
21
  if (options.defaultState !== undefined && options.state !== undefined && !didWarnValueDefaultValue) {
188
- console.error("A component contains a ProseMirror editor with both value and defaultValue props. " + "ProseMirror editors must be either controlled or uncontrolled " + "(specify either the state prop, or the defaultState prop, but not both). " + "Decide between using a controlled or uncontrolled ProseMirror editor " + "and remove one of these props. More info: " + "https://reactjs.org/link/controlled-components");
22
+ console.error("A component contains a ProseMirror editor with both state and defaultState props. " + "ProseMirror editors must be either controlled or uncontrolled " + "(specify either the state prop, or the defaultState prop, but not both). " + "Decide between using a controlled or uncontrolled ProseMirror editor " + "and remove one of these props. More info: " + "https://reactjs.org/link/controlled-components");
189
23
  didWarnValueDefaultValue = true;
190
24
  }
191
25
  }
@@ -232,30 +66,18 @@ let didWarnValueDefaultValue = false;
232
66
  options.dispatchTransaction,
233
67
  options.state
234
68
  ]);
235
- const cleanup = setSsrStubs();
236
- const tempDom = document.createElement("div");
237
- cleanup();
238
- const docViewDescRef = useRef(new NodeViewDesc(undefined, [], ()=>-1, state.doc, [], DecorationSet.empty, tempDom, null, tempDom, ()=>false, ()=>{
239
- /* The doc node can't have a node selection*/ }, ()=>{
240
- /* The doc node can't have a node selection*/ }, ()=>false));
241
69
  const directEditorProps = {
242
70
  ...options,
243
71
  state,
244
72
  plugins,
245
- dispatchTransaction,
246
- docView: docViewDescRef.current,
247
- ready: true
73
+ dispatchTransaction
248
74
  };
249
- const [view, setView] = useState(// During the initial render, we create something of a dummy
250
- // EditorView. This allows us to ensure that the first render actually
251
- // renders the document, which is necessary for SSR.
252
- ()=>new ReactEditorView(null, {
253
- ...directEditorProps,
254
- ready: false
255
- }));
75
+ const [view, setView] = useState(()=>{
76
+ return new StaticEditorView(directEditorProps);
77
+ });
256
78
  useClientLayoutEffect(()=>{
257
79
  return ()=>{
258
- view?.destroy();
80
+ view.destroy();
259
81
  };
260
82
  }, [
261
83
  view
@@ -265,44 +87,33 @@ let didWarnValueDefaultValue = false;
265
87
  // so this is not a concern.
266
88
  // eslint-disable-next-line react-hooks/exhaustive-deps
267
89
  useClientLayoutEffect(()=>{
268
- if (!mount) {
269
- setView(null);
270
- return;
271
- }
272
- if (!view || view.dom !== mount) {
273
- const newView = new ReactEditorView({
274
- mount
275
- }, directEditorProps);
276
- setView(newView);
277
- newView.dom.addEventListener("compositionend", forceUpdate);
278
- return;
279
- }
280
- // TODO: We should be able to put this in previous branch,
281
- // but we need to convince EditorView's constructor not to
282
- // clear out the DOM when passed a mount that already has
283
- // content in it, otherwise React blows up when it tries
284
- // to clean up.
285
- if (view.needsRedraw) {
286
- setView(null);
287
- return;
90
+ if (mount !== view.dom) {
91
+ if (mount) {
92
+ const view = new ReactEditorView({
93
+ mount
94
+ }, directEditorProps);
95
+ view.dom.addEventListener("compositionend", forceUpdate);
96
+ setView(view);
97
+ } else {
98
+ const view = new StaticEditorView(directEditorProps);
99
+ setView(view);
100
+ }
101
+ } else if (view instanceof ReactEditorView) {
102
+ view.commitPendingEffects();
288
103
  }
289
- // @ts-expect-error Internal property - domObserver
290
- view?.domObserver.selectionToDOM();
291
- view?.runPendingEffects();
292
104
  });
293
- view?.pureSetProps(directEditorProps);
105
+ view.update(directEditorProps);
294
106
  const editor = useMemo(()=>({
295
- view: view,
296
- registerEventListener,
297
- unregisterEventListener,
107
+ view,
298
108
  cursorWrapper,
299
- docViewDescRef,
300
- flushSyncRef
109
+ flushSyncRef,
110
+ registerEventListener,
111
+ unregisterEventListener
301
112
  }), [
302
- view,
113
+ cursorWrapper,
303
114
  registerEventListener,
304
115
  unregisterEventListener,
305
- cursorWrapper
116
+ view
306
117
  ]);
307
118
  return {
308
119
  editor,
@@ -1,4 +1,5 @@
1
1
  /* Copyright (c) The New York Times Company */ import { useContext } from "react";
2
+ import { ReactEditorView } from "../ReactEditorView.js";
2
3
  import { EditorContext } from "../contexts/EditorContext.js";
3
4
  import { useLayoutGroupEffect } from "./useLayoutGroupEffect.js";
4
5
  /**
@@ -24,8 +25,7 @@ import { useLayoutGroupEffect } from "./useLayoutGroupEffect.js";
24
25
  // every time it changes, because it will most likely
25
26
  // be defined inline and run on every re-render.
26
27
  useLayoutGroupEffect(()=>{
27
- // @ts-expect-error We're making use of knowledge of internal attributes here
28
- if (view?.docView && view.ready) {
28
+ if (view instanceof ReactEditorView) {
29
29
  flushSyncRef.current = false;
30
30
  const result = effect(view);
31
31
  flushSyncRef.current = true;
@@ -1,6 +1,13 @@
1
1
  /* Copyright (c) The New York Times Company */ import { useCallback, useContext, useRef } from "react";
2
+ import { ReactEditorView } from "../ReactEditorView.js";
2
3
  import { EditorContext } from "../contexts/EditorContext.js";
3
4
  import { useEditorEffect } from "./useEditorEffect.js";
5
+ function assertIsReactEditorView(view) {
6
+ if (view instanceof ReactEditorView) {
7
+ return;
8
+ }
9
+ throw new DOMException("ProseMirror document is not mounted", "InvalidStateError");
10
+ }
4
11
  /**
5
12
  * Returns a stable function reference to be used as an
6
13
  * event handler callback.
@@ -25,11 +32,7 @@ import { useEditorEffect } from "./useEditorEffect.js";
25
32
  for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
26
33
  args[_key] = arguments[_key];
27
34
  }
28
- // It's not actually possible for an event handler to run
29
- // while view is null, since view is only ever set to
30
- // null in a layout effect that then immediately triggers
31
- // a re-render which sets view to a new EditorView
32
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
35
+ assertIsReactEditorView(view);
33
36
  return ref.current(view, ...args);
34
37
  }, [
35
38
  view
@@ -6,7 +6,7 @@ export function useIgnoreMutation(ignoreMutation) {
6
6
  const register = useContext(IgnoreMutationContext);
7
7
  const ignoreMutationMemo = useEditorEventCallback(ignoreMutation);
8
8
  useEditorEffect(()=>{
9
- register(ignoreMutationMemo);
9
+ return register(ignoreMutationMemo);
10
10
  }, [
11
11
  register,
12
12
  ignoreMutationMemo
@@ -1,109 +1,152 @@
1
- import { useCallback, useContext, useRef, useState } from "react";
1
+ import { useCallback, useContext, useMemo, useRef, useState } from "react";
2
+ import { ReactEditorView } from "../ReactEditorView.js";
2
3
  import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
3
4
  import { EditorContext } from "../contexts/EditorContext.js";
4
- import { CompositionViewDesc, NodeViewDesc, sortViewDescs } from "../viewdesc.js";
5
+ import { CompositionViewDesc, ReactNodeViewDesc, sortViewDescs } from "../viewdesc.js";
5
6
  import { useClientLayoutEffect } from "./useClientLayoutEffect.js";
6
- export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDecorations, outerDecorations, viewDesc, contentDOMRef) {
7
+ function findContentDOM(source, children) {
8
+ return source?.contentDOM ?? children[0]?.dom?.parentElement ?? null;
9
+ }
10
+ export function useNodeViewDescriptor(ref, constructor, props) {
7
11
  const { view } = useContext(EditorContext);
8
- const [hasContentDOM, setHasContentDOM] = useState(true);
9
- const nodeViewDescRef = useRef(viewDesc);
10
- const stopEvent = useRef(()=>false);
11
- const setStopEvent = useCallback((newStopEvent)=>{
12
- stopEvent.current = newStopEvent;
13
- }, []);
14
- const ignoreMutation = useRef(()=>false);
15
- const setIgnoreMutation = useCallback((newIgnoreMutation)=>{
16
- ignoreMutation.current = newIgnoreMutation;
17
- }, []);
18
- const selectNode = useRef(()=>{
19
- if (!nodeDomRef.current || !node) return;
20
- if (nodeDomRef.current.nodeType == 1) nodeDomRef.current.classList.add("ProseMirror-selectednode");
21
- if (contentDOMRef?.current || !node.type.spec.draggable) (domRef?.current ?? nodeDomRef.current).draggable = true;
22
- });
23
- const deselectNode = useRef(()=>{
24
- if (!nodeDomRef.current || !node) return;
25
- if (nodeDomRef.current.nodeType == 1) {
26
- nodeDomRef.current.classList.remove("ProseMirror-selectednode");
27
- if (contentDOMRef?.current || !node.type.spec.draggable) (domRef?.current ?? nodeDomRef.current).removeAttribute("draggable");
12
+ const { parentRef, siblingsRef } = useContext(ChildDescriptorsContext);
13
+ const [dom, setDOM] = useState(null);
14
+ const [nodeDOM, setNodeDOM] = useState(null);
15
+ const [contentDOM, setContentDOM] = useState(null);
16
+ const viewDescRef = useRef();
17
+ const childrenRef = useRef([]);
18
+ const create = useCallback((props)=>{
19
+ if (!(view instanceof ReactEditorView)) {
20
+ return;
28
21
  }
29
- });
30
- const setSelectNode = useCallback((newSelectNode, newDeselectNode)=>{
31
- selectNode.current = newSelectNode;
32
- deselectNode.current = newDeselectNode;
33
- }, []);
34
- const { siblingsRef, parentRef } = useContext(ChildDescriptorsContext);
35
- const childDescriptors = useRef([]);
36
- useClientLayoutEffect(()=>{
22
+ const dom = ref.current;
23
+ if (!dom) {
24
+ return;
25
+ }
26
+ const { node, getPos, decorations, innerDecorations } = props;
27
+ const nodeView = constructor(node, view, getPos, decorations, innerDecorations);
28
+ if (!nodeView) {
29
+ return;
30
+ }
31
+ const parent = parentRef.current;
32
+ const children = childrenRef.current;
33
+ const contentDOM = findContentDOM(nodeView, children);
34
+ const nodeDOM = nodeView.dom;
35
+ const viewDesc = new ReactNodeViewDesc(parent, children, getPos, node, decorations, innerDecorations, dom, contentDOM, nodeDOM, nodeView);
36
+ setDOM(dom);
37
+ setContentDOM(contentDOM);
38
+ setNodeDOM(nodeDOM);
39
+ return viewDesc;
40
+ }, [
41
+ ref,
42
+ parentRef,
43
+ constructor,
44
+ view
45
+ ]);
46
+ const update = useCallback((props)=>{
47
+ if (!(view instanceof ReactEditorView)) {
48
+ return false;
49
+ }
50
+ const viewDesc = viewDescRef.current;
51
+ if (!viewDesc) {
52
+ return false;
53
+ }
54
+ const dom = ref.current;
55
+ if (!dom || dom !== viewDesc.dom) {
56
+ return false;
57
+ }
58
+ const contentDOM = findContentDOM(viewDesc, viewDesc.children);
59
+ if (contentDOM !== viewDesc.contentDOM) {
60
+ return false;
61
+ }
62
+ if (!dom.contains(viewDesc.nodeDOM)) {
63
+ return false;
64
+ }
65
+ const { node, decorations, innerDecorations } = props;
66
+ return viewDesc.matchesNode(node, decorations, innerDecorations) || viewDesc.update(node, decorations, innerDecorations, view);
67
+ }, [
68
+ ref,
69
+ view
70
+ ]);
71
+ const destroy = useCallback(()=>{
72
+ const viewDesc = viewDescRef.current;
73
+ if (!viewDesc) {
74
+ return;
75
+ }
76
+ viewDesc.destroy();
37
77
  const siblings = siblingsRef.current;
38
- return ()=>{
39
- if (!nodeViewDescRef.current) return;
40
- if (siblings.includes(nodeViewDescRef.current)) {
41
- const index = siblings.indexOf(nodeViewDescRef.current);
42
- siblings.splice(index, 1);
43
- }
44
- };
78
+ if (siblings.includes(viewDesc)) {
79
+ const index = siblings.indexOf(viewDesc);
80
+ siblings.splice(index, 1);
81
+ }
82
+ setDOM(null);
83
+ setContentDOM(null);
84
+ setNodeDOM(null);
45
85
  }, [
46
86
  siblingsRef
47
87
  ]);
48
- // eslint-disable-next-line react-hooks/exhaustive-deps
49
88
  useClientLayoutEffect(()=>{
50
- if (!node || !nodeDomRef.current) return;
51
- const firstChildDesc = childDescriptors.current[0];
52
- if (!nodeViewDescRef.current) {
53
- nodeViewDescRef.current = new NodeViewDesc(parentRef.current, childDescriptors.current, getPos, node, outerDecorations, innerDecorations, domRef?.current ?? nodeDomRef.current, firstChildDesc?.dom.parentElement ?? null, nodeDomRef.current, (event)=>!!stopEvent.current(event), ()=>selectNode.current(), ()=>deselectNode.current(), (mutation)=>ignoreMutation.current(mutation));
54
- } else {
55
- nodeViewDescRef.current.parent = parentRef.current;
56
- nodeViewDescRef.current.children = childDescriptors.current;
57
- nodeViewDescRef.current.node = node;
58
- nodeViewDescRef.current.getPos = getPos;
59
- nodeViewDescRef.current.outerDeco = outerDecorations;
60
- nodeViewDescRef.current.innerDeco = innerDecorations;
61
- nodeViewDescRef.current.dom = domRef?.current ?? nodeDomRef.current;
62
- nodeViewDescRef.current.dom.pmViewDesc = nodeViewDescRef.current;
63
- nodeViewDescRef.current.contentDOM = // If there's already a contentDOM, we can just
64
- // keep it; it won't have changed. This is especially
65
- // important during compositions, where the
66
- // firstChildDesc might not have a correct dom node set yet.
67
- contentDOMRef?.current ?? nodeViewDescRef.current.contentDOM ?? firstChildDesc?.dom.parentElement ?? null;
68
- nodeViewDescRef.current.nodeDOM = nodeDomRef.current;
89
+ if (!update(props)) {
90
+ destroy();
91
+ viewDescRef.current = create(props);
92
+ }
93
+ const viewDesc = viewDescRef.current;
94
+ if (!viewDesc) {
95
+ return;
69
96
  }
70
- setHasContentDOM(nodeViewDescRef.current.contentDOM !== null);
71
- if (!siblingsRef.current.includes(nodeViewDescRef.current)) {
72
- siblingsRef.current.push(nodeViewDescRef.current);
97
+ if (view.dom === viewDesc.dom && view instanceof ReactEditorView) {
98
+ view.docView = viewDesc;
99
+ }
100
+ const parent = parentRef.current;
101
+ const siblings = siblingsRef.current;
102
+ const children = childrenRef.current;
103
+ viewDesc.parent = parent;
104
+ if (!siblings.includes(viewDesc)) {
105
+ siblings.push(viewDesc);
73
106
  }
74
- siblingsRef.current.sort(sortViewDescs);
75
- for (const childDesc of childDescriptors.current){
76
- childDesc.parent = nodeViewDescRef.current;
107
+ siblings.sort(sortViewDescs);
108
+ for (const child of children){
109
+ child.parent = viewDesc;
77
110
  // Because TextNodeViews can't locate the DOM nodes
78
111
  // for compositions, we need to override them here
79
- if (childDesc instanceof CompositionViewDesc) {
80
- const compositionTopDOM = nodeViewDescRef.current.contentDOM?.firstChild;
112
+ if (child instanceof CompositionViewDesc) {
113
+ const compositionTopDOM = viewDesc?.contentDOM?.firstChild;
81
114
  if (!compositionTopDOM) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
82
115
  let textDOM = compositionTopDOM;
83
116
  while(textDOM.firstChild){
84
117
  textDOM = textDOM.firstChild;
85
118
  }
86
119
  if (!textDOM || !(textDOM instanceof Text)) throw new Error(`Started a composition but couldn't find the text node it belongs to.`);
87
- childDesc.dom = compositionTopDOM;
88
- childDesc.textDOM = textDOM;
89
- childDesc.text = textDOM.data;
90
- childDesc.textDOM.pmViewDesc = childDesc;
91
- // @ts-expect-error Internal property -- input
92
- view?.input.compositionNodes.push(childDesc);
120
+ child.dom = compositionTopDOM;
121
+ child.textDOM = textDOM;
122
+ child.text = textDOM.data;
123
+ child.textDOM.pmViewDesc = child;
124
+ // It should not be possible to be in a composition because one could
125
+ // not start between the renders that switch the view type.
126
+ view.input.compositionNodes.push(child);
93
127
  }
94
128
  }
129
+ });
130
+ useClientLayoutEffect(()=>{
95
131
  return ()=>{
96
- if (nodeViewDescRef.current?.children[0] instanceof CompositionViewDesc && !view?.composing) {
97
- nodeViewDescRef.current?.children[0].dom.parentNode?.removeChild(nodeViewDescRef.current?.children[0].dom);
98
- }
132
+ destroy();
133
+ viewDescRef.current = undefined;
99
134
  };
100
- });
135
+ }, [
136
+ destroy
137
+ ]);
138
+ const childContextValue = useMemo(()=>({
139
+ parentRef: viewDescRef,
140
+ siblingsRef: childrenRef
141
+ }), [
142
+ childrenRef,
143
+ viewDescRef
144
+ ]);
101
145
  return {
102
- hasContentDOM,
103
- childDescriptors,
104
- nodeViewDescRef,
105
- setStopEvent,
106
- setSelectNode,
107
- setIgnoreMutation
146
+ childContextValue,
147
+ dom,
148
+ contentDOM,
149
+ nodeDOM,
150
+ ref
108
151
  };
109
152
  }
@@ -3,5 +3,5 @@ import { EditorContext } from "../contexts/EditorContext.js";
3
3
  import { reactKeysPluginKey } from "../plugins/reactKeys.js";
4
4
  export function useReactKeys() {
5
5
  const { view } = useContext(EditorContext);
6
- return view && reactKeysPluginKey.getState(view.state);
6
+ return reactKeysPluginKey.getState(view.state);
7
7
  }
@@ -2,17 +2,19 @@ import { useContext } from "react";
2
2
  import { SelectNodeContext } from "../contexts/SelectNodeContext.js";
3
3
  import { useEditorEffect } from "./useEditorEffect.js";
4
4
  import { useEditorEventCallback } from "./useEditorEventCallback.js";
5
- export function useSelectNode(selectNode, deselectNode) {
5
+ function noop() {
6
+ // empty
7
+ }
8
+ export function useSelectNode(selectNode) {
9
+ let deselectNode = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : noop;
6
10
  const register = useContext(SelectNodeContext);
7
11
  const selectNodeMemo = useEditorEventCallback(selectNode);
8
- const deselectNodeMemo = useEditorEventCallback(deselectNode ?? (()=>{
9
- // empty
10
- }));
12
+ const deselectNodeMemo = useEditorEventCallback(deselectNode);
11
13
  return useEditorEffect(()=>{
12
- register(selectNodeMemo, deselectNodeMemo);
14
+ return register(selectNodeMemo, deselectNodeMemo);
13
15
  }, [
14
- deselectNodeMemo,
15
16
  register,
16
- selectNodeMemo
17
+ selectNodeMemo,
18
+ deselectNodeMemo
17
19
  ]);
18
20
  }
@@ -6,7 +6,7 @@ export function useStopEvent(stopEvent) {
6
6
  const register = useContext(StopEventContext);
7
7
  const stopEventMemo = useEditorEventCallback(stopEvent);
8
8
  useEditorEffect(()=>{
9
- register(stopEventMemo);
9
+ return register(stopEventMemo);
10
10
  }, [
11
11
  register,
12
12
  stopEventMemo
@@ -86,6 +86,14 @@ export function beforeInputPlugin(setCursorWrapper) {
86
86
  case "insertParagraph":
87
87
  case "insertLineBreak":
88
88
  {
89
+ // ProseMirror-view has a hack that runs the Enter event handlers
90
+ // on iOS, to avoid a bug in Safari with calling event.preventDefault() on
91
+ // Enter events.
92
+ //
93
+ // We want to prevent that hack, because we run the Enter event handlers
94
+ // here, where there is no such bug. So we set this flag, which prosemirror-view
95
+ // uses to check whether it should run the deferred event handlers.
96
+ view.input.lastIOSEnter = 0;
89
97
  // Fire a synthetic keydown event to trigger ProseMirror's keymap
90
98
  const keyEvent = new KeyboardEvent("keydown", {
91
99
  bubbles: true,
@@ -119,8 +127,12 @@ export function beforeInputPlugin(setCursorWrapper) {
119
127
  break;
120
128
  }
121
129
  case "deleteWordBackward":
130
+ case "deleteHardLineBackward":
131
+ case "deleteSoftLineBackward":
122
132
  case "deleteContentBackward":
123
133
  case "deleteWordForward":
134
+ case "deleteHardLineForward":
135
+ case "deleteSoftLineForward":
124
136
  case "deleteContentForward":
125
137
  case "deleteContent":
126
138
  {
@@ -62,8 +62,6 @@ export function tempEditor(param) {
62
62
  }, /*#__PURE__*/ React.createElement(Test, null), /*#__PURE__*/ React.createElement(ProseMirrorDoc, null)));
63
63
  return view;
64
64
  }
65
- // We need two renders for the hasContentDOM state to settle
66
- rerenderEditor();
67
65
  return {
68
66
  view: view,
69
67
  rerender: rerenderEditor,