UNPKG

@codegouvfr/react-dsfr

Version:

French State Design System React integration library

122 lines (88 loc) 4.14 kB
"use client"; import { useEffect, useState } from "react"; import { assert } from "tsafe/assert"; import { symToStr } from "tsafe/symToStr"; export function useIsModalOpen(modal: { isOpenedByDefault: boolean; id: string }): boolean { const { id, isOpenedByDefault } = modal; const [isModalOpen, setIsModalOpen] = useState(isOpenedByDefault); useEffect(() => { const cleanups: (() => void)[] = []; const observeDialogHtmlElement = (element: HTMLElement) => { const onConceal = () => setIsModalOpen(false); const onDisclose = () => setIsModalOpen(true); 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; }