@handlewithcare/react-prosemirror 2.4.11 → 2.5.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 (107) 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 +59 -30
  5. package/dist/cjs/components/CustomNodeView.js +9 -25
  6. package/dist/cjs/components/DocNodeView.js +6 -15
  7. package/dist/cjs/components/MarkView.js +1 -2
  8. package/dist/cjs/components/NativeWidgetView.js +2 -3
  9. package/dist/cjs/components/NodeView.js +1 -1
  10. package/dist/cjs/components/ProseMirror.js +11 -14
  11. package/dist/cjs/components/ReactNodeView.js +3 -4
  12. package/dist/cjs/components/SeparatorHackView.js +1 -2
  13. package/dist/cjs/components/TextNodeView.js +4 -5
  14. package/dist/cjs/components/TrailingHackView.js +1 -2
  15. package/dist/cjs/components/WidgetView.js +2 -4
  16. package/dist/cjs/constants.js +33 -0
  17. package/dist/cjs/hooks/useEditor.js +32 -230
  18. package/dist/cjs/hooks/useEditorEffect.js +2 -2
  19. package/dist/cjs/hooks/useEditorEventCallback.js +8 -5
  20. package/dist/cjs/hooks/useNodeViewDescriptor.js +10 -10
  21. package/dist/cjs/hooks/useReactKeys.js +1 -1
  22. package/dist/cjs/testing/editorViewTestHelpers.js +0 -2
  23. package/dist/cjs/viewdesc.js +10 -9
  24. package/dist/esm/AbstractEditorView.js +1 -0
  25. package/dist/esm/ReactEditorView.js +156 -0
  26. package/dist/esm/StaticEditorView.js +76 -0
  27. package/dist/esm/components/ChildNodeViews.js +60 -32
  28. package/dist/esm/components/CustomNodeView.js +9 -25
  29. package/dist/esm/components/DocNodeView.js +6 -15
  30. package/dist/esm/components/MarkView.js +1 -2
  31. package/dist/esm/components/NativeWidgetView.js +2 -3
  32. package/dist/esm/components/NodeView.js +1 -1
  33. package/dist/esm/components/ProseMirror.js +11 -14
  34. package/dist/esm/components/ReactNodeView.js +3 -4
  35. package/dist/esm/components/SeparatorHackView.js +1 -2
  36. package/dist/esm/components/TextNodeView.js +4 -5
  37. package/dist/esm/components/TrailingHackView.js +1 -2
  38. package/dist/esm/components/WidgetView.js +2 -4
  39. package/dist/esm/constants.js +15 -0
  40. package/dist/esm/hooks/useEditor.js +28 -219
  41. package/dist/esm/hooks/useEditorEffect.js +2 -2
  42. package/dist/esm/hooks/useEditorEventCallback.js +8 -5
  43. package/dist/esm/hooks/useNodeViewDescriptor.js +10 -10
  44. package/dist/esm/hooks/useReactKeys.js +1 -1
  45. package/dist/esm/testing/editorViewTestHelpers.js +0 -2
  46. package/dist/esm/viewdesc.js +3 -2
  47. package/dist/tsconfig.tsbuildinfo +1 -1
  48. package/dist/types/AbstractEditorView.d.ts +27 -0
  49. package/dist/types/ReactEditorView.d.ts +79 -0
  50. package/dist/types/StaticEditorView.d.ts +24 -0
  51. package/dist/types/components/ChildNodeViews.d.ts +2 -2
  52. package/dist/types/components/CustomNodeView.d.ts +2 -2
  53. package/dist/types/components/DocNodeView.d.ts +2 -5
  54. package/dist/types/components/MarkView.d.ts +2 -2
  55. package/dist/types/components/NativeWidgetView.d.ts +2 -2
  56. package/dist/types/components/NodeView.d.ts +2 -2
  57. package/dist/types/components/ReactNodeView.d.ts +2 -2
  58. package/dist/types/components/SeparatorHackView.d.ts +2 -2
  59. package/dist/types/components/TextNodeView.d.ts +4 -3
  60. package/dist/types/components/TrailingHackView.d.ts +2 -2
  61. package/dist/types/components/WidgetView.d.ts +2 -2
  62. package/dist/types/constants.d.ts +4 -0
  63. package/dist/types/contexts/EditorContext.d.ts +6 -4
  64. package/dist/types/decorations/computeDocDeco.d.ts +3 -2
  65. package/dist/types/decorations/viewDecorations.d.ts +3 -2
  66. package/dist/types/hooks/useEditor.d.ts +5 -46
  67. package/dist/types/hooks/useNodeViewDescriptor.d.ts +1 -1
  68. package/dist/types/hooks/useReactKeys.d.ts +1 -1
  69. package/dist/types/props.d.ts +26 -26
  70. package/dist/types/viewdesc.d.ts +6 -5
  71. package/package.json +6 -2
  72. package/dist/cjs/components/__tests__/ProseMirror.composition.test.js +0 -398
  73. package/dist/cjs/components/__tests__/ProseMirror.domchange.test.js +0 -270
  74. package/dist/cjs/components/__tests__/ProseMirror.draw-decoration.test.js +0 -1010
  75. package/dist/cjs/components/__tests__/ProseMirror.draw.test.js +0 -337
  76. package/dist/cjs/components/__tests__/ProseMirror.node-view.test.js +0 -315
  77. package/dist/cjs/components/__tests__/ProseMirror.selection.test.js +0 -444
  78. package/dist/cjs/components/__tests__/ProseMirror.test.js +0 -382
  79. package/dist/cjs/contexts/__tests__/DeferredLayoutEffects.test.js +0 -141
  80. package/dist/cjs/hooks/__tests__/useEditorViewLayoutEffect.test.js +0 -108
  81. package/dist/cjs/plugins/__tests__/reactKeys.test.js +0 -81
  82. package/dist/cjs/selection/SelectionDOMObserver.js +0 -171
  83. package/dist/cjs/selection/hasFocusAndSelection.js +0 -35
  84. package/dist/cjs/selection/selectionFromDOM.js +0 -77
  85. package/dist/cjs/selection/selectionToDOM.js +0 -226
  86. package/dist/cjs/ssr.js +0 -85
  87. package/dist/esm/components/__tests__/ProseMirror.composition.test.js +0 -395
  88. package/dist/esm/components/__tests__/ProseMirror.domchange.test.js +0 -266
  89. package/dist/esm/components/__tests__/ProseMirror.draw-decoration.test.js +0 -967
  90. package/dist/esm/components/__tests__/ProseMirror.draw.test.js +0 -294
  91. package/dist/esm/components/__tests__/ProseMirror.node-view.test.js +0 -272
  92. package/dist/esm/components/__tests__/ProseMirror.selection.test.js +0 -440
  93. package/dist/esm/components/__tests__/ProseMirror.test.js +0 -339
  94. package/dist/esm/contexts/__tests__/DeferredLayoutEffects.test.js +0 -98
  95. package/dist/esm/hooks/__tests__/useEditorViewLayoutEffect.test.js +0 -99
  96. package/dist/esm/plugins/__tests__/reactKeys.test.js +0 -77
  97. package/dist/esm/selection/SelectionDOMObserver.js +0 -161
  98. package/dist/esm/selection/hasFocusAndSelection.js +0 -17
  99. package/dist/esm/selection/selectionFromDOM.js +0 -59
  100. package/dist/esm/selection/selectionToDOM.js +0 -196
  101. package/dist/esm/ssr.js +0 -82
  102. package/dist/types/hooks/__tests__/useEditorViewLayoutEffect.test.d.ts +0 -1
  103. package/dist/types/selection/SelectionDOMObserver.d.ts +0 -33
  104. package/dist/types/selection/hasFocusAndSelection.d.ts +0 -3
  105. package/dist/types/selection/selectionFromDOM.d.ts +0 -4
  106. package/dist/types/selection/selectionToDOM.d.ts +0 -9
  107. package/dist/types/ssr.d.ts +0 -19
@@ -32,7 +32,7 @@ export const MarkView = /*#__PURE__*/ memo(/*#__PURE__*/ forwardRef(function Mar
32
32
  if (!domRef.current) return;
33
33
  const firstChildDesc = childDescriptors.current[0];
34
34
  if (!viewDescRef.current) {
35
- viewDescRef.current = new MarkViewDesc(parentRef.current, childDescriptors.current, ()=>getPos.current(), mark, domRef.current, firstChildDesc?.dom.parentElement ?? domRef.current, {
35
+ viewDescRef.current = new MarkViewDesc(parentRef.current, childDescriptors.current, getPos, mark, domRef.current, firstChildDesc?.dom.parentElement ?? domRef.current, {
36
36
  dom: domRef.current,
37
37
  contentDOM: firstChildDesc?.dom.parentElement ?? domRef.current
38
38
  });
@@ -42,7 +42,6 @@ export const MarkView = /*#__PURE__*/ memo(/*#__PURE__*/ forwardRef(function Mar
42
42
  viewDescRef.current.children = childDescriptors.current;
43
43
  viewDescRef.current.spec.contentDOM = viewDescRef.current.contentDOM = firstChildDesc?.dom.parentElement ?? domRef.current;
44
44
  viewDescRef.current.mark = mark;
45
- viewDescRef.current.getPos = ()=>getPos.current();
46
45
  }
47
46
  if (!siblingsRef.current.includes(viewDescRef.current)) {
48
47
  siblingsRef.current.push(viewDescRef.current);
@@ -24,7 +24,7 @@ export function NativeWidgetView(param) {
24
24
  if (!rootDomRef.current) return;
25
25
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
26
  const toDOM = widget.type.toDOM;
27
- let dom = typeof toDOM === "function" ? toDOM(view, ()=>getPos.current()) : toDOM;
27
+ let dom = typeof toDOM === "function" ? toDOM(view, getPos) : toDOM;
28
28
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
29
  if (!widget.type.spec.raw) {
30
30
  if (dom.nodeType != 1) {
@@ -41,11 +41,10 @@ export function NativeWidgetView(param) {
41
41
  useClientLayoutEffect(()=>{
42
42
  if (!rootDomRef.current) return;
43
43
  if (!viewDescRef.current) {
44
- viewDescRef.current = new WidgetViewDesc(parentRef.current, ()=>getPos.current(), widget, rootDomRef.current);
44
+ viewDescRef.current = new WidgetViewDesc(parentRef.current, getPos, widget, rootDomRef.current);
45
45
  } else {
46
46
  viewDescRef.current.parent = parentRef.current;
47
47
  viewDescRef.current.widget = widget;
48
- viewDescRef.current.getPos = ()=>getPos.current();
49
48
  viewDescRef.current.dom = rootDomRef.current;
50
49
  }
51
50
  if (!siblingsRef.current.includes(viewDescRef.current)) {
@@ -5,7 +5,7 @@ import { ReactNodeView } from "./ReactNodeView.js";
5
5
  export const NodeView = /*#__PURE__*/ memo(function NodeView(param) {
6
6
  let { outerDeco, getPos, node, innerDeco, ...props } = param;
7
7
  const { view } = useContext(EditorContext);
8
- const customNodeView = view?.someProp("nodeViews", (nodeViews)=>nodeViews?.[node.type.name]);
8
+ const customNodeView = view.nodeViews[node.type.name];
9
9
  if (customNodeView) {
10
10
  return /*#__PURE__*/ React.createElement(CustomNodeView, {
11
11
  customNodeView: customNodeView,
@@ -1,4 +1,3 @@
1
- import { DecorationSet } from "prosemirror-view";
2
1
  import React, { useMemo, useState } from "react";
3
2
  import { EditorContext } from "../contexts/EditorContext.js";
4
3
  import { EditorStateContext } from "../contexts/EditorStateContext.js";
@@ -8,7 +7,6 @@ import { viewDecorations } from "../decorations/viewDecorations.js";
8
7
  import { useEditor } from "../hooks/useEditor.js";
9
8
  import { LayoutGroup } from "./LayoutGroup.js";
10
9
  import { DocNodeViewContext } from "./ProseMirrorDoc.js";
11
- const EMPTY_OUTER_DECOS = [];
12
10
  function ProseMirrorInner(param) {
13
11
  let { className, children, nodeViews, customNodeViews, ...props } = param;
14
12
  const [mount, setMount] = useState(null);
@@ -16,26 +14,25 @@ function ProseMirrorInner(param) {
16
14
  ...props,
17
15
  nodeViews: customNodeViews
18
16
  });
19
- const innerDecos = editor.view ? viewDecorations(editor.view, editor.cursorWrapper) : DecorationSet.empty;
20
- const outerDecos = editor.view ? computeDocDeco(editor.view) : EMPTY_OUTER_DECOS;
21
17
  const nodeViewContextValue = useMemo(()=>({
22
18
  nodeViews: nodeViews ?? {}
23
19
  }), [
24
20
  nodeViews
25
21
  ]);
22
+ const node = state.doc;
23
+ const innerDeco = viewDecorations(editor.view, editor.cursorWrapper);
24
+ const outerDeco = computeDocDeco(editor.view);
26
25
  const docNodeViewContextValue = useMemo(()=>({
27
- className: className,
28
- setMount: setMount,
29
- node: editor.view?.state.doc,
30
- innerDeco: innerDecos,
31
- outerDeco: outerDecos,
32
- viewDesc: editor.docViewDescRef.current
26
+ className,
27
+ setMount,
28
+ node,
29
+ innerDeco,
30
+ outerDeco
33
31
  }), [
34
32
  className,
35
- editor.docViewDescRef,
36
- editor.view?.state.doc,
37
- innerDecos,
38
- outerDecos
33
+ node,
34
+ innerDeco,
35
+ outerDeco
39
36
  ]);
40
37
  return /*#__PURE__*/ React.createElement(EditorContext.Provider, {
41
38
  value: editor
@@ -12,14 +12,13 @@ export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
12
12
  const domRef = useRef(null);
13
13
  const nodeDomRef = useRef(null);
14
14
  const contentDomRef = useRef(null);
15
- const getPosFunc = useRef(()=>getPos.current()).current;
16
15
  const { nodeViews } = useContext(NodeViewContext);
17
16
  let element = null;
18
17
  const Component = nodeViews[node.type.name];
19
18
  const outputSpec = useMemo(()=>node.type.spec.toDOM?.(node), [
20
19
  node
21
20
  ]);
22
- const { hasContentDOM, childDescriptors, setStopEvent, setSelectNode, setIgnoreMutation, nodeViewDescRef } = useNodeViewDescriptor(node, ()=>getPos.current(), domRef, nodeDomRef, innerDeco, outerDeco, undefined, contentDomRef);
21
+ const { hasContentDOM, childDescriptors, setStopEvent, setSelectNode, setIgnoreMutation, nodeViewDescRef } = useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDeco, outerDeco, contentDomRef);
23
22
  const finalProps = {
24
23
  ...props,
25
24
  ...!hasContentDOM && nodeDomRef.current?.tagName !== "BR" && {
@@ -28,11 +27,11 @@ export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
28
27
  };
29
28
  const nodeProps = useMemo(()=>({
30
29
  node: node,
31
- getPos: getPosFunc,
30
+ getPos: getPos,
32
31
  decorations: outerDeco,
33
32
  innerDecorations: innerDeco
34
33
  }), [
35
- getPosFunc,
34
+ getPos,
36
35
  innerDeco,
37
36
  node,
38
37
  outerDeco
@@ -32,11 +32,10 @@ export function SeparatorHackView(param) {
32
32
  }
33
33
  if (!ref.current) return;
34
34
  if (!viewDescRef.current) {
35
- viewDescRef.current = new TrailingHackViewDesc(parentRef.current, [], ()=>getPos.current(), ref.current, null);
35
+ viewDescRef.current = new TrailingHackViewDesc(parentRef.current, [], getPos, ref.current, null);
36
36
  } else {
37
37
  viewDescRef.current.parent = parentRef.current;
38
38
  viewDescRef.current.dom = ref.current;
39
- viewDescRef.current.getPos = ()=>getPos.current();
40
39
  }
41
40
  if (!siblingsRef.current.includes(viewDescRef.current)) {
42
41
  siblingsRef.current.push(viewDescRef.current);
@@ -37,8 +37,8 @@ export class TextNodeView extends Component {
37
37
  // when a composition was started that produces a new text node.
38
38
  // Otherwise we just rely on re-rendering the renderRef
39
39
  if (!dom) {
40
- if (!view?.composing) return;
41
- this.viewDescRef = new CompositionViewDesc(parentRef.current, ()=>getPos.current(), // These are just placeholders/dummies. We can't
40
+ if (!view.composing) return;
41
+ this.viewDescRef = new CompositionViewDesc(parentRef.current, getPos, // These are just placeholders/dummies. We can't
42
42
  // actually find the correct DOM nodes from here,
43
43
  // so we let our parent do it.
44
44
  // Passing a valid element here just so that the
@@ -51,12 +51,11 @@ export class TextNodeView extends Component {
51
51
  textNode = textNode.firstChild;
52
52
  }
53
53
  if (!this.viewDescRef || this.viewDescRef instanceof CompositionViewDesc) {
54
- this.viewDescRef = new TextViewDesc(undefined, [], ()=>getPos.current(), node, decorations, DecorationSet.empty, dom, textNode);
54
+ this.viewDescRef = new TextViewDesc(undefined, [], getPos, node, decorations, DecorationSet.empty, dom, textNode);
55
55
  } else {
56
56
  this.viewDescRef.parent = parentRef.current;
57
57
  this.viewDescRef.children = [];
58
58
  this.viewDescRef.node = node;
59
- this.viewDescRef.getPos = ()=>getPos.current();
60
59
  this.viewDescRef.outerDeco = decorations;
61
60
  this.viewDescRef.innerDeco = DecorationSet.empty;
62
61
  this.viewDescRef.dom = dom;
@@ -92,7 +91,7 @@ export class TextNodeView extends Component {
92
91
  // an active composition and the selection is in this node,
93
92
  // we freeze the DOM of this element so that it doesn't
94
93
  // interrupt the composition
95
- if (view?.composing && view.state.selection.from >= getPos.current() && view.state.selection.from <= getPos.current() + node.nodeSize) {
94
+ if (view.composing && view.state.selection.from >= getPos() && view.state.selection.from <= getPos() + node.nodeSize) {
96
95
  return this.renderRef;
97
96
  }
98
97
  this.renderRef = decorations.reduce(wrapInDeco, node.text);
@@ -22,11 +22,10 @@ export function TrailingHackView(param) {
22
22
  useClientLayoutEffect(()=>{
23
23
  if (!ref.current) return;
24
24
  if (!viewDescRef.current) {
25
- viewDescRef.current = new TrailingHackViewDesc(parentRef.current, [], ()=>getPos.current(), ref.current, null);
25
+ viewDescRef.current = new TrailingHackViewDesc(parentRef.current, [], getPos, ref.current, null);
26
26
  } else {
27
27
  viewDescRef.current.parent = parentRef.current;
28
28
  viewDescRef.current.dom = ref.current;
29
- viewDescRef.current.getPos = ()=>getPos.current();
30
29
  }
31
30
  if (!siblingsRef.current.includes(viewDescRef.current)) {
32
31
  siblingsRef.current.push(viewDescRef.current);
@@ -6,7 +6,6 @@ export function WidgetView(param) {
6
6
  let { widget, getPos } = param;
7
7
  const { siblingsRef, parentRef } = useContext(ChildDescriptorsContext);
8
8
  const viewDescRef = useRef(null);
9
- const getPosFunc = useRef(()=>getPos.current()).current;
10
9
  const domRef = useRef(null);
11
10
  useClientLayoutEffect(()=>{
12
11
  const siblings = siblingsRef.current;
@@ -23,11 +22,10 @@ export function WidgetView(param) {
23
22
  useClientLayoutEffect(()=>{
24
23
  if (!domRef.current) return;
25
24
  if (!viewDescRef.current) {
26
- viewDescRef.current = new WidgetViewDesc(parentRef.current, ()=>getPos.current(), widget, domRef.current);
25
+ viewDescRef.current = new WidgetViewDesc(parentRef.current, getPos, widget, domRef.current);
27
26
  } else {
28
27
  viewDescRef.current.parent = parentRef.current;
29
28
  viewDescRef.current.widget = widget;
30
- viewDescRef.current.getPos = ()=>getPos.current();
31
29
  viewDescRef.current.dom = domRef.current;
32
30
  }
33
31
  if (!siblingsRef.current.includes(viewDescRef.current)) {
@@ -39,7 +37,7 @@ export function WidgetView(param) {
39
37
  return Component && /*#__PURE__*/ React.createElement(Component, {
40
38
  ref: domRef,
41
39
  widget: widget,
42
- getPos: getPosFunc,
40
+ getPos: getPos,
43
41
  contentEditable: false
44
42
  });
45
43
  }
@@ -0,0 +1,15 @@
1
+ import { Schema } from "prosemirror-model";
2
+ import { EditorState } from "prosemirror-state";
3
+ export const EMPTY_SCHEMA = new Schema({
4
+ nodes: {
5
+ doc: {
6
+ content: "text*"
7
+ },
8
+ text: {
9
+ inline: true
10
+ }
11
+ }
12
+ });
13
+ export const EMPTY_STATE = EditorState.create({
14
+ schema: EMPTY_SCHEMA
15
+ });
@@ -1,180 +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
- plugins: props.state.plugins
56
- }),
57
- plugins: props.plugins
58
- });
59
- cleanup();
60
- this.ready = props.ready;
61
- this.shouldUpdatePluginViews = true;
62
- this._props = props;
63
- this.oldProps = {
64
- state: props.state
65
- };
66
- this.state = props.state;
67
- // @ts-expect-error We're making use of knowledge of internal attributes here
68
- this.domObserver.stop();
69
- // @ts-expect-error We're making use of knowledge of internal attributes here
70
- this.domObserver = new SelectionDOMObserver(this);
71
- // @ts-expect-error We're making use of knowledge of internal attributes here
72
- this.domObserver.start();
73
- this.editable = getEditable(this);
74
- // Destroy the DOM created by the default
75
- // ProseMirror ViewDesc implementation; we
76
- // have a NodeViewDesc from React instead.
77
- // @ts-expect-error We're making use of knowledge of internal attributes here
78
- this.docView.dom.replaceChildren();
79
- // @ts-expect-error We're making use of knowledge of internal attributes here
80
- this.nodeViews = buildNodeViews(this);
81
- // @ts-expect-error We're making use of knowledge of internal attributes here
82
- this.docView = props.docView;
83
- }
84
- /**
85
- * Whether the EditorView's updateStateInner method thinks that the
86
- * docView needs to be blown away and redrawn.
87
- *
88
- * @privateremarks
89
- *
90
- * When ProseMirror View detects that the EditorState has been reconfigured
91
- * to provide new custom node views, it calls an internal function that
92
- * we can't override in order to recreate the entire editor DOM.
93
- *
94
- * This property mimics that check, so that we can replace the EditorView
95
- * with another of our own, preventing ProseMirror View from taking over
96
- * DOM management responsibility.
97
- */ get needsRedraw() {
98
- if (this.oldProps.state.plugins === this._props.state.plugins && this._props.plugins === this.oldProps.plugins) {
99
- return false;
100
- }
101
- const newNodeViews = buildNodeViews(this);
102
- // @ts-expect-error Internal property
103
- return changedNodeViews(this.nodeViews, newNodeViews);
104
- }
105
- /**
106
- * Like setProps, but without executing any side effects.
107
- * Safe to use in a component render method.
108
- */ pureSetProps(props) {
109
- // this.oldProps = this.props;
110
- this._props = {
111
- ...this._props,
112
- ...props
113
- };
114
- this.state = this._props.state;
115
- this.editable = getEditable(this);
116
- }
117
- /**
118
- * Triggers any side effects that have been queued by previous
119
- * calls to pureSetProps.
120
- */ runPendingEffects() {
121
- if (changedProps(this.props, this.oldProps)) {
122
- const newProps = this.props;
123
- this._props = this.oldProps;
124
- this.state = this._props.state;
125
- this.update(newProps);
126
- }
127
- }
128
- update(props) {
129
- super.update(props);
130
- // Ensure that side effects aren't re-triggered until
131
- // pureSetProps is called again
132
- this.oldProps = props;
133
- }
134
- updatePluginViews(prevState) {
135
- if (this.shouldUpdatePluginViews) {
136
- // @ts-expect-error We're making use of knowledge of internal methods here
137
- super.updatePluginViews(prevState);
138
- }
139
- }
140
- // We want to trigger the default EditorView cleanup, but without
141
- // the actual view.dom cleanup (which React will have already handled).
142
- // So we give the EditorView a dummy DOM element and ask it to clean up
143
- destroy() {
144
- // If needsRedraw returns true, then we will destroy and recreate
145
- // the EditorView, but will likely leave the ProseMirrorDoc node as-is.
146
- // In this case, this.dom will still be connected when destroy runs, and
147
- // it will be reused for the next EditorView, so we need to manually reset
148
- // it.
149
- if (this.dom.isConnected) {
150
- // We need to manually execute this code from super.destroy(),
151
- // because when super.destroy() runs, it will have been given a dummy
152
- // dom node
153
- // @ts-expect-error Internal property - input
154
- for(const type in this.input.eventHandlers){
155
- // @ts-expect-error Internal property - input
156
- this.dom.removeEventListener(type, this.input.eventHandlers[type]);
157
- }
158
- }
159
- // @ts-expect-error we're intentionally overwriting this property
160
- // to prevent side effects
161
- this.dom = document.createElement("div");
162
- super.destroy();
163
- }
164
- }
165
- const EMPTY_SCHEMA = new Schema({
166
- nodes: {
167
- doc: {
168
- content: "text*"
169
- },
170
- text: {
171
- inline: true
172
- }
173
- }
174
- });
175
- const EMPTY_STATE = EditorState.create({
176
- schema: EMPTY_SCHEMA
177
- });
178
10
  let didWarnValueDefaultValue = false;
179
11
  /**
180
12
  * Creates, mounts, and manages a ProseMirror `EditorView`.
@@ -234,30 +66,18 @@ let didWarnValueDefaultValue = false;
234
66
  options.dispatchTransaction,
235
67
  options.state
236
68
  ]);
237
- const cleanup = setSsrStubs();
238
- const tempDom = document.createElement("div");
239
- cleanup();
240
- const docViewDescRef = useRef(new NodeViewDesc(undefined, [], ()=>-1, state.doc, [], DecorationSet.empty, tempDom, null, tempDom, ()=>false, ()=>{
241
- /* The doc node can't have a node selection*/ }, ()=>{
242
- /* The doc node can't have a node selection*/ }, ()=>false));
243
69
  const directEditorProps = {
244
70
  ...options,
245
71
  state,
246
72
  plugins,
247
- dispatchTransaction,
248
- docView: docViewDescRef.current,
249
- ready: true
73
+ dispatchTransaction
250
74
  };
251
- const [view, setView] = useState(// During the initial render, we create something of a dummy
252
- // EditorView. This allows us to ensure that the first render actually
253
- // renders the document, which is necessary for SSR.
254
- ()=>new ReactEditorView(null, {
255
- ...directEditorProps,
256
- ready: false
257
- }));
75
+ const [view, setView] = useState(()=>{
76
+ return new StaticEditorView(directEditorProps);
77
+ });
258
78
  useClientLayoutEffect(()=>{
259
79
  return ()=>{
260
- view?.destroy();
80
+ view.destroy();
261
81
  };
262
82
  }, [
263
83
  view
@@ -267,44 +87,33 @@ let didWarnValueDefaultValue = false;
267
87
  // so this is not a concern.
268
88
  // eslint-disable-next-line react-hooks/exhaustive-deps
269
89
  useClientLayoutEffect(()=>{
270
- if (!mount) {
271
- setView(null);
272
- return;
273
- }
274
- if (!view || view.dom !== mount) {
275
- const newView = new ReactEditorView({
276
- mount
277
- }, directEditorProps);
278
- setView(newView);
279
- newView.dom.addEventListener("compositionend", forceUpdate);
280
- return;
281
- }
282
- // TODO: We should be able to put this in previous branch,
283
- // but we need to convince EditorView's constructor not to
284
- // clear out the DOM when passed a mount that already has
285
- // content in it, otherwise React blows up when it tries
286
- // to clean up.
287
- if (view.needsRedraw) {
288
- setView(null);
289
- 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();
290
103
  }
291
- // @ts-expect-error Internal property - domObserver
292
- view?.domObserver.selectionToDOM();
293
- view?.runPendingEffects();
294
104
  });
295
- view?.pureSetProps(directEditorProps);
105
+ view.update(directEditorProps);
296
106
  const editor = useMemo(()=>({
297
- view: view,
298
- registerEventListener,
299
- unregisterEventListener,
107
+ view,
300
108
  cursorWrapper,
301
- docViewDescRef,
302
- flushSyncRef
109
+ flushSyncRef,
110
+ registerEventListener,
111
+ unregisterEventListener
303
112
  }), [
304
- view,
113
+ cursorWrapper,
305
114
  registerEventListener,
306
115
  unregisterEventListener,
307
- cursorWrapper
116
+ view
308
117
  ]);
309
118
  return {
310
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
@@ -3,10 +3,10 @@ import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js"
3
3
  import { EditorContext } from "../contexts/EditorContext.js";
4
4
  import { CompositionViewDesc, NodeViewDesc, sortViewDescs } from "../viewdesc.js";
5
5
  import { useClientLayoutEffect } from "./useClientLayoutEffect.js";
6
- export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDecorations, outerDecorations, viewDesc, contentDOMRef) {
6
+ export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDecorations, outerDecorations, contentDOMRef) {
7
7
  const { view } = useContext(EditorContext);
8
8
  const [hasContentDOM, setHasContentDOM] = useState(true);
9
- const nodeViewDescRef = useRef(viewDesc);
9
+ const nodeViewDescRef = useRef();
10
10
  const stopEvent = useRef(()=>false);
11
11
  const setStopEvent = useCallback((newStopEvent)=>{
12
12
  stopEvent.current = newStopEvent;
@@ -16,12 +16,12 @@ export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDec
16
16
  ignoreMutation.current = newIgnoreMutation;
17
17
  }, []);
18
18
  const selectNode = useRef(()=>{
19
- if (!nodeDomRef.current || !node) return;
19
+ if (!nodeDomRef.current) return;
20
20
  if (nodeDomRef.current.nodeType == 1) nodeDomRef.current.classList.add("ProseMirror-selectednode");
21
21
  if (contentDOMRef?.current || !node.type.spec.draggable) (domRef?.current ?? nodeDomRef.current).draggable = true;
22
22
  });
23
23
  const deselectNode = useRef(()=>{
24
- if (!nodeDomRef.current || !node) return;
24
+ if (!nodeDomRef.current) return;
25
25
  if (nodeDomRef.current.nodeType == 1) {
26
26
  nodeDomRef.current.classList.remove("ProseMirror-selectednode");
27
27
  if (contentDOMRef?.current || !node.type.spec.draggable) (domRef?.current ?? nodeDomRef.current).removeAttribute("draggable");
@@ -47,15 +47,14 @@ export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDec
47
47
  ]);
48
48
  // eslint-disable-next-line react-hooks/exhaustive-deps
49
49
  useClientLayoutEffect(()=>{
50
- if (!node || !nodeDomRef.current) return;
50
+ if (!nodeDomRef.current) return;
51
51
  const firstChildDesc = childDescriptors.current[0];
52
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));
53
+ nodeViewDescRef.current = new NodeViewDesc(parentRef.current, childDescriptors.current, getPos, node, outerDecorations, innerDecorations, domRef?.current ?? nodeDomRef.current, contentDOMRef?.current ?? firstChildDesc?.dom.parentElement ?? null, nodeDomRef.current, (event)=>!!stopEvent.current(event), ()=>selectNode.current(), ()=>deselectNode.current(), (mutation)=>ignoreMutation.current(mutation));
54
54
  } else {
55
55
  nodeViewDescRef.current.parent = parentRef.current;
56
56
  nodeViewDescRef.current.children = childDescriptors.current;
57
57
  nodeViewDescRef.current.node = node;
58
- nodeViewDescRef.current.getPos = getPos;
59
58
  nodeViewDescRef.current.outerDeco = outerDecorations;
60
59
  nodeViewDescRef.current.innerDeco = innerDecorations;
61
60
  nodeViewDescRef.current.dom = domRef?.current ?? nodeDomRef.current;
@@ -88,12 +87,13 @@ export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDec
88
87
  childDesc.textDOM = textDOM;
89
88
  childDesc.text = textDOM.data;
90
89
  childDesc.textDOM.pmViewDesc = childDesc;
91
- // @ts-expect-error Internal property -- input
92
- view?.input.compositionNodes.push(childDesc);
90
+ // It should not be possible to be in a composition because one could
91
+ // not start between the renders that switch the view type.
92
+ view.input.compositionNodes.push(childDesc);
93
93
  }
94
94
  }
95
95
  return ()=>{
96
- if (nodeViewDescRef.current?.children[0] instanceof CompositionViewDesc && !view?.composing) {
96
+ if (nodeViewDescRef.current?.children[0] instanceof CompositionViewDesc && !view.composing) {
97
97
  nodeViewDescRef.current?.children[0].dom.parentNode?.removeChild(nodeViewDescRef.current?.children[0].dom);
98
98
  }
99
99
  };
@@ -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
  }
@@ -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,