@handlewithcare/react-prosemirror 2.5.4 → 2.6.0-tiptap.2

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 (78) hide show
  1. package/dist/cjs/ReactEditorView.js +0 -24
  2. package/dist/cjs/components/__tests__/ProseMirror.composition.test.js +398 -0
  3. package/dist/cjs/components/__tests__/ProseMirror.domchange.test.js +270 -0
  4. package/dist/cjs/components/__tests__/ProseMirror.draw-decoration.test.js +1010 -0
  5. package/dist/cjs/components/__tests__/ProseMirror.draw.test.js +337 -0
  6. package/dist/cjs/components/__tests__/ProseMirror.node-view.test.js +315 -0
  7. package/dist/cjs/components/__tests__/ProseMirror.selection.test.js +444 -0
  8. package/dist/cjs/components/__tests__/ProseMirror.test.js +382 -0
  9. package/dist/cjs/contexts/__tests__/DeferredLayoutEffects.test.js +141 -0
  10. package/dist/cjs/hooks/__tests__/useEditorViewLayoutEffect.test.js +108 -0
  11. package/dist/cjs/hooks/useClientOnly.js +19 -0
  12. package/dist/cjs/hooks/useEditor.js +17 -19
  13. package/dist/cjs/hooks/useNodeViewDescriptor.js +24 -16
  14. package/dist/cjs/plugins/__tests__/reactKeys.test.js +81 -0
  15. package/dist/cjs/selection/SelectionDOMObserver.js +171 -0
  16. package/dist/cjs/selection/hasFocusAndSelection.js +35 -0
  17. package/dist/cjs/selection/selectionFromDOM.js +77 -0
  18. package/dist/cjs/selection/selectionToDOM.js +226 -0
  19. package/dist/cjs/ssr.js +85 -0
  20. package/dist/cjs/tiptap/TiptapEditorContent.js +144 -0
  21. package/dist/cjs/tiptap/TiptapEditorView.js +91 -0
  22. package/dist/cjs/tiptap/hooks/useTiptapEditor.js +17 -0
  23. package/dist/cjs/tiptap/hooks/useTiptapEditorEffect.js +27 -0
  24. package/dist/cjs/tiptap/hooks/useTiptapEditorEventCallback.js +26 -0
  25. package/dist/cjs/tiptap/index.js +36 -0
  26. package/dist/cjs/tiptap/tiptapNodeView.js +180 -0
  27. package/dist/esm/ReactEditorView.js +0 -24
  28. package/dist/esm/components/__tests__/ProseMirror.composition.test.js +395 -0
  29. package/dist/esm/components/__tests__/ProseMirror.domchange.test.js +266 -0
  30. package/dist/esm/components/__tests__/ProseMirror.draw-decoration.test.js +967 -0
  31. package/dist/esm/components/__tests__/ProseMirror.draw.test.js +294 -0
  32. package/dist/esm/components/__tests__/ProseMirror.node-view.test.js +272 -0
  33. package/dist/esm/components/__tests__/ProseMirror.selection.test.js +440 -0
  34. package/dist/esm/components/__tests__/ProseMirror.test.js +339 -0
  35. package/dist/esm/contexts/__tests__/DeferredLayoutEffects.test.js +98 -0
  36. package/dist/esm/hooks/__tests__/useEditorViewLayoutEffect.test.js +99 -0
  37. package/dist/esm/hooks/useClientOnly.js +9 -0
  38. package/dist/esm/hooks/useEditor.js +17 -19
  39. package/dist/esm/hooks/useEditorEffect.js +4 -0
  40. package/dist/esm/hooks/useEditorEventCallback.js +3 -5
  41. package/dist/esm/hooks/useNodeViewDescriptor.js +25 -17
  42. package/dist/esm/plugins/__tests__/reactKeys.test.js +77 -0
  43. package/dist/esm/selection/SelectionDOMObserver.js +161 -0
  44. package/dist/esm/selection/hasFocusAndSelection.js +17 -0
  45. package/dist/esm/selection/selectionFromDOM.js +59 -0
  46. package/dist/esm/selection/selectionToDOM.js +196 -0
  47. package/dist/esm/ssr.js +82 -0
  48. package/dist/esm/tiptap/TiptapEditorContent.js +93 -0
  49. package/dist/esm/tiptap/TiptapEditorView.js +42 -0
  50. package/dist/esm/tiptap/hooks/useTiptapEditor.js +7 -0
  51. package/dist/esm/tiptap/hooks/useTiptapEditorEffect.js +34 -0
  52. package/dist/esm/tiptap/hooks/useTiptapEditorEventCallback.js +26 -0
  53. package/dist/esm/tiptap/index.js +6 -0
  54. package/dist/esm/tiptap/tiptapNodeView.js +148 -0
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/dist/types/ReactEditorView.d.ts +0 -12
  57. package/dist/types/constants.d.ts +1 -1
  58. package/dist/types/hooks/__tests__/useEditorViewLayoutEffect.test.d.ts +1 -0
  59. package/dist/types/hooks/useClientOnly.d.ts +1 -0
  60. package/dist/types/hooks/useEditorEffect.d.ts +4 -0
  61. package/dist/types/hooks/useEditorEventCallback.d.ts +3 -5
  62. package/dist/types/props.d.ts +24 -24
  63. package/dist/types/selection/SelectionDOMObserver.d.ts +33 -0
  64. package/dist/types/selection/hasFocusAndSelection.d.ts +3 -0
  65. package/dist/types/selection/selectionFromDOM.d.ts +4 -0
  66. package/dist/types/selection/selectionToDOM.d.ts +9 -0
  67. package/dist/types/ssr.d.ts +19 -0
  68. package/dist/types/tiptap/TiptapEditorContent.d.ts +19 -0
  69. package/dist/types/tiptap/TiptapEditorView.d.ts +13 -0
  70. package/dist/types/tiptap/hooks/useTiptapEditor.d.ts +4 -0
  71. package/dist/types/tiptap/hooks/useTiptapEditorEffect.d.ts +21 -0
  72. package/dist/types/tiptap/hooks/useTiptapEditorEventCallback.d.ts +13 -0
  73. package/dist/types/tiptap/index.d.ts +6 -0
  74. package/dist/types/tiptap/tiptapNodeView.d.ts +48 -0
  75. package/package.json +8 -1
  76. package/dist/cjs/hooks/useEffectEvent.js +0 -34
  77. package/dist/esm/hooks/useEffectEvent.js +0 -24
  78. package/dist/types/hooks/useEffectEvent.d.ts +0 -1
@@ -1,10 +1,9 @@
1
- import { useContext, useMemo, useRef, useState } from "react";
1
+ import { useCallback, useContext, useMemo, useRef, useState } from "react";
2
2
  import { ReactEditorView } from "../ReactEditorView.js";
3
3
  import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
4
4
  import { EditorContext } from "../contexts/EditorContext.js";
5
5
  import { CompositionViewDesc, ReactNodeViewDesc, sortViewDescs } from "../viewdesc.js";
6
6
  import { useClientLayoutEffect } from "./useClientLayoutEffect.js";
7
- import { useEffectEvent } from "./useEffectEvent.js";
8
7
  function findContentDOM(source, children) {
9
8
  return source?.contentDOM ?? children[0]?.dom?.parentElement ?? null;
10
9
  }
@@ -16,7 +15,7 @@ export function useNodeViewDescriptor(ref, constructor, props) {
16
15
  const [contentDOM, setContentDOM] = useState(null);
17
16
  const viewDescRef = useRef();
18
17
  const childrenRef = useRef([]);
19
- const create = useEffectEvent(()=>{
18
+ const create = useCallback((props)=>{
20
19
  if (!(view instanceof ReactEditorView)) {
21
20
  return;
22
21
  }
@@ -38,8 +37,13 @@ export function useNodeViewDescriptor(ref, constructor, props) {
38
37
  setContentDOM(contentDOM);
39
38
  setNodeDOM(nodeDOM);
40
39
  return viewDesc;
41
- });
42
- const update = useEffectEvent(()=>{
40
+ }, [
41
+ ref,
42
+ parentRef,
43
+ constructor,
44
+ view
45
+ ]);
46
+ const update = useCallback((props)=>{
43
47
  if (!(view instanceof ReactEditorView)) {
44
48
  return false;
45
49
  }
@@ -60,8 +64,11 @@ export function useNodeViewDescriptor(ref, constructor, props) {
60
64
  }
61
65
  const { node, decorations, innerDecorations } = props;
62
66
  return viewDesc.matchesNode(node, decorations, innerDecorations) || viewDesc.update(node, decorations, innerDecorations, view);
63
- });
64
- const destroy = useEffectEvent(()=>{
67
+ }, [
68
+ ref,
69
+ view
70
+ ]);
71
+ const destroy = useCallback(()=>{
65
72
  const viewDesc = viewDescRef.current;
66
73
  if (!viewDesc) {
67
74
  return;
@@ -75,20 +82,13 @@ export function useNodeViewDescriptor(ref, constructor, props) {
75
82
  setDOM(null);
76
83
  setContentDOM(null);
77
84
  setNodeDOM(null);
78
- });
79
- useClientLayoutEffect(()=>{
80
- viewDescRef.current = create();
81
- return ()=>{
82
- destroy();
83
- };
84
85
  }, [
85
- create,
86
- destroy
86
+ siblingsRef
87
87
  ]);
88
88
  useClientLayoutEffect(()=>{
89
- if (!update()) {
89
+ if (!update(props)) {
90
90
  destroy();
91
- viewDescRef.current = create();
91
+ viewDescRef.current = create(props);
92
92
  }
93
93
  const viewDesc = viewDescRef.current;
94
94
  if (!viewDesc) {
@@ -127,6 +127,14 @@ export function useNodeViewDescriptor(ref, constructor, props) {
127
127
  }
128
128
  }
129
129
  });
130
+ useClientLayoutEffect(()=>{
131
+ return ()=>{
132
+ destroy();
133
+ viewDescRef.current = undefined;
134
+ };
135
+ }, [
136
+ destroy
137
+ ]);
130
138
  const childContextValue = useMemo(()=>({
131
139
  parentRef: viewDescRef,
132
140
  siblingsRef: childrenRef
@@ -0,0 +1,77 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Schema } from "prosemirror-model";
2
+ import { EditorState } from "prosemirror-state";
3
+ import { reactKeys, reactKeysPluginKey } from "../reactKeys.js";
4
+ const schema = new Schema({
5
+ nodes: {
6
+ doc: {
7
+ content: "block+"
8
+ },
9
+ paragraph: {
10
+ group: "block",
11
+ content: "inline*"
12
+ },
13
+ list: {
14
+ group: "block",
15
+ content: "list_item+"
16
+ },
17
+ list_item: {
18
+ content: "inline*"
19
+ },
20
+ text: {
21
+ group: "inline"
22
+ }
23
+ }
24
+ });
25
+ describe("reactNodeViewPlugin", ()=>{
26
+ it("should create a unique key for each node", ()=>{
27
+ const editorState = EditorState.create({
28
+ doc: schema.topNodeType.create(null, [
29
+ schema.nodes.paragraph.create(),
30
+ schema.nodes.paragraph.create(),
31
+ schema.nodes.paragraph.create()
32
+ ]),
33
+ plugins: [
34
+ reactKeys()
35
+ ]
36
+ });
37
+ const pluginState = reactKeysPluginKey.getState(editorState);
38
+ expect(pluginState.posToKey.size).toBe(3);
39
+ });
40
+ it("should maintain key stability when possible", ()=>{
41
+ const initialEditorState = EditorState.create({
42
+ doc: schema.topNodeType.create(null, [
43
+ schema.nodes.paragraph.create({}, schema.text("Hello")),
44
+ schema.nodes.paragraph.create(),
45
+ schema.nodes.paragraph.create()
46
+ ]),
47
+ plugins: [
48
+ reactKeys()
49
+ ]
50
+ });
51
+ const initialPluginState = reactKeysPluginKey.getState(initialEditorState);
52
+ const nextEditorState = initialEditorState.apply(initialEditorState.tr.insertText(", world!", 6));
53
+ const nextPluginState = reactKeysPluginKey.getState(nextEditorState);
54
+ expect(Array.from(initialPluginState.keyToPos.keys())).toEqual(Array.from(nextPluginState.keyToPos.keys()));
55
+ });
56
+ it("should create unique keys for new nodes", ()=>{
57
+ const initialEditorState = EditorState.create({
58
+ doc: schema.topNodeType.create(null, [
59
+ schema.nodes.paragraph.create(),
60
+ schema.nodes.paragraph.create(),
61
+ schema.nodes.paragraph.create()
62
+ ]),
63
+ plugins: [
64
+ reactKeys()
65
+ ]
66
+ });
67
+ const initialPluginState = reactKeysPluginKey.getState(initialEditorState);
68
+ const nextEditorState = initialEditorState.apply(initialEditorState.tr.insert(0, schema.nodes.list.createAndFill()));
69
+ const nextPluginState = reactKeysPluginKey.getState(nextEditorState);
70
+ // Adds new keys for new nodes
71
+ expect(nextPluginState.keyToPos.size).toBe(5);
72
+ // Maintains keys for previous nodes that are still there
73
+ Array.from(initialPluginState.keyToPos.keys()).forEach((key)=>{
74
+ expect(Array.from(nextPluginState.keyToPos.keys())).toContain(key);
75
+ });
76
+ });
77
+ });
@@ -0,0 +1,161 @@
1
+ import { Selection } from "prosemirror-state";
2
+ import { browser } from "../browser.js";
3
+ import { parentNode, selectionCollapsed } from "../dom.js";
4
+ import { hasFocusAndSelection } from "./hasFocusAndSelection.js";
5
+ import { selectionFromDOM } from "./selectionFromDOM.js";
6
+ import { isEquivalentPosition, selectionToDOM } from "./selectionToDOM.js";
7
+ let SelectionState = class SelectionState {
8
+ anchorNode = null;
9
+ anchorOffset = 0;
10
+ focusNode = null;
11
+ focusOffset = 0;
12
+ set(sel) {
13
+ this.anchorNode = sel.anchorNode;
14
+ this.anchorOffset = sel.anchorOffset;
15
+ this.focusNode = sel.focusNode;
16
+ this.focusOffset = sel.focusOffset;
17
+ }
18
+ clear() {
19
+ this.anchorNode = this.focusNode = null;
20
+ }
21
+ eq(sel) {
22
+ return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset && sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset;
23
+ }
24
+ };
25
+ export class SelectionDOMObserver {
26
+ view;
27
+ flushingSoon;
28
+ currentSelection;
29
+ suppressingSelectionUpdates;
30
+ constructor(view){
31
+ this.view = view;
32
+ this.flushingSoon = -1;
33
+ this.currentSelection = new SelectionState();
34
+ this.suppressingSelectionUpdates = false;
35
+ this.view = view;
36
+ this.onSelectionChange = this.onSelectionChange.bind(this);
37
+ }
38
+ connectSelection() {
39
+ this.view.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
40
+ }
41
+ disconnectSelection() {
42
+ this.view.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
43
+ }
44
+ stop() {
45
+ this.disconnectSelection();
46
+ }
47
+ start() {
48
+ this.connectSelection();
49
+ }
50
+ suppressSelectionUpdates() {
51
+ this.suppressingSelectionUpdates = true;
52
+ setTimeout(()=>this.suppressingSelectionUpdates = false, 50);
53
+ }
54
+ setCurSelection() {
55
+ // @ts-expect-error Internal method
56
+ this.currentSelection.set(this.view.domSelectionRange());
57
+ }
58
+ ignoreSelectionChange(sel) {
59
+ if (!sel.focusNode) return true;
60
+ const ancestors = new Set();
61
+ let container;
62
+ for(let scan = sel.focusNode; scan; scan = parentNode(scan))ancestors.add(scan);
63
+ for(let scan = sel.anchorNode; scan; scan = parentNode(scan))if (ancestors.has(scan)) {
64
+ container = scan;
65
+ break;
66
+ }
67
+ // @ts-expect-error Internal property (docView)
68
+ const desc = container && this.view.docView.nearestDesc(container);
69
+ if (desc && desc.ignoreMutation({
70
+ type: "selection",
71
+ target: container?.nodeType == 3 ? container?.parentNode : container
72
+ })) {
73
+ this.setCurSelection();
74
+ return true;
75
+ }
76
+ return;
77
+ }
78
+ registerMutation() {
79
+ // pass
80
+ }
81
+ flushSoon() {
82
+ if (this.flushingSoon < 0) this.flushingSoon = window.setTimeout(()=>{
83
+ this.flushingSoon = -1;
84
+ this.flush();
85
+ }, 20);
86
+ }
87
+ updateSelection() {
88
+ const { view } = this;
89
+ const compositionID = // @ts-expect-error Internal property (input)
90
+ view.input.compositionPendingChanges || // @ts-expect-error Internal property (input)
91
+ (view.composing ? view.input.compositionID : 0);
92
+ // @ts-expect-error Internal property (input)
93
+ view.input.compositionPendingChanges = 0;
94
+ const origin = // @ts-expect-error Internal property (input)
95
+ view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null;
96
+ const newSel = selectionFromDOM(view, origin);
97
+ if (newSel && !view.state.selection.eq(newSel)) {
98
+ const tr = view.state.tr.setSelection(newSel);
99
+ if (origin == "pointer") tr.setMeta("pointer", true);
100
+ else if (origin == "key") tr.scrollIntoView();
101
+ if (compositionID) tr.setMeta("composition", compositionID);
102
+ view.dispatch(tr);
103
+ }
104
+ }
105
+ selectionToDOM() {
106
+ const { view } = this;
107
+ selectionToDOM(view);
108
+ // @ts-expect-error Internal property (domSelectionRange)
109
+ const sel = view.domSelectionRange();
110
+ this.currentSelection.set(sel);
111
+ }
112
+ flush() {
113
+ const { view } = this;
114
+ // @ts-expect-error Internal property (docView)
115
+ if (!view.docView || this.flushingSoon > -1) return;
116
+ // @ts-expect-error Internal property (domSelectionRange)
117
+ const sel = view.domSelectionRange();
118
+ const newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
119
+ let readSel = null;
120
+ // If it looks like the browser has reset the selection to the
121
+ // start of the document after focus, restore the selection from
122
+ // the state
123
+ if (newSel && // @ts-expect-error Internal property (input)
124
+ view.input.lastFocus > Date.now() - 200 && // @ts-expect-error Internal property (input)
125
+ Math.max(view.input.lastTouch, view.input.lastClick.time) < Date.now() - 300 && selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) && readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
126
+ // @ts-expect-error Internal property (input)
127
+ view.input.lastFocus = 0;
128
+ selectionToDOM(view);
129
+ this.currentSelection.set(sel);
130
+ // @ts-expect-error Internal property (scrollToSelection)
131
+ view.scrollToSelection();
132
+ } else if (newSel) {
133
+ this.updateSelection();
134
+ if (!this.currentSelection.eq(sel)) selectionToDOM(view);
135
+ this.currentSelection.set(sel);
136
+ }
137
+ }
138
+ forceFlush() {
139
+ if (this.flushingSoon > -1) {
140
+ window.clearTimeout(this.flushingSoon);
141
+ this.flushingSoon = -1;
142
+ this.flush();
143
+ }
144
+ }
145
+ onSelectionChange() {
146
+ if (!hasFocusAndSelection(this.view)) return;
147
+ if (this.view.composing) return;
148
+ if (this.suppressingSelectionUpdates) return selectionToDOM(this.view);
149
+ // Deletions on IE11 fire their events in the wrong order, giving
150
+ // us a selection change event before the DOM changes are
151
+ // reported.
152
+ if (browser.ie && browser.ie_version <= 11 && !this.view.state.selection.empty) {
153
+ // @ts-expect-error Internal method
154
+ const sel = this.view.domSelectionRange();
155
+ // Selection.isCollapsed isn't reliable on IE
156
+ if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
157
+ sel.anchorNode, sel.anchorOffset)) return this.flushSoon();
158
+ }
159
+ this.flush();
160
+ }
161
+ }
@@ -0,0 +1,17 @@
1
+ export function hasFocusAndSelection(view) {
2
+ if (view.editable && !view.hasFocus()) return false;
3
+ return hasSelection(view);
4
+ }
5
+ export function hasSelection(view) {
6
+ // @ts-expect-error Internal method
7
+ const sel = view.domSelectionRange();
8
+ if (!sel.anchorNode) return false;
9
+ try {
10
+ // Firefox will raise 'permission denied' errors when accessing
11
+ // properties of `sel.anchorNode` when it's in a generated CSS
12
+ // element.
13
+ return view.dom.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode) && (view.editable || view.dom.contains(sel.focusNode?.nodeType == 3 ? sel.focusNode?.parentNode : sel.focusNode));
14
+ } catch (_) {
15
+ return false;
16
+ }
17
+ }
@@ -0,0 +1,59 @@
1
+ import { NodeSelection, TextSelection } from "prosemirror-state";
2
+ import { isOnEdge, selectionCollapsed } from "../dom.js";
3
+ export function selectionBetween(view, $anchor, $head, bias) {
4
+ return view.someProp("createSelectionBetween", (f)=>f(view, $anchor, $head)) || TextSelection.between($anchor, $head, bias);
5
+ }
6
+ export function selectionFromDOM(view) {
7
+ let origin = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : null;
8
+ // @ts-expect-error Internal method
9
+ const domSel = view.domSelectionRange(), doc = view.state.doc;
10
+ if (!domSel.focusNode) return null;
11
+ // @ts-expect-error Internal method
12
+ let nearestDesc = view.docView.nearestDesc(domSel.focusNode);
13
+ const inWidget = nearestDesc && nearestDesc.size == 0;
14
+ // @ts-expect-error Internal method
15
+ let head = view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset, 1);
16
+ if (head < 0) return null;
17
+ let $head = doc.resolve(head), anchor, selection;
18
+ if (selectionCollapsed(domSel)) {
19
+ anchor = head;
20
+ while(nearestDesc && !nearestDesc.node)nearestDesc = nearestDesc.parent;
21
+ const nearestDescNode = nearestDesc.node;
22
+ if (nearestDesc && nearestDescNode.isAtom && NodeSelection.isSelectable(nearestDescNode) && nearestDesc.parent && !(nearestDescNode.isInline && isOnEdge(domSel.focusNode, domSel.focusOffset, nearestDesc.dom))) {
23
+ const pos = nearestDesc.posBefore;
24
+ selection = new NodeSelection(head == pos ? $head : doc.resolve(pos));
25
+ }
26
+ } else {
27
+ if (// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
28
+ domSel instanceof view.dom.ownerDocument.defaultView.Selection && domSel.rangeCount > 1) {
29
+ let min = head, max = head;
30
+ for(let i = 0; i < domSel.rangeCount; i++){
31
+ const range = domSel.getRangeAt(i);
32
+ min = Math.min(min, // @ts-expect-error Internal method
33
+ view.docView.posFromDOM(range.startContainer, range.startOffset, 1));
34
+ max = Math.max(max, // @ts-expect-error Internal method
35
+ view.docView.posFromDOM(range.endContainer, range.endOffset, -1));
36
+ }
37
+ if (min < 0) return null;
38
+ [anchor, head] = max == view.state.selection.anchor ? [
39
+ max,
40
+ min
41
+ ] : [
42
+ min,
43
+ max
44
+ ];
45
+ $head = doc.resolve(head);
46
+ } else {
47
+ // @ts-expect-error Internal method
48
+ anchor = view.docView.posFromDOM(// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
49
+ domSel.anchorNode, domSel.anchorOffset, 1);
50
+ }
51
+ if (anchor < 0) return null;
52
+ }
53
+ const $anchor = doc.resolve(anchor);
54
+ if (!selection) {
55
+ const bias = origin == "pointer" || view.state.selection.head < $head.pos && !inWidget ? 1 : -1;
56
+ selection = selectionBetween(view, $anchor, $head, bias);
57
+ }
58
+ return selection;
59
+ }
@@ -0,0 +1,196 @@
1
+ import { NodeSelection, TextSelection } from "prosemirror-state";
2
+ import { browser } from "../browser.js";
3
+ // Scans forward and backward through DOM positions equivalent to the
4
+ // given one to see if the two are in the same place (i.e. after a
5
+ // text node vs at the end of that text node)
6
+ export const isEquivalentPosition = function(node, off, targetNode, targetOff) {
7
+ return targetNode && (scanFor(node, off, targetNode, targetOff, -1) || scanFor(node, off, targetNode, targetOff, 1));
8
+ };
9
+ export function hasBlockDesc(dom) {
10
+ let desc;
11
+ for(let cur = dom; cur; cur = cur.parentNode)if (desc = cur.pmViewDesc) break;
12
+ return desc && desc.node && desc.node.isBlock && (desc.dom == dom || desc.contentDOM == dom);
13
+ }
14
+ const atomElements = /^(img|br|input|textarea|hr)$/i;
15
+ function scanFor(node, off, targetNode, targetOff, dir) {
16
+ for(;;){
17
+ if (node == targetNode && off == targetOff) return true;
18
+ if (off == (dir < 0 ? 0 : nodeSize(node))) {
19
+ const parent = node.parentNode;
20
+ if (!parent || parent.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName) || node.contentEditable == "false") return false;
21
+ off = domIndex(node) + (dir < 0 ? 0 : 1);
22
+ node = parent;
23
+ } else if (node.nodeType == 1) {
24
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25
+ node = node.childNodes[off + (dir < 0 ? -1 : 0)];
26
+ if (node.contentEditable == "false") return false;
27
+ off = dir < 0 ? nodeSize(node) : 0;
28
+ } else {
29
+ return false;
30
+ }
31
+ }
32
+ }
33
+ export const domIndex = function(node) {
34
+ let n = node;
35
+ for(let index = 0;; index++){
36
+ n = n.previousSibling;
37
+ if (!n) return index;
38
+ }
39
+ };
40
+ export function nodeSize(node) {
41
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
42
+ return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
43
+ }
44
+ export function syncNodeSelection(view, sel) {
45
+ const v = view;
46
+ if (sel instanceof NodeSelection) {
47
+ const desc = v.docView.descAt(sel.from);
48
+ if (desc != v.lastSelectedViewDesc) {
49
+ clearNodeSelection(v);
50
+ if (desc) desc.selectNode();
51
+ v.lastSelectedViewDesc = desc;
52
+ }
53
+ } else {
54
+ clearNodeSelection(v);
55
+ }
56
+ }
57
+ // Clear all DOM statefulness of the last node selection.
58
+ function clearNodeSelection(view) {
59
+ const v = view;
60
+ if (v.lastSelectedViewDesc) {
61
+ if (v.lastSelectedViewDesc.parent) v.lastSelectedViewDesc.deselectNode();
62
+ v.lastSelectedViewDesc = undefined;
63
+ }
64
+ }
65
+ export function hasSelection(view) {
66
+ const v = view;
67
+ const sel = v.domSelectionRange();
68
+ if (!sel.anchorNode) return false;
69
+ try {
70
+ // Firefox will raise 'permission denied' errors when accessing
71
+ // properties of `sel.anchorNode` when it's in a generated CSS
72
+ // element.
73
+ return v.dom.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode) && (v.editable || v.dom.contains(// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
74
+ sel.focusNode.nodeType == 3 ? sel.focusNode.parentNode : sel.focusNode));
75
+ } catch (_) {
76
+ return false;
77
+ }
78
+ }
79
+ function editorOwnsSelection(view) {
80
+ return view.editable ? view.hasFocus() : hasSelection(view) && document.activeElement && document.activeElement.contains(view.dom);
81
+ }
82
+ function selectCursorWrapper(view) {
83
+ const v = view;
84
+ const domSel = v.domSelection(), range = document.createRange();
85
+ if (!domSel) return;
86
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
87
+ const node = v.cursorWrapper.dom, img = node.nodeName == "IMG";
88
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
89
+ if (img) range.setStart(node.parentNode, domIndex(node) + 1);
90
+ else range.setStart(node, 0);
91
+ range.collapse(true);
92
+ domSel.removeAllRanges();
93
+ domSel.addRange(range);
94
+ // Kludge to kill 'control selection' in IE11 when selecting an
95
+ // invisible cursor wrapper, since that would result in those weird
96
+ // resize handles and a selection that considers the absolutely
97
+ // positioned wrapper, rather than the root editable node, the
98
+ // focused element.
99
+ if (!img && !v.state.selection.visible && browser.ie && browser.ie_version <= 11) {
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ node.disabled = true;
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ node.disabled = false;
104
+ }
105
+ }
106
+ function temporarilyEditableNear(view, pos) {
107
+ const v = view;
108
+ const { node, offset } = v.docView.domFromPos(pos, 0);
109
+ const after = offset < node.childNodes.length ? node.childNodes[offset] : null;
110
+ const before = offset ? node.childNodes[offset - 1] : null;
111
+ if (browser.safari && after && after.contentEditable == "false") return setEditable(after);
112
+ if ((!after || after.contentEditable == "false") && (!before || before.contentEditable == "false")) {
113
+ if (after) return setEditable(after);
114
+ else if (before) return setEditable(before);
115
+ }
116
+ return;
117
+ }
118
+ function setEditable(element) {
119
+ element.contentEditable = "true";
120
+ if (browser.safari && element.draggable) {
121
+ element.draggable = false;
122
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
+ element.wasDraggable = true;
124
+ }
125
+ return element;
126
+ }
127
+ function resetEditable(element) {
128
+ element.contentEditable = "false";
129
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
+ if (element.wasDraggable) {
131
+ element.draggable = true;
132
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
133
+ element.wasDraggable = null;
134
+ }
135
+ }
136
+ function removeClassOnSelectionChange(view) {
137
+ const v = view;
138
+ const doc = v.dom.ownerDocument;
139
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
140
+ doc.removeEventListener("selectionchange", v.input.hideSelectionGuard);
141
+ const domSel = v.domSelectionRange();
142
+ const node = domSel.anchorNode, offset = domSel.anchorOffset;
143
+ doc.addEventListener("selectionchange", v.input.hideSelectionGuard = ()=>{
144
+ if (domSel.anchorNode != node || domSel.anchorOffset != offset) {
145
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
146
+ doc.removeEventListener("selectionchange", v.input.hideSelectionGuard);
147
+ setTimeout(()=>{
148
+ if (!editorOwnsSelection(v) || v.state.selection.visible) v.dom.classList.remove("ProseMirror-hideselection");
149
+ }, 20);
150
+ }
151
+ });
152
+ }
153
+ const brokenSelectBetweenUneditable = browser.safari || browser.chrome && browser.chrome_version < 63;
154
+ export function selectionToDOM(view) {
155
+ let force = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : false;
156
+ const v = view;
157
+ const sel = v.state.selection;
158
+ syncNodeSelection(v, sel);
159
+ if (!editorOwnsSelection(v)) return;
160
+ // The delayed drag selection causes issues with Cell Selections
161
+ // in Safari. And the drag selection delay is to workarond issues
162
+ // which only present in Chrome.
163
+ if (!force && v.input.mouseDown && v.input.mouseDown.allowDefault && browser.chrome) {
164
+ const domSel = v.domSelectionRange(), curSel = v.domObserver.currentSelection;
165
+ if (domSel.anchorNode && curSel.anchorNode && isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset, curSel.anchorNode, curSel.anchorOffset)) {
166
+ v.input.mouseDown.delayedSelectionSync = true;
167
+ v.domObserver.setCurSelection();
168
+ return;
169
+ }
170
+ }
171
+ v.domObserver.disconnectSelection();
172
+ if (v.cursorWrapper) {
173
+ selectCursorWrapper(v);
174
+ } else {
175
+ const { anchor, head } = sel;
176
+ let resetEditableFrom;
177
+ let resetEditableTo;
178
+ if (brokenSelectBetweenUneditable && !(sel instanceof TextSelection)) {
179
+ if (!sel.$from.parent.inlineContent) resetEditableFrom = temporarilyEditableNear(v, sel.from);
180
+ if (!sel.empty && !sel.$from.parent.inlineContent) resetEditableTo = temporarilyEditableNear(v, sel.to);
181
+ }
182
+ v.docView.setSelection(anchor, head, v, force);
183
+ if (brokenSelectBetweenUneditable) {
184
+ if (resetEditableFrom) resetEditable(resetEditableFrom);
185
+ if (resetEditableTo) resetEditable(resetEditableTo);
186
+ }
187
+ if (sel.visible) {
188
+ v.dom.classList.remove("ProseMirror-hideselection");
189
+ } else {
190
+ v.dom.classList.add("ProseMirror-hideselection");
191
+ if ("onselectionchange" in document) removeClassOnSelectionChange(v);
192
+ }
193
+ }
194
+ v.domObserver.setCurSelection();
195
+ v.domObserver.connectSelection();
196
+ }
@@ -0,0 +1,82 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */ /**
2
+ * @fileoverview
3
+ *
4
+ * Stubs for ProseMirror View during SSR. These are extremely
5
+ * barebones, because they _do not need to actually work_. They
6
+ * just need to prevent errors from being thrown when ProseMirror
7
+ * View attemps to access these APIs while constructing the
8
+ * initial EditorView. None of these APIs are necessary for SSR to
9
+ * work properly, so it's fine that they're all no-ops.
10
+ */ let ClassList = class ClassList {
11
+ add() {}
12
+ remove() {}
13
+ };
14
+ let ElementStub = class ElementStub {
15
+ get parent() {
16
+ return new ElementStub();
17
+ }
18
+ get parentNode() {
19
+ return new ElementStub();
20
+ }
21
+ nodeName = "div";
22
+ appendChild() {
23
+ return new ElementStub();
24
+ }
25
+ setAttribute() {}
26
+ hasAttribute() {
27
+ return false;
28
+ }
29
+ insertBefore() {}
30
+ get classList() {
31
+ return new ClassList();
32
+ }
33
+ get ownerDocument() {
34
+ return new DocumentStub();
35
+ }
36
+ style = {};
37
+ addEventListener() {}
38
+ removeEventListener() {}
39
+ replaceChildren() {}
40
+ };
41
+ let DocumentStub = class DocumentStub {
42
+ createElement() {
43
+ return new ElementStub();
44
+ }
45
+ addEventListener() {}
46
+ removeEventListener() {}
47
+ get documentElement() {
48
+ return new ElementStub();
49
+ }
50
+ };
51
+ /**
52
+ * Sets up tiny no-op stubs for the global window and document.
53
+ * These are used to prevent errors from being thrown when ProseMirror's
54
+ * EditorView attempts to access the DOM in its constructor during SSR.
55
+ *
56
+ * Returns a cleanup function that resets the window and document back
57
+ * to their original values (undefined).
58
+ */ export function setSsrStubs() {
59
+ const prevWindow = globalThis.window;
60
+ // @ts-expect-error HACK - EditorView checks for window.MutationObserver
61
+ // in its constructor, which breaks SSR. We temporarily set window
62
+ // to an empty object to prevent an error from being thrown, and then
63
+ // clean it up so that other isomorphic code doesn't get confused about
64
+ // whether there's a functioning global window object
65
+ globalThis.window ??= {
66
+ visualViewport: null
67
+ };
68
+ const prevDocument = globalThis.document;
69
+ // @ts-expect-error HACK: This is only used during SSR, and only
70
+ // to prevent outright errors when ProseMirror View attempts to
71
+ // access document properties either on import or when constructing
72
+ // the EditorView.
73
+ globalThis.document ??= new DocumentStub();
74
+ return function cleanupSsrStubs() {
75
+ if (globalThis.window !== prevWindow) {
76
+ globalThis.window = prevWindow;
77
+ }
78
+ if (globalThis.document !== prevDocument) {
79
+ globalThis.document = prevDocument;
80
+ }
81
+ };
82
+ }