Skip to content

Commit

Permalink
Fix domAtPos with MarkViews
Browse files Browse the repository at this point in the history
  • Loading branch information
smoores-dev committed Aug 5, 2023
1 parent 8193f8e commit 7f74ac5
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 141 deletions.
100 changes: 72 additions & 28 deletions src/components/DocNodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { DecorationSet } from "prosemirror-view";
import React, {
ForwardedRef,
ReactNode,
cloneElement,
createElement,
forwardRef,
useContext,
useImperativeHandle,
useLayoutEffect,
useRef,
} from "react";
Expand All @@ -14,8 +16,12 @@ import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js"
import { NodeViewContext } from "../contexts/NodeViewContext.js";
import { ReactWidgetType } from "../decorations/ReactWidgetType.js";
import { NodeViewDesc, ViewDesc, iterDeco } from "../descriptors/ViewDesc.js";
import { DecorationSourceInternal } from "../prosemirror-internal/DecorationInternal.js";
import {
DecorationSourceInternal,
NonWidgetType,
} from "../prosemirror-internal/DecorationInternal.js";

import { MarkView } from "./MarkView.js";
import { NodeView } from "./NodeView.js";
import { TextNodeView } from "./TextNodeView.js";

Expand All @@ -27,12 +33,20 @@ type Props = {

export const DocNodeView = forwardRef(function DocNodeView(
{ node, contentEditable, decorations, ...props }: Props,
ref: ForwardedRef<HTMLDivElement>
ref: ForwardedRef<HTMLDivElement | null>
) {
const { posToDesc, domToDesc } = useContext(NodeViewContext);
const siblingDescriptors = useContext(ChildDescriptorsContext);
const childDescriptors: ViewDesc[] = [];
const innerRef = useRef<Element | null>(null);
const innerRef = useRef<HTMLDivElement | null>(null);

useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(
ref,
() => {
return innerRef.current;
},
[]
);

useLayoutEffect(() => {
if (!innerRef.current) return;
Expand All @@ -41,6 +55,7 @@ export const DocNodeView = forwardRef(function DocNodeView(

const desc = new NodeViewDesc(
undefined,
childDescriptors,
node,
[],
DecorationSet.empty,
Expand All @@ -50,7 +65,6 @@ export const DocNodeView = forwardRef(function DocNodeView(
posToDesc,
domToDesc
);
desc.children = childDescriptors;
posToDesc.set(-1, desc);
domToDesc.set(innerRef.current, desc);
siblingDescriptors.push(desc);
Expand All @@ -62,6 +76,7 @@ export const DocNodeView = forwardRef(function DocNodeView(

const children: ReactNode[] = [];
const innerPos = 0;

iterDeco(
node,
decorations,
Expand All @@ -74,30 +89,59 @@ export const DocNodeView = forwardRef(function DocNodeView(
},
(childNode, outerDeco, innerDeco, offset) => {
const childPos = innerPos + offset;
if (childNode.isText) {
children.push(
<ChildDescriptorsContext.Consumer key={childPos}>
{(siblingDescriptors) => (
<TextNodeView
node={childNode}
pos={childPos}
siblingDescriptors={siblingDescriptors}
decorations={outerDeco}
/>
)}
</ChildDescriptorsContext.Consumer>
);
} else {
children.push(
<NodeView
key={childPos}
node={childNode}
pos={childPos}
decorations={outerDeco}
innerDecorations={innerDeco}
/>
);
}
const nodeElement = childNode.isText ? (
<ChildDescriptorsContext.Consumer>
{(siblingDescriptors) => (
<TextNodeView
node={childNode}
pos={childPos}
siblingDescriptors={siblingDescriptors}
decorations={outerDeco}
/>
)}
</ChildDescriptorsContext.Consumer>
) : (
<NodeView
node={childNode}
pos={childPos}
decorations={outerDeco}
innerDecorations={innerDeco}
/>
);

const childElement = outerDeco.reduce(
(element, deco) => {
const {
nodeName,
class: className,
style: _,
...attrs
} = (deco.type as NonWidgetType).attrs;

if (nodeName) {
return createElement(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
nodeName!,
{
className,
...attrs,
},
element
);
}
return cloneElement(element, {
className,
...attrs,
});
},

childNode.marks.reduce(
(element, mark) => <MarkView mark={mark}>{element}</MarkView>,
nodeElement
)
);

children.push(cloneElement(childElement, { key: childPos }));
}
);

Expand Down
55 changes: 52 additions & 3 deletions src/components/MarkView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { Mark } from "prosemirror-model";
import React, { ReactNode, forwardRef } from "react";
import React, {
ReactNode,
forwardRef,
useContext,
useImperativeHandle,
useLayoutEffect,
useRef,
} from "react";

import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js";
import { NodeViewContext } from "../contexts/NodeViewContext.js";
import { MarkViewDesc, ViewDesc } from "../descriptors/ViewDesc.js";

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

Expand All @@ -12,13 +23,51 @@ export const MarkView = forwardRef(function MarkView(
{ mark, children }: Props,
ref
) {
const { posToDesc, domToDesc } = useContext(NodeViewContext);
const siblingDescriptors = useContext(ChildDescriptorsContext);
const childDescriptors: ViewDesc[] = [];
const domRef = useRef<HTMLElement | null>(null);

useImperativeHandle(
ref,
() => {
return domRef.current;
},
[]
);

const outputSpec = mark.type.spec.toDOM?.(mark, true);
if (!outputSpec)
throw new Error(`Mark spec for ${mark.type.name} is missing toDOM`);

useLayoutEffect(() => {
if (!domRef.current) return;

const firstChildDesc = childDescriptors[0];

const desc = new MarkViewDesc(
undefined,
childDescriptors,
mark,
domRef.current,
firstChildDesc?.dom.parentElement ?? domRef.current,
posToDesc,
domToDesc
);
// posToDesc.set(pos, desc);
domToDesc.set(domRef.current, desc);
siblingDescriptors.push(desc);

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

return (
<OutputSpec ref={ref} outputSpec={outputSpec}>
{children}
<OutputSpec ref={domRef} outputSpec={outputSpec}>
<ChildDescriptorsContext.Provider value={childDescriptors}>
{children}
</ChildDescriptorsContext.Provider>
</OutputSpec>
);
});
Loading

0 comments on commit 7f74ac5

Please sign in to comment.