UNPKG

@kobalte/core

Version:

Unstyled components and primitives for building accessible web apps and design systems with SolidJS.

134 lines (131 loc) 3.97 kB
import { DATA_TOP_LAYER_ATTR } from "./3NI6FTA2.jsx"; import { DATA_LIVE_ANNOUNCER_ATTR } from "./JHMNWOLY.jsx"; // src/primitives/create-hide-outside/create-hide-outside.ts import { access } from "@kobalte/utils"; import { createEffect, onCleanup } from "solid-js"; function createHideOutside(props) { createEffect(() => { if (access(props.isDisabled)) { return; } onCleanup(ariaHideOutside(access(props.targets), access(props.root))); }); } var refCountMap = /* @__PURE__ */ new WeakMap(); var observerStack = []; function ariaHideOutside(targets, root = document.body) { const visibleNodes = new Set(targets); const hiddenNodes = /* @__PURE__ */ new Set(); const walk = (root2) => { for (const element of root2.querySelectorAll( `[${DATA_LIVE_ANNOUNCER_ATTR}], [${DATA_TOP_LAYER_ATTR}]` )) { visibleNodes.add(element); } const acceptNode = (node) => { if (visibleNodes.has(node) || node.parentElement && hiddenNodes.has(node.parentElement) && node.parentElement.getAttribute("role") !== "row") { return NodeFilter.FILTER_REJECT; } for (const target of visibleNodes) { if (node.contains(target)) { return NodeFilter.FILTER_SKIP; } } return NodeFilter.FILTER_ACCEPT; }; const walker = document.createTreeWalker(root2, NodeFilter.SHOW_ELEMENT, { acceptNode }); const acceptRoot = acceptNode(root2); if (acceptRoot === NodeFilter.FILTER_ACCEPT) { hide(root2); } if (acceptRoot !== NodeFilter.FILTER_REJECT) { let node = walker.nextNode(); while (node != null) { hide(node); node = walker.nextNode(); } } }; const hide = (node) => { const refCount = refCountMap.get(node) ?? 0; if (node.getAttribute("aria-hidden") === "true" && refCount === 0) { return; } if (refCount === 0) { node.setAttribute("aria-hidden", "true"); } hiddenNodes.add(node); refCountMap.set(node, refCount + 1); }; if (observerStack.length) { observerStack[observerStack.length - 1].disconnect(); } walk(root); const observer = new MutationObserver((changes) => { for (const change of changes) { if (change.type !== "childList" || change.addedNodes.length === 0) { continue; } if (![...visibleNodes, ...hiddenNodes].some( (node) => node.contains(change.target) )) { for (const node of change.removedNodes) { if (node instanceof Element) { visibleNodes.delete(node); hiddenNodes.delete(node); } } for (const node of change.addedNodes) { if ((node instanceof HTMLElement || node instanceof SVGElement) && (node.dataset.liveAnnouncer === "true" || node.dataset.reactAriaTopLayer === "true")) { visibleNodes.add(node); } else if (node instanceof Element) { walk(node); } } } } }); observer.observe(root, { childList: true, subtree: true }); const observerWrapper = { observe() { observer.observe(root, { childList: true, subtree: true }); }, disconnect() { observer.disconnect(); } }; observerStack.push(observerWrapper); return () => { observer.disconnect(); for (const node of hiddenNodes) { const count = refCountMap.get(node); if (count == null) { return; } if (count === 1) { node.removeAttribute("aria-hidden"); refCountMap.delete(node); } else { refCountMap.set(node, count - 1); } } if (observerWrapper === observerStack[observerStack.length - 1]) { observerStack.pop(); if (observerStack.length) { observerStack[observerStack.length - 1].observe(); } } else { observerStack.splice(observerStack.indexOf(observerWrapper), 1); } }; } export { createHideOutside, ariaHideOutside };