@handlewithcare/react-prosemirror 2.0.0 → 2.2.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 (44) hide show
  1. package/README.md +29 -5
  2. package/dist/cjs/components/CustomNodeView.js +80 -4
  3. package/dist/cjs/components/MarkView.js +6 -3
  4. package/dist/cjs/components/NodeView.js +13 -133
  5. package/dist/cjs/components/OutputSpec.js +1 -1
  6. package/dist/cjs/components/ReactNodeView.js +136 -0
  7. package/dist/cjs/components/TextNodeView.js +2 -2
  8. package/dist/cjs/findDOMNode.js +46 -0
  9. package/dist/cjs/hooks/useEditor.js +14 -3
  10. package/dist/cjs/hooks/useEditorEffect.js +5 -2
  11. package/dist/cjs/hooks/useIsNodeSelected.js +21 -0
  12. package/dist/cjs/index.js +4 -0
  13. package/dist/cjs/plugins/componentEventListeners.js +1 -1
  14. package/dist/cjs/plugins/reactKeys.js +1 -1
  15. package/dist/cjs/selection/SelectionDOMObserver.js +0 -3
  16. package/dist/cjs/selection/selectionToDOM.js +1 -1
  17. package/dist/cjs/viewdesc.js +18 -8
  18. package/dist/esm/components/CustomNodeView.js +82 -6
  19. package/dist/esm/components/MarkView.js +6 -3
  20. package/dist/esm/components/NodeView.js +14 -134
  21. package/dist/esm/components/OutputSpec.js +1 -1
  22. package/dist/esm/components/ReactNodeView.js +85 -0
  23. package/dist/esm/components/TextNodeView.js +1 -1
  24. package/dist/esm/findDOMNode.js +31 -0
  25. package/dist/esm/hooks/useEditor.js +14 -3
  26. package/dist/esm/hooks/useEditorEffect.js +5 -2
  27. package/dist/esm/hooks/useIsNodeSelected.js +11 -0
  28. package/dist/esm/index.js +1 -0
  29. package/dist/esm/plugins/componentEventListeners.js +1 -1
  30. package/dist/esm/plugins/reactKeys.js +1 -1
  31. package/dist/esm/selection/SelectionDOMObserver.js +0 -3
  32. package/dist/esm/selection/selectionToDOM.js +1 -1
  33. package/dist/esm/viewdesc.js +18 -8
  34. package/dist/tsconfig.tsbuildinfo +1 -1
  35. package/dist/types/components/CustomNodeView.d.ts +3 -12
  36. package/dist/types/components/ReactNodeView.d.ts +11 -0
  37. package/dist/types/contexts/EditorContext.d.ts +2 -0
  38. package/dist/types/findDOMNode.d.ts +3 -0
  39. package/dist/types/hooks/useEditor.d.ts +1 -0
  40. package/dist/types/hooks/useIsNodeSelected.d.ts +1 -0
  41. package/dist/types/index.d.ts +1 -0
  42. package/dist/types/selection/SelectionDOMObserver.d.ts +0 -1
  43. package/dist/types/viewdesc.d.ts +19 -6
  44. package/package.json +19 -6
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "useIsNodeSelected", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return useIsNodeSelected;
9
+ }
10
+ });
11
+ const _react = require("react");
12
+ const _useSelectNode = require("./useSelectNode.js");
13
+ function useIsNodeSelected() {
14
+ const [isSelected, setIsSelected] = (0, _react.useState)(false);
15
+ (0, _useSelectNode.useSelectNode)(()=>{
16
+ setIsSelected(true);
17
+ }, ()=>{
18
+ setIsSelected(false);
19
+ });
20
+ return isSelected;
21
+ }
package/dist/cjs/index.js CHANGED
@@ -31,6 +31,9 @@ _export(exports, {
31
31
  useEditorState: function() {
32
32
  return _useEditorState.useEditorState;
33
33
  },
34
+ useIsNodeSelected: function() {
35
+ return _useIsNodeSelected.useIsNodeSelected;
36
+ },
34
37
  useSelectNode: function() {
35
38
  return _useSelectNode.useSelectNode;
36
39
  },
@@ -49,5 +52,6 @@ const _useEditorEventListener = require("./hooks/useEditorEventListener.js");
49
52
  const _useEditorState = require("./hooks/useEditorState.js");
50
53
  const _useStopEvent = require("./hooks/useStopEvent.js");
51
54
  const _useSelectNode = require("./hooks/useSelectNode.js");
55
+ const _useIsNodeSelected = require("./hooks/useIsNodeSelected.js");
52
56
  const _reactKeys = require("./plugins/reactKeys.js");
53
57
  const _ReactWidgetType = require("./decorations/ReactWidgetType.js");
@@ -26,7 +26,7 @@ function componentEventListeners(eventHandlerRegistry) {
26
26
  domEventHandlers[eventType] = handleEvent;
27
27
  }
28
28
  const plugin = new _prosemirrorstate.Plugin({
29
- key: new _prosemirrorstate.PluginKey("@nytimes/react-prosemirror/componentEventListeners"),
29
+ key: new _prosemirrorstate.PluginKey("@handlewithcare/react-prosemirror/componentEventListeners"),
30
30
  props: {
31
31
  handleDOMEvents: domEventHandlers
32
32
  }
@@ -24,7 +24,7 @@ function createNodeKey() {
24
24
  const key = Math.floor(Math.random() * 0xffffffffffff).toString(16);
25
25
  return key;
26
26
  }
27
- const reactKeysPluginKey = new _prosemirrorstate.PluginKey("@nytimes/react-prosemirror/reactKeys");
27
+ const reactKeysPluginKey = new _prosemirrorstate.PluginKey("@handlewithcare/react-prosemirror/reactKeys");
28
28
  function reactKeys() {
29
29
  let composing = false;
30
30
  return new _prosemirrorstate.Plugin({
@@ -145,9 +145,6 @@ let SelectionDOMObserver = class SelectionDOMObserver {
145
145
  this.currentSelection.set(sel);
146
146
  }
147
147
  }
148
- selectionChanged(sel) {
149
- return !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && (0, _hasFocusAndSelection.hasFocusAndSelection)(this.view) && !this.ignoreSelectionChange(sel);
150
- }
151
148
  forceFlush() {
152
149
  if (this.flushingSoon > -1) {
153
150
  window.clearTimeout(this.flushingSoon);
@@ -209,7 +209,7 @@ function selectionToDOM(view) {
209
209
  if (!sel.$from.parent.inlineContent) resetEditableFrom = temporarilyEditableNear(v, sel.from);
210
210
  if (!sel.empty && !sel.$from.parent.inlineContent) resetEditableTo = temporarilyEditableNear(v, sel.to);
211
211
  }
212
- v.docView.setSelection(anchor, head, v.root, force);
212
+ v.docView.setSelection(anchor, head, v, force);
213
213
  if (brokenSelectBetweenUneditable) {
214
214
  if (resetEditableFrom) resetEditable(resetEditableFrom);
215
215
  if (resetEditableTo) resetEditable(resetEditableTo);
@@ -336,18 +336,20 @@ let ViewDesc = class ViewDesc {
336
336
  // custom things with the selection. Note that this falls apart when
337
337
  // a selection starts in such a node and ends in another, in which
338
338
  // case we just use whatever domFromPos produces as a best effort.
339
- setSelection(anchor, head, root) {
339
+ setSelection(anchor, head, view) {
340
340
  let force = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : false;
341
341
  // If the selection falls entirely in a child, give it to that child
342
342
  const from = Math.min(anchor, head), to = Math.max(anchor, head);
343
343
  for(let i = 0, offset = 0; i < this.children.length; i++){
344
344
  const child = this.children[i], end = offset + child.size;
345
- if (from > offset && to < end) return child.setSelection(anchor - offset - child.border, head - offset - child.border, root, force);
345
+ if (from > offset && to < end) return child.setSelection(anchor - offset - child.border, head - offset - child.border, view, force);
346
346
  offset = end;
347
347
  }
348
348
  let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1);
349
349
  let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1);
350
- const domSel = root.getSelection();
350
+ const domSel = view.root.getSelection();
351
+ // @ts-expect-error - Internal method domSelectionRange
352
+ const selRange = view.domSelectionRange();
351
353
  let brKludge = false;
352
354
  // On Firefox, using Selection.collapse to put the cursor after a
353
355
  // BR node for some reason doesn't always work (#1073). On Safari,
@@ -379,11 +381,11 @@ let ViewDesc = class ViewDesc {
379
381
  }
380
382
  // Firefox can act strangely when the selection is in front of an
381
383
  // uneditable node. See #1163 and https://bugzilla.mozilla.org/show_bug.cgi?id=1709536
382
- if (_browser.browser.gecko && domSel.focusNode && domSel.focusNode != headDOM.node && domSel.focusNode.nodeType == 1) {
383
- const after = domSel.focusNode.childNodes[domSel.focusOffset];
384
+ if (_browser.browser.gecko && selRange.focusNode && selRange.focusNode != headDOM.node && selRange.focusNode.nodeType == 1) {
385
+ const after = selRange.focusNode.childNodes[selRange.focusOffset];
384
386
  if (after && after.contentEditable == "false") force = true;
385
387
  }
386
- if (!(force || brKludge && _browser.browser.safari) && (0, _selectionToDOM.isEquivalentPosition)(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset) && (0, _selectionToDOM.isEquivalentPosition)(headDOM.node, headDOM.offset, domSel.focusNode, domSel.focusOffset)) return;
388
+ if (!(force || brKludge && _browser.browser.safari) && (0, _selectionToDOM.isEquivalentPosition)(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) && (0, _selectionToDOM.isEquivalentPosition)(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset)) return;
387
389
  // Selection.extend can be used to create an 'inverted' selection
388
390
  // (one where the focus is before the anchor), but not all
389
391
  // browsers support it yet.
@@ -508,8 +510,9 @@ let CompositionViewDesc = class CompositionViewDesc extends ViewDesc {
508
510
  };
509
511
  let MarkViewDesc = class MarkViewDesc extends ViewDesc {
510
512
  mark;
511
- constructor(parent, children, getPos, mark, dom, contentDOM){
512
- super(parent, children, getPos, dom, contentDOM), this.mark = mark;
513
+ spec;
514
+ constructor(parent, children, getPos, mark, dom, contentDOM, spec){
515
+ super(parent, children, getPos, dom, contentDOM), this.mark = mark, this.spec = spec;
513
516
  }
514
517
  parseRule() {
515
518
  if (this.dirty & NODE_DIRTY || this.mark.type.spec.reparseInView) return null;
@@ -532,6 +535,13 @@ let MarkViewDesc = class MarkViewDesc extends ViewDesc {
532
535
  this.dirty = NOT_DIRTY;
533
536
  }
534
537
  }
538
+ ignoreMutation(mutation) {
539
+ return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation);
540
+ }
541
+ destroy() {
542
+ if (this.spec.destroy) this.spec.destroy();
543
+ super.destroy();
544
+ }
535
545
  };
536
546
  let NodeViewDesc = class NodeViewDesc extends ViewDesc {
537
547
  node;
@@ -1,12 +1,79 @@
1
- import React, { createElement, useContext } from "react";
1
+ import React, { cloneElement, createElement, memo, useContext, useLayoutEffect, useMemo, useRef } from "react";
2
2
  import { createPortal } from "react-dom";
3
+ import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
3
4
  import { EditorContext } from "../contexts/EditorContext.js";
4
5
  import { useClientOnly } from "../hooks/useClientOnly.js";
5
- import { ChildNodeViews } from "./ChildNodeViews.js";
6
- export function CustomNodeView(param) {
7
- let { contentDomRef, customNodeViewRef, customNodeViewRootRef, customNodeView, initialNode, node, getPos, initialOuterDeco, initialInnerDeco, innerDeco } = param;
6
+ import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
7
+ import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";
8
+ export const CustomNodeView = /*#__PURE__*/ memo(function CustomNodeView(param) {
9
+ let { customNodeView, node, getPos, innerDeco, outerDeco } = param;
8
10
  const { view } = useContext(EditorContext);
11
+ const domRef = useRef(null);
12
+ const nodeDomRef = useRef(null);
13
+ const contentDomRef = useRef(null);
14
+ const getPosFunc = useRef(()=>getPos.current()).current;
15
+ // this is ill-conceived; should revisit
16
+ const initialNode = useRef(node);
17
+ const initialOuterDeco = useRef(outerDeco);
18
+ const initialInnerDeco = useRef(innerDeco);
19
+ const customNodeViewRootRef = useRef(null);
20
+ const customNodeViewRef = useRef(null);
9
21
  const shouldRender = useClientOnly();
22
+ useLayoutEffect(()=>{
23
+ if (!customNodeViewRef.current || !customNodeViewRootRef.current || !shouldRender) return;
24
+ const { dom } = customNodeViewRef.current;
25
+ nodeDomRef.current = customNodeViewRootRef.current;
26
+ customNodeViewRootRef.current.appendChild(dom);
27
+ return ()=>{
28
+ customNodeViewRef.current?.destroy?.();
29
+ };
30
+ }, [
31
+ customNodeViewRef,
32
+ customNodeViewRootRef,
33
+ nodeDomRef,
34
+ shouldRender
35
+ ]);
36
+ useLayoutEffect(()=>{
37
+ if (!customNodeView || !customNodeViewRef.current || !shouldRender) return;
38
+ const { destroy, update } = customNodeViewRef.current;
39
+ const updated = update?.call(customNodeViewRef.current, node, outerDeco, innerDeco) ?? true;
40
+ if (updated) return;
41
+ destroy?.call(customNodeViewRef.current);
42
+ if (!customNodeViewRootRef.current) return;
43
+ initialNode.current = node;
44
+ initialOuterDeco.current = outerDeco;
45
+ initialInnerDeco.current = innerDeco;
46
+ customNodeViewRef.current = customNodeView(initialNode.current, // customNodeView will only be set if view is set, and we can only reach
47
+ // this line if customNodeView is set
48
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
49
+ view, getPosFunc, initialOuterDeco.current, initialInnerDeco.current);
50
+ const { dom } = customNodeViewRef.current;
51
+ nodeDomRef.current = customNodeViewRootRef.current;
52
+ customNodeViewRootRef.current.appendChild(dom);
53
+ }, [
54
+ customNodeView,
55
+ view,
56
+ innerDeco,
57
+ node,
58
+ outerDeco,
59
+ getPos,
60
+ customNodeViewRef,
61
+ customNodeViewRootRef,
62
+ initialNode,
63
+ initialOuterDeco,
64
+ initialInnerDeco,
65
+ nodeDomRef,
66
+ shouldRender,
67
+ getPosFunc
68
+ ]);
69
+ const { childDescriptors, nodeViewDescRef } = useNodeViewDescriptor(node, ()=>getPos.current(), domRef, nodeDomRef, innerDeco, outerDeco, undefined, contentDomRef);
70
+ const childContextValue = useMemo(()=>({
71
+ parentRef: nodeViewDescRef,
72
+ siblingsRef: childDescriptors
73
+ }), [
74
+ childDescriptors,
75
+ nodeViewDescRef
76
+ ]);
10
77
  if (!shouldRender) return null;
11
78
  if (!customNodeViewRef.current) {
12
79
  customNodeViewRef.current = customNodeView(initialNode.current, // customNodeView will only be set if view is set, and we can only reach
@@ -16,7 +83,7 @@ export function CustomNodeView(param) {
16
83
  }
17
84
  const { contentDOM } = customNodeViewRef.current;
18
85
  contentDomRef.current = contentDOM ?? null;
19
- return /*#__PURE__*/ createElement(node.isInline ? "span" : "div", {
86
+ const element = /*#__PURE__*/ createElement(node.isInline ? "span" : "div", {
20
87
  ref: customNodeViewRootRef,
21
88
  contentEditable: !!contentDOM,
22
89
  suppressContentEditableWarning: true
@@ -25,4 +92,13 @@ export function CustomNodeView(param) {
25
92
  node: node,
26
93
  innerDecorations: innerDeco
27
94
  }), contentDOM));
28
- }
95
+ const decoratedElement = /*#__PURE__*/ cloneElement(outerDeco.reduce(wrapInDeco, element), // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
+ outerDeco.some((d)=>d.type.attrs.nodeName) ? {
97
+ ref: domRef
98
+ } : // we've already passed the domRef to the NodeView component
99
+ // as a prop
100
+ undefined);
101
+ return /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
102
+ value: childContextValue
103
+ }, decoratedElement);
104
+ });
@@ -31,12 +31,15 @@ export const MarkView = /*#__PURE__*/ memo(/*#__PURE__*/ forwardRef(function Mar
31
31
  if (!domRef.current) return;
32
32
  const firstChildDesc = childDescriptors.current[0];
33
33
  if (!viewDescRef.current) {
34
- viewDescRef.current = new MarkViewDesc(parentRef.current, childDescriptors.current, ()=>getPos.current(), mark, domRef.current, firstChildDesc?.dom.parentElement ?? domRef.current);
34
+ viewDescRef.current = new MarkViewDesc(parentRef.current, childDescriptors.current, ()=>getPos.current(), mark, domRef.current, firstChildDesc?.dom.parentElement ?? domRef.current, {
35
+ dom: domRef.current,
36
+ contentDOM: firstChildDesc?.dom.parentElement ?? domRef.current
37
+ });
35
38
  } else {
36
39
  viewDescRef.current.parent = parentRef.current;
37
- viewDescRef.current.dom = domRef.current;
40
+ viewDescRef.current.spec.dom = viewDescRef.current.dom = domRef.current;
38
41
  viewDescRef.current.children = childDescriptors.current;
39
- viewDescRef.current.contentDOM = firstChildDesc?.dom.parentElement ?? domRef.current;
42
+ viewDescRef.current.spec.contentDOM = viewDescRef.current.contentDOM = firstChildDesc?.dom.parentElement ?? domRef.current;
40
43
  viewDescRef.current.mark = mark;
41
44
  viewDescRef.current.getPos = ()=>getPos.current();
42
45
  }
@@ -1,145 +1,25 @@
1
- import React, { cloneElement, memo, useContext, useLayoutEffect, useMemo, useRef } from "react";
2
- import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
1
+ import React, { memo, useContext } from "react";
3
2
  import { EditorContext } from "../contexts/EditorContext.js";
4
- import { NodeViewContext } from "../contexts/NodeViewContext.js";
5
- import { SelectNodeContext } from "../contexts/SelectNodeContext.js";
6
- import { StopEventContext } from "../contexts/StopEventContext.js";
7
- import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
8
- import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";
9
3
  import { CustomNodeView } from "./CustomNodeView.js";
10
- import { OutputSpec } from "./OutputSpec.js";
4
+ import { ReactNodeView } from "./ReactNodeView.js";
11
5
  export const NodeView = /*#__PURE__*/ memo(function NodeView(param) {
12
6
  let { outerDeco, getPos, node, innerDeco, ...props } = param;
13
- const domRef = useRef(null);
14
- const nodeDomRef = useRef(null);
15
- const contentDomRef = useRef(null);
16
- const getPosFunc = useRef(()=>getPos.current()).current;
17
- // this is ill-conceived; should revisit
18
- const initialNode = useRef(node);
19
- const initialOuterDeco = useRef(outerDeco);
20
- const initialInnerDeco = useRef(innerDeco);
21
- const customNodeViewRootRef = useRef(null);
22
- const customNodeViewRef = useRef(null);
23
- // const state = useEditorState();
24
- const { nodeViews } = useContext(NodeViewContext);
25
7
  const { view } = useContext(EditorContext);
26
- let element = null;
27
- const Component = nodeViews[node.type.name];
28
- const outputSpec = useMemo(()=>node.type.spec.toDOM?.(node), [
29
- node
30
- ]);
31
- // TODO: Would be great to pull all of the custom node view stuff into
32
- // a hook
33
8
  const customNodeView = view?.someProp("nodeViews", (nodeViews)=>nodeViews?.[node.type.name]);
34
- useLayoutEffect(()=>{
35
- if (!customNodeViewRef.current || !customNodeViewRootRef.current) return;
36
- const { dom } = customNodeViewRef.current;
37
- nodeDomRef.current = customNodeViewRootRef.current;
38
- customNodeViewRootRef.current.appendChild(dom);
39
- return ()=>{
40
- customNodeViewRef.current?.destroy?.();
41
- };
42
- }, []);
43
- useLayoutEffect(()=>{
44
- if (!customNodeView || !customNodeViewRef.current) return;
45
- const { destroy, update } = customNodeViewRef.current;
46
- const updated = update?.call(customNodeViewRef.current, node, outerDeco, innerDeco) ?? true;
47
- if (updated) return;
48
- destroy?.call(customNodeViewRef.current);
49
- if (!customNodeViewRootRef.current) return;
50
- initialNode.current = node;
51
- initialOuterDeco.current = outerDeco;
52
- initialInnerDeco.current = innerDeco;
53
- customNodeViewRef.current = customNodeView(initialNode.current, // customNodeView will only be set if view is set, and we can only reach
54
- // this line if customNodeView is set
55
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
56
- view, ()=>getPos.current(), initialOuterDeco.current, initialInnerDeco.current);
57
- const { dom } = customNodeViewRef.current;
58
- nodeDomRef.current = customNodeViewRootRef.current;
59
- customNodeViewRootRef.current.appendChild(dom);
60
- }, [
61
- customNodeView,
62
- view,
63
- innerDeco,
64
- node,
65
- outerDeco,
66
- getPos
67
- ]);
68
- const { hasContentDOM, childDescriptors, setStopEvent, setSelectNode, nodeViewDescRef } = useNodeViewDescriptor(node, ()=>getPos.current(), domRef, nodeDomRef, innerDeco, outerDeco, undefined, contentDomRef);
69
- const finalProps = {
70
- ...props,
71
- ...!hasContentDOM && {
72
- contentEditable: false
73
- }
74
- };
75
- const nodeProps = useMemo(()=>({
76
- node: node,
77
- getPos: getPosFunc,
78
- decorations: outerDeco,
79
- innerDecorations: innerDeco
80
- }), [
81
- getPosFunc,
82
- innerDeco,
83
- node,
84
- outerDeco
85
- ]);
86
- if (Component) {
87
- element = /*#__PURE__*/ React.createElement(Component, {
88
- ...finalProps,
89
- ref: nodeDomRef,
90
- nodeProps: nodeProps
91
- }, /*#__PURE__*/ React.createElement(ChildNodeViews, {
92
- getPos: getPos,
93
- node: node,
94
- innerDecorations: innerDeco
95
- }));
96
- } else if (customNodeView) {
97
- element = /*#__PURE__*/ React.createElement(CustomNodeView, {
98
- contentDomRef: contentDomRef,
9
+ if (customNodeView) {
10
+ return /*#__PURE__*/ React.createElement(CustomNodeView, {
99
11
  customNodeView: customNodeView,
100
- customNodeViewRef: customNodeViewRef,
101
- customNodeViewRootRef: customNodeViewRootRef,
102
- initialInnerDeco: initialInnerDeco,
103
- initialNode: initialNode,
104
- initialOuterDeco: initialOuterDeco,
105
12
  node: node,
106
- getPos: getPos,
107
- innerDeco: innerDeco
13
+ innerDeco: innerDeco,
14
+ outerDeco: outerDeco,
15
+ getPos: getPos
108
16
  });
109
- } else {
110
- if (outputSpec) {
111
- element = /*#__PURE__*/ React.createElement(OutputSpec, {
112
- ...finalProps,
113
- ref: nodeDomRef,
114
- outputSpec: outputSpec
115
- }, /*#__PURE__*/ React.createElement(ChildNodeViews, {
116
- getPos: getPos,
117
- node: node,
118
- innerDecorations: innerDeco
119
- }));
120
- }
121
- }
122
- if (!element) {
123
- throw new Error(`Node spec for ${node.type.name} is missing toDOM`);
124
17
  }
125
- const decoratedElement = /*#__PURE__*/ cloneElement(outerDeco.reduce(wrapInDeco, element), // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
- outerDeco.some((d)=>d.type.attrs.nodeName) ? {
127
- ref: domRef
128
- } : // we've already passed the domRef to the NodeView component
129
- // as a prop
130
- undefined);
131
- const childContextValue = useMemo(()=>({
132
- parentRef: nodeViewDescRef,
133
- siblingsRef: childDescriptors
134
- }), [
135
- childDescriptors,
136
- nodeViewDescRef
137
- ]);
138
- return /*#__PURE__*/ React.createElement(SelectNodeContext.Provider, {
139
- value: setSelectNode
140
- }, /*#__PURE__*/ React.createElement(StopEventContext.Provider, {
141
- value: setStopEvent
142
- }, /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
143
- value: childContextValue
144
- }, decoratedElement)));
18
+ return /*#__PURE__*/ React.createElement(ReactNodeView, {
19
+ node: node,
20
+ innerDeco: innerDeco,
21
+ outerDeco: outerDeco,
22
+ getPos: getPos,
23
+ ...props
24
+ });
145
25
  });
@@ -6,7 +6,7 @@ const ForwardedOutputSpec = /*#__PURE__*/ memo(/*#__PURE__*/ forwardRef(function
6
6
  return /*#__PURE__*/ React.createElement(React.Fragment, null, outputSpec);
7
7
  }
8
8
  if (!Array.isArray(outputSpec)) {
9
- throw new Error("@nytimes/react-prosemirror only supports strings and arrays in toDOM");
9
+ throw new Error("@handlewithcare/react-prosemirror only supports strings and arrays in toDOM");
10
10
  }
11
11
  const tagSpec = outputSpec[0];
12
12
  const tagName = tagSpec.replace(" ", ":");
@@ -0,0 +1,85 @@
1
+ import React, { cloneElement, memo, useContext, useMemo, useRef } from "react";
2
+ import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
3
+ import { NodeViewContext } from "../contexts/NodeViewContext.js";
4
+ import { SelectNodeContext } from "../contexts/SelectNodeContext.js";
5
+ import { StopEventContext } from "../contexts/StopEventContext.js";
6
+ import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js";
7
+ import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";
8
+ import { OutputSpec } from "./OutputSpec.js";
9
+ export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
10
+ let { outerDeco, getPos, node, innerDeco, ...props } = param;
11
+ const domRef = useRef(null);
12
+ const nodeDomRef = useRef(null);
13
+ const contentDomRef = useRef(null);
14
+ const getPosFunc = useRef(()=>getPos.current()).current;
15
+ const { nodeViews } = useContext(NodeViewContext);
16
+ let element = null;
17
+ const Component = nodeViews[node.type.name];
18
+ const outputSpec = useMemo(()=>node.type.spec.toDOM?.(node), [
19
+ node
20
+ ]);
21
+ const { hasContentDOM, childDescriptors, setStopEvent, setSelectNode, nodeViewDescRef } = useNodeViewDescriptor(node, ()=>getPos.current(), domRef, nodeDomRef, innerDeco, outerDeco, undefined, contentDomRef);
22
+ const finalProps = {
23
+ ...props,
24
+ ...!hasContentDOM && {
25
+ contentEditable: false
26
+ }
27
+ };
28
+ const nodeProps = useMemo(()=>({
29
+ node: node,
30
+ getPos: getPosFunc,
31
+ decorations: outerDeco,
32
+ innerDecorations: innerDeco
33
+ }), [
34
+ getPosFunc,
35
+ innerDeco,
36
+ node,
37
+ outerDeco
38
+ ]);
39
+ if (Component) {
40
+ element = /*#__PURE__*/ React.createElement(Component, {
41
+ ...finalProps,
42
+ ref: nodeDomRef,
43
+ nodeProps: nodeProps
44
+ }, /*#__PURE__*/ React.createElement(ChildNodeViews, {
45
+ getPos: getPos,
46
+ node: node,
47
+ innerDecorations: innerDeco
48
+ }));
49
+ } else {
50
+ if (outputSpec) {
51
+ element = /*#__PURE__*/ React.createElement(OutputSpec, {
52
+ ...finalProps,
53
+ ref: nodeDomRef,
54
+ outputSpec: outputSpec
55
+ }, /*#__PURE__*/ React.createElement(ChildNodeViews, {
56
+ getPos: getPos,
57
+ node: node,
58
+ innerDecorations: innerDeco
59
+ }));
60
+ }
61
+ }
62
+ if (!element) {
63
+ throw new Error(`Node spec for ${node.type.name} is missing toDOM`);
64
+ }
65
+ const decoratedElement = /*#__PURE__*/ cloneElement(outerDeco.reduce(wrapInDeco, element), // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ outerDeco.some((d)=>d.type.attrs.nodeName) ? {
67
+ ref: domRef
68
+ } : // we've already passed the domRef to the NodeView component
69
+ // as a prop
70
+ undefined);
71
+ const childContextValue = useMemo(()=>({
72
+ parentRef: nodeViewDescRef,
73
+ siblingsRef: childDescriptors
74
+ }), [
75
+ childDescriptors,
76
+ nodeViewDescRef
77
+ ]);
78
+ return /*#__PURE__*/ React.createElement(SelectNodeContext.Provider, {
79
+ value: setSelectNode
80
+ }, /*#__PURE__*/ React.createElement(StopEventContext.Provider, {
81
+ value: setStopEvent
82
+ }, /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
83
+ value: childContextValue
84
+ }, decoratedElement)));
85
+ });
@@ -1,6 +1,6 @@
1
1
  import { DecorationSet } from "prosemirror-view";
2
2
  import { Component } from "react";
3
- import { findDOMNode } from "react-dom";
3
+ import { findDOMNode } from "../findDOMNode.js";
4
4
  import { CompositionViewDesc, TextViewDesc, sortViewDescs } from "../viewdesc.js";
5
5
  import { wrapInDeco } from "./ChildNodeViews.js";
6
6
  function shallowEqual(objA, objB) {
@@ -0,0 +1,31 @@
1
+ //@ts-expect-error This module isn't typed
2
+ import { findCurrentHostFiber } from "react-reconciler/reflection";
3
+ // https://github.com/facebook/react/blob/main/packages/shared/ReactInstanceMap.js#L18
4
+ export function getInstance(key) {
5
+ return key._reactInternals;
6
+ }
7
+ // https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js#L322
8
+ function getPublicInstance(instance) {
9
+ return instance;
10
+ }
11
+ // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberReconciler.js#L153
12
+ function findHostInstance(component) {
13
+ const fiber = getInstance(component);
14
+ if (fiber === undefined) {
15
+ if (typeof component.render === "function") {
16
+ throw new Error("Unable to find node on an unmounted component.");
17
+ } else {
18
+ const keys = Object.keys(component).join(",");
19
+ throw new Error(`Argument appears to not be a ReactComponent. Keys: ${keys}`);
20
+ }
21
+ }
22
+ const hostFiber = findCurrentHostFiber(fiber);
23
+ if (hostFiber === null) {
24
+ return null;
25
+ }
26
+ return getPublicInstance(hostFiber.stateNode);
27
+ }
28
+ // https://github.com/facebook/react/blob/main/packages/react-dom/src/client/ReactDOMClient.js#L43
29
+ export function findDOMNode(componentOrElement) {
30
+ return findHostInstance(componentOrElement);
31
+ }
@@ -171,6 +171,7 @@ let didWarnValueDefaultValue = false;
171
171
  didWarnValueDefaultValue = true;
172
172
  }
173
173
  }
174
+ const flushSyncRef = useRef(true);
174
175
  const [cursorWrapper, _setCursorWrapper] = useState(null);
175
176
  const forceUpdate = useForceUpdate();
176
177
  const defaultState = options.defaultState ?? EMPTY_STATE;
@@ -192,14 +193,23 @@ let didWarnValueDefaultValue = false;
192
193
  setCursorWrapper
193
194
  ]);
194
195
  const dispatchTransaction = useCallback(function dispatchTransaction(tr) {
195
- flushSync(()=>{
196
+ if (flushSyncRef.current) {
197
+ flushSync(()=>{
198
+ if (!options.state) {
199
+ setState((s)=>s.apply(tr));
200
+ }
201
+ if (options.dispatchTransaction) {
202
+ options.dispatchTransaction.call(this, tr);
203
+ }
204
+ });
205
+ } else {
196
206
  if (!options.state) {
197
207
  setState((s)=>s.apply(tr));
198
208
  }
199
209
  if (options.dispatchTransaction) {
200
210
  options.dispatchTransaction.call(this, tr);
201
211
  }
202
- });
212
+ }
203
213
  }, [
204
214
  options.dispatchTransaction,
205
215
  options.state
@@ -264,7 +274,8 @@ let didWarnValueDefaultValue = false;
264
274
  registerEventListener,
265
275
  unregisterEventListener,
266
276
  cursorWrapper,
267
- docViewDescRef
277
+ docViewDescRef,
278
+ flushSyncRef
268
279
  }), [
269
280
  view,
270
281
  registerEventListener,
@@ -15,7 +15,7 @@ import { useLayoutGroupEffect } from "./useLayoutGroupEffect.js";
15
15
  * _after_ the EditorView has been updated, even when the
16
16
  * EditorView lives in an ancestor component.
17
17
  */ export function useEditorEffect(effect, dependencies) {
18
- const { view } = useContext(EditorContext);
18
+ const { view, flushSyncRef } = useContext(EditorContext);
19
19
  // The rules of hooks want `effect` to be included in the
20
20
  // dependency list, but dependency issues for `effect` will
21
21
  // be caught by the linter at the call-site for
@@ -25,7 +25,10 @@ import { useLayoutGroupEffect } from "./useLayoutGroupEffect.js";
25
25
  // be defined inline and run on every re-render.
26
26
  useLayoutGroupEffect(()=>{
27
27
  if (view) {
28
- return effect(view);
28
+ flushSyncRef.current = false;
29
+ const result = effect(view);
30
+ flushSyncRef.current = true;
31
+ return result;
29
32
  }
30
33
  }, // The rules of hooks want to be able to statically
31
34
  // verify the dependencies for the effect, but this will
@@ -0,0 +1,11 @@
1
+ import { useState } from "react";
2
+ import { useSelectNode } from "./useSelectNode.js";
3
+ export function useIsNodeSelected() {
4
+ const [isSelected, setIsSelected] = useState(false);
5
+ useSelectNode(()=>{
6
+ setIsSelected(true);
7
+ }, ()=>{
8
+ setIsSelected(false);
9
+ });
10
+ return isSelected;
11
+ }
package/dist/esm/index.js CHANGED
@@ -7,5 +7,6 @@ export { useEditorEventListener } from "./hooks/useEditorEventListener.js";
7
7
  export { useEditorState } from "./hooks/useEditorState.js";
8
8
  export { useStopEvent } from "./hooks/useStopEvent.js";
9
9
  export { useSelectNode } from "./hooks/useSelectNode.js";
10
+ export { useIsNodeSelected } from "./hooks/useIsNodeSelected.js";
10
11
  export { reactKeys } from "./plugins/reactKeys.js";
11
12
  export { widget } from "./decorations/ReactWidgetType.js";