@handlewithcare/react-prosemirror 2.2.4 → 2.3.1

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 (35) hide show
  1. package/README.md +13 -2
  2. package/dist/cjs/components/CustomNodeView.js +68 -29
  3. package/dist/cjs/components/ReactNodeView.js +5 -2
  4. package/dist/cjs/contexts/IgnoreMutationContext.js +12 -0
  5. package/dist/cjs/hooks/useClientOnly.js +6 -5
  6. package/dist/cjs/hooks/useEditor.js +1 -1
  7. package/dist/cjs/hooks/useEditorEventCallback.js +6 -4
  8. package/dist/cjs/hooks/useIgnoreMutation.js +24 -0
  9. package/dist/cjs/hooks/useNodeViewDescriptor.js +7 -2
  10. package/dist/cjs/index.js +4 -0
  11. package/dist/cjs/props.js +36 -25
  12. package/dist/cjs/viewdesc.js +6 -6
  13. package/dist/esm/components/CustomNodeView.js +68 -29
  14. package/dist/esm/components/ReactNodeView.js +5 -2
  15. package/dist/esm/contexts/IgnoreMutationContext.js +2 -0
  16. package/dist/esm/hooks/useClientOnly.js +7 -6
  17. package/dist/esm/hooks/useEditor.js +1 -1
  18. package/dist/esm/hooks/useEditorEventCallback.js +6 -4
  19. package/dist/esm/hooks/useIgnoreMutation.js +14 -0
  20. package/dist/esm/hooks/useNodeViewDescriptor.js +7 -2
  21. package/dist/esm/index.js +1 -0
  22. package/dist/esm/props.js +36 -23
  23. package/dist/esm/viewdesc.js +6 -6
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/dist/types/components/NodeViewComponentProps.d.ts +2 -1
  26. package/dist/types/components/ProseMirror.d.ts +2 -2
  27. package/dist/types/contexts/IgnoreMutationContext.d.ts +4 -0
  28. package/dist/types/contexts/NodeViewContext.d.ts +2 -2
  29. package/dist/types/hooks/useEditorEventCallback.d.ts +1 -1
  30. package/dist/types/hooks/useIgnoreMutation.d.ts +2 -0
  31. package/dist/types/hooks/useNodeViewDescriptor.d.ts +2 -1
  32. package/dist/types/index.d.ts +1 -0
  33. package/dist/types/props.d.ts +1 -7
  34. package/dist/types/viewdesc.d.ts +2 -2
  35. package/package.json +1 -2
@@ -1,5 +1,6 @@
1
1
  import React, { cloneElement, memo, useContext, useMemo, useRef } from "react";
2
2
  import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
3
+ import { IgnoreMutationContext } from "../contexts/IgnoreMutationContext.js";
3
4
  import { NodeViewContext } from "../contexts/NodeViewContext.js";
4
5
  import { SelectNodeContext } from "../contexts/SelectNodeContext.js";
5
6
  import { StopEventContext } from "../contexts/StopEventContext.js";
@@ -18,7 +19,7 @@ export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
18
19
  const outputSpec = useMemo(()=>node.type.spec.toDOM?.(node), [
19
20
  node
20
21
  ]);
21
- const { hasContentDOM, childDescriptors, setStopEvent, setSelectNode, nodeViewDescRef } = useNodeViewDescriptor(node, ()=>getPos.current(), domRef, nodeDomRef, innerDeco, outerDeco, undefined, contentDomRef);
22
+ const { hasContentDOM, childDescriptors, setStopEvent, setSelectNode, setIgnoreMutation, nodeViewDescRef } = useNodeViewDescriptor(node, ()=>getPos.current(), domRef, nodeDomRef, innerDeco, outerDeco, undefined, contentDomRef);
22
23
  const finalProps = {
23
24
  ...props,
24
25
  ...!hasContentDOM && {
@@ -79,7 +80,9 @@ export const ReactNodeView = /*#__PURE__*/ memo(function ReactNodeView(param) {
79
80
  value: setSelectNode
80
81
  }, /*#__PURE__*/ React.createElement(StopEventContext.Provider, {
81
82
  value: setStopEvent
83
+ }, /*#__PURE__*/ React.createElement(IgnoreMutationContext.Provider, {
84
+ value: setIgnoreMutation
82
85
  }, /*#__PURE__*/ React.createElement(ChildDescriptorsContext.Provider, {
83
86
  value: childContextValue
84
- }, decoratedElement)));
87
+ }, decoratedElement))));
85
88
  });
@@ -0,0 +1,2 @@
1
+ import { createContext } from "react";
2
+ export const IgnoreMutationContext = createContext(null);
@@ -1,8 +1,9 @@
1
- import { useEffect, useState } from "react";
1
+ import { useSyncExternalStore } from "react";
2
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
3
+ function unsubscribe() {}
4
+ function subscribe() {
5
+ return unsubscribe;
6
+ }
2
7
  export function useClientOnly() {
3
- const [render, setRender] = useState(false);
4
- useEffect(()=>{
5
- setRender(true);
6
- }, []);
7
- return render;
8
+ return useSyncExternalStore(subscribe, ()=>true, ()=>false);
8
9
  }
@@ -220,7 +220,7 @@ let didWarnValueDefaultValue = false;
220
220
  cleanup();
221
221
  const docViewDescRef = useRef(new NodeViewDesc(undefined, [], ()=>-1, state.doc, [], DecorationSet.empty, tempDom, null, tempDom, ()=>false, ()=>{
222
222
  /* The doc node can't have a node selection*/ }, ()=>{
223
- /* The doc node can't have a node selection*/ }));
223
+ /* The doc node can't have a node selection*/ }, ()=>false));
224
224
  const directEditorProps = {
225
225
  ...options,
226
226
  state,
@@ -25,10 +25,12 @@ import { useEditorEffect } from "./useEditorEffect.js";
25
25
  for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
26
26
  args[_key] = arguments[_key];
27
27
  }
28
- if (view) {
29
- return ref.current(view, ...args);
30
- }
31
- return;
28
+ // It's not actually possible for an event handler to run
29
+ // while view is null, since view is only ever set to
30
+ // null in a layout effect that then immediately triggers
31
+ // a re-render which sets view to a new EditorView
32
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
33
+ return ref.current(view, ...args);
32
34
  }, [
33
35
  view
34
36
  ]);
@@ -0,0 +1,14 @@
1
+ import { useContext } from "react";
2
+ import { IgnoreMutationContext } from "../contexts/IgnoreMutationContext.js";
3
+ import { useEditorEffect } from "./useEditorEffect.js";
4
+ import { useEditorEventCallback } from "./useEditorEventCallback.js";
5
+ export function useIgnoreMutation(ignoreMutation) {
6
+ const register = useContext(IgnoreMutationContext);
7
+ const ignoreMutationMemo = useEditorEventCallback(ignoreMutation);
8
+ useEditorEffect(()=>{
9
+ register(ignoreMutationMemo);
10
+ }, [
11
+ register,
12
+ ignoreMutationMemo
13
+ ]);
14
+ }
@@ -11,6 +11,10 @@ export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDec
11
11
  const setStopEvent = useCallback((newStopEvent)=>{
12
12
  stopEvent.current = newStopEvent;
13
13
  }, []);
14
+ const ignoreMutation = useRef(()=>false);
15
+ const setIgnoreMutation = useCallback((newIgnoreMutation)=>{
16
+ ignoreMutation.current = newIgnoreMutation;
17
+ }, []);
14
18
  const selectNode = useRef(()=>{
15
19
  if (!nodeDomRef.current || !node) return;
16
20
  if (nodeDomRef.current.nodeType == 1) nodeDomRef.current.classList.add("ProseMirror-selectednode");
@@ -46,7 +50,7 @@ export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDec
46
50
  if (!node || !nodeDomRef.current) return;
47
51
  const firstChildDesc = childDescriptors.current[0];
48
52
  if (!nodeViewDescRef.current) {
49
- nodeViewDescRef.current = new NodeViewDesc(parentRef.current, childDescriptors.current, getPos, node, outerDecorations, innerDecorations, domRef?.current ?? nodeDomRef.current, firstChildDesc?.dom.parentElement ?? null, nodeDomRef.current, (event)=>!!stopEvent.current(event), ()=>selectNode.current(), ()=>deselectNode.current());
53
+ nodeViewDescRef.current = new NodeViewDesc(parentRef.current, childDescriptors.current, getPos, node, outerDecorations, innerDecorations, domRef?.current ?? nodeDomRef.current, firstChildDesc?.dom.parentElement ?? null, nodeDomRef.current, (event)=>!!stopEvent.current(event), ()=>selectNode.current(), ()=>deselectNode.current(), (mutation)=>ignoreMutation.current(mutation));
50
54
  } else {
51
55
  nodeViewDescRef.current.parent = parentRef.current;
52
56
  nodeViewDescRef.current.children = childDescriptors.current;
@@ -101,6 +105,7 @@ export function useNodeViewDescriptor(node, getPos, domRef, nodeDomRef, innerDec
101
105
  childDescriptors,
102
106
  nodeViewDescRef,
103
107
  setStopEvent,
104
- setSelectNode
108
+ setSelectNode,
109
+ setIgnoreMutation
105
110
  };
106
111
  }
package/dist/esm/index.js CHANGED
@@ -7,6 +7,7 @@ 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 { useIgnoreMutation } from "./hooks/useIgnoreMutation.js";
10
11
  export { useIsNodeSelected } from "./hooks/useIsNodeSelected.js";
11
12
  export { reactKeys } from "./plugins/reactKeys.js";
12
13
  export { widget } from "./decorations/ReactWidgetType.js";
package/dist/esm/props.js CHANGED
@@ -1,27 +1,31 @@
1
1
  import cx from "classnames";
2
- import { generate, parse } from "css-tree";
3
- export function kebabCaseToCamelCase(str) {
4
- return str.replaceAll(/-[a-z]/g, (g)=>g[1]?.toUpperCase() ?? "");
5
- }
6
- /**
7
- * Converts a CSS style string to an object
8
- * that can be passed to a React component's
9
- * `style` prop.
10
- */ export function cssToStyles(css) {
11
- const ast = parse(`* { ${css} }`);
12
- if (ast.type !== "StyleSheet") return {};
13
- const rule = ast.children.first;
14
- if (rule?.type !== "Rule") return {};
15
- const block = rule.block;
16
- const styles = {};
17
- for (const declaration of block.children){
18
- if (declaration.type !== "Declaration") continue;
19
- const property = declaration.property;
20
- const value = declaration.value.type === "Raw" ? declaration.value.value : generate(declaration.value);
21
- const camelCasePropertyName = property.startsWith("--") ? property : kebabCaseToCamelCase(property);
22
- styles[camelCasePropertyName] = value;
2
+ let patched = false;
3
+ function patchConsoleError() {
4
+ if (patched) return;
5
+ /* eslint-disable no-console */ const consoleError = console.error;
6
+ console.error = function() {
7
+ for(var _len = arguments.length, data = new Array(_len), _key = 0; _key < _len; _key++){
8
+ data[_key] = arguments[_key];
9
+ }
10
+ const [message, prop, correction] = data;
11
+ if (typeof message === "string" && message.startsWith("Warning: Invalid DOM property `%s`. Did you mean `%s`?") && prop === "STYLE" && correction === "style") {
12
+ return;
13
+ }
14
+ consoleError(...data);
15
+ };
16
+ patched = true;
17
+ /* eslint-enable no-console */ }
18
+ function mergeStyleProps(a, b) {
19
+ if (!("STYLE" in a)) {
20
+ if (!("STYLE" in b)) {
21
+ return undefined;
22
+ }
23
+ return b.STYLE;
24
+ }
25
+ if (!("STYLE" in b)) {
26
+ return a.STYLE;
23
27
  }
24
- return styles;
28
+ return `${a.STYLE.match(/;\s*$/) ? a.STYLE : `${a.STYLE}`} ${b.STYLE}`;
25
29
  }
26
30
  /**
27
31
  * Merges two sets of React props. Class names
@@ -32,6 +36,7 @@ export function kebabCaseToCamelCase(str) {
32
36
  ...a,
33
37
  ...b,
34
38
  className: cx(a.className, b.className),
39
+ STYLE: mergeStyleProps(a, b),
35
40
  style: {
36
41
  ...a.style,
37
42
  ...b.style
@@ -42,6 +47,7 @@ export function kebabCaseToCamelCase(str) {
42
47
  * Given a record of HTML attributes, returns tho
43
48
  * equivalent React props.
44
49
  */ export function htmlAttrsToReactProps(attrs) {
50
+ patchConsoleError();
45
51
  const props = {};
46
52
  for (const [attrName, attrValue] of Object.entries(attrs)){
47
53
  switch(attrName.toLowerCase()){
@@ -52,7 +58,14 @@ export function kebabCaseToCamelCase(str) {
52
58
  }
53
59
  case "style":
54
60
  {
55
- props.style = cssToStyles(attrValue);
61
+ // HACK: React expects the `style` prop to be an
62
+ // object mapping from CSS property name to value.
63
+ // However, it will pass un-recognized props through
64
+ // to the underlying DOM element, and HTML attributes
65
+ // are case insensitive. So we use `STYLE` instead,
66
+ // which React doesn't intercept, but the DOM treats
67
+ // as `style`
68
+ props.STYLE = attrValue;
56
69
  break;
57
70
  }
58
71
  case "autocapitalize":
@@ -539,8 +539,9 @@ export class NodeViewDesc extends ViewDesc {
539
539
  stopEvent;
540
540
  selectNode;
541
541
  deselectNode;
542
- constructor(parent, children, getPos, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, stopEvent, selectNode, deselectNode){
543
- super(parent, children, getPos, dom, contentDOM), this.node = node, this.outerDeco = outerDeco, this.innerDeco = innerDeco, this.nodeDOM = nodeDOM, this.stopEvent = stopEvent, this.selectNode = selectNode, this.deselectNode = deselectNode;
542
+ ignoreMutation;
543
+ constructor(parent, children, getPos, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, stopEvent, selectNode, deselectNode, ignoreMutation){
544
+ super(parent, children, getPos, dom, contentDOM), this.node = node, this.outerDeco = outerDeco, this.innerDeco = innerDeco, this.nodeDOM = nodeDOM, this.stopEvent = stopEvent, this.selectNode = selectNode, this.deselectNode = deselectNode, this.ignoreMutation = ignoreMutation;
544
545
  }
545
546
  updateOuterDeco() {
546
547
  // pass
@@ -600,7 +601,9 @@ export class TextViewDesc extends NodeViewDesc {
600
601
  constructor(parent, children, getPos, node, outerDeco, innerDeco, dom, nodeDOM){
601
602
  super(parent, children, getPos, node, outerDeco, innerDeco, dom, null, nodeDOM, ()=>false, ()=>{
602
603
  /* Text nodes can't have node selections */ }, ()=>{
603
- /* Text nodes can't have node selections */ });
604
+ /* Text nodes can't have node selections */ }, (mutation)=>{
605
+ return mutation.type != "characterData" && mutation.type != "selection";
606
+ });
604
607
  }
605
608
  parseRule() {
606
609
  let skip = this.nodeDOM.parentNode;
@@ -627,9 +630,6 @@ export class TextViewDesc extends NodeViewDesc {
627
630
  if (dom == this.nodeDOM) return this.posAtStart + Math.min(offset, this.node.text.length);
628
631
  return super.localPosFromDOM(dom, offset, bias);
629
632
  }
630
- ignoreMutation(mutation) {
631
- return mutation.type != "characterData" && mutation.type != "selection";
632
- }
633
633
  markDirty(from, to) {
634
634
  super.markDirty(from, to);
635
635
  if (this.dom != this.nodeDOM && (from == 0 || to == this.nodeDOM.nodeValue.length)) this.dirty = NODE_DIRTY;