UNPKG

recharts

Version:
140 lines (132 loc) 5.38 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ZIndexLayer = ZIndexLayer; var _react = require("react"); var _reactDom = require("react-dom"); var _hooks = require("../state/hooks"); var _zIndexSelectors = require("./zIndexSelectors"); var _zIndexSlice = require("../state/zIndexSlice"); var _chartLayoutContext = require("../context/chartLayoutContext"); var _PanoramaContext = require("../context/PanoramaContext"); /** * @since 3.4 */ /** * A layer that renders its children into a portal corresponding to the given zIndex. * We can't use regular CSS `z-index` because SVG does not support it. * So instead, we create separate DOM nodes for each zIndex layer * and render the children into the corresponding DOM node using React portals. * * This component must be used inside a Chart component. * * @param zIndex numeric zIndex value, higher values are rendered on top of lower values * @param children the content to render inside this zIndex layer * * @since 3.4 */ function ZIndexLayer(_ref) { var { zIndex, children } = _ref; /* * If we are outside of chart, then we can't rely on the zIndex portal state, * so we just render normally. */ var isInChartContext = (0, _chartLayoutContext.useIsInChartContext)(); /* * If zIndex is undefined then we render normally without portals. * Also, if zIndex is 0, we render normally without portals, * because 0 is the default layer that does not need a portal. */ var shouldRenderInPortal = isInChartContext && zIndex !== undefined && zIndex !== 0; var isPanorama = (0, _PanoramaContext.useIsPanorama)(); /** * When zIndex changes, the new portal element is not immediately available because * it requires a full render cycle through AllZIndexPortals → ZIndexSvgPortal. * During this transition we keep rendering into the previous portal element * to avoid an unmount/remount cycle that would cause children to briefly disappear. * * `registeredZIndexesRef` tracks every zIndex we have registered so that * we can defer unregistration of old values until the new portal is ready. * `lastPortalElementRef` caches the most recent valid portal DOM node. */ var lastPortalElementRef = (0, _react.useRef)(undefined); var registeredZIndexesRef = (0, _react.useRef)(new Set()); var dispatch = (0, _hooks.useAppDispatch)(); var portalElement = (0, _hooks.useAppSelector)(state => (0, _zIndexSelectors.selectZIndexPortalElement)(state, zIndex, isPanorama)); /* * Lifecycle effect — handles both registration and deferred cleanup. * * Registration: when zIndex changes we register the new value WITHOUT * immediately unregistering the old one. This keeps the old <g> element * alive in the DOM so `lastPortalElementRef` remains a valid render target. * * Deferred cleanup: once `portalElement` for the *new* zIndex becomes * available we unregister every stale zIndex that is no longer needed. */ (0, _react.useLayoutEffect)(() => { if (!shouldRenderInPortal) { // Portal rendering was disabled — clean up any stale registrations var registered = registeredZIndexesRef.current; registered.forEach(z => { dispatch((0, _zIndexSlice.unregisterZIndexPortal)({ zIndex: z })); }); registered.clear(); lastPortalElementRef.current = undefined; return; } /* * Because zIndexes are dynamic (meaning, we're not working with a predefined set of layers, * but we allow users to define any zIndex at any time), we need to register * the requested zIndex in the global store. This way, the ZIndexPortals component * can render the corresponding portals and only the requested ones. */ // Register the current zIndex (idempotent — skips if already registered) if (!registeredZIndexesRef.current.has(zIndex)) { dispatch((0, _zIndexSlice.registerZIndexPortal)({ zIndex })); registeredZIndexesRef.current.add(zIndex); } // When the new portal element is ready, retire old zIndex registrations if (portalElement) { lastPortalElementRef.current = portalElement; var _registered = registeredZIndexesRef.current; _registered.forEach(z => { if (z !== zIndex) { dispatch((0, _zIndexSlice.unregisterZIndexPortal)({ zIndex: z })); _registered.delete(z); } }); } }, [dispatch, zIndex, shouldRenderInPortal, portalElement]); // Unmount-only cleanup — unregister everything when the component is removed (0, _react.useLayoutEffect)(() => { var registered = registeredZIndexesRef.current; return () => { registered.forEach(z => { dispatch((0, _zIndexSlice.unregisterZIndexPortal)({ zIndex: z })); }); registered.clear(); }; }, [dispatch]); if (!shouldRenderInPortal) { return children; } // Prefer the current portal; fall back to the cached one during transitions var targetElement = portalElement !== null && portalElement !== void 0 ? portalElement : lastPortalElementRef.current; if (!targetElement) { // Very first render — no portal has ever been registered yet return null; } return /*#__PURE__*/(0, _reactDom.createPortal)(children, targetElement); }