0
# Component Extensions
1
2
Extensions and utilities for integrating React components as ProseMirror node views and managing the portal system.
3
4
## Capabilities
5
6
### React Component Extension
7
8
Main extension for integrating React components as node views in the editor.
9
10
```typescript { .api }
11
/**
12
* Extension for integrating React components as ProseMirror node views
13
*/
14
class ReactComponentExtension extends Extension<ReactComponentOptions> {
15
/** Extension name */
16
static readonly name = 'reactComponent';
17
18
/** Create node view for React components */
19
createNodeViews(): NodeViewMethod | {};
20
21
/** Create plugin for managing React components */
22
createPlugin(): ProsemirrorPlugin;
23
}
24
25
interface ReactComponentOptions {
26
/** Default component environment */
27
defaultEnvironment?: ReactComponentEnvironment;
28
/** Whether to use strict mode */
29
strict?: boolean;
30
/** Custom portal container */
31
portalContainer?: HTMLElement;
32
}
33
34
type ReactComponentEnvironment = 'ssr' | 'dom' | 'both';
35
```
36
37
**Usage Example:**
38
39
```typescript
40
import React from 'react';
41
import { ReactComponentExtension, useRemirror } from '@remirror/react';
42
43
// Custom node component
44
function CustomNode({ node, updateAttributes }) {
45
return (
46
<div className="custom-node">
47
<h3>{node.attrs.title}</h3>
48
<button onClick={() => updateAttributes({ title: 'Updated!' })}>
49
Update Title
50
</button>
51
</div>
52
);
53
}
54
55
function MyEditor() {
56
const { manager, state } = useRemirror({
57
extensions: () => [
58
new ReactComponentExtension({
59
defaultEnvironment: 'dom',
60
}),
61
// Your other extensions that use React components
62
],
63
});
64
65
return <Remirror manager={manager} initialContent={state} />;
66
}
67
```
68
69
### Portal System
70
71
Advanced React portal management for rendering components in specific locations within the editor.
72
73
```typescript { .api }
74
/**
75
* Container for managing React portals within the editor
76
*/
77
class PortalContainer {
78
/** Render a React component in a portal */
79
render(Component: ComponentType, props?: any, key?: string): string;
80
81
/** Remove a portal by key */
82
remove(key: string): void;
83
84
/** Update a portal with new props */
85
update(key: string, props: any): void;
86
87
/** Clear all portals */
88
clear(): void;
89
90
/** Get all mounted portals */
91
getPortals(): PortalMap;
92
}
93
94
/**
95
* Component that renders all active portals into the DOM
96
*/
97
interface RemirrorPortals extends React.Component<RemirrorPortalsProps> {}
98
99
interface RemirrorPortalsProps {
100
/** Portal container instance */
101
container?: PortalContainer;
102
/** Custom render function */
103
renderPortal?: (portal: MountedPortal) => React.ReactNode;
104
}
105
106
/**
107
* Hook to subscribe to portal container updates
108
* @param container - Portal container to subscribe to
109
* @returns Current portal map and update trigger
110
*/
111
function usePortals(container?: PortalContainer): {
112
portals: PortalMap;
113
updateCount: number;
114
};
115
```
116
117
**Usage Example:**
118
119
```typescript
120
import React, { useEffect } from 'react';
121
import {
122
usePortalContainer,
123
RemirrorPortals,
124
useRemirror,
125
ReactComponentExtension
126
} from '@remirror/react';
127
128
function FloatingComponent({ position }: { position: { x: number; y: number } }) {
129
return (
130
<div
131
style={{
132
position: 'absolute',
133
left: position.x,
134
top: position.y,
135
background: 'white',
136
border: '1px solid #ccc',
137
padding: '8px',
138
}}
139
>
140
Floating content!
141
</div>
142
);
143
}
144
145
function EditorWithPortals() {
146
const { manager, state } = useRemirror({
147
extensions: () => [new ReactComponentExtension()],
148
});
149
150
const portalContainer = usePortalContainer();
151
152
useEffect(() => {
153
// Render a floating component
154
const key = portalContainer.render(FloatingComponent, {
155
position: { x: 100, y: 100 }
156
});
157
158
// Clean up on unmount
159
return () => portalContainer.remove(key);
160
}, [portalContainer]);
161
162
return (
163
<Remirror manager={manager} initialContent={state}>
164
<RemirrorPortals />
165
</Remirror>
166
);
167
}
168
```
169
170
### Node View Creation
171
172
Utilities and interfaces for creating React-based node views.
173
174
```typescript { .api }
175
/**
176
* Props passed to React node view components
177
*/
178
interface NodeViewComponentProps {
179
/** The ProseMirror node */
180
node: ProsemirrorNode;
181
/** Node view instance */
182
view: EditorView;
183
/** Function to get current position */
184
getPos: () => number;
185
/** Node decorations */
186
decorations: readonly Decoration[];
187
/** Whether node is selected */
188
selected: boolean;
189
/** Update node attributes */
190
updateAttributes: (attrs: ProsemirrorAttributes) => void;
191
/** Forward ref to DOM element */
192
forwardRef?: React.Ref<HTMLElement>;
193
}
194
195
/**
196
* Properties for creating node views with React components
197
*/
198
interface CreateNodeViewProps {
199
/** Node name */
200
name: string;
201
/** React component to render */
202
component: ComponentType<NodeViewComponentProps>;
203
/** Environment where component should render */
204
environment?: ReactComponentEnvironment;
205
/** Whether content is editable */
206
contentEditable?: boolean;
207
/** Custom wrapper element */
208
wrapper?: string;
209
}
210
```
211
212
## Portal Types
213
214
```typescript { .api }
215
/**
216
* A mounted portal with render props and key
217
*/
218
interface MountedPortal {
219
/** Unique portal key */
220
key: string;
221
/** Component to render */
222
Component: ComponentType;
223
/** Props to pass to component */
224
props: any;
225
/** Portal container element */
226
container: HTMLElement;
227
}
228
229
/**
230
* Array of portal container/component tuples
231
*/
232
type PortalList = Array<[HTMLElement, ComponentType]>;
233
234
/**
235
* Map of HTML elements to mounted portals
236
*/
237
type PortalMap = Map<HTMLElement, MountedPortal[]>;
238
239
/**
240
* Props containing Component to render
241
*/
242
interface RenderProps {
243
/** Component to render in portal */
244
Component: ComponentType;
245
/** Props to pass to component */
246
props?: any;
247
}
248
249
/**
250
* Props for individual render methods
251
*/
252
interface SingleRenderMethodProps extends RenderProps {
253
/** Unique key for the render */
254
key?: string;
255
}
256
```
257
258
## Extension Integration
259
260
```typescript { .api }
261
/**
262
* Create a node extension that uses React components
263
* @param config - Configuration for the React node extension
264
* @returns Configured node extension
265
*/
266
function createReactNodeExtension<T extends ProsemirrorAttributes = {}>(
267
config: ReactNodeExtensionConfig<T>
268
): NodeExtension<ReactNodeOptions<T>>;
269
270
interface ReactNodeExtensionConfig<T extends ProsemirrorAttributes = {}> {
271
/** Extension name */
272
name: string;
273
/** React component for the node */
274
component: ComponentType<NodeViewComponentProps & { attrs: T }>;
275
/** Node schema */
276
schema: NodeSpec;
277
/** Default attributes */
278
defaults?: T;
279
/** Environment configuration */
280
environment?: ReactComponentEnvironment;
281
}
282
283
interface ReactNodeOptions<T extends ProsemirrorAttributes = {}>
284
extends ReactComponentOptions {
285
/** Node-specific options */
286
nodeOptions?: {
287
/** Default attributes */
288
defaults?: T;
289
/** Custom node view factory */
290
createNodeView?: (props: CreateNodeViewProps) => NodeView;
291
};
292
}
293
```
294
295
**Usage Example:**
296
297
```typescript
298
import React from 'react';
299
import { createReactNodeExtension, NodeViewComponentProps } from '@remirror/react';
300
301
// Define your React component
302
function ImageNode({ node, updateAttributes }: NodeViewComponentProps) {
303
const { src, alt, width } = node.attrs;
304
305
return (
306
<div className="image-node">
307
<img
308
src={src}
309
alt={alt}
310
width={width}
311
style={{ maxWidth: '100%' }}
312
/>
313
<div className="image-controls">
314
<input
315
type="range"
316
min="100"
317
max="800"
318
value={width}
319
onChange={(e) => updateAttributes({ width: parseInt(e.target.value) })}
320
/>
321
<span>{width}px</span>
322
</div>
323
</div>
324
);
325
}
326
327
// Create the extension
328
const ImageExtension = createReactNodeExtension({
329
name: 'image',
330
component: ImageNode,
331
schema: {
332
attrs: {
333
src: { default: null },
334
alt: { default: '' },
335
width: { default: 400 },
336
},
337
parseDOM: [{
338
tag: 'img',
339
getAttrs: (element) => ({
340
src: element.getAttribute('src'),
341
alt: element.getAttribute('alt'),
342
width: parseInt(element.getAttribute('width') || '400'),
343
}),
344
}],
345
toDOM: (node) => ['img', {
346
src: node.attrs.src,
347
alt: node.attrs.alt,
348
width: node.attrs.width,
349
}],
350
},
351
defaults: {
352
src: '',
353
alt: '',
354
width: 400,
355
},
356
});
357
```
358
359
## Environment Handling
360
361
```typescript { .api }
362
/**
363
* Utilities for handling different rendering environments
364
*/
365
interface EnvironmentHandler {
366
/** Check if running in browser */
367
isBrowser: () => boolean;
368
369
/** Check if running in Node.js/SSR */
370
isServer: () => boolean;
371
372
/** Get appropriate renderer for environment */
373
getRenderer: (environment: ReactComponentEnvironment) => ComponentType;
374
375
/** Handle environment-specific initialization */
376
initialize: (environment: ReactComponentEnvironment) => void;
377
}
378
379
/**
380
* HOC for environment-aware component rendering
381
* @param Component - Component to wrap
382
* @param environment - Target environment
383
* @returns Environment-aware component
384
*/
385
function withEnvironment<P extends {}>(
386
Component: ComponentType<P>,
387
environment: ReactComponentEnvironment
388
): ComponentType<P>;
389
```
390
391
## SSR Support
392
393
```typescript { .api }
394
/**
395
* Server-side rendering utilities for React components
396
*/
397
interface SSRRenderer {
398
/** Render component to string for SSR */
399
renderToString: (component: ReactElement) => string;
400
401
/** Render component to static markup */
402
renderToStaticMarkup: (component: ReactElement) => string;
403
404
/** Hydrate server-rendered content */
405
hydrate: (component: ReactElement, container: HTMLElement) => void;
406
}
407
408
/**
409
* Configuration for SSR-compatible node views
410
*/
411
interface SSRNodeViewConfig {
412
/** Whether to render on server */
413
serverRender: boolean;
414
415
/** Whether to hydrate on client */
416
clientHydrate: boolean;
417
418
/** Fallback content for non-JS environments */
419
fallback?: string | ComponentType;
420
}
421
```