@@ -5,42 +5,139 @@ import {
5
5
NodeViewConstructor ,
6
6
NodeView as NodeViewT ,
7
7
} from "prosemirror-view" ;
8
- import React , { MutableRefObject , createElement , useContext } from "react" ;
8
+ import React , {
9
+ MutableRefObject ,
10
+ cloneElement ,
11
+ createElement ,
12
+ memo ,
13
+ useContext ,
14
+ useLayoutEffect ,
15
+ useMemo ,
16
+ useRef ,
17
+ } from "react" ;
9
18
import { createPortal } from "react-dom" ;
10
19
20
+ import { ChildDescriptorsContext } from "../contexts/ChildDescriptorsContext.js" ;
11
21
import { EditorContext } from "../contexts/EditorContext.js" ;
12
22
import { useClientOnly } from "../hooks/useClientOnly.js" ;
23
+ import { useNodeViewDescriptor } from "../hooks/useNodeViewDescriptor.js" ;
13
24
14
- import { ChildNodeViews } from "./ChildNodeViews.js" ;
25
+ import { ChildNodeViews , wrapInDeco } from "./ChildNodeViews.js" ;
15
26
16
27
interface Props {
17
- customNodeViewRootRef : MutableRefObject < HTMLDivElement | null > ;
18
- customNodeViewRef : MutableRefObject < NodeViewT | null > ;
19
- contentDomRef : MutableRefObject < HTMLElement | null > ;
20
28
customNodeView : NodeViewConstructor ;
21
- initialNode : MutableRefObject < Node > ;
22
29
node : Node ;
23
30
getPos : MutableRefObject < ( ) => number > ;
24
- initialOuterDeco : MutableRefObject < readonly Decoration [ ] > ;
25
- initialInnerDeco : MutableRefObject < DecorationSource > ;
26
31
innerDeco : DecorationSource ;
32
+ outerDeco : readonly Decoration [ ] ;
27
33
}
28
34
29
- export function CustomNodeView ( {
30
- contentDomRef,
31
- customNodeViewRef,
32
- customNodeViewRootRef,
35
+ export const CustomNodeView = memo ( function CustomNodeView ( {
33
36
customNodeView,
34
- initialNode,
35
37
node,
36
38
getPos,
37
- initialOuterDeco,
38
- initialInnerDeco,
39
39
innerDeco,
40
+ outerDeco,
40
41
} : Props ) {
41
42
const { view } = useContext ( EditorContext ) ;
43
+ const domRef = useRef < HTMLElement | null > ( null ) ;
44
+ const nodeDomRef = useRef < HTMLElement | null > ( null ) ;
45
+ const contentDomRef = useRef < HTMLElement | null > ( null ) ;
46
+ const getPosFunc = useRef ( ( ) => getPos . current ( ) ) . current ;
47
+
48
+ // this is ill-conceived; should revisit
49
+ const initialNode = useRef ( node ) ;
50
+ const initialOuterDeco = useRef ( outerDeco ) ;
51
+ const initialInnerDeco = useRef ( innerDeco ) ;
52
+
53
+ const customNodeViewRootRef = useRef < HTMLDivElement | null > ( null ) ;
54
+ const customNodeViewRef = useRef < NodeViewT | null > ( null ) ;
42
55
43
56
const shouldRender = useClientOnly ( ) ;
57
+
58
+ useLayoutEffect ( ( ) => {
59
+ if (
60
+ ! customNodeViewRef . current ||
61
+ ! customNodeViewRootRef . current ||
62
+ ! shouldRender
63
+ )
64
+ return ;
65
+
66
+ const { dom } = customNodeViewRef . current ;
67
+ nodeDomRef . current = customNodeViewRootRef . current ;
68
+ customNodeViewRootRef . current . appendChild ( dom ) ;
69
+ return ( ) => {
70
+ customNodeViewRef . current ?. destroy ?.( ) ;
71
+ } ;
72
+ } , [ customNodeViewRef , customNodeViewRootRef , nodeDomRef , shouldRender ] ) ;
73
+
74
+ useLayoutEffect ( ( ) => {
75
+ if ( ! customNodeView || ! customNodeViewRef . current || ! shouldRender ) return ;
76
+
77
+ const { destroy, update } = customNodeViewRef . current ;
78
+
79
+ const updated =
80
+ update ?. call ( customNodeViewRef . current , node , outerDeco , innerDeco ) ??
81
+ true ;
82
+ if ( updated ) return ;
83
+
84
+ destroy ?. call ( customNodeViewRef . current ) ;
85
+
86
+ if ( ! customNodeViewRootRef . current ) return ;
87
+
88
+ initialNode . current = node ;
89
+ initialOuterDeco . current = outerDeco ;
90
+ initialInnerDeco . current = innerDeco ;
91
+
92
+ customNodeViewRef . current = customNodeView (
93
+ initialNode . current ,
94
+ // customNodeView will only be set if view is set, and we can only reach
95
+ // this line if customNodeView is set
96
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
97
+ view ! ,
98
+ getPosFunc ,
99
+ initialOuterDeco . current ,
100
+ initialInnerDeco . current
101
+ ) ;
102
+ const { dom } = customNodeViewRef . current ;
103
+ nodeDomRef . current = customNodeViewRootRef . current ;
104
+ customNodeViewRootRef . current . appendChild ( dom ) ;
105
+ } , [
106
+ customNodeView ,
107
+ view ,
108
+ innerDeco ,
109
+ node ,
110
+ outerDeco ,
111
+ getPos ,
112
+ customNodeViewRef ,
113
+ customNodeViewRootRef ,
114
+ initialNode ,
115
+ initialOuterDeco ,
116
+ initialInnerDeco ,
117
+ nodeDomRef ,
118
+ shouldRender ,
119
+ getPosFunc ,
120
+ ] ) ;
121
+
122
+ const { childDescriptors, nodeViewDescRef } = useNodeViewDescriptor (
123
+ node ,
124
+ ( ) => getPos . current ( ) ,
125
+ domRef ,
126
+ nodeDomRef ,
127
+ innerDeco ,
128
+ outerDeco ,
129
+ undefined ,
130
+ contentDomRef
131
+ ) ;
132
+
133
+ const childContextValue = useMemo (
134
+ ( ) => ( {
135
+ parentRef : nodeViewDescRef ,
136
+ siblingsRef : childDescriptors ,
137
+ } ) ,
138
+ [ childDescriptors , nodeViewDescRef ]
139
+ ) ;
140
+
44
141
if ( ! shouldRender ) return null ;
45
142
46
143
if ( ! customNodeViewRef . current ) {
@@ -57,7 +154,7 @@ export function CustomNodeView({
57
154
}
58
155
const { contentDOM } = customNodeViewRef . current ;
59
156
contentDomRef . current = contentDOM ?? null ;
60
- return createElement (
157
+ const element = createElement (
61
158
node . isInline ? "span" : "div" ,
62
159
{
63
160
ref : customNodeViewRootRef ,
@@ -74,4 +171,21 @@ export function CustomNodeView({
74
171
contentDOM
75
172
)
76
173
) ;
77
- }
174
+
175
+ const decoratedElement = cloneElement (
176
+ outerDeco . reduce ( wrapInDeco , element ) ,
177
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
+ outerDeco . some ( ( d ) => ( d as any ) . type . attrs . nodeName )
179
+ ? { ref : domRef }
180
+ : // If all of the node decorations were attr-only, then
181
+ // we've already passed the domRef to the NodeView component
182
+ // as a prop
183
+ undefined
184
+ ) ;
185
+
186
+ return (
187
+ < ChildDescriptorsContext . Provider value = { childContextValue } >
188
+ { decoratedElement }
189
+ </ ChildDescriptorsContext . Provider >
190
+ ) ;
191
+ } ) ;
0 commit comments