/* eslint-disable @typescript-eslint/no-shadow */ import type { MarkViewProps, MarkViewRenderer, MarkViewRendererOptions } from '@tiptap/core' import { MarkView } from '@tiptap/core' import React from 'react' // import { flushSync } from 'react-dom' import { ReactRenderer } from './ReactRenderer.js' export interface MarkViewContextProps { markViewContentRef: (element: HTMLElement | null) => void } export const ReactMarkViewContext = React.createContext({ markViewContentRef: () => { // do nothing }, }) export type MarkViewContentProps = { as?: T } & Omit, 'as'> export const MarkViewContent = ( props: MarkViewContentProps, ) => { const { as: Tag = 'span', ...rest } = props const { markViewContentRef } = React.useContext(ReactMarkViewContext) return ( // @ts-ignore ) } export interface ReactMarkViewRendererOptions extends MarkViewRendererOptions { /** * The tag name of the element wrapping the React component. */ as?: string className?: string attrs?: { [key: string]: string } } export class ReactMarkView extends MarkView, ReactMarkViewRendererOptions> { renderer: ReactRenderer contentDOMElement: HTMLElement constructor( component: React.ComponentType, props: MarkViewProps, options?: Partial, ) { super(component, props, options) const { as = 'span', attrs, className = '' } = options || {} const componentProps = { ...props, updateAttributes: this.updateAttributes.bind(this) } satisfies MarkViewProps this.contentDOMElement = document.createElement('span') const markViewContentRef: MarkViewContextProps['markViewContentRef'] = el => { if (el && !el.contains(this.contentDOMElement)) { el.appendChild(this.contentDOMElement) } } const context: MarkViewContextProps = { markViewContentRef, } // For performance reasons, we memoize the provider component // And all of the things it requires are declared outside of the component, so it doesn't need to re-render const ReactMarkViewProvider: React.FunctionComponent = React.memo(componentProps => { return ( {React.createElement(component, componentProps)} ) }) ReactMarkViewProvider.displayName = 'ReactMarkView' this.renderer = new ReactRenderer(ReactMarkViewProvider, { editor: props.editor, props: componentProps, as, className: `mark-${props.mark.type.name} ${className}`.trim(), }) if (attrs) { this.renderer.updateAttributes(attrs) } } get dom() { return this.renderer.element } get contentDOM() { return this.contentDOMElement } } export function ReactMarkViewRenderer( component: React.ComponentType, options: Partial = {}, ): MarkViewRenderer { return props => new ReactMarkView(component, props, options) }