UNPKG

@neo4j-ndl/react

Version:

React implementation of Neo4j Design System

160 lines 7.16 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.useSpotlightContext = exports.SpotlightProvider = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); /** * * Copyright (c) "Neo4j" * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ const react_1 = require("@floating-ui/react"); const classnames_1 = __importDefault(require("classnames")); const react_2 = require("react"); const SpotlightContext = (0, react_2.createContext)(null); const SpotlightProvider = ({ children, hasOverlay = true, isOverlayTransparent = false, }) => { const [active, setActive] = (0, react_2.useState)(null); const [isOpen, setIsOpen] = (0, react_2.useState)(false); const [targets, setTargets] = (0, react_2.useState)({}); const [targetRect, setTargetRect] = (0, react_2.useState)(null); const registerTarget = (0, react_2.useCallback)((id, ref) => { setTargets((prev) => (Object.assign(Object.assign({}, prev), { [id]: ref }))); }, []); const unregisterTarget = (0, react_2.useCallback)((id) => { setTargets((prev) => { const newTargets = Object.assign({}, prev); delete newTargets[id]; return newTargets; }); }, []); // Calculate target element position and size for mask (0, react_2.useEffect)(() => { var _a; if (active !== null && targets[active] !== undefined && ((_a = targets[active]) === null || _a === void 0 ? void 0 : _a.current) !== null) { const element = targets[active].current; const rect = element.getBoundingClientRect(); setTargetRect(rect); } else { setTargetRect(null); } }, [active, targets]); const contextValue = { hasOverlay, isActiveSpotlight: (0, react_2.useCallback)((target) => target === active, [active]), isOpen, registerTarget, registeredTargets: targets, setActiveSpotlight: (0, react_2.useCallback)((target) => setActive(target), []), setIsOpen: (0, react_2.useCallback)((open) => setIsOpen(open), []), unregisterTarget, }; const overlayClasses = (0, classnames_1.default)('ndl-spotlight-overlay', { 'ndl-spotlight-overlay-opaque': !isOverlayTransparent, 'ndl-spotlight-overlay-open': isOpen && active !== null, }); const overlayRootRef = (0, react_2.useRef)(null); if (active !== null) { overlayRootRef.current = document.getElementById(active); } // Get the border radius from the target element const getHoleRadius = (0, react_2.useCallback)(() => { var _a; if (active !== null && targets[active] !== undefined && ((_a = targets[active]) === null || _a === void 0 ? void 0 : _a.current) !== null) { const element = targets[active].current; const computedStyle = window.getComputedStyle(element); const borderRadius = computedStyle.borderRadius; // Parse the border radius value (handle px, rem, em, etc.) const radiusValue = parseFloat(borderRadius); return isNaN(radiusValue) ? 0 : radiusValue; } return 0; }, [active, targets]); const getMaskStyle = () => { if (!targetRect || isOverlayTransparent) { return {}; } const { left, top, width, height } = targetRect; const x = Math.max(0, left); const y = Math.max(0, top); const w = Math.max(0, width); const h = Math.max(0, height); const r = Math.max(0, Math.min(getHoleRadius(), Math.min(w, h) / 2)); const vw = window.innerWidth; const vh = window.innerHeight; // SVG mask: white = visible overlay, black = hole (transparent) const svg = ` <svg xmlns="http://www.w3.org/2000/svg" width="${vw}" height="${vh}" viewBox="0 0 ${vw} ${vh}"> <defs> <mask id="spotlightMask" maskUnits="userSpaceOnUse"> <rect x="0" y="0" width="${vw}" height="${vh}" fill="white"/> <rect x="${x}" y="${y}" width="${w}" height="${h}" rx="${r}" ry="${r}" fill="black"/> </mask> </defs> <rect x="0" y="0" width="${vw}" height="${vh}" fill="black" mask="url(#spotlightMask)"/> </svg>`.trim(); const encoded = `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`; return { WebkitMaskImage: `url("${encoded}")`, WebkitMaskRepeat: 'no-repeat', WebkitMaskSize: `${vw}px ${vh}px`, maskImage: `url("${encoded}")`, maskRepeat: 'no-repeat', maskSize: `${vw}px ${vh}px`, }; }; /** useEffect to update the target rect, so that the mask tracks the target element */ (0, react_2.useEffect)(() => { var _a; if (active === null) { return; } const el = (_a = targets[active]) === null || _a === void 0 ? void 0 : _a.current; if (el === null || el === undefined) { return; } const update = () => setTargetRect(el.getBoundingClientRect()); update(); const ro = new ResizeObserver(update); ro.observe(el); window.addEventListener('scroll', update, true); window.addEventListener('resize', update); return () => { ro.disconnect(); window.removeEventListener('scroll', update, true); window.removeEventListener('resize', update); }; }, [active, targets]); return ((0, jsx_runtime_1.jsxs)(SpotlightContext.Provider, { value: contextValue, children: [hasOverlay && active !== null && isOpen && ((0, jsx_runtime_1.jsx)(react_1.FloatingPortal, { children: (0, jsx_runtime_1.jsx)(react_1.FloatingOverlay, { lockScroll: true, className: overlayClasses, style: getMaskStyle(), "data-testid": "ndl-spotlight-overlay" }) })), children] })); }; exports.SpotlightProvider = SpotlightProvider; const useSpotlightContext = () => { const context = (0, react_2.useContext)(SpotlightContext); if (context === null) { throw new Error('Spotlight components must be wrapped in <SpotlightProvider />'); } return context; }; exports.useSpotlightContext = useSpotlightContext; //# sourceMappingURL=SpotlightProvider.js.map