import { memo, useLayoutEffect, useMemo, useRef } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import { Outlet, Routes, UNSAFE_LocationContext, useLocation } from 'react-router-dom' import { TableStateProvider } from '../context/TableStateContext' import { NavigationTabActiveContext, NavigationTabIdContext, createNavigationTabActiveStore, useNavigationTabs } from '../context/NavigationTabsContext' import { wrapDashboardChildRoutes } from './DashboardChildRoutes' const locationsEqual = (left, right) => Boolean( left && right && left.pathname === right.pathname && (left.search || '') === (right.search || '') && (left.hash || '') === (right.hash || '') ) const toTabLocation = (tabId, source) => ({ pathname: source?.pathname || '/', search: source?.search ?? '', hash: source?.hash ?? '', state: source?.state ?? null, key: `tab-${tabId}` }) const reuseTabLocation = (tabId, previous, next) => { if ( previous && locationsEqual(previous, next) && previous.key === `tab-${tabId}` && (previous.state ?? null) === (next.state ?? null) ) { return previous } return toTabLocation(tabId, next) } const entryToLocation = (entry, fallbackLocation, tabId) => toTabLocation(tabId, { pathname: entry?.pathname || fallbackLocation.pathname, search: entry?.search ?? fallbackLocation.search ?? '', hash: entry?.hash ?? fallbackLocation.hash ?? '', state: entry?.state ?? null }) const getTabCurrentEntry = (tab) => tab?.history?.[tab.historyIndex] || tab?.history?.[tab.history?.length - 1] const tabMatchesLocation = (tab, loc) => { const entry = getTabCurrentEntry(tab) if (!entry || !loc) return false return locationsEqual(entry, loc) } const locationBelongsToLeavingTab = (tab, frozen, loc) => { if (!loc) return false if (frozen && locationsEqual(frozen, loc)) return true return tabMatchesLocation(tab, loc) } const CachedTabRouteLayout = () => ( ) const areLocationsEqual = (left, right) => left === right || (locationsEqual(left, right) && left?.key === right?.key && (left?.state ?? null) === (right?.state ?? null)) const arePanePropsEqual = (prev, next) => prev.tabId === next.tabId && prev.isActive === next.isActive && prev.registerTabPane === next.registerTabPane && areLocationsEqual(prev.loc, next.loc) const DashboardTabPaneRoutes = memo(function DashboardTabPaneRoutes({ loc }) { const tabRoutes = useMemo( () => wrapDashboardChildRoutes(), [] ) const locationContextValue = useMemo( () => ({ location: loc, navigationType: 'POP' }), [loc] ) return ( {tabRoutes} ) }, (prev, next) => areLocationsEqual(prev.loc, next.loc)) DashboardTabPaneRoutes.displayName = 'DashboardTabPaneRoutes' DashboardTabPaneRoutes.propTypes = { loc: PropTypes.shape({ pathname: PropTypes.string, search: PropTypes.string, hash: PropTypes.string, key: PropTypes.string, state: PropTypes.any }).isRequired } const DashboardTabPane = memo(function DashboardTabPane({ tabId, isActive, loc, registerTabPane }) { const storeRef = useRef(null) if (storeRef.current == null) { storeRef.current = createNavigationTabActiveStore(isActive) } useLayoutEffect(() => { storeRef.current.setActive(isActive) }, [isActive]) return (
registerTabPane(tabId, element)} data-tab-id={tabId} className={classNames('dashboard-tab-pane', { 'dashboard-tab-pane-active': isActive, 'dashboard-tab-pane-inactive': !isActive })} aria-hidden={!isActive} inert={!isActive} >
) }, arePanePropsEqual) DashboardTabPane.displayName = 'DashboardTabPane' DashboardTabPane.propTypes = { tabId: PropTypes.string.isRequired, isActive: PropTypes.bool, loc: PropTypes.shape({ pathname: PropTypes.string, search: PropTypes.string, hash: PropTypes.string, key: PropTypes.string, state: PropTypes.any }).isRequired, registerTabPane: PropTypes.func.isRequired } const DashboardTabPanes = () => { const location = useLocation() const { tabs, activeTabId, registerTabPane } = useNavigationTabs() const frozenRef = useRef(new Map()) const prevActiveIdRef = useRef(activeTabId) const lastLocationRef = useRef(location) const pendingRestoreRef = useRef(false) const prevActiveId = prevActiveIdRef.current const switchedTabs = Boolean(prevActiveId && activeTabId && prevActiveId !== activeTabId) if (switchedTabs) { const previousTab = tabs.find((tab) => tab.id === prevActiveId) const previousFrozen = frozenRef.current.get(prevActiveId) const lastLoc = lastLocationRef.current if (locationBelongsToLeavingTab(previousTab, previousFrozen, lastLoc)) { frozenRef.current.set( prevActiveId, reuseTabLocation(prevActiveId, previousFrozen, lastLoc) ) } else if (!previousFrozen && previousTab) { frozenRef.current.set( prevActiveId, entryToLocation(getTabCurrentEntry(previousTab), lastLoc, prevActiveId) ) } pendingRestoreRef.current = true } const activeTab = tabs.find((tab) => tab.id === activeTabId) if (activeTab && tabMatchesLocation(activeTab, location)) { pendingRestoreRef.current = false frozenRef.current.set( activeTabId, reuseTabLocation( activeTabId, frozenRef.current.get(activeTabId), location ) ) } for (const tabId of [...frozenRef.current.keys()]) { if (!tabs.some((tab) => tab.id === tabId)) { frozenRef.current.delete(tabId) } } prevActiveIdRef.current = activeTabId lastLocationRef.current = location if (tabs.length === 0) { return } return (
{tabs.map((tab) => { const isActive = tab.id === activeTabId const loc = isActive && !pendingRestoreRef.current ? reuseTabLocation(tab.id, frozenRef.current.get(tab.id), location) : frozenRef.current.get(tab.id) || entryToLocation(getTabCurrentEntry(tab), location, tab.id) frozenRef.current.set(tab.id, loc) return ( ) })}
) } export default DashboardTabPanes