@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,135 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Schema } from "prosemirror-model";
2
- import { EditorState } from "prosemirror-state";
3
- import { findWrapping } from "prosemirror-transform";
4
- import { react, reactPluginKey } from "../react.js";
5
- const schema = new Schema({
6
- nodes: {
7
- doc: {
8
- content: "block+"
9
- },
10
- paragraph: {
11
- group: "block",
12
- content: "inline*"
13
- },
14
- list: {
15
- group: "block",
16
- content: "list_item+"
17
- },
18
- list_item: {
19
- content: "paragraph+"
20
- },
21
- text: {
22
- group: "inline"
23
- }
24
- }
25
- });
26
- describe("reactNodeViewPlugin", ()=>{
27
- it("should create a unique key for each node", ()=>{
28
- const editorState = EditorState.create({
29
- doc: schema.topNodeType.create(null, [
30
- schema.nodes.paragraph.create(),
31
- schema.nodes.paragraph.create(),
32
- schema.nodes.paragraph.create()
33
- ]),
34
- plugins: [
35
- react()
36
- ]
37
- });
38
- const pluginState = reactPluginKey.getState(editorState);
39
- expect(pluginState.posToKey.size).toBe(3);
40
- });
41
- it("should maintain key stability when possible", ()=>{
42
- const initialEditorState = EditorState.create({
43
- doc: schema.topNodeType.create(null, [
44
- schema.nodes.paragraph.create(),
45
- schema.nodes.paragraph.create(),
46
- schema.nodes.paragraph.create()
47
- ]),
48
- plugins: [
49
- react()
50
- ]
51
- });
52
- const initialPluginState = reactPluginKey.getState(initialEditorState);
53
- const nextEditorState = initialEditorState.apply(initialEditorState.tr.insertText("Hello, world!", 1));
54
- const nextPluginState = reactPluginKey.getState(nextEditorState);
55
- expect(Array.from(nextPluginState.keyToPos.keys())).toEqual(Array.from(initialPluginState.keyToPos.keys()));
56
- });
57
- it("should create unique keys for new nodes", ()=>{
58
- const initialEditorState = EditorState.create({
59
- doc: schema.topNodeType.create(null, [
60
- schema.nodes.paragraph.create(),
61
- schema.nodes.paragraph.create(),
62
- schema.nodes.paragraph.create()
63
- ]),
64
- plugins: [
65
- react()
66
- ]
67
- });
68
- const initialPluginState = reactPluginKey.getState(initialEditorState);
69
- const nextEditorState = initialEditorState.apply(initialEditorState.tr.insert(0, schema.nodes.list.createAndFill()));
70
- const nextPluginState = reactPluginKey.getState(nextEditorState);
71
- // Adds new keys for new nodes
72
- expect(nextPluginState.keyToPos.size).toBe(6);
73
- // Maintains keys for previous nodes that are still there
74
- Array.from(initialPluginState.keyToPos.keys()).forEach((key)=>{
75
- expect(Array.from(nextPluginState.keyToPos.keys())).toContain(key);
76
- });
77
- });
78
- it("should maintain key stability when splitting a node", ()=>{
79
- const initialEditorState = EditorState.create({
80
- doc: schema.topNodeType.create(null, [
81
- schema.nodes.list.create(null, [
82
- schema.nodes.list_item.create(null, [
83
- schema.nodes.paragraph.create(null, [
84
- schema.text("first")
85
- ])
86
- ])
87
- ])
88
- ]),
89
- plugins: [
90
- react()
91
- ]
92
- });
93
- const initialPluginState = reactPluginKey.getState(initialEditorState);
94
- const nextEditorState = initialEditorState.apply(initialEditorState.tr.insert(1, schema.nodes.list_item.create(null, [
95
- schema.nodes.paragraph.create(null, [
96
- schema.text("second")
97
- ])
98
- ])));
99
- const nextPluginState = reactPluginKey.getState(nextEditorState);
100
- // The new list item was inserted before the original one,
101
- // pushing it further into the document. The original list
102
- // item should keep its original key, and the new list item
103
- // should be assigned a new one
104
- expect(nextPluginState.posToKey.get(11)).toBe(initialPluginState.posToKey.get(1));
105
- expect(nextPluginState.posToKey.get(1)).not.toBe(initialPluginState.posToKey.get(1));
106
- });
107
- it("should maintain key stability when wrapping a node", ()=>{
108
- const initialEditorState = EditorState.create({
109
- doc: schema.topNodeType.create(null, [
110
- schema.nodes.paragraph.create(null, [
111
- schema.text("content")
112
- ])
113
- ]),
114
- plugins: [
115
- react()
116
- ]
117
- });
118
- const initialPluginState = reactPluginKey.getState(initialEditorState);
119
- const start = 1;
120
- const end = 9;
121
- const tr = initialEditorState.tr.delete(start, end);
122
- const $start = tr.doc.resolve(start);
123
- const range = $start.blockRange();
124
- const wrapping = range && findWrapping(range, schema.nodes.list, null);
125
- tr.wrap(range, wrapping);
126
- const nextEditorState = initialEditorState.apply(tr);
127
- const nextPluginState = reactPluginKey.getState(nextEditorState);
128
- // The new list and list item nodes were wrapped around the
129
- // paragraph, pushing it further into the document. The paragraph
130
- // should keep its original key, and the new nodes
131
- // should be assigned a new one
132
- expect(nextPluginState.posToKey.get(2)).toBe(initialPluginState.posToKey.get(0));
133
- expect(nextPluginState.posToKey.get(0)).not.toBe(initialPluginState.posToKey.get(0));
134
- });
135
- });
@@ -1,64 +0,0 @@
1
- import { Plugin, PluginKey } from "prosemirror-state";
2
- /**
3
- * This is a stand-in for the doc node itself, which doesn't have a
4
- * unique position to map to.
5
- */ export const ROOT_NODE_KEY = Symbol("@nytimes/react-prosemirror/root-node-key");
6
- export function createNodeKey() {
7
- return Math.floor(Math.random() * 0xffffff).toString(16);
8
- }
9
- export const reactPluginKey = new PluginKey("@nytimes/react-prosemirror/react");
10
- /**
11
- * Tracks a unique key for each (non-text) node in the
12
- * document, identified by its current position. Keys are
13
- * (mostly) stable across transaction applications. The
14
- * key for a given node can be accessed by that node's
15
- * current position in the document, and vice versa.
16
- */ export function react() {
17
- return new Plugin({
18
- key: reactPluginKey,
19
- state: {
20
- init (_, state) {
21
- const next = {
22
- posToKey: new Map(),
23
- keyToPos: new Map()
24
- };
25
- state.doc.descendants((node, pos)=>{
26
- if (node.isText) return false;
27
- const key = createNodeKey();
28
- next.posToKey.set(pos, key);
29
- next.keyToPos.set(key, pos);
30
- return true;
31
- });
32
- return next;
33
- },
34
- /**
35
- * Keeps node keys (mostly) stable across transactions.
36
- *
37
- * To accomplish this, we map each node position backwards
38
- * through the transaction to identify its previous position,
39
- * and thereby retrieve its previous key.
40
- */ apply (tr, value, _, newState) {
41
- if (!tr.docChanged) return value;
42
- const next = {
43
- posToKey: new Map(),
44
- keyToPos: new Map()
45
- };
46
- for (const [pos, key] of value.posToKey.entries()){
47
- const { pos: newPos , deleted } = tr.mapping.mapResult(pos);
48
- if (deleted) continue;
49
- next.posToKey.set(newPos, key);
50
- next.keyToPos.set(key, newPos);
51
- }
52
- newState.doc.descendants((node, pos)=>{
53
- if (node.isText) return false;
54
- if (next.posToKey.has(pos)) return true;
55
- const key = createNodeKey();
56
- next.posToKey.set(pos, key);
57
- next.keyToPos.set(key, pos);
58
- return true;
59
- });
60
- return next;
61
- }
62
- }
63
- });
64
- }
@@ -1,161 +0,0 @@
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
- }
@@ -1,17 +0,0 @@
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
- }
@@ -1,59 +0,0 @@
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
- }
@@ -1,196 +0,0 @@
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
- }