@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,133 @@
1
+ import { Plugin } from "prosemirror-state";
2
+ import { CursorWrapper } from "../components/CursorWrapper.js";
3
+ import { widget } from "../decorations/ReactWidgetType.js";
4
+ import { reactKeysPluginKey } from "./reactKeys.js";
5
+ function insertText(view, eventData) {
6
+ let options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
7
+ if (eventData === null) return false;
8
+ const from = options.from ?? view.state.selection.from;
9
+ const to = options.to ?? view.state.selection.to;
10
+ if (view.someProp("handleTextInput", (f)=>f(view, from, to, eventData))) {
11
+ return true;
12
+ }
13
+ const { tr } = view.state;
14
+ if (options.marks) tr.ensureMarks(options.marks);
15
+ tr.insertText(eventData, from, to);
16
+ if (options.bust) {
17
+ const $from = view.state.doc.resolve(from);
18
+ const sharedAncestorDepth = $from.sharedDepth(to);
19
+ const sharedAncestorPos = $from.start(sharedAncestorDepth);
20
+ const parentKey = reactKeysPluginKey.getState(view.state)?.posToKey.get(sharedAncestorPos - 1);
21
+ tr.setMeta(reactKeysPluginKey, {
22
+ type: "bustKey",
23
+ payload: {
24
+ key: parentKey
25
+ }
26
+ });
27
+ }
28
+ view.dispatch(tr);
29
+ return true;
30
+ }
31
+ export function beforeInputPlugin(setCursorWrapper) {
32
+ let compositionText = null;
33
+ let compositionMarks = null;
34
+ return new Plugin({
35
+ props: {
36
+ handleDOMEvents: {
37
+ compositionstart (view) {
38
+ const { state } = view;
39
+ view.dispatch(state.tr.deleteSelection());
40
+ const $pos = state.selection.$from;
41
+ if (state.selection.empty && (state.storedMarks || !$pos.textOffset && $pos.parentOffset && $pos.nodeBefore?.marks.some((m)=>m.type.spec.inclusive === false))) {
42
+ setCursorWrapper(widget(state.selection.from, CursorWrapper, {
43
+ key: "cursor-wrapper",
44
+ marks: state.storedMarks ?? $pos.marks()
45
+ }));
46
+ }
47
+ compositionMarks = state.storedMarks ?? $pos.marks();
48
+ // @ts-expect-error Internal property - input
49
+ view.input.composing = true;
50
+ return true;
51
+ },
52
+ compositionupdate () {
53
+ return true;
54
+ },
55
+ compositionend (view) {
56
+ // @ts-expect-error Internal property - input
57
+ view.input.composing = false;
58
+ if (compositionText === null) return;
59
+ insertText(view, compositionText, {
60
+ // TODO: Rather than busting the reactKey cache here,
61
+ // which is pretty blunt and doesn't work for
62
+ // multi-node replacements, we should attempt to
63
+ // snapshot the selected DOM during compositionstart
64
+ // and restore it before we end the composition.
65
+ // This should allow React to successfully clean up
66
+ // and insert the newly composed text, without requiring
67
+ // any remounts
68
+ bust: true,
69
+ marks: compositionMarks
70
+ });
71
+ compositionText = null;
72
+ compositionMarks = null;
73
+ setCursorWrapper(null);
74
+ return true;
75
+ },
76
+ beforeinput (view, event) {
77
+ event.preventDefault();
78
+ switch(event.inputType){
79
+ case "insertCompositionText":
80
+ {
81
+ if (event.data === null) break;
82
+ compositionText = event.data;
83
+ break;
84
+ }
85
+ case "insertReplacementText":
86
+ {
87
+ const ranges = event.getTargetRanges();
88
+ event.dataTransfer?.items[0]?.getAsString((data)=>{
89
+ for (const range of ranges){
90
+ const from = view.posAtDOM(range.startContainer, range.startOffset, 1);
91
+ const to = view.posAtDOM(range.endContainer, range.endOffset, 1);
92
+ insertText(view, data, {
93
+ from,
94
+ to
95
+ });
96
+ }
97
+ });
98
+ break;
99
+ }
100
+ case "insertText":
101
+ {
102
+ insertText(view, event.data);
103
+ break;
104
+ }
105
+ case "deleteWordBackward":
106
+ case "deleteContentBackward":
107
+ case "deleteWordForward":
108
+ case "deleteContentForward":
109
+ case "deleteContent":
110
+ {
111
+ const targetRanges = event.getTargetRanges();
112
+ const { tr } = view.state;
113
+ for (const range of targetRanges){
114
+ const start = view.posAtDOM(range.startContainer, range.startOffset);
115
+ const end = view.posAtDOM(range.endContainer, range.endOffset);
116
+ const { doc } = view.state;
117
+ const storedMarks = doc.resolve(start).marksAcross(doc.resolve(end));
118
+ tr.delete(start, end).setStoredMarks(storedMarks);
119
+ }
120
+ view.dispatch(tr);
121
+ break;
122
+ }
123
+ default:
124
+ {
125
+ break;
126
+ }
127
+ }
128
+ return true;
129
+ }
130
+ }
131
+ }
132
+ });
133
+ }
@@ -0,0 +1,25 @@
1
+ import { Plugin, PluginKey } from "prosemirror-state";
2
+ import { unstable_batchedUpdates as batch } from "react-dom";
3
+ export function componentEventListeners(eventHandlerRegistry) {
4
+ const domEventHandlers = {};
5
+ for (const [eventType, handlers] of eventHandlerRegistry.entries()){
6
+ function handleEvent(view, event) {
7
+ for (const handler of handlers){
8
+ let handled = false;
9
+ batch(()=>{
10
+ handled = !!handler.call(this, view, event);
11
+ });
12
+ if (handled || event.defaultPrevented) return true;
13
+ }
14
+ return false;
15
+ }
16
+ domEventHandlers[eventType] = handleEvent;
17
+ }
18
+ const plugin = new Plugin({
19
+ key: new PluginKey("@nytimes/react-prosemirror/componentEventListeners"),
20
+ props: {
21
+ handleDOMEvents: domEventHandlers
22
+ }
23
+ });
24
+ return plugin;
25
+ }
@@ -0,0 +1,25 @@
1
+ /* Copyright (c) The New York Times Company */ import { Plugin, PluginKey } from "prosemirror-state";
2
+ import { unstable_batchedUpdates as batch } from "react-dom";
3
+ export function createComponentEventListenersPlugin(eventHandlerRegistry) {
4
+ const domEventHandlers = {};
5
+ for (const [eventType, handlers] of eventHandlerRegistry.entries()){
6
+ function handleEvent(view, event) {
7
+ for (const handler of handlers){
8
+ let handled = false;
9
+ batch(()=>{
10
+ handled = !!handler.call(this, view, event);
11
+ });
12
+ if (handled || event.defaultPrevented) return true;
13
+ }
14
+ return false;
15
+ }
16
+ domEventHandlers[eventType] = handleEvent;
17
+ }
18
+ const plugin = new Plugin({
19
+ key: new PluginKey("componentEventListeners"),
20
+ props: {
21
+ handleDOMEvents: domEventHandlers
22
+ }
23
+ });
24
+ return plugin;
25
+ }
@@ -0,0 +1,81 @@
1
+ import { Plugin, PluginKey } from "prosemirror-state";
2
+ export function createNodeKey() {
3
+ const key = Math.floor(Math.random() * 0xffffffffffff).toString(16);
4
+ return key;
5
+ }
6
+ export const reactKeysPluginKey = new PluginKey("@nytimes/react-prosemirror/reactKeys");
7
+ /**
8
+ * Tracks a unique key for each (non-text) node in the
9
+ * document, identified by its current position. Keys are
10
+ * (mostly) stable across transaction applications. The
11
+ * key for a given node can be accessed by that node's
12
+ * current position in the document, and vice versa.
13
+ */ export function reactKeys() {
14
+ let composing = false;
15
+ return new Plugin({
16
+ key: reactKeysPluginKey,
17
+ state: {
18
+ init (_, state) {
19
+ const next = {
20
+ posToKey: new Map(),
21
+ keyToPos: new Map()
22
+ };
23
+ state.doc.descendants((_, pos)=>{
24
+ const key = createNodeKey();
25
+ next.posToKey.set(pos, key);
26
+ next.keyToPos.set(key, pos);
27
+ return true;
28
+ });
29
+ return next;
30
+ },
31
+ /**
32
+ * Keeps node keys stable across transactions.
33
+ *
34
+ * To accomplish this, we map each node position forwards
35
+ * through the transaction to identify its current position,
36
+ * and assign its key to that new position, dropping it if the
37
+ * node was deleted.
38
+ */ apply (tr, value, _, newState) {
39
+ if (!tr.docChanged || composing) return value;
40
+ const meta = tr.getMeta(reactKeysPluginKey);
41
+ const keyToBust = meta?.type === "bustKey" && meta.payload.key;
42
+ const next = {
43
+ posToKey: new Map(),
44
+ keyToPos: new Map()
45
+ };
46
+ const posToKeyEntries = Array.from(value.posToKey.entries()).sort((param, param1)=>{
47
+ let [a] = param, [b] = param1;
48
+ return a - b;
49
+ });
50
+ for (const [pos, key] of posToKeyEntries){
51
+ const { pos: newPos, deleted } = tr.mapping.mapResult(pos);
52
+ if (deleted) continue;
53
+ let newKey = key;
54
+ if (keyToBust === key) {
55
+ newKey = createNodeKey();
56
+ }
57
+ next.posToKey.set(newPos, newKey);
58
+ next.keyToPos.set(newKey, newPos);
59
+ }
60
+ newState.doc.descendants((_, pos)=>{
61
+ if (next.posToKey.has(pos)) return true;
62
+ const key = createNodeKey();
63
+ next.posToKey.set(pos, key);
64
+ next.keyToPos.set(key, pos);
65
+ return true;
66
+ });
67
+ return next;
68
+ }
69
+ },
70
+ props: {
71
+ handleDOMEvents: {
72
+ compositionstart: ()=>{
73
+ composing = true;
74
+ },
75
+ compositionend: ()=>{
76
+ composing = false;
77
+ }
78
+ }
79
+ }
80
+ });
81
+ }
@@ -0,0 +1,251 @@
1
+ import cx from "classnames";
2
+ export function kebabCaseToCamelCase(str) {
3
+ return str.replaceAll(/-[a-z]/g, (g)=>g[1]?.toUpperCase() ?? "");
4
+ }
5
+ /**
6
+ * Converts a CSS style string to an object
7
+ * that can be passed to a React component's
8
+ * `style` prop.
9
+ */ export function cssToStyles(css) {
10
+ const stylesheet = new CSSStyleSheet();
11
+ stylesheet.insertRule(`* { ${css} }`);
12
+ const insertedRule = stylesheet.cssRules[0];
13
+ const declaration = insertedRule.style;
14
+ const styles = {};
15
+ for(let i = 0; i < declaration.length; i++){
16
+ const property = declaration.item(i);
17
+ const value = declaration.getPropertyValue(property);
18
+ const camelCasePropertyName = property.startsWith("--") ? property : kebabCaseToCamelCase(property);
19
+ styles[camelCasePropertyName] = value;
20
+ }
21
+ return styles;
22
+ }
23
+ /**
24
+ * Merges two sets of React props. Class names
25
+ * will be concatenated and style objects
26
+ * will be merged.
27
+ */ export function mergeReactProps(a, b) {
28
+ return {
29
+ ...a,
30
+ ...b,
31
+ className: cx(a.className, b.className),
32
+ style: {
33
+ ...a.style,
34
+ ...b.style
35
+ }
36
+ };
37
+ }
38
+ /**
39
+ * Given a record of HTML attributes, returns tho
40
+ * equivalent React props.
41
+ */ export function htmlAttrsToReactProps(attrs) {
42
+ const props = {};
43
+ for (const [attrName, attrValue] of Object.entries(attrs)){
44
+ switch(attrName.toLowerCase()){
45
+ case "class":
46
+ {
47
+ props.className = attrValue;
48
+ break;
49
+ }
50
+ case "style":
51
+ {
52
+ props.style = cssToStyles(attrValue);
53
+ break;
54
+ }
55
+ case "autocapitalize":
56
+ {
57
+ props.autoCapitalize = attrValue;
58
+ break;
59
+ }
60
+ case "contenteditable":
61
+ {
62
+ if (attrValue === "" || attrValue === "true") {
63
+ props.contentEditable = true;
64
+ break;
65
+ }
66
+ if (attrValue === "false") {
67
+ props.contentEditable = false;
68
+ break;
69
+ }
70
+ if (attrValue === "plaintext-only") {
71
+ props.contentEditable = "plaintext-only";
72
+ break;
73
+ }
74
+ props.contentEditable = null;
75
+ break;
76
+ }
77
+ case "draggable":
78
+ {
79
+ props.draggable = attrValue != null;
80
+ break;
81
+ }
82
+ case "enterkeyhint":
83
+ {
84
+ props.enterKeyHint = attrValue;
85
+ break;
86
+ }
87
+ case "for":
88
+ {
89
+ props.htmlFor = attrValue;
90
+ break;
91
+ }
92
+ case "hidden":
93
+ {
94
+ props.hidden = attrValue;
95
+ break;
96
+ }
97
+ case "inputmode":
98
+ {
99
+ props.inputMode = attrValue;
100
+ break;
101
+ }
102
+ case "itemprop":
103
+ {
104
+ props.itemProp = attrValue;
105
+ break;
106
+ }
107
+ case "spellcheck":
108
+ {
109
+ if (attrValue === "" || attrValue === "true") {
110
+ props.spellCheck = true;
111
+ break;
112
+ }
113
+ if (attrValue === "false") {
114
+ props.spellCheck = false;
115
+ break;
116
+ }
117
+ props.spellCheck = null;
118
+ break;
119
+ }
120
+ case "tabindex":
121
+ {
122
+ const numValue = parseInt(attrValue, 10);
123
+ if (!Number.isNaN(numValue)) {
124
+ props.tabIndex = numValue;
125
+ }
126
+ break;
127
+ }
128
+ case "autocomplete":
129
+ {
130
+ props.autoComplete = attrValue;
131
+ break;
132
+ }
133
+ case "autofocus":
134
+ {
135
+ props.autoFocus = attrValue != null;
136
+ break;
137
+ }
138
+ case "formaction":
139
+ {
140
+ props.formAction = attrValue;
141
+ break;
142
+ }
143
+ case "formenctype":
144
+ {
145
+ props.formEnctype = attrValue;
146
+ break;
147
+ }
148
+ case "formmethod":
149
+ {
150
+ props.formMethod = attrValue;
151
+ break;
152
+ }
153
+ case "formnovalidate":
154
+ {
155
+ props.formNoValidate = attrValue;
156
+ break;
157
+ }
158
+ case "formtarget":
159
+ {
160
+ props.formTarget = attrValue;
161
+ break;
162
+ }
163
+ case "maxlength":
164
+ {
165
+ const numValue = parseInt(attrValue, 10);
166
+ if (!Number.isNaN(numValue)) {
167
+ props.maxLength = attrValue;
168
+ }
169
+ break;
170
+ }
171
+ case "minlength":
172
+ {
173
+ const numValue = parseInt(attrValue, 10);
174
+ if (!Number.isNaN(numValue)) {
175
+ props.minLength = attrValue;
176
+ }
177
+ break;
178
+ }
179
+ case "max":
180
+ {
181
+ const numValue = parseInt(attrValue, 10);
182
+ if (!Number.isNaN(numValue)) {
183
+ props.max = attrValue;
184
+ }
185
+ break;
186
+ }
187
+ case "min":
188
+ {
189
+ const numValue = parseInt(attrValue, 10);
190
+ if (!Number.isNaN(numValue)) {
191
+ props.min = attrValue;
192
+ }
193
+ break;
194
+ }
195
+ case "multiple":
196
+ {
197
+ props.multiple = attrValue != null;
198
+ break;
199
+ }
200
+ case "readonly":
201
+ {
202
+ props.readOnly = attrValue != null;
203
+ break;
204
+ }
205
+ case "required":
206
+ {
207
+ props.required = attrValue != null;
208
+ break;
209
+ }
210
+ case "size":
211
+ {
212
+ const numValue = parseInt(attrValue, 10);
213
+ if (!Number.isNaN(numValue)) {
214
+ props.size = attrValue;
215
+ }
216
+ break;
217
+ }
218
+ case "step":
219
+ {
220
+ if (attrValue === "any") {
221
+ props.step = attrValue;
222
+ break;
223
+ }
224
+ const numValue = parseInt(attrValue, 10);
225
+ if (!Number.isNaN(numValue) && numValue > 0) {
226
+ props.step = attrValue;
227
+ }
228
+ break;
229
+ }
230
+ case "disabled":
231
+ {
232
+ props.disabled = attrValue != null;
233
+ break;
234
+ }
235
+ case "rows":
236
+ {
237
+ const numValue = parseInt(attrValue, 10);
238
+ if (!Number.isNaN(numValue)) {
239
+ props.rows = attrValue;
240
+ }
241
+ break;
242
+ }
243
+ default:
244
+ {
245
+ props[attrName] = attrValue;
246
+ break;
247
+ }
248
+ }
249
+ }
250
+ return props;
251
+ }
@@ -0,0 +1,164 @@
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
+ selectionChanged(sel) {
139
+ return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(this.view) && !this.ignoreSelectionChange(sel);
140
+ }
141
+ forceFlush() {
142
+ if (this.flushingSoon > -1) {
143
+ window.clearTimeout(this.flushingSoon);
144
+ this.flushingSoon = -1;
145
+ this.flush();
146
+ }
147
+ }
148
+ onSelectionChange() {
149
+ if (!hasFocusAndSelection(this.view)) return;
150
+ if (this.view.composing) return;
151
+ if (this.suppressingSelectionUpdates) return selectionToDOM(this.view);
152
+ // Deletions on IE11 fire their events in the wrong order, giving
153
+ // us a selection change event before the DOM changes are
154
+ // reported.
155
+ if (browser.ie && browser.ie_version <= 11 && !this.view.state.selection.empty) {
156
+ // @ts-expect-error Internal method
157
+ const sel = this.view.domSelectionRange();
158
+ // Selection.isCollapsed isn't reliable on IE
159
+ if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
160
+ sel.anchorNode, sel.anchorOffset)) return this.flushSoon();
161
+ }
162
+ this.flush();
163
+ }
164
+ }