import { wait } from "@web3r/flowerkit/fn"; import type { ReactElement, ReactNode, RefObject, } from "react"; import { Children, cloneElement, isValidElement, createElement, useEffect, useState, useRef, } from "react"; import { getSanitizedHTML } from "../../../../utils/getSanitizedHTML"; import type { TPoint } from "../../config/types"; const getMetaPaneContent = (data: TPoint): ReactNode => { const pane = data?.meta?.pane; if (!pane) { return null; } const title = typeof pane.title === "string" ? pane.title : null; const description = typeof pane.description === "string" ? pane.description : null; if (!title && !description) { return null; } const content: ReactNode[] = []; if (title) { content.push(createElement("div", { key: "title", dangerouslySetInnerHTML: { __html: getSanitizedHTML(title) }, })); } if (description) { content.push(createElement("div", { key: "description", dangerouslySetInnerHTML: { __html: getSanitizedHTML(description) }, })); } return content; }; /** * Получает контент для слота панели * @param children{ReactNode|Function} * @param data{TPoint} * @returns {ReactElement} */ const getSlot: ((children: (ReactNode | ((args: TPoint) => ReactNode)), data?: TPoint | null) => ReactNode) = (children, data) => { if (!!data) { switch (true) { case typeof children === "function": { return children(data); } case isValidElement(children): { return Children .toArray(children) .filter((child) => isValidElement(child)) .map((child) => { if (!isValidElement(child)) { return child; } const element = child as ReactElement<{ data?: TPoint; }>; return cloneElement(element, { ...(element.props ?? {}), key: element.key ?? `panel_${data.id}`, data, }); }); } case children === null || children === undefined: return getMetaPaneContent(data); default: console.error(`[Map] Panel slot must be valid React element or Function`); return null; } } else { return null; } }; /** * Хук позволяет использовать условный рендеринг с CSS-свойством `transition` * @param onOpen{null|Function=} * @param onHide{null|Function=} * @param ref{RefObject} * @param delay{Number=} * @returns {{ * isVisible: boolean, * show: Function, * hide: Function * }} */ const useVisibility = ({ ref, delay = 100, onShow = null, onHide = null, }: { ref: RefObject; delay?: number; onShow?: null | (() => void); onHide?: null | (() => void); }): { isVisible: boolean; show: Function; hide: Function; } => { const [ isVisible, setIsVisible ] = useState(false); const visibilityRef = useRef(isVisible); const show = () => { setIsVisible(true); }; const hide = () => { if (ref.current) { ref.current.addEventListener("transitionend", () => { setIsVisible(false); }, { once: true }); ref.current.classList.remove("isVisible"); } }; useEffect(() => { if (visibilityRef.current !== isVisible) { if (isVisible) { wait(delay).then(() => { return ref.current?.classList.add("isVisible"); }) .finally(() => { if (typeof onShow === "function") { onShow(); } }); } else { if (typeof onHide === "function") { onHide(); } } visibilityRef.current = isVisible; } }, [ isVisible ]); return { isVisible, show, hide, }; }; export { getSlot, useVisibility, };