@handlewithcare/react-prosemirror 2.4.12 → 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 (127) 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 -228
  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 -217
  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 +3 -3
  70. package/dist/types/viewdesc.d.ts +6 -5
  71. package/package.json +6 -2
  72. package/dist/cjs/components/Editor.js +0 -28
  73. package/dist/cjs/components/NodeViews.js +0 -73
  74. package/dist/cjs/components/__tests__/LayoutGroup.test.js +0 -141
  75. package/dist/cjs/components/__tests__/ProseMirror.test.js +0 -255
  76. package/dist/cjs/contexts/NodeViewsContext.js +0 -10
  77. package/dist/cjs/hooks/__tests__/useEditorViewLayoutEffect.test.js +0 -107
  78. package/dist/cjs/hooks/__tests__/useNodeViews.test.js +0 -159
  79. package/dist/cjs/hooks/useEditorView.js +0 -100
  80. package/dist/cjs/hooks/useNodePos.js +0 -69
  81. package/dist/cjs/hooks/useNodeViews.js +0 -100
  82. package/dist/cjs/nodeViews/createReactNodeViewConstructor.js +0 -244
  83. package/dist/cjs/nodeViews/phrasingContentTags.js +0 -57
  84. package/dist/cjs/plugins/__tests__/react.test.js +0 -139
  85. package/dist/cjs/plugins/react.js +0 -71
  86. package/dist/cjs/selection/SelectionDOMObserver.js +0 -171
  87. package/dist/cjs/selection/hasFocusAndSelection.js +0 -35
  88. package/dist/cjs/selection/selectionFromDOM.js +0 -77
  89. package/dist/cjs/selection/selectionToDOM.js +0 -226
  90. package/dist/cjs/ssr.js +0 -85
  91. package/dist/esm/components/Editor.js +0 -15
  92. package/dist/esm/components/NodeViews.js +0 -26
  93. package/dist/esm/components/__tests__/LayoutGroup.test.js +0 -98
  94. package/dist/esm/components/__tests__/ProseMirror.test.js +0 -207
  95. package/dist/esm/contexts/NodeViewsContext.js +0 -9
  96. package/dist/esm/hooks/__tests__/useEditorViewLayoutEffect.test.js +0 -98
  97. package/dist/esm/hooks/__tests__/useNodeViews.test.js +0 -116
  98. package/dist/esm/hooks/useEditorView.js +0 -99
  99. package/dist/esm/hooks/useNodePos.js +0 -16
  100. package/dist/esm/hooks/useNodeViews.js +0 -53
  101. package/dist/esm/nodeViews/createReactNodeViewConstructor.js +0 -214
  102. package/dist/esm/nodeViews/phrasingContentTags.js +0 -49
  103. package/dist/esm/plugins/__tests__/react.test.js +0 -135
  104. package/dist/esm/plugins/react.js +0 -64
  105. package/dist/esm/selection/SelectionDOMObserver.js +0 -161
  106. package/dist/esm/selection/hasFocusAndSelection.js +0 -17
  107. package/dist/esm/selection/selectionFromDOM.js +0 -59
  108. package/dist/esm/selection/selectionToDOM.js +0 -196
  109. package/dist/esm/ssr.js +0 -82
  110. package/dist/types/components/Editor.d.ts +0 -7
  111. package/dist/types/components/NodeViews.d.ts +0 -6
  112. package/dist/types/components/__tests__/LayoutGroup.test.d.ts +0 -1
  113. package/dist/types/contexts/NodeViewsContext.d.ts +0 -19
  114. package/dist/types/hooks/__tests__/useEditorViewLayoutEffect.test.d.ts +0 -1
  115. package/dist/types/hooks/__tests__/useNodeViews.test.d.ts +0 -1
  116. package/dist/types/hooks/useEditorView.d.ts +0 -23
  117. package/dist/types/hooks/useNodePos.d.ts +0 -9
  118. package/dist/types/hooks/useNodeViews.d.ts +0 -5
  119. package/dist/types/nodeViews/createReactNodeViewConstructor.d.ts +0 -48
  120. package/dist/types/nodeViews/phrasingContentTags.d.ts +0 -1
  121. package/dist/types/plugins/__tests__/react.test.d.ts +0 -1
  122. package/dist/types/plugins/react.d.ts +0 -21
  123. package/dist/types/selection/SelectionDOMObserver.d.ts +0 -33
  124. package/dist/types/selection/hasFocusAndSelection.d.ts +0 -3
  125. package/dist/types/selection/selectionFromDOM.d.ts +0 -4
  126. package/dist/types/selection/selectionToDOM.d.ts +0 -9
  127. 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,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`.
@@ -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
@@ -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,