import type React from 'react'; import { forwardRef, useCallback, useEffect, useId } from 'react'; import { useCellId } from '../../hooks/use-cell-id'; import { usePaper } from '../../hooks/use-paper'; import type { dia } from '@joint/core'; import { useChildrenRef } from '../../hooks/use-children-ref'; import { useHighlighter } from '../../hooks/use-highlighter'; import typedMemo from '../../utils/typed-memo'; export type OnCreateHighlighter< Highlighter extends dia.HighlighterView.Options = dia.HighlighterView.Options, > = ( cellView: | dia.ElementView> | dia.LinkView>, element: SVGElement | Record, highlighterId: string, options: Highlighter ) => dia.HighlighterView; export interface CustomHighlighterProps< Highlighter extends dia.HighlighterView.Options = dia.HighlighterView.Options, > { /** * Child elements to render inside the highlighter. */ readonly children?: React.ReactNode | null | false; /** * Callback function should return any highlighter. * @param cellView - The cell view to which the highlighter is attached. * @param element - The SVG element to which the highlighter is attached. * @param highlighterId - The ID of the highlighter. * @param options - The options for the highlighter. * @returns The created highlighter. */ readonly onCreateHighlighter: OnCreateHighlighter; /** * This should be memoized */ readonly options: Highlighter; /** * If the highlighter is disabled or not. */ readonly isHidden?: boolean; } // eslint-disable-next-line jsdoc/require-jsdoc function RawComponent< Highlighter extends dia.HighlighterView.Options = dia.HighlighterView.Options, >(props: CustomHighlighterProps, forwardedRef: React.Ref) { const { children, options, onCreateHighlighter, isHidden } = props; const id = useCellId(); const paper = usePaper(); const highlighterId = useId(); const { elementRef, elementChildren } = useChildrenRef(children, forwardedRef); // ERROR HANDLING useEffect(() => { if (!elementRef.current) { throw new Error('Highlighter children component must have accessible ref'); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const create = useCallback( (hOptions: Highlighter) => { const cellView = paper.findViewByModel(id); if (!cellView) { return; } return onCreateHighlighter(cellView, elementRef.current ?? {}, highlighterId, hOptions); }, [onCreateHighlighter, elementRef, highlighterId, id, paper] ); const update = useCallback((instance: ReturnType, hOptions: Highlighter) => { const oldOptions = instance?.options ?? {}; if (!instance?.options) { return; } instance.options = { ...oldOptions, ...hOptions, }; // @ts-expect-error Internal API instance.update(); }, []); useHighlighter(create, update, options, isHidden); return elementChildren; } const ForwardedComponent = forwardRef(RawComponent); /** * Custom highlighter component. * Allows to create a custom highlighter. * @group Components * @example * ```tsx * import { Highlighter } from '@joint/react' * return * ``` */ export const Custom = typedMemo(ForwardedComponent);