@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
@@ -1,98 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-empty-function */ import { render } from "@testing-library/react";
2
- import React from "react";
3
- import { LayoutGroup } from "../../components/LayoutGroup.js";
4
- import { EditorContext } from "../../contexts/EditorContext.js";
5
- import { useEditorEffect } from "../useEditorEffect.js";
6
- function TestComponent(param) {
7
- let { effect , dependencies =[] } = param;
8
- useEditorEffect(effect, [
9
- effect,
10
- ...dependencies
11
- ]);
12
- return null;
13
- }
14
- describe("useEditorViewLayoutEffect", ()=>{
15
- it("should run the effect", ()=>{
16
- const effect = jest.fn();
17
- const editorView = {};
18
- const editorState = {};
19
- const registerEventListener = ()=>{};
20
- const unregisterEventListener = ()=>{};
21
- render(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
22
- value: {
23
- editorView,
24
- editorState,
25
- registerEventListener,
26
- unregisterEventListener
27
- }
28
- }, /*#__PURE__*/ React.createElement(TestComponent, {
29
- effect: effect
30
- }))));
31
- expect(effect).toHaveBeenCalled();
32
- expect(effect).toHaveBeenCalledWith(editorView);
33
- });
34
- it("should not re-run the effect if no dependencies change", ()=>{
35
- const effect = jest.fn();
36
- const editorView = {};
37
- const editorState = {};
38
- const registerEventListener = ()=>{};
39
- const unregisterEventListener = ()=>{};
40
- const { rerender } = render(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
41
- value: {
42
- editorView,
43
- editorState,
44
- registerEventListener,
45
- unregisterEventListener
46
- }
47
- }, /*#__PURE__*/ React.createElement(TestComponent, {
48
- effect: effect,
49
- dependencies: []
50
- }))));
51
- rerender(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
52
- value: {
53
- editorView,
54
- editorState,
55
- registerEventListener,
56
- unregisterEventListener
57
- }
58
- }, /*#__PURE__*/ React.createElement(TestComponent, {
59
- effect: effect,
60
- dependencies: []
61
- }))));
62
- expect(effect).toHaveBeenCalledTimes(1);
63
- });
64
- it("should re-run the effect if dependencies change", ()=>{
65
- const effect = jest.fn();
66
- const editorView = {};
67
- const editorState = {};
68
- const registerEventListener = ()=>{};
69
- const unregisterEventListener = ()=>{};
70
- const { rerender } = render(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
71
- value: {
72
- editorView,
73
- editorState,
74
- registerEventListener,
75
- unregisterEventListener
76
- }
77
- }, /*#__PURE__*/ React.createElement(TestComponent, {
78
- effect: effect,
79
- dependencies: [
80
- "one"
81
- ]
82
- }))));
83
- rerender(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
84
- value: {
85
- editorView,
86
- editorState,
87
- registerEventListener,
88
- unregisterEventListener
89
- }
90
- }, /*#__PURE__*/ React.createElement(TestComponent, {
91
- effect: effect,
92
- dependencies: [
93
- "two"
94
- ]
95
- }))));
96
- expect(effect).toHaveBeenCalledTimes(2);
97
- });
98
- });
@@ -1,116 +0,0 @@
1
- import { act, render, screen } from "@testing-library/react";
2
- import { Schema } from "prosemirror-model";
3
- import { EditorState } from "prosemirror-state";
4
- import React, { createContext, useContext, useState } from "react";
5
- import { ProseMirror } from "../../components/ProseMirror.js";
6
- import { react } from "../../plugins/react.js";
7
- // Mock `ReactDOM.flushSync` to call `act` to flush updates from DOM mutations.
8
- jest.mock("react-dom", ()=>({
9
- ...jest.requireActual("react-dom"),
10
- flushSync: (fn)=>act(fn)
11
- }));
12
- const schema = new Schema({
13
- nodes: {
14
- doc: {
15
- content: "block+"
16
- },
17
- list: {
18
- group: "block",
19
- content: "list_item+"
20
- },
21
- list_item: {
22
- content: "inline*"
23
- },
24
- text: {
25
- group: "inline"
26
- }
27
- }
28
- });
29
- const state = EditorState.create({
30
- doc: schema.topNodeType.create(null, schema.nodes.list.createAndFill()),
31
- schema,
32
- plugins: [
33
- react()
34
- ]
35
- });
36
- describe("useNodeViews", ()=>{
37
- it("should render node views", ()=>{
38
- function List(param) {
39
- let { children } = param;
40
- return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("span", {
41
- contentEditable: false
42
- }, "list"), /*#__PURE__*/ React.createElement("ul", null, children));
43
- }
44
- function ListItem(param) {
45
- let { children } = param;
46
- return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("span", {
47
- contentEditable: false
48
- }, "list item"), /*#__PURE__*/ React.createElement("li", null, children));
49
- }
50
- const nodeViews = {
51
- list: ()=>({
52
- component: List,
53
- dom: document.createElement("div"),
54
- contentDOM: document.createElement("div")
55
- }),
56
- list_item: ()=>({
57
- component: ListItem,
58
- dom: document.createElement("div"),
59
- contentDOM: document.createElement("div")
60
- })
61
- };
62
- function TestEditor() {
63
- const [mount, setMount] = useState(null);
64
- return /*#__PURE__*/ React.createElement(ProseMirror, {
65
- mount: mount,
66
- nodeViews: nodeViews,
67
- defaultState: state
68
- }, /*#__PURE__*/ React.createElement("div", {
69
- ref: setMount
70
- }));
71
- }
72
- render(/*#__PURE__*/ React.createElement(TestEditor, null));
73
- expect(screen.getByText("list")).toBeTruthy();
74
- expect(screen.getByText("list item")).toBeTruthy();
75
- });
76
- it("should render child node views as children of their parents", ()=>{
77
- const TestContext = /*#__PURE__*/ createContext("default");
78
- function List(param) {
79
- let { children } = param;
80
- return /*#__PURE__*/ React.createElement(TestContext.Provider, {
81
- value: "overriden"
82
- }, /*#__PURE__*/ React.createElement("ul", null, children));
83
- }
84
- function ListItem(param) {
85
- let { children } = param;
86
- const testContextValue = useContext(TestContext);
87
- return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("span", {
88
- contentEditable: false
89
- }, testContextValue), /*#__PURE__*/ React.createElement("li", null, children));
90
- }
91
- const nodeViews = {
92
- list: ()=>({
93
- component: List,
94
- dom: document.createElement("div"),
95
- contentDOM: document.createElement("div")
96
- }),
97
- list_item: ()=>({
98
- component: ListItem,
99
- dom: document.createElement("div"),
100
- contentDOM: document.createElement("div")
101
- })
102
- };
103
- function TestEditor() {
104
- const [mount, setMount] = useState(null);
105
- return /*#__PURE__*/ React.createElement(ProseMirror, {
106
- mount: mount,
107
- nodeViews: nodeViews,
108
- defaultState: state
109
- }, /*#__PURE__*/ React.createElement("div", {
110
- ref: setMount
111
- }));
112
- }
113
- render(/*#__PURE__*/ React.createElement(TestEditor, null));
114
- expect(screen.getByText("overriden")).toBeTruthy();
115
- });
116
- });
@@ -1,99 +0,0 @@
1
- import { Schema } from "prosemirror-model";
2
- import { EditorState } from "prosemirror-state";
3
- import { EditorView } from "prosemirror-view";
4
- import { useLayoutEffect, useMemo, useState } from "react";
5
- import { flushSync } from "react-dom";
6
- import { useComponentEventListeners } from "./useComponentEventListeners.js";
7
- const EMPTY_SCHEMA = new Schema({
8
- nodes: {
9
- doc: {
10
- content: "text*"
11
- },
12
- text: {
13
- inline: true
14
- }
15
- }
16
- });
17
- const EMPTY_STATE = EditorState.create({
18
- schema: EMPTY_SCHEMA
19
- });
20
- let didWarnValueDefaultValue = false;
21
- /**
22
- * Creates, mounts, and manages a ProseMirror `EditorView`.
23
- *
24
- * All state and props updates are executed in a layout effect.
25
- * To ensure that the EditorState and EditorView are never out of
26
- * sync, it's important that the EditorView produced by this hook
27
- * is only accessed through the provided hooks.
28
- */ export function useEditorView(mount, options) {
29
- if (process.env.NODE_ENV !== "production") {
30
- if (options.defaultState !== undefined && options.state !== undefined && !didWarnValueDefaultValue) {
31
- 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");
32
- didWarnValueDefaultValue = true;
33
- }
34
- }
35
- const defaultState = options.defaultState ?? EMPTY_STATE;
36
- const [_state, setState] = useState(defaultState);
37
- const state = options.state ?? _state;
38
- const { componentEventListenersPlugin , registerEventListener , unregisterEventListener } = useComponentEventListeners();
39
- const plugins = useMemo(()=>[
40
- ...options.plugins ?? [],
41
- componentEventListenersPlugin
42
- ], [
43
- options.plugins,
44
- componentEventListenersPlugin
45
- ]);
46
- function dispatchTransaction(tr) {
47
- flushSync(()=>{
48
- if (!options.state) {
49
- setState((s)=>s.apply(tr));
50
- }
51
- if (options.dispatchTransaction) {
52
- options.dispatchTransaction.call(this, tr);
53
- }
54
- });
55
- }
56
- const directEditorProps = {
57
- ...options,
58
- state,
59
- plugins,
60
- dispatchTransaction
61
- };
62
- const [_view, setView] = useState(null);
63
- const view = options.view ?? _view;
64
- useLayoutEffect(()=>{
65
- return ()=>{
66
- if (_view) {
67
- _view.destroy();
68
- }
69
- };
70
- }, [
71
- _view
72
- ]);
73
- // This effect runs on every render and handles the view lifecycle.
74
- // eslint-disable-next-line react-hooks/exhaustive-deps
75
- useLayoutEffect(()=>{
76
- if (_view) {
77
- if (_view.dom === mount) {
78
- _view.setProps(directEditorProps);
79
- } else {
80
- setView(null);
81
- }
82
- } else if (mount) {
83
- setView(new EditorView({
84
- mount
85
- }, directEditorProps));
86
- }
87
- });
88
- return useMemo(()=>({
89
- editorState: state,
90
- editorView: view,
91
- registerEventListener,
92
- unregisterEventListener
93
- }), [
94
- state,
95
- _view,
96
- registerEventListener,
97
- unregisterEventListener
98
- ]);
99
- }
@@ -1,16 +0,0 @@
1
- import React, { createContext, useContext } from "react";
2
- import { reactPluginKey } from "../plugins/react.js";
3
- import { useEditorState } from "./useEditorState.js";
4
- const NodePosContext = /*#__PURE__*/ createContext(null);
5
- export function NodePosProvider(param) {
6
- let { nodeKey , children } = param;
7
- const editorState = useEditorState();
8
- const pluginState = reactPluginKey.getState(editorState);
9
- if (!pluginState) return /*#__PURE__*/ React.createElement(React.Fragment, null, children);
10
- return /*#__PURE__*/ React.createElement(NodePosContext.Provider, {
11
- value: pluginState.keyToPos.get(nodeKey) ?? 0
12
- }, children);
13
- }
14
- export function useNodePos() {
15
- return useContext(NodePosContext);
16
- }
@@ -1,53 +0,0 @@
1
- import React, { useCallback, useMemo, useState } from "react";
2
- import { NodeViews } from "../components/NodeViews.js";
3
- import { createReactNodeViewConstructor, findNodeKeyUp } from "../nodeViews/createReactNodeViewConstructor.js";
4
- export function useNodeViews(nodeViews) {
5
- const [portals, setPortals] = useState({});
6
- const registerPortal = useCallback((view, getPos, portal)=>{
7
- const nearestAncestorKey = findNodeKeyUp(view, getPos());
8
- setPortals((oldPortals)=>{
9
- const oldChildPortals = oldPortals[nearestAncestorKey] ?? [];
10
- const newChildPortals = oldChildPortals.concat({
11
- getPos,
12
- portal
13
- });
14
- return {
15
- ...oldPortals,
16
- [nearestAncestorKey]: newChildPortals
17
- };
18
- });
19
- return ()=>{
20
- setPortals((oldPortals)=>{
21
- const oldChildPortals = oldPortals[nearestAncestorKey] ?? [];
22
- const newChildPortals = oldChildPortals.filter((param)=>{
23
- let { portal: p } = param;
24
- return p !== portal;
25
- });
26
- return {
27
- ...oldPortals,
28
- [nearestAncestorKey]: newChildPortals
29
- };
30
- });
31
- };
32
- }, []);
33
- const wrappedNodeViews = useMemo(()=>{
34
- const nodeViewEntries = Object.entries(nodeViews ?? {});
35
- const wrappedNodeViewEntries = nodeViewEntries.map((param)=>{
36
- let [name, constructor] = param;
37
- return [
38
- name,
39
- createReactNodeViewConstructor(constructor, registerPortal)
40
- ];
41
- });
42
- return Object.fromEntries(wrappedNodeViewEntries);
43
- }, [
44
- nodeViews,
45
- registerPortal
46
- ]);
47
- return {
48
- nodeViews: wrappedNodeViews,
49
- nodeViewsComponent: /*#__PURE__*/ React.createElement(NodeViews, {
50
- portals: portals
51
- })
52
- };
53
- }
@@ -1,214 +0,0 @@
1
- import React, { forwardRef, useContext, useImperativeHandle, useState } from "react";
2
- import { createPortal } from "react-dom";
3
- import { NodeViewsContext } from "../contexts/NodeViewsContext.js";
4
- import { useEditorEffect } from "../hooks/useEditorEffect.js";
5
- import { NodePosProvider } from "../hooks/useNodePos.js";
6
- import { ROOT_NODE_KEY, createNodeKey, reactPluginKey } from "../plugins/react.js";
7
- import { phrasingContentTags } from "./phrasingContentTags.js";
8
- /**
9
- * Identifies a node view constructor as having been created
10
- * by @nytimes/react-prosemirror
11
- */ export const REACT_NODE_VIEW = Symbol("react node view");
12
- let didWarnReactPlugin = false;
13
- /**
14
- * Searches upward for the nearest node with a node key,
15
- * returning the first node key it finds associated with
16
- * a React node view.
17
- *
18
- * Returns the root key if no ancestor nodes have node keys.
19
- */ export function findNodeKeyUp(editorView, pos) {
20
- const pluginState = reactPluginKey.getState(editorView.state);
21
- if (!pluginState) return ROOT_NODE_KEY;
22
- const $pos = editorView.state.doc.resolve(pos);
23
- for(let d = $pos.depth; d > 0; d--){
24
- const ancestorNodeTypeName = $pos.node(d).type.name;
25
- const ancestorNodeView = editorView.props.nodeViews?.[ancestorNodeTypeName];
26
- if (!ancestorNodeView?.[REACT_NODE_VIEW]) continue;
27
- const ancestorPos = $pos.before(d);
28
- const ancestorKey = pluginState.posToKey.get(ancestorPos);
29
- if (ancestorKey) return ancestorKey;
30
- }
31
- return ROOT_NODE_KEY;
32
- }
33
- /**
34
- * Factory function for creating nodeViewConstructors that
35
- * render as React components.
36
- *
37
- * `NodeView` can be any React component that takes
38
- * `NodeViewComponentProps`. It will be passed all of the
39
- * arguments to the `nodeViewConstructor` except for
40
- * `editorView`. NodeView components that need access
41
- * directly to the EditorView should use the
42
- * `useEditorViewEvent` and `useEditorViewLayoutEffect`
43
- * hooks to ensure safe access.
44
- *
45
- * For contentful Nodes, the NodeView component will also
46
- * be passed a `children` prop containing an empty element.
47
- * ProseMirror will render content nodes into this element.
48
- */ export function createReactNodeViewConstructor(nodeViewConstructor, registerPortal) {
49
- function nodeViewConstructorWrapper(node, editorView, getPos, decorations, innerDecorations) {
50
- const nodeView = nodeViewConstructor(node, editorView, getPos, decorations, innerDecorations);
51
- const { component: NodeView } = nodeView;
52
- if (!NodeView) {
53
- return nodeView;
54
- }
55
- const reactPluginState = reactPluginKey.getState(editorView.state);
56
- if (!reactPluginState) {
57
- if (!didWarnReactPlugin) {
58
- console.error("The React ProseMirror plugin is required to use React node views. " + "Make sure to add it to the ProseMirror editor state.");
59
- didWarnReactPlugin = true;
60
- }
61
- return nodeView;
62
- }
63
- const { dom , contentDOM } = nodeView;
64
- // Use a span if the provided contentDOM is in the "phrasing" content
65
- // category. Otherwise use a div. This is our best attempt at not
66
- // breaking the intended content model, for now.
67
- //
68
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Content_categories#phrasing_content
69
- const ContentDOMWrapper = contentDOM && (phrasingContentTags.includes(contentDOM.tagName.toLocaleLowerCase()) ? "span" : "div");
70
- const nodeKey = reactPluginState.posToKey.get(getPos()) ?? createNodeKey();
71
- /**
72
- * Wrapper component to provide some imperative handles for updating
73
- * and re-rendering its child. Takes and renders an arbitrary ElementType
74
- * that expects NodeViewComponentProps as props.
75
- */ const NodeViewWrapper = /*#__PURE__*/ forwardRef(function NodeViewWrapper(param, ref) {
76
- let { initialState } = param;
77
- const [node, setNode] = useState(initialState.node);
78
- const [decorations, setDecorations] = useState(initialState.decorations);
79
- const [isSelected, setIsSelected] = useState(initialState.isSelected);
80
- const nodeViews = useContext(NodeViewsContext);
81
- const childNodeViews = nodeViews[nodeKey];
82
- const [childNodeViewPortals, setChildNodeViewPortals] = useState(childNodeViews?.map((param)=>{
83
- let { portal } = param;
84
- return portal;
85
- }));
86
- // `getPos` is technically derived from the EditorView
87
- // state, so it's not safe to call until after the EditorView
88
- // has been updated
89
- useEditorEffect(()=>{
90
- setChildNodeViewPortals(childNodeViews?.sort((a, b)=>a.getPos() - b.getPos()).map((param)=>{
91
- let { portal } = param;
92
- return portal;
93
- }));
94
- }, [
95
- childNodeViews
96
- ]);
97
- const [contentDOMWrapper, setContentDOMWrapper] = useState(null);
98
- const [contentDOMParent, setContentDOMParent] = useState(null);
99
- useImperativeHandle(ref, ()=>({
100
- node,
101
- contentDOMWrapper: contentDOMWrapper,
102
- contentDOMParent: contentDOMParent,
103
- setNode,
104
- setDecorations,
105
- setIsSelected
106
- }), [
107
- node,
108
- contentDOMWrapper,
109
- contentDOMParent
110
- ]);
111
- return /*#__PURE__*/ React.createElement(NodePosProvider, {
112
- nodeKey: nodeKey
113
- }, /*#__PURE__*/ React.createElement(NodeView, {
114
- node: node,
115
- decorations: decorations,
116
- isSelected: isSelected
117
- }, childNodeViewPortals, ContentDOMWrapper && /*#__PURE__*/ React.createElement(ContentDOMWrapper, {
118
- style: {
119
- display: "contents"
120
- },
121
- ref: (nextContentDOMWrapper)=>{
122
- setContentDOMWrapper(nextContentDOMWrapper);
123
- // we preserve a reference to the contentDOMWrapper'
124
- // parent so that later we can reassemble the DOM hierarchy
125
- // React expects when cleaning up the ContentDOMWrapper element
126
- if (nextContentDOMWrapper?.parentNode) {
127
- setContentDOMParent(nextContentDOMWrapper.parentNode);
128
- }
129
- }
130
- })));
131
- });
132
- NodeViewWrapper.displayName = `NodeView(${NodeView.displayName ?? NodeView.name})`;
133
- let componentRef = null;
134
- const element = /*#__PURE__*/ React.createElement(NodeViewWrapper, {
135
- initialState: {
136
- node,
137
- decorations,
138
- isSelected: false
139
- },
140
- ref: (c)=>{
141
- componentRef = c;
142
- if (!componentRef || componentRef.node.isLeaf) return;
143
- const contentDOMWrapper = componentRef.contentDOMWrapper;
144
- if (!contentDOMWrapper || !(contentDOMWrapper instanceof HTMLElement)) {
145
- return;
146
- }
147
- // We always set contentDOM when !node.isLeaf, which is checked above
148
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
149
- contentDOMWrapper.appendChild(contentDOM);
150
- // Synchronize the ProseMirror selection to the DOM, because mounting the
151
- // component changes the DOM outside of a ProseMirror update.
152
- const { node } = componentRef;
153
- const pos = getPos();
154
- const end = pos + node.nodeSize;
155
- const { from , to } = editorView.state.selection;
156
- if (editorView.hasFocus() && pos < from && to < end) {
157
- // This call seems like it should be a no-op, given the editor already has
158
- // focus, but it causes ProseMirror to synchronize the DOM selection with
159
- // its state again, placing the DOM selection in a reasonable place within
160
- // the node.
161
- editorView.focus();
162
- }
163
- }
164
- });
165
- const portal = /*#__PURE__*/ createPortal(element, dom, nodeKey);
166
- const unregisterPortal = registerPortal(editorView, getPos, portal);
167
- return {
168
- ignoreMutation (record) {
169
- return !contentDOM?.contains(record.target);
170
- },
171
- ...nodeView,
172
- selectNode () {
173
- componentRef?.setIsSelected(true);
174
- nodeView.selectNode?.();
175
- },
176
- deselectNode () {
177
- componentRef?.setIsSelected(false);
178
- nodeView.deselectNode?.();
179
- },
180
- update (node, decorations, innerDecorations) {
181
- // If this node view's parent has been removed from the registry, we
182
- // need to rebuild it and its children with new registry keys
183
- const positionRegistry = reactPluginKey.getState(editorView.state);
184
- if (positionRegistry && nodeKey !== positionRegistry.posToKey.get(getPos())) {
185
- return false;
186
- }
187
- if (nodeView.update?.(node, decorations, innerDecorations) === false) {
188
- return false;
189
- }
190
- if (node.type === componentRef?.node.type) {
191
- componentRef?.setNode(node);
192
- componentRef?.setDecorations(decorations);
193
- return true;
194
- }
195
- return false;
196
- },
197
- destroy () {
198
- // React expects the contentDOMParent to be a child of the
199
- // DOM element where the portal was mounted, but in some situations
200
- // contenteditable may have already detached the contentDOMParent
201
- // from the DOM. Here we attempt to reassemble the DOM that React
202
- // expects when cleaning up the portal.
203
- if (componentRef?.contentDOMParent) {
204
- this.dom.appendChild(componentRef.contentDOMParent);
205
- }
206
- unregisterPortal();
207
- nodeView.destroy?.();
208
- }
209
- };
210
- }
211
- return Object.assign(nodeViewConstructorWrapper, {
212
- [REACT_NODE_VIEW]: true
213
- });
214
- }
@@ -1,49 +0,0 @@
1
- export const phrasingContentTags = [
2
- "abbr",
3
- "audio",
4
- "b",
5
- "bdo",
6
- "br",
7
- "button",
8
- "canvas",
9
- "cite",
10
- "code",
11
- "data",
12
- "datalist",
13
- "dfn",
14
- "em",
15
- "embed",
16
- "i",
17
- "iframe",
18
- "img",
19
- "input",
20
- "kbd",
21
- "keygen",
22
- "label",
23
- "mark",
24
- "math",
25
- "meter",
26
- "noscript",
27
- "object",
28
- "output",
29
- "picture",
30
- "progress",
31
- "q",
32
- "ruby",
33
- "s",
34
- "samp",
35
- "script",
36
- "select",
37
- "small",
38
- "span",
39
- "strong",
40
- "sub",
41
- "sup",
42
- "svg",
43
- "textarea",
44
- "time",
45
- "u",
46
- "var",
47
- "video",
48
- "wbr"
49
- ];