UNPKG

@codegouvfr/react-dsfr

Version:

French State Design System React integration library

139 lines (104 loc) 4.61 kB
"use client"; import { useEffect, useState } from "react"; import { assert } from "tsafe/assert"; import { symToStr } from "tsafe/symToStr"; import { useConstCallback } from "../tools/powerhooks/useConstCallback"; export function useIsModalOpen( modal: { isOpenedByDefault: boolean; id: string }, callbacks?: { onConceal?: () => void; onDisclose?: () => void; } ): boolean { const { id, isOpenedByDefault } = modal; const [isModalOpen, setIsModalOpen] = useState(isOpenedByDefault); const getCurrentCallbacks = useConstCallback(() => callbacks); useEffect(() => { const cleanups: (() => void)[] = []; const observeDialogHtmlElement = (element: HTMLElement) => { const onConceal = () => { setIsModalOpen(false); setTimeout(() => { getCurrentCallbacks()?.onConceal?.(); }, 0); }; const onDisclose = () => { setIsModalOpen(true); getCurrentCallbacks()?.onDisclose?.(); }; element.addEventListener("dsfr.conceal", onConceal); element.addEventListener("dsfr.disclose", onDisclose); const removeEventListeners = () => { element.removeEventListener("dsfr.conceal", onConceal); element.removeEventListener("dsfr.disclose", onDisclose); }; cleanups.push(removeEventListeners); const observer = new MutationObserver(mutationsList => { const isDetached = mutationsList.find( mutation => Array.from(mutation.removedNodes).indexOf(element) !== -1 ) !== undefined; if (!isDetached) { return; } cleanups.splice(cleanups.indexOf(removeEventListeners), 1); observer.disconnect(); cleanups.splice(cleanups.indexOf(disconnectObserver), 1); setIsModalOpen(false); observeMountAndObserveDialogHtmlElement({ "isInitialCall": false }); }); observer.observe(document, { childList: true, subtree: true }); const disconnectObserver = () => observer.disconnect(); cleanups.push(disconnectObserver); }; const observeMountAndObserveDialogHtmlElement = (params: { isInitialCall: boolean }) => { const { isInitialCall } = params; if (isInitialCall) { assert( !isOpenedByDefault, [ `The ${id} modal isn't initially mounted,`, `it's ok but in this case ${symToStr({ isOpenedByDefault })} must be set to false.`, "This limitation is to prevent inconsistent state in SSR setups." ].join(" ") ); } const observer = new MutationObserver((mutationsList, observer) => { // Filter the mutations list for nodes that match our criteria const matchedNode = mutationsList .map(mutation => Array.from(mutation.addedNodes)) .reduce((acc, curr) => acc.concat(curr), []) .find( node => node.nodeType === Node.ELEMENT_NODE && node.nodeName === "DIALOG" && (node as HTMLElement).id === id ); if (matchedNode === undefined) { return; } observer.disconnect(); cleanups.splice(cleanups.indexOf(disconnectObserver), 1); observeDialogHtmlElement(matchedNode as HTMLElement); if (isOpenedByDefault) { setIsModalOpen(true); } }); observer.observe(document, { "childList": true, "subtree": true }); const disconnectObserver = () => observer.disconnect(); cleanups.push(disconnectObserver); }; const element = document.getElementById(id); if (element !== null) { observeDialogHtmlElement(element); } else { observeMountAndObserveDialogHtmlElement({ "isInitialCall": true }); } return () => { cleanups.forEach(cleanup => cleanup()); }; }, [id]); return isModalOpen; }