Skip to content

Commit

Permalink
Keep docView stable and only protect active text node during composition
Browse files Browse the repository at this point in the history
  • Loading branch information
smoores-dev committed Oct 6, 2023
1 parent 3417741 commit 5b29bfa
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 48 deletions.
30 changes: 15 additions & 15 deletions docs/assets/index-191f530f.js → docs/assets/index-6451b138.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React-ProseMirror Demo</title>
<script type="module" crossorigin src="/react-prosemirror/assets/index-191f530f.js"></script>
<script type="module" crossorigin src="/react-prosemirror/assets/index-6451b138.js"></script>
<link rel="stylesheet" href="/react-prosemirror/assets/index-17fff6c1.css">
</head>
<body>
Expand Down
1 change: 1 addition & 0 deletions src/components/ChildNodeViews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ function InlineView({ innerPos, childViews }: SharedMarksProps) {
<TextNodeView
view={editorView}
node={child.node}
pos={childPos}
siblingDescriptors={siblingDescriptors}
decorations={child.outerDeco}
/>
Expand Down
7 changes: 5 additions & 2 deletions src/components/DocNodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Decoration,
DecorationSource,
} from "../prosemirror-view/decoration.js";
import { NodeViewDesc } from "../prosemirror-view/viewdesc.js";

import { ChildNodeViews, wrapInDeco } from "./ChildNodeViews.js";

Expand All @@ -27,10 +28,11 @@ type Props = {
innerDeco: DecorationSource;
outerDeco: Decoration[];
as?: ReactElement;
viewDesc?: NodeViewDesc;
};

export const DocNodeView = forwardRef(function DocNodeView(
{ className, node, innerDeco, outerDeco, as }: Props,
{ className, node, innerDeco, outerDeco, as, viewDesc }: Props,
ref: ForwardedRef<HTMLDivElement | null>
) {
const innerRef = useRef<HTMLDivElement | null>(null);
Expand All @@ -48,7 +50,8 @@ export const DocNodeView = forwardRef(function DocNodeView(
innerRef,
innerRef,
innerDeco,
outerDeco
outerDeco,
viewDesc
);

const props = {
Expand Down
32 changes: 29 additions & 3 deletions src/components/ProseMirror.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Command, EditorState, Transaction } from "prosemirror-state";
import { DecorationSet } from "prosemirror-view";
import React, {
ForwardRefExoticComponent,
ReactElement,
ReactNode,
RefAttributes,
useEffect,
useMemo,
useRef,
useState,
} from "react";

Expand All @@ -17,13 +17,17 @@ import { useComponentEventListeners } from "../hooks/useComponentEventListeners.
import { useEditorView } from "../hooks/useEditorView.js";
import { useSyncSelection } from "../hooks/useSyncSelection.js";
import { usePluginViews } from "../hooks/useViewPlugins.js";
import { viewDecorations } from "../prosemirror-view/decoration.js";
import {
DecorationSet,
viewDecorations,
} from "../prosemirror-view/decoration.js";
import {
DecorationSet as DecorationSetInternal,
DirectEditorProps,
EditorView as EditorViewClass,
computeDocDeco,
} from "../prosemirror-view/index.js";
import { NodeViewDesc } from "../prosemirror-view/viewdesc.js";

import { DocNodeView } from "./DocNodeView.js";
import { NodeViewComponentProps } from "./NodeViewComponentProps.js";
Expand Down Expand Up @@ -83,7 +87,28 @@ export function ProseMirror({
[props.plugins, componentEventListenersPlugin]
);

const editorView = useEditorView(mount, { ...props, plugins });
const initialEditorState = (
"defaultState" in props ? props.defaultState : props.state
) as EditorState;
const tempDom = document.createElement("div");
const docViewDescRef = useRef<NodeViewDesc>(
new NodeViewDesc(
undefined,
[],
initialEditorState.doc,
[],
DecorationSetInternal.empty,
tempDom,
null,
tempDom
)
);

const editorView = useEditorView(mount, {
...props,
docView: docViewDescRef.current,
plugins,
});

const editorState =
"state" in props ? props.state ?? null : editorView?.state ?? null;
Expand Down Expand Up @@ -139,6 +164,7 @@ export function ProseMirror({
innerDeco={innerDecos as unknown as DecorationSetInternal}
outerDeco={outerDecos}
as={as}
viewDesc={docViewDescRef.current}
/>
{children}
</>
Expand Down
8 changes: 7 additions & 1 deletion src/components/TextNodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { wrapInDeco } from "./ChildNodeViews.js";
type Props = {
view: EditorView | null;
node: Node;
pos: number;
siblingDescriptors: ViewDesc[];
decorations: readonly Decoration[];
};
Expand Down Expand Up @@ -65,7 +66,12 @@ export class TextNodeView extends Component<Props> {
}

render() {
if (this.props.view?.composing) {
const { view, pos, node } = this.props;
if (
view?.composing &&
view.state.selection.from >= pos &&
view.state.selection.from <= pos + node.nodeSize
) {
return this.renderRef;
}

Expand Down
10 changes: 0 additions & 10 deletions src/hooks/useEditorView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
storeScrollPos,
} from "../prosemirror-view/domcoords.js";
import { DirectEditorProps, EditorView } from "../prosemirror-view/index.js";
import { NodeViewDesc } from "../prosemirror-view/viewdesc.js";

import { useForceUpdate } from "./useForceUpdate.js";

Expand Down Expand Up @@ -52,15 +51,6 @@ class ReactEditorView extends EditorView {
resetScrollPos(oldScrollPos);
}
}

// @ts-expect-error We need this to be an accessor
set docView(_) {
// disallowed
}

get docView() {
return this.dom.pmViewDesc as NodeViewDesc;
}
}

function withBatchedUpdates<This, T extends unknown[]>(
Expand Down
44 changes: 30 additions & 14 deletions src/hooks/useNodeViewDescriptor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Node } from "prosemirror-model";
import { MutableRefObject, useContext, useLayoutEffect } from "react";
import { MutableRefObject, useContext, useLayoutEffect, useRef } from "react";

import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
import {
Expand All @@ -13,8 +13,10 @@ export function useNodeViewDescriptor(
domRef: undefined | MutableRefObject<HTMLElement | null>,
nodeDomRef: MutableRefObject<HTMLElement | null>,
innerDecorations: DecorationSource,
outerDecorations: readonly Decoration[]
outerDecorations: readonly Decoration[],
viewDesc?: NodeViewDesc
) {
const nodeViewDescRef = useRef<NodeViewDesc | undefined>(viewDesc);
const siblingDescriptors = useContext(ChildDescriptorsContext);
const childDescriptors: ViewDesc[] = [];

Expand All @@ -23,20 +25,34 @@ export function useNodeViewDescriptor(

const firstChildDesc = childDescriptors[0];

const desc = new NodeViewDesc(
undefined,
childDescriptors,
node,
outerDecorations,
innerDecorations,
domRef?.current ?? nodeDomRef.current,
firstChildDesc?.dom.parentElement ?? null,
nodeDomRef.current
);
siblingDescriptors.push(desc);
if (!nodeViewDescRef.current) {
nodeViewDescRef.current = new NodeViewDesc(
undefined,
childDescriptors,
node,
outerDecorations,
innerDecorations,
domRef?.current ?? nodeDomRef.current,
firstChildDesc?.dom.parentElement ?? null,
nodeDomRef.current
);
} else {
nodeViewDescRef.current.parent = undefined;
nodeViewDescRef.current.children = childDescriptors;
nodeViewDescRef.current.node = node;
nodeViewDescRef.current.outerDeco = outerDecorations;
nodeViewDescRef.current.innerDeco = innerDecorations;
nodeViewDescRef.current.dom = domRef?.current ?? nodeDomRef.current;
// @ts-expect-error ???
nodeViewDescRef.current.dom.pmViewDesc = nodeViewDescRef.current;
nodeViewDescRef.current.contentDOM =
firstChildDesc?.dom.parentElement ?? null;
nodeViewDescRef.current.nodeDOM = nodeDomRef.current;
}
siblingDescriptors.push(nodeViewDescRef.current);

for (const childDesc of childDescriptors) {
childDesc.parent = desc;
childDesc.parent = nodeViewDescRef.current;
}
});

Expand Down
4 changes: 3 additions & 1 deletion src/prosemirror-view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class EditorView {

this.editable = getEditable(this)
this.nodeViews = buildNodeViews(this)
this.docView = null as unknown as NodeViewDesc
this.docView = props.docView ?? docViewDesc(this.state.doc, computeDocDeco(this), viewDecorations(this), this.dom, this)

this.domObserver = new DOMObserver(this, (from, to, typeOver, added) => readDOMChange(this, from, to, typeOver, added))
this.init();
Expand Down Expand Up @@ -797,4 +797,6 @@ export interface DirectEditorProps extends EditorProps {
/// [applied](#state.EditorState.apply). The callback will be bound to have
/// the view instance as its `this` binding.
dispatchTransaction?: (tr: Transaction) => void

docView?: NodeViewDesc
}
2 changes: 1 addition & 1 deletion src/prosemirror-view/viewdesc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ export class NodeViewDesc extends ViewDesc {
public innerDeco: DecorationSource,
dom: DOMNode,
contentDOM: HTMLElement | null,
readonly nodeDOM: DOMNode,
public nodeDOM: DOMNode,
) {
super(parent, children, dom, contentDOM)
}
Expand Down

0 comments on commit 5b29bfa

Please sign in to comment.