@blockslides/react 0.1.0
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.
- package/LICENSE.md +36 -0
- package/dist/index.cjs +1100 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +349 -0
- package/dist/index.d.ts +349 -0
- package/dist/index.js +1042 -0
- package/dist/index.js.map +1 -0
- package/dist/menus/index.cjs +166 -0
- package/dist/menus/index.cjs.map +1 -0
- package/dist/menus/index.d.cts +19 -0
- package/dist/menus/index.d.ts +19 -0
- package/dist/menus/index.js +128 -0
- package/dist/menus/index.js.map +1 -0
- package/package.json +75 -0
- package/src/Context.tsx +60 -0
- package/src/Editor.ts +15 -0
- package/src/EditorContent.tsx +229 -0
- package/src/NodeViewContent.tsx +30 -0
- package/src/NodeViewWrapper.tsx +27 -0
- package/src/ReactMarkViewRenderer.tsx +106 -0
- package/src/ReactNodeViewRenderer.tsx +344 -0
- package/src/ReactRenderer.tsx +265 -0
- package/src/index.ts +12 -0
- package/src/menus/BubbleMenu.tsx +89 -0
- package/src/menus/FloatingMenu.tsx +69 -0
- package/src/menus/index.ts +2 -0
- package/src/types.ts +6 -0
- package/src/useEditor.ts +405 -0
- package/src/useEditorState.ts +188 -0
- package/src/useReactNodeView.ts +28 -0
package/src/Context.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Editor } from '@blockslides/core'
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
3
|
+
import React, { createContext, useContext, useMemo } from 'react'
|
|
4
|
+
|
|
5
|
+
import { EditorContent } from './EditorContent.js'
|
|
6
|
+
import type { UseEditorOptions } from './useEditor.js'
|
|
7
|
+
import { useEditor } from './useEditor.js'
|
|
8
|
+
|
|
9
|
+
export type EditorContextValue = {
|
|
10
|
+
editor: Editor | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const EditorContext = createContext<EditorContextValue>({
|
|
14
|
+
editor: null,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export const EditorConsumer = EditorContext.Consumer
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A hook to get the current editor instance.
|
|
21
|
+
*/
|
|
22
|
+
export const useCurrentEditor = () => useContext(EditorContext)
|
|
23
|
+
|
|
24
|
+
export type EditorProviderProps = {
|
|
25
|
+
children?: ReactNode
|
|
26
|
+
slotBefore?: ReactNode
|
|
27
|
+
slotAfter?: ReactNode
|
|
28
|
+
editorContainerProps?: HTMLAttributes<HTMLDivElement>
|
|
29
|
+
} & UseEditorOptions
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* This is the provider component for the editor.
|
|
33
|
+
* It allows the editor to be accessible across the entire component tree
|
|
34
|
+
* with `useCurrentEditor`.
|
|
35
|
+
*/
|
|
36
|
+
export function EditorProvider({
|
|
37
|
+
children,
|
|
38
|
+
slotAfter,
|
|
39
|
+
slotBefore,
|
|
40
|
+
editorContainerProps = {},
|
|
41
|
+
...editorOptions
|
|
42
|
+
}: EditorProviderProps) {
|
|
43
|
+
const editor = useEditor(editorOptions)
|
|
44
|
+
const contextValue = useMemo(() => ({ editor }), [editor])
|
|
45
|
+
|
|
46
|
+
if (!editor) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<EditorContext.Provider value={contextValue}>
|
|
52
|
+
{slotBefore}
|
|
53
|
+
<EditorConsumer>
|
|
54
|
+
{({ editor: currentEditor }) => <EditorContent editor={currentEditor} {...editorContainerProps} />}
|
|
55
|
+
</EditorConsumer>
|
|
56
|
+
{children}
|
|
57
|
+
{slotAfter}
|
|
58
|
+
</EditorContext.Provider>
|
|
59
|
+
)
|
|
60
|
+
}
|
package/src/Editor.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Editor } from "@blockslides/core";
|
|
2
|
+
import type { ReactPortal } from "react";
|
|
3
|
+
|
|
4
|
+
import type { ReactRenderer } from "./ReactRenderer.js";
|
|
5
|
+
|
|
6
|
+
export type EditorWithContentComponent = Editor & {
|
|
7
|
+
contentComponent?: ContentComponent | null;
|
|
8
|
+
};
|
|
9
|
+
export type ContentComponent = {
|
|
10
|
+
setRenderer(id: string, renderer: ReactRenderer): void;
|
|
11
|
+
removeRenderer(id: string): void;
|
|
12
|
+
subscribe: (callback: () => void) => () => void;
|
|
13
|
+
getSnapshot: () => Record<string, ReactPortal>;
|
|
14
|
+
getServerSnapshot: () => Record<string, ReactPortal>;
|
|
15
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { Editor } from '@blockslides/core'
|
|
2
|
+
import type { ForwardedRef, HTMLProps, LegacyRef, MutableRefObject } from 'react'
|
|
3
|
+
import React, { forwardRef } from 'react'
|
|
4
|
+
import ReactDOM from 'react-dom'
|
|
5
|
+
import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js'
|
|
6
|
+
|
|
7
|
+
import type { ContentComponent, EditorWithContentComponent } from './Editor.js'
|
|
8
|
+
import type { ReactRenderer } from './ReactRenderer.js'
|
|
9
|
+
|
|
10
|
+
const mergeRefs = <T extends HTMLDivElement>(...refs: Array<MutableRefObject<T> | LegacyRef<T> | undefined>) => {
|
|
11
|
+
return (node: T) => {
|
|
12
|
+
refs.forEach(ref => {
|
|
13
|
+
if (typeof ref === 'function') {
|
|
14
|
+
ref(node)
|
|
15
|
+
} else if (ref) {
|
|
16
|
+
; (ref as MutableRefObject<T | null>).current = node
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* This component renders all of the editor's node views.
|
|
24
|
+
*/
|
|
25
|
+
const Portals: React.FC<{ contentComponent: ContentComponent }> = ({ contentComponent }) => {
|
|
26
|
+
// For performance reasons, we render the node view portals on state changes only
|
|
27
|
+
const renderers = useSyncExternalStore(
|
|
28
|
+
contentComponent.subscribe,
|
|
29
|
+
contentComponent.getSnapshot,
|
|
30
|
+
contentComponent.getServerSnapshot,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
// This allows us to directly render the portals without any additional wrapper
|
|
34
|
+
return <>{Object.values(renderers)}</>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
|
|
38
|
+
editor: Editor | null
|
|
39
|
+
innerRef?: ForwardedRef<HTMLDivElement | null>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getInstance(): ContentComponent {
|
|
43
|
+
const subscribers = new Set<() => void>()
|
|
44
|
+
let renderers: Record<string, React.ReactPortal> = {}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
/**
|
|
48
|
+
* Subscribe to the editor instance's changes.
|
|
49
|
+
*/
|
|
50
|
+
subscribe(callback: () => void) {
|
|
51
|
+
subscribers.add(callback)
|
|
52
|
+
return () => {
|
|
53
|
+
subscribers.delete(callback)
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
getSnapshot() {
|
|
57
|
+
return renderers
|
|
58
|
+
},
|
|
59
|
+
getServerSnapshot() {
|
|
60
|
+
return renderers
|
|
61
|
+
},
|
|
62
|
+
/**
|
|
63
|
+
* Adds a new NodeView Renderer to the editor.
|
|
64
|
+
*/
|
|
65
|
+
setRenderer(id: string, renderer: ReactRenderer) {
|
|
66
|
+
renderers = {
|
|
67
|
+
...renderers,
|
|
68
|
+
[id]: ReactDOM.createPortal(renderer.reactElement, renderer.element, id),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
subscribers.forEach(subscriber => subscriber())
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* Removes a NodeView Renderer from the editor.
|
|
75
|
+
*/
|
|
76
|
+
removeRenderer(id: string) {
|
|
77
|
+
const nextRenderers = { ...renderers }
|
|
78
|
+
|
|
79
|
+
delete nextRenderers[id]
|
|
80
|
+
renderers = nextRenderers
|
|
81
|
+
subscribers.forEach(subscriber => subscriber())
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class PureEditorContent extends React.Component<
|
|
87
|
+
EditorContentProps,
|
|
88
|
+
{ hasContentComponentInitialized: boolean }
|
|
89
|
+
> {
|
|
90
|
+
editorContentRef: React.RefObject<any>
|
|
91
|
+
|
|
92
|
+
initialized: boolean
|
|
93
|
+
|
|
94
|
+
unsubscribeToContentComponent?: () => void
|
|
95
|
+
|
|
96
|
+
constructor(props: EditorContentProps) {
|
|
97
|
+
super(props)
|
|
98
|
+
this.editorContentRef = React.createRef()
|
|
99
|
+
this.initialized = false
|
|
100
|
+
|
|
101
|
+
this.state = {
|
|
102
|
+
hasContentComponentInitialized: Boolean((props.editor as EditorWithContentComponent | null)?.contentComponent),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
componentDidMount() {
|
|
107
|
+
this.init()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
componentDidUpdate() {
|
|
111
|
+
this.init()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
init() {
|
|
115
|
+
const editor = this.props.editor as EditorWithContentComponent | null
|
|
116
|
+
|
|
117
|
+
if (editor && !editor.isDestroyed && editor.options.element) {
|
|
118
|
+
if (editor.contentComponent) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const element = this.editorContentRef.current
|
|
123
|
+
|
|
124
|
+
element.append(editor.view.dom)
|
|
125
|
+
|
|
126
|
+
editor.setOptions({
|
|
127
|
+
element,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
editor.contentComponent = getInstance()
|
|
131
|
+
|
|
132
|
+
// Has the content component been initialized?
|
|
133
|
+
if (!this.state.hasContentComponentInitialized) {
|
|
134
|
+
// Subscribe to the content component
|
|
135
|
+
this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => {
|
|
136
|
+
this.setState(prevState => {
|
|
137
|
+
if (!prevState.hasContentComponentInitialized) {
|
|
138
|
+
return {
|
|
139
|
+
hasContentComponentInitialized: true,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return prevState
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Unsubscribe to previous content component
|
|
146
|
+
if (this.unsubscribeToContentComponent) {
|
|
147
|
+
this.unsubscribeToContentComponent()
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
editor.createNodeViews()
|
|
153
|
+
|
|
154
|
+
this.initialized = true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
componentWillUnmount() {
|
|
159
|
+
const editor = this.props.editor as EditorWithContentComponent | null
|
|
160
|
+
|
|
161
|
+
if (!editor) {
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.initialized = false
|
|
166
|
+
|
|
167
|
+
if (!editor.isDestroyed) {
|
|
168
|
+
editor.view.setProps({
|
|
169
|
+
nodeViews: {},
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (this.unsubscribeToContentComponent) {
|
|
174
|
+
this.unsubscribeToContentComponent()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
editor.contentComponent = null
|
|
178
|
+
|
|
179
|
+
// try to reset the editor element
|
|
180
|
+
// may fail if this editor's view.dom was never initialized/mounted yet
|
|
181
|
+
try {
|
|
182
|
+
if (!editor.view.dom?.firstChild) {
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// TODO using the new editor.mount method might allow us to remove this
|
|
187
|
+
const newElement = document.createElement('div')
|
|
188
|
+
|
|
189
|
+
newElement.append(editor.view.dom)
|
|
190
|
+
|
|
191
|
+
editor.setOptions({
|
|
192
|
+
element: newElement,
|
|
193
|
+
})
|
|
194
|
+
} catch {
|
|
195
|
+
// do nothing, nothing to reset
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
render() {
|
|
200
|
+
const { editor, innerRef, ...rest } = this.props
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<>
|
|
204
|
+
<div ref={mergeRefs(innerRef, this.editorContentRef)} {...rest} />
|
|
205
|
+
{/* @ts-ignore */}
|
|
206
|
+
{editor?.contentComponent && <Portals contentComponent={editor.contentComponent} />}
|
|
207
|
+
</>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// EditorContent should be re-created whenever the Editor instance changes
|
|
213
|
+
const EditorContentWithKey = forwardRef<HTMLDivElement, EditorContentProps>(
|
|
214
|
+
(props: Omit<EditorContentProps, 'innerRef'>, ref) => {
|
|
215
|
+
const key = React.useMemo(() => {
|
|
216
|
+
return Math.floor(Math.random() * 0xffffffff).toString()
|
|
217
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
218
|
+
}, [props.editor])
|
|
219
|
+
|
|
220
|
+
// Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement
|
|
221
|
+
return React.createElement(PureEditorContent, {
|
|
222
|
+
key,
|
|
223
|
+
innerRef: ref,
|
|
224
|
+
...props,
|
|
225
|
+
})
|
|
226
|
+
},
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
export const EditorContent = React.memo(EditorContentWithKey)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { useReactNodeView } from './useReactNodeView.js'
|
|
5
|
+
|
|
6
|
+
export type NodeViewContentProps<T extends keyof React.JSX.IntrinsicElements = 'div'> = {
|
|
7
|
+
as?: NoInfer<T>
|
|
8
|
+
} & ComponentProps<T>
|
|
9
|
+
|
|
10
|
+
export function NodeViewContent<T extends keyof React.JSX.IntrinsicElements = 'div'>({
|
|
11
|
+
as: Tag = 'div' as T,
|
|
12
|
+
...props
|
|
13
|
+
}: NodeViewContentProps<T>) {
|
|
14
|
+
const { nodeViewContentRef, nodeViewContentChildren } = useReactNodeView()
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
<Tag
|
|
19
|
+
{...props}
|
|
20
|
+
ref={nodeViewContentRef}
|
|
21
|
+
data-node-view-content=""
|
|
22
|
+
style={{
|
|
23
|
+
whiteSpace: 'pre-wrap',
|
|
24
|
+
...props.style,
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
{nodeViewContentChildren}
|
|
28
|
+
</Tag>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { useReactNodeView } from './useReactNodeView.js'
|
|
4
|
+
|
|
5
|
+
export interface NodeViewWrapperProps {
|
|
6
|
+
[key: string]: any
|
|
7
|
+
as?: React.ElementType
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const NodeViewWrapper: React.FC<NodeViewWrapperProps> = React.forwardRef((props, ref) => {
|
|
11
|
+
const { onDragStart } = useReactNodeView()
|
|
12
|
+
const Tag = props.as || 'div'
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
<Tag
|
|
17
|
+
{...props}
|
|
18
|
+
ref={ref}
|
|
19
|
+
data-node-view-wrapper=""
|
|
20
|
+
onDragStart={onDragStart}
|
|
21
|
+
style={{
|
|
22
|
+
whiteSpace: 'normal',
|
|
23
|
+
...props.style,
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-shadow */
|
|
2
|
+
import type { MarkViewProps, MarkViewRenderer, MarkViewRendererOptions } from '@blockslides/core'
|
|
3
|
+
import { MarkView } from '@blockslides/core'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
// import { flushSync } from 'react-dom'
|
|
7
|
+
import { ReactRenderer } from './ReactRenderer.js'
|
|
8
|
+
|
|
9
|
+
export interface MarkViewContextProps {
|
|
10
|
+
markViewContentRef: (element: HTMLElement | null) => void
|
|
11
|
+
}
|
|
12
|
+
export const ReactMarkViewContext = React.createContext<MarkViewContextProps>({
|
|
13
|
+
markViewContentRef: () => {
|
|
14
|
+
// do nothing
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export type MarkViewContentProps<T extends keyof React.JSX.IntrinsicElements = 'span'> = {
|
|
19
|
+
as?: T
|
|
20
|
+
} & Omit<React.ComponentProps<T>, 'as'>
|
|
21
|
+
|
|
22
|
+
export const MarkViewContent = <T extends keyof React.JSX.IntrinsicElements = 'span'>(
|
|
23
|
+
props: MarkViewContentProps<T>,
|
|
24
|
+
) => {
|
|
25
|
+
const { as: Tag = 'span', ...rest } = props
|
|
26
|
+
const { markViewContentRef } = React.useContext(ReactMarkViewContext)
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
<Tag {...rest} ref={markViewContentRef} data-mark-view-content="" />
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ReactMarkViewRendererOptions extends MarkViewRendererOptions {
|
|
35
|
+
/**
|
|
36
|
+
* The tag name of the element wrapping the React component.
|
|
37
|
+
*/
|
|
38
|
+
as?: string
|
|
39
|
+
className?: string
|
|
40
|
+
attrs?: { [key: string]: string }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class ReactMarkView extends MarkView<React.ComponentType<MarkViewProps>, ReactMarkViewRendererOptions> {
|
|
44
|
+
renderer: ReactRenderer
|
|
45
|
+
contentDOMElement: HTMLElement
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
component: React.ComponentType<MarkViewProps>,
|
|
49
|
+
props: MarkViewProps,
|
|
50
|
+
options?: Partial<ReactMarkViewRendererOptions>,
|
|
51
|
+
) {
|
|
52
|
+
super(component, props, options)
|
|
53
|
+
|
|
54
|
+
const { as = 'span', attrs, className = '' } = options || {}
|
|
55
|
+
const componentProps = { ...props, updateAttributes: this.updateAttributes.bind(this) } satisfies MarkViewProps
|
|
56
|
+
|
|
57
|
+
this.contentDOMElement = document.createElement('span')
|
|
58
|
+
|
|
59
|
+
const markViewContentRef: MarkViewContextProps['markViewContentRef'] = el => {
|
|
60
|
+
if (el && !el.contains(this.contentDOMElement)) {
|
|
61
|
+
el.appendChild(this.contentDOMElement)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const context: MarkViewContextProps = {
|
|
65
|
+
markViewContentRef,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// For performance reasons, we memoize the provider component
|
|
69
|
+
// And all of the things it requires are declared outside of the component, so it doesn't need to re-render
|
|
70
|
+
const ReactMarkViewProvider: React.FunctionComponent<MarkViewProps> = React.memo(componentProps => {
|
|
71
|
+
return (
|
|
72
|
+
<ReactMarkViewContext.Provider value={context}>
|
|
73
|
+
{React.createElement(component, componentProps)}
|
|
74
|
+
</ReactMarkViewContext.Provider>
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
ReactMarkViewProvider.displayName = 'ReactMarkView'
|
|
79
|
+
|
|
80
|
+
this.renderer = new ReactRenderer(ReactMarkViewProvider, {
|
|
81
|
+
editor: props.editor,
|
|
82
|
+
props: componentProps,
|
|
83
|
+
as,
|
|
84
|
+
className: `mark-${props.mark.type.name} ${className}`.trim(),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (attrs) {
|
|
88
|
+
this.renderer.updateAttributes(attrs)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get dom() {
|
|
93
|
+
return this.renderer.element
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get contentDOM() {
|
|
97
|
+
return this.contentDOMElement
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function ReactMarkViewRenderer(
|
|
102
|
+
component: React.ComponentType<MarkViewProps>,
|
|
103
|
+
options: Partial<ReactMarkViewRendererOptions> = {},
|
|
104
|
+
): MarkViewRenderer {
|
|
105
|
+
return props => new ReactMarkView(component, props, options)
|
|
106
|
+
}
|