@handlewithcare/react-prosemirror 2.0.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 (209) hide show
  1. package/LICENSE.txt +12 -0
  2. package/README.md +705 -0
  3. package/dist/cjs/browser.js +53 -0
  4. package/dist/cjs/components/ChildNodeViews.js +376 -0
  5. package/dist/cjs/components/CursorWrapper.js +91 -0
  6. package/dist/cjs/components/CustomNodeView.js +79 -0
  7. package/dist/cjs/components/DocNodeView.js +104 -0
  8. package/dist/cjs/components/LayoutGroup.js +111 -0
  9. package/dist/cjs/components/MarkView.js +115 -0
  10. package/dist/cjs/components/NativeWidgetView.js +109 -0
  11. package/dist/cjs/components/NodeView.js +196 -0
  12. package/dist/cjs/components/NodeViewComponentProps.js +4 -0
  13. package/dist/cjs/components/OutputSpec.js +88 -0
  14. package/dist/cjs/components/ProseMirror.js +103 -0
  15. package/dist/cjs/components/ProseMirrorDoc.js +92 -0
  16. package/dist/cjs/components/SeparatorHackView.js +100 -0
  17. package/dist/cjs/components/TextNodeView.js +112 -0
  18. package/dist/cjs/components/TrailingHackView.js +90 -0
  19. package/dist/cjs/components/WidgetView.js +95 -0
  20. package/dist/cjs/components/WidgetViewComponentProps.js +4 -0
  21. package/dist/cjs/components/__tests__/ProseMirror.composition.test.js +398 -0
  22. package/dist/cjs/components/__tests__/ProseMirror.domchange.test.js +270 -0
  23. package/dist/cjs/components/__tests__/ProseMirror.draw-decoration.test.js +1010 -0
  24. package/dist/cjs/components/__tests__/ProseMirror.draw.test.js +337 -0
  25. package/dist/cjs/components/__tests__/ProseMirror.node-view.test.js +315 -0
  26. package/dist/cjs/components/__tests__/ProseMirror.selection.test.js +444 -0
  27. package/dist/cjs/components/__tests__/ProseMirror.test.js +382 -0
  28. package/dist/cjs/contexts/ChildDescriptorsContext.js +19 -0
  29. package/dist/cjs/contexts/EditorContext.js +12 -0
  30. package/dist/cjs/contexts/EditorStateContext.js +12 -0
  31. package/dist/cjs/contexts/LayoutGroupContext.js +12 -0
  32. package/dist/cjs/contexts/NodeViewContext.js +12 -0
  33. package/dist/cjs/contexts/SelectNodeContext.js +12 -0
  34. package/dist/cjs/contexts/StopEventContext.js +12 -0
  35. package/dist/cjs/contexts/__tests__/DeferredLayoutEffects.test.js +141 -0
  36. package/dist/cjs/decorations/ReactWidgetType.js +58 -0
  37. package/dist/cjs/decorations/computeDocDeco.js +44 -0
  38. package/dist/cjs/decorations/internalTypes.js +4 -0
  39. package/dist/cjs/decorations/iterDeco.js +79 -0
  40. package/dist/cjs/decorations/viewDecorations.js +163 -0
  41. package/dist/cjs/dom.js +142 -0
  42. package/dist/cjs/hooks/__tests__/useEditorViewLayoutEffect.test.js +108 -0
  43. package/dist/cjs/hooks/useClientOnly.js +18 -0
  44. package/dist/cjs/hooks/useComponentEventListeners.js +39 -0
  45. package/dist/cjs/hooks/useEditor.js +287 -0
  46. package/dist/cjs/hooks/useEditorEffect.js +35 -0
  47. package/dist/cjs/hooks/useEditorEventCallback.js +33 -0
  48. package/dist/cjs/hooks/useEditorEventListener.js +34 -0
  49. package/dist/cjs/hooks/useEditorState.js +16 -0
  50. package/dist/cjs/hooks/useForceUpdate.js +15 -0
  51. package/dist/cjs/hooks/useLayoutGroupEffect.js +19 -0
  52. package/dist/cjs/hooks/useNodeViewDescriptor.js +115 -0
  53. package/dist/cjs/hooks/useReactKeys.js +17 -0
  54. package/dist/cjs/hooks/useSelectNode.js +28 -0
  55. package/dist/cjs/hooks/useStopEvent.js +24 -0
  56. package/dist/cjs/index.js +53 -0
  57. package/dist/cjs/package.json +3 -0
  58. package/dist/cjs/plugins/__tests__/reactKeys.test.js +81 -0
  59. package/dist/cjs/plugins/beforeInputPlugin.js +143 -0
  60. package/dist/cjs/plugins/componentEventListeners.js +35 -0
  61. package/dist/cjs/plugins/componentEventListenersPlugin.js +35 -0
  62. package/dist/cjs/plugins/reactKeys.js +96 -0
  63. package/dist/cjs/props.js +269 -0
  64. package/dist/cjs/selection/SelectionDOMObserver.js +174 -0
  65. package/dist/cjs/selection/hasFocusAndSelection.js +35 -0
  66. package/dist/cjs/selection/selectionFromDOM.js +77 -0
  67. package/dist/cjs/selection/selectionToDOM.js +226 -0
  68. package/dist/cjs/ssr.js +85 -0
  69. package/dist/cjs/testing/editorViewTestHelpers.js +111 -0
  70. package/dist/cjs/testing/setupProseMirrorView.js +94 -0
  71. package/dist/cjs/viewdesc.js +664 -0
  72. package/dist/esm/browser.js +43 -0
  73. package/dist/esm/components/ChildNodeViews.js +318 -0
  74. package/dist/esm/components/CursorWrapper.js +40 -0
  75. package/dist/esm/components/CustomNodeView.js +28 -0
  76. package/dist/esm/components/DocNodeView.js +53 -0
  77. package/dist/esm/components/LayoutGroup.js +66 -0
  78. package/dist/esm/components/MarkView.js +64 -0
  79. package/dist/esm/components/NativeWidgetView.js +58 -0
  80. package/dist/esm/components/NodeView.js +145 -0
  81. package/dist/esm/components/NodeViewComponentProps.js +1 -0
  82. package/dist/esm/components/OutputSpec.js +38 -0
  83. package/dist/esm/components/ProseMirror.js +52 -0
  84. package/dist/esm/components/ProseMirrorDoc.js +34 -0
  85. package/dist/esm/components/SeparatorHackView.js +49 -0
  86. package/dist/esm/components/TextNodeView.js +102 -0
  87. package/dist/esm/components/TrailingHackView.js +39 -0
  88. package/dist/esm/components/WidgetView.js +44 -0
  89. package/dist/esm/components/WidgetViewComponentProps.js +1 -0
  90. package/dist/esm/components/__tests__/ProseMirror.composition.test.js +395 -0
  91. package/dist/esm/components/__tests__/ProseMirror.domchange.test.js +266 -0
  92. package/dist/esm/components/__tests__/ProseMirror.draw-decoration.test.js +967 -0
  93. package/dist/esm/components/__tests__/ProseMirror.draw.test.js +294 -0
  94. package/dist/esm/components/__tests__/ProseMirror.node-view.test.js +272 -0
  95. package/dist/esm/components/__tests__/ProseMirror.selection.test.js +440 -0
  96. package/dist/esm/components/__tests__/ProseMirror.test.js +339 -0
  97. package/dist/esm/contexts/ChildDescriptorsContext.js +9 -0
  98. package/dist/esm/contexts/EditorContext.js +7 -0
  99. package/dist/esm/contexts/EditorStateContext.js +2 -0
  100. package/dist/esm/contexts/LayoutGroupContext.js +2 -0
  101. package/dist/esm/contexts/NodeViewContext.js +2 -0
  102. package/dist/esm/contexts/SelectNodeContext.js +2 -0
  103. package/dist/esm/contexts/StopEventContext.js +2 -0
  104. package/dist/esm/contexts/__tests__/DeferredLayoutEffects.test.js +98 -0
  105. package/dist/esm/decorations/ReactWidgetType.js +40 -0
  106. package/dist/esm/decorations/computeDocDeco.js +44 -0
  107. package/dist/esm/decorations/internalTypes.js +1 -0
  108. package/dist/esm/decorations/iterDeco.js +73 -0
  109. package/dist/esm/decorations/viewDecorations.js +163 -0
  110. package/dist/esm/dom.js +105 -0
  111. package/dist/esm/hooks/__tests__/useEditorViewLayoutEffect.test.js +99 -0
  112. package/dist/esm/hooks/useClientOnly.js +8 -0
  113. package/dist/esm/hooks/useComponentEventListeners.js +54 -0
  114. package/dist/esm/hooks/useEditor.js +278 -0
  115. package/dist/esm/hooks/useEditorEffect.js +38 -0
  116. package/dist/esm/hooks/useEditorEventCallback.js +35 -0
  117. package/dist/esm/hooks/useEditorEventListener.js +28 -0
  118. package/dist/esm/hooks/useEditorState.js +8 -0
  119. package/dist/esm/hooks/useForceUpdate.js +8 -0
  120. package/dist/esm/hooks/useLayoutGroupEffect.js +9 -0
  121. package/dist/esm/hooks/useNodeViewDescriptor.js +105 -0
  122. package/dist/esm/hooks/useReactKeys.js +7 -0
  123. package/dist/esm/hooks/useSelectNode.js +18 -0
  124. package/dist/esm/hooks/useStopEvent.js +14 -0
  125. package/dist/esm/index.js +11 -0
  126. package/dist/esm/plugins/__tests__/reactKeys.test.js +77 -0
  127. package/dist/esm/plugins/beforeInputPlugin.js +133 -0
  128. package/dist/esm/plugins/componentEventListeners.js +25 -0
  129. package/dist/esm/plugins/componentEventListenersPlugin.js +25 -0
  130. package/dist/esm/plugins/reactKeys.js +81 -0
  131. package/dist/esm/props.js +251 -0
  132. package/dist/esm/selection/SelectionDOMObserver.js +164 -0
  133. package/dist/esm/selection/hasFocusAndSelection.js +17 -0
  134. package/dist/esm/selection/selectionFromDOM.js +59 -0
  135. package/dist/esm/selection/selectionToDOM.js +196 -0
  136. package/dist/esm/ssr.js +82 -0
  137. package/dist/esm/testing/editorViewTestHelpers.js +88 -0
  138. package/dist/esm/testing/setupProseMirrorView.js +76 -0
  139. package/dist/esm/viewdesc.js +654 -0
  140. package/dist/tsconfig.tsbuildinfo +1 -0
  141. package/dist/types/browser.d.ts +15 -0
  142. package/dist/types/components/ChildNodeViews.d.ts +9 -0
  143. package/dist/types/components/CursorWrapper.d.ts +5 -0
  144. package/dist/types/components/CustomNodeView.d.ts +21 -0
  145. package/dist/types/components/DocNodeView.d.ts +20 -0
  146. package/dist/types/components/LayoutGroup.d.ts +12 -0
  147. package/dist/types/components/MarkView.d.ts +9 -0
  148. package/dist/types/components/NativeWidgetView.d.ts +8 -0
  149. package/dist/types/components/NodeView.d.ts +11 -0
  150. package/dist/types/components/NodeViewComponentProps.d.ts +12 -0
  151. package/dist/types/components/OutputSpec.d.ts +8 -0
  152. package/dist/types/components/ProseMirror.d.ts +15 -0
  153. package/dist/types/components/ProseMirrorDoc.d.ts +10 -0
  154. package/dist/types/components/SeparatorHackView.d.ts +6 -0
  155. package/dist/types/components/TextNodeView.d.ts +23 -0
  156. package/dist/types/components/TrailingHackView.d.ts +6 -0
  157. package/dist/types/components/WidgetView.d.ts +8 -0
  158. package/dist/types/components/WidgetViewComponentProps.d.ts +6 -0
  159. package/dist/types/components/__tests__/ProseMirror.composition.test.d.ts +1 -0
  160. package/dist/types/components/__tests__/ProseMirror.domchange.test.d.ts +1 -0
  161. package/dist/types/components/__tests__/ProseMirror.draw-decoration.test.d.ts +1 -0
  162. package/dist/types/components/__tests__/ProseMirror.draw.test.d.ts +1 -0
  163. package/dist/types/components/__tests__/ProseMirror.node-view.test.d.ts +1 -0
  164. package/dist/types/components/__tests__/ProseMirror.selection.test.d.ts +1 -0
  165. package/dist/types/components/__tests__/ProseMirror.test.d.ts +1 -0
  166. package/dist/types/contexts/ChildDescriptorsContext.d.ts +6 -0
  167. package/dist/types/contexts/EditorContext.d.ts +14 -0
  168. package/dist/types/contexts/EditorStateContext.d.ts +2 -0
  169. package/dist/types/contexts/LayoutGroupContext.d.ts +5 -0
  170. package/dist/types/contexts/NodeViewContext.d.ts +6 -0
  171. package/dist/types/contexts/SelectNodeContext.d.ts +3 -0
  172. package/dist/types/contexts/StopEventContext.d.ts +3 -0
  173. package/dist/types/contexts/__tests__/DeferredLayoutEffects.test.d.ts +1 -0
  174. package/dist/types/decorations/ReactWidgetType.d.ts +39 -0
  175. package/dist/types/decorations/computeDocDeco.d.ts +13 -0
  176. package/dist/types/decorations/internalTypes.d.ts +16 -0
  177. package/dist/types/decorations/iterDeco.d.ts +3 -0
  178. package/dist/types/decorations/viewDecorations.d.ts +13 -0
  179. package/dist/types/dom.d.ts +22 -0
  180. package/dist/types/hooks/__tests__/useEditorViewLayoutEffect.test.d.ts +1 -0
  181. package/dist/types/hooks/useClientOnly.d.ts +1 -0
  182. package/dist/types/hooks/useComponentEventListeners.d.ts +33 -0
  183. package/dist/types/hooks/useEditor.d.ts +66 -0
  184. package/dist/types/hooks/useEditorEffect.d.ts +17 -0
  185. package/dist/types/hooks/useEditorEventCallback.d.ts +15 -0
  186. package/dist/types/hooks/useEditorEventListener.d.ts +8 -0
  187. package/dist/types/hooks/useEditorState.d.ts +5 -0
  188. package/dist/types/hooks/useForceUpdate.d.ts +5 -0
  189. package/dist/types/hooks/useLayoutGroupEffect.d.ts +3 -0
  190. package/dist/types/hooks/useNodeViewDescriptor.d.ts +11 -0
  191. package/dist/types/hooks/useReactKeys.d.ts +5 -0
  192. package/dist/types/hooks/useSelectNode.d.ts +1 -0
  193. package/dist/types/hooks/useStopEvent.d.ts +2 -0
  194. package/dist/types/index.d.ts +12 -0
  195. package/dist/types/plugins/__tests__/reactKeys.test.d.ts +1 -0
  196. package/dist/types/plugins/beforeInputPlugin.d.ts +3 -0
  197. package/dist/types/plugins/componentEventListeners.d.ts +4 -0
  198. package/dist/types/plugins/componentEventListenersPlugin.d.ts +4 -0
  199. package/dist/types/plugins/reactKeys.d.ts +19 -0
  200. package/dist/types/props.d.ts +1174 -0
  201. package/dist/types/selection/SelectionDOMObserver.d.ts +34 -0
  202. package/dist/types/selection/hasFocusAndSelection.d.ts +3 -0
  203. package/dist/types/selection/selectionFromDOM.d.ts +4 -0
  204. package/dist/types/selection/selectionToDOM.d.ts +9 -0
  205. package/dist/types/ssr.d.ts +19 -0
  206. package/dist/types/testing/editorViewTestHelpers.d.ts +23 -0
  207. package/dist/types/testing/setupProseMirrorView.d.ts +2 -0
  208. package/dist/types/viewdesc.d.ts +131 -0
  209. package/package.json +113 -0
@@ -0,0 +1,163 @@
1
+ import { DecorationSet } from "prosemirror-view";
2
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
+ const none = [], noSpec = {};
4
+ const empty = DecorationSet.empty;
5
+ // An abstraction that allows the code dealing with decorations to
6
+ // treat multiple DecorationSet objects as if it were a single object
7
+ // with (a subset of) the same interface.
8
+ let DecorationGroup = class DecorationGroup {
9
+ members;
10
+ constructor(members){
11
+ this.members = members;
12
+ }
13
+ map(mapping, doc) {
14
+ const mappedDecos = this.members.map((member)=>member.map(mapping, doc, noSpec));
15
+ return DecorationGroup.from(mappedDecos);
16
+ }
17
+ forChild(offset, child) {
18
+ if (child.isLeaf) return DecorationSet.empty;
19
+ let found = [];
20
+ for(let i = 0; i < this.members.length; i++){
21
+ const result = this.members[i].forChild(offset, child);
22
+ if (result == empty) continue;
23
+ if (result instanceof DecorationGroup) found = found.concat(result.members);
24
+ else found.push(result);
25
+ }
26
+ return DecorationGroup.from(found);
27
+ }
28
+ eq(other) {
29
+ if (!(other instanceof DecorationGroup) || other.members.length != this.members.length) return false;
30
+ for(let i = 0; i < this.members.length; i++)if (!this.members[i].eq(other.members[i])) return false;
31
+ return true;
32
+ }
33
+ locals(node) {
34
+ let result, sorted = true;
35
+ for(let i = 0; i < this.members.length; i++){
36
+ const locals = this.members[i].localsInner(node);
37
+ if (!locals.length) continue;
38
+ if (!result) {
39
+ result = locals;
40
+ } else {
41
+ if (sorted) {
42
+ result = result.slice();
43
+ sorted = false;
44
+ }
45
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
46
+ for(let j = 0; j < locals.length; j++)result.push(locals[j]);
47
+ }
48
+ }
49
+ return result ? removeOverlap(sorted ? result : result.sort(byPos)) : none;
50
+ }
51
+ // Create a group for the given array of decoration sets, or return
52
+ // a single set when possible.
53
+ static from(members) {
54
+ switch(members.length){
55
+ case 0:
56
+ return empty;
57
+ case 1:
58
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
59
+ return members[0];
60
+ default:
61
+ return new DecorationGroup(members.every((m)=>m instanceof DecorationSet) ? members : members.reduce((r, m)=>r.concat(m instanceof DecorationSet ? m : m.members), []));
62
+ }
63
+ }
64
+ forEachSet(f) {
65
+ for(let i = 0; i < this.members.length; i++)// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
66
+ this.members[i].forEachSet(f);
67
+ }
68
+ };
69
+ // Used to sort decorations so that ones with a low start position
70
+ // come first, and within a set with the same start position, those
71
+ // with an smaller end position come first.
72
+ function byPos(a, b) {
73
+ return a.from - b.from || a.to - b.to;
74
+ }
75
+ // Scan a sorted array of decorations for partially overlapping spans,
76
+ // and split those so that only fully overlapping spans are left (to
77
+ // make subsequent rendering easier). Will return the input array if
78
+ // no partially overlapping spans are found (the common case).
79
+ function removeOverlap(spans) {
80
+ let working = spans;
81
+ for(let i = 0; i < working.length - 1; i++){
82
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
83
+ const span = working[i];
84
+ if (span.from != span.to) for(let j = i + 1; j < working.length; j++){
85
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
86
+ const next = working[j];
87
+ if (next.from == span.from) {
88
+ if (next.to != span.to) {
89
+ if (working == spans) working = spans.slice();
90
+ // Followed by a partially overlapping larger span. Split that
91
+ // span.
92
+ working[j] = next.copy(next.from, span.to);
93
+ insertAhead(working, j + 1, next.copy(span.to, next.to));
94
+ }
95
+ continue;
96
+ } else {
97
+ if (next.from < span.to) {
98
+ if (working == spans) working = spans.slice();
99
+ // The end of this one overlaps with a subsequent span. Split
100
+ // this one.
101
+ working[i] = span.copy(span.from, next.from);
102
+ insertAhead(working, j, span.copy(next.from, span.to));
103
+ }
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ return working;
109
+ }
110
+ function insertAhead(array, i, deco) {
111
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
112
+ while(i < array.length && byPos(deco, array[i]) > 0)i++;
113
+ array.splice(i, 0, deco);
114
+ }
115
+ const ViewDecorationsCache = new WeakMap();
116
+ /**
117
+ * Produces the DecorationSource for the current state, based
118
+ * on the decorations editor prop.
119
+ *
120
+ * The return value of this function is memoized; if it is to
121
+ * return an equivalent value to the last time it was called for
122
+ * a given EditorView, it will return exactly that previous value.
123
+ *
124
+ * This makes it safe to call in a React render function, even
125
+ * if its result is used in a dependencies array for a hook.
126
+ */ export function viewDecorations(view, cursorWrapper) {
127
+ const found = [];
128
+ view.someProp("decorations", (f)=>{
129
+ const result = f(view.state);
130
+ if (result && result != empty) found.push(result);
131
+ });
132
+ // We don't have access to types for view.cursorWrapper here
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ if (cursorWrapper) {
135
+ found.push(// eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ DecorationSet.create(view.state.doc, [
137
+ cursorWrapper
138
+ ]));
139
+ }
140
+ const previous = ViewDecorationsCache.get(view);
141
+ if (!previous) {
142
+ const result = DecorationGroup.from(found);
143
+ ViewDecorationsCache.set(view, result);
144
+ return result;
145
+ }
146
+ let numPrevious = 0;
147
+ let areSetsEqual = true;
148
+ previous.forEachSet((set)=>{
149
+ const next = found[numPrevious++];
150
+ if (next !== set) {
151
+ areSetsEqual = false;
152
+ }
153
+ });
154
+ if (numPrevious !== found.length) {
155
+ areSetsEqual = false;
156
+ }
157
+ if (!areSetsEqual) {
158
+ const result = DecorationGroup.from(found);
159
+ ViewDecorationsCache.set(view, result);
160
+ return result;
161
+ }
162
+ return previous;
163
+ }
@@ -0,0 +1,105 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ export const domIndex = function(node) {
2
+ for(let index = 0;; index++){
3
+ node = node.previousSibling;
4
+ if (!node) return index;
5
+ }
6
+ };
7
+ export const parentNode = function(node) {
8
+ const parent = node.assignedSlot || node.parentNode;
9
+ return parent && parent.nodeType == 11 ? parent.host : parent;
10
+ };
11
+ let reusedRange = null;
12
+ // Note that this will always return the same range, because DOM range
13
+ // objects are every expensive, and keep slowing down subsequent DOM
14
+ // updates, for some reason.
15
+ export const textRange = function(node, from, to) {
16
+ const range = reusedRange || (reusedRange = document.createRange());
17
+ range.setEnd(node, to == null ? node.nodeValue.length : to);
18
+ range.setStart(node, from || 0);
19
+ return range;
20
+ };
21
+ // Scans forward and backward through DOM positions equivalent to the
22
+ // given one to see if the two are in the same place (i.e. after a
23
+ // text node vs at the end of that text node)
24
+ export const isEquivalentPosition = function(node, off, targetNode, targetOff) {
25
+ return targetNode && (scanFor(node, off, targetNode, targetOff, -1) || scanFor(node, off, targetNode, targetOff, 1));
26
+ };
27
+ const atomElements = /^(img|br|input|textarea|hr)$/i;
28
+ function scanFor(node, off, targetNode, targetOff, dir) {
29
+ for(;;){
30
+ if (node == targetNode && off == targetOff) return true;
31
+ if (off == (dir < 0 ? 0 : nodeSize(node))) {
32
+ const parent = node.parentNode;
33
+ if (!parent || parent.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName) || node.contentEditable == "false") return false;
34
+ off = domIndex(node) + (dir < 0 ? 0 : 1);
35
+ node = parent;
36
+ } else if (node.nodeType == 1) {
37
+ node = node.childNodes[off + (dir < 0 ? -1 : 0)];
38
+ if (node.contentEditable == "false") return false;
39
+ off = dir < 0 ? nodeSize(node) : 0;
40
+ } else {
41
+ return false;
42
+ }
43
+ }
44
+ }
45
+ export function nodeSize(node) {
46
+ return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
47
+ }
48
+ export function isOnEdge(node, offset, parent) {
49
+ for(let atStart = offset == 0, atEnd = offset == nodeSize(node); atStart || atEnd;){
50
+ if (node == parent) return true;
51
+ const index = domIndex(node);
52
+ node = node.parentNode;
53
+ if (!node) return false;
54
+ atStart = atStart && index == 0;
55
+ atEnd = atEnd && index == nodeSize(node);
56
+ }
57
+ return false;
58
+ }
59
+ export function hasBlockDesc(dom) {
60
+ let desc;
61
+ for(let cur = dom; cur; cur = cur.parentNode)if (desc = cur.pmViewDesc) break;
62
+ return desc && desc.node && desc.node.isBlock && (desc.dom == dom || desc.contentDOM == dom);
63
+ }
64
+ // Work around Chrome issue https://bugs.chromium.org/p/chromium/issues/detail?id=447523
65
+ // (isCollapsed inappropriately returns true in shadow dom)
66
+ export const selectionCollapsed = function(domSel) {
67
+ return domSel.focusNode && isEquivalentPosition(domSel.focusNode, domSel.focusOffset, domSel.anchorNode, domSel.anchorOffset);
68
+ };
69
+ export function keyEvent(keyCode, key) {
70
+ const event = document.createEvent("Event");
71
+ event.initEvent("keydown", true, true);
72
+ event.keyCode = keyCode;
73
+ event.key = event.code = key;
74
+ return event;
75
+ }
76
+ export function deepActiveElement(doc) {
77
+ let elt = doc.activeElement;
78
+ while(elt && elt.shadowRoot)elt = elt.shadowRoot.activeElement;
79
+ return elt;
80
+ }
81
+ export function caretFromPoint(doc, x, y) {
82
+ if (doc.caretPositionFromPoint) {
83
+ try {
84
+ // Firefox throws for this call in hard-to-predict circumstances (#994)
85
+ const pos = doc.caretPositionFromPoint(x, y);
86
+ // Clip the offset, because Chrome will return a text offset
87
+ // into <input> nodes, which can't be treated as a regular DOM
88
+ // offset
89
+ if (pos) return {
90
+ node: pos.offsetNode,
91
+ offset: Math.min(nodeSize(pos.offsetNode), pos.offset)
92
+ };
93
+ } catch (_) {
94
+ // pass
95
+ }
96
+ }
97
+ if (doc.caretRangeFromPoint) {
98
+ const range = doc.caretRangeFromPoint(x, y);
99
+ if (range) return {
100
+ node: range.startContainer,
101
+ offset: Math.min(nodeSize(range.startContainer), range.startOffset)
102
+ };
103
+ }
104
+ return;
105
+ }
@@ -0,0 +1,99 @@
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 { EditorStateContext } from "../../contexts/EditorStateContext.js";
6
+ import { useEditorEffect } from "../useEditorEffect.js";
7
+ function TestComponent(param) {
8
+ let { effect , dependencies =[] } = param;
9
+ // eslint-disable-next-line react-hooks/exhaustive-deps
10
+ useEditorEffect(effect, dependencies);
11
+ return null;
12
+ }
13
+ describe("useEditorViewLayoutEffect", ()=>{
14
+ it("should run the effect", ()=>{
15
+ const effect = jest.fn();
16
+ const editorView = {};
17
+ const editorState = {};
18
+ const registerEventListener = ()=>{};
19
+ const unregisterEventListener = ()=>{};
20
+ render(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
21
+ value: {
22
+ view: editorView,
23
+ registerEventListener,
24
+ unregisterEventListener
25
+ }
26
+ }, /*#__PURE__*/ React.createElement(EditorStateContext.Provider, {
27
+ value: editorState
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 contextValue = {
41
+ view: editorView,
42
+ registerEventListener,
43
+ unregisterEventListener
44
+ };
45
+ const { rerender } = render(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
46
+ value: contextValue
47
+ }, /*#__PURE__*/ React.createElement(EditorStateContext.Provider, {
48
+ value: editorState
49
+ }, /*#__PURE__*/ React.createElement(TestComponent, {
50
+ effect: effect,
51
+ dependencies: []
52
+ })), " ")));
53
+ rerender(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
54
+ value: contextValue
55
+ }, /*#__PURE__*/ React.createElement(EditorStateContext.Provider, {
56
+ value: editorState
57
+ }, /*#__PURE__*/ React.createElement(TestComponent, {
58
+ effect: effect,
59
+ dependencies: []
60
+ })))));
61
+ expect(effect).toHaveBeenCalledTimes(1);
62
+ });
63
+ it("should re-run the effect if dependencies change", ()=>{
64
+ const effect = jest.fn();
65
+ const editorView = {};
66
+ const editorState = {};
67
+ const registerEventListener = ()=>{};
68
+ const unregisterEventListener = ()=>{};
69
+ const { rerender } = render(/*#__PURE__*/ React.createElement(LayoutGroup, null, /*#__PURE__*/ React.createElement(EditorContext.Provider, {
70
+ value: {
71
+ view: editorView,
72
+ registerEventListener,
73
+ unregisterEventListener
74
+ }
75
+ }, /*#__PURE__*/ React.createElement(EditorStateContext.Provider, {
76
+ value: editorState
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
+ view: editorView,
86
+ registerEventListener,
87
+ unregisterEventListener
88
+ }
89
+ }, /*#__PURE__*/ React.createElement(EditorStateContext.Provider, {
90
+ value: editorState
91
+ }, /*#__PURE__*/ React.createElement(TestComponent, {
92
+ effect: effect,
93
+ dependencies: [
94
+ "two"
95
+ ]
96
+ })))));
97
+ expect(effect).toHaveBeenCalledTimes(2);
98
+ });
99
+ });
@@ -0,0 +1,8 @@
1
+ import { useEffect, useState } from "react";
2
+ export function useClientOnly() {
3
+ const [render, setRender] = useState(false);
4
+ useEffect(()=>{
5
+ setRender(true);
6
+ }, []);
7
+ return render;
8
+ }
@@ -0,0 +1,54 @@
1
+ /* Copyright (c) The New York Times Company */ import { useCallback, useMemo, useState } from "react";
2
+ import { componentEventListeners } from "../plugins/componentEventListeners.js";
3
+ /**
4
+ * Produces a plugin that can be used with ProseMirror to handle DOM
5
+ * events at the EditorView.dom element.
6
+ *
7
+ * - `reactEventsPlugin` is a ProseMirror plugin for handling DOM events
8
+ * at the EditorView.dom element. It should be passed to `useEditorView`,
9
+ * along with any other plugins.
10
+ *
11
+ * - `registerEventListener` and `unregisterEventListener` should be
12
+ * passed to `EditorContext.Provider`.
13
+ *
14
+ * @privateRemarks
15
+ *
16
+ * This hook uses a combination of mutable and immutable updates to give
17
+ * us precise control over when we re-create the ProseMirror plugin.
18
+ *
19
+ * The plugin has a mutable reference to the set of handlers for each
20
+ * event type, but the set of event types is static. This means that we
21
+ * need to produce a new ProseMirror plugin whenever a new event type is
22
+ * registered. We avoid producing a new ProseMirrer plugin in any other
23
+ * scenario to avoid the performance overhead of reconfiguring the plugins
24
+ * in the EditorView.
25
+ *
26
+ * To accomplish this, we shallowly clone the registry whenever a new event
27
+ * type is registered.
28
+ */ export function useComponentEventListeners() {
29
+ const [registry, setRegistry] = useState(new Map());
30
+ const registerEventListener = useCallback((eventType, handler)=>{
31
+ const handlers = registry.get(eventType) ?? [];
32
+ handlers.unshift(handler);
33
+ if (!registry.has(eventType)) {
34
+ registry.set(eventType, handlers);
35
+ setRegistry(new Map(registry));
36
+ }
37
+ }, [
38
+ registry
39
+ ]);
40
+ const unregisterEventListener = useCallback((eventType, handler)=>{
41
+ const handlers = registry.get(eventType);
42
+ handlers?.splice(handlers.indexOf(handler), 1);
43
+ }, [
44
+ registry
45
+ ]);
46
+ const componentEventListenersPlugin = useMemo(()=>componentEventListeners(registry), [
47
+ registry
48
+ ]);
49
+ return {
50
+ registerEventListener,
51
+ unregisterEventListener,
52
+ componentEventListenersPlugin
53
+ };
54
+ }