import type { RefObject } from 'react'; import type { DataGridProps } from '../types'; import { throttleEvent } from '../../../utils'; const GRID_MAIN_CLASS = 'MuiDataGrid-main'; const GRID_VIRTUAL_SCROLLER_CLASS = 'MuiDataGrid-virtualScroller'; const CUSTOMIZED_WEBKIT_SCROLLBAR_CLASS = 'customizedWebkitScrollbar'; const INVISIBLE_WEBKIT_SCROLLBAR_CLASS = 'invisibleWebkitScrollbar'; const STICKY_HORIZONTAL_SCROLLBAR_HEIGHT = 16; const STICKY_HORIZONTAL_SCROLLER_CLASS = 'autoHeightStickyHorizontalScroller'; const STICKY_HORIZONTAL_SCROLLER_ID = 'p81DataGrid-scrollerId'; const STICKY_PAGINATION_CONTAINER_INNER_CLASS = 'stickyPaginationContainerInner'; function createStickyScroller(instanceId: number) { const scrollbarWidth = Math.max( window.innerWidth - document.documentElement.clientWidth, 1 // 0px height will hide StickyScroller ); const outerEl = document.createElement('div'); outerEl.className = STICKY_HORIZONTAL_SCROLLER_CLASS; outerEl.id = `${STICKY_HORIZONTAL_SCROLLER_ID}-${instanceId}`; outerEl.style.position = 'fixed'; outerEl.style.bottom = '0px'; outerEl.style.zIndex = '4'; outerEl.style.minHeight = `${scrollbarWidth}px`; outerEl.style.overflow = 'auto hidden'; const innerEl = document.createElement('div'); innerEl.style.position = 'absolute'; innerEl.style.height = '1px'; outerEl.appendChild(innerEl); return outerEl; } function isScrollerVisible(rootEl: HTMLDivElement, headerHeight?: number, footerHeight = 0) { const viewportHeight = Math.max( document.documentElement.clientHeight || 0, window.innerHeight || 0 ); const rootRect = rootEl.getBoundingClientRect(); return ( rootRect.top + (headerHeight || 0) + STICKY_HORIZONTAL_SCROLLBAR_HEIGHT + footerHeight <= viewportHeight && rootRect.bottom + footerHeight >= viewportHeight ); } function setElementVisibility(element: HTMLElement, isVisible: boolean) { if (isVisible) { element.style.opacity = '1'; element.style.visibility = 'visible'; } else { element.style.opacity = '0'; element.style.visibility = 'hidden'; } } export const useStickyHorizontalScrollerHandler = ( apiRef: DataGridProps['apiRef'], stickyPaginationRef?: RefObject, headerHeight?: DataGridProps['headerHeight'], customizeScrollbars?: boolean ) => { const instanceId = apiRef?.current.instanceId || 0; let stickyScrollerEl: HTMLDivElement; let virtualScrollerEl: HTMLDivElement; const stickyPaginationEl = stickyPaginationRef?.current; const stickyPaginationHeight = stickyPaginationEl?.offsetHeight || 0; const stickyScrollerHandler = () => (stickyScrollerEl.scrollLeft = virtualScrollerEl.scrollLeft); const virtualScrollerHandler = () => (virtualScrollerEl.scrollLeft = stickyScrollerEl.scrollLeft); return (e?: Event, unmount?: boolean) => { const rootEl = getGridRootElement(); const mainEl = rootEl?.querySelector(`.${GRID_MAIN_CLASS}`); if (!rootEl || !mainEl) return; if (unmount) { stickyScrollerEl?.remove(); syncStickyPagination(false); return; } stickyScrollerEl = mainEl.querySelector( `#${STICKY_HORIZONTAL_SCROLLER_ID}-${instanceId}.${STICKY_HORIZONTAL_SCROLLER_CLASS}` ) as HTMLDivElement; virtualScrollerEl = mainEl.querySelector(`.${GRID_VIRTUAL_SCROLLER_CLASS}`) as HTMLDivElement; let isNewStickyScroller = false; if (!stickyScrollerEl) { isNewStickyScroller = true; stickyScrollerEl = createStickyScroller(instanceId); mainEl.appendChild(stickyScrollerEl); } if (!stickyScrollerEl || !virtualScrollerEl) return; if (customizeScrollbars) { customizeWebkitScrollbars(true); syncScrollerAndPaginationVisibility( isScrollerVisible(rootEl, headerHeight, stickyPaginationHeight) ); syncScrollerAndDataGridWidths(); if (isNewStickyScroller) { setScrollEventListeners(); } } else { customizeWebkitScrollbars(false); syncScrollerAndPaginationVisibility(false); } }; function customizeWebkitScrollbars(enable = false) { if (enable) { stickyScrollerEl.classList.add(CUSTOMIZED_WEBKIT_SCROLLBAR_CLASS); virtualScrollerEl.classList.add(CUSTOMIZED_WEBKIT_SCROLLBAR_CLASS); } else { stickyScrollerEl.classList.remove(CUSTOMIZED_WEBKIT_SCROLLBAR_CLASS); virtualScrollerEl.classList.remove(CUSTOMIZED_WEBKIT_SCROLLBAR_CLASS); } } function getGridRootElement() { return apiRef?.current?.rootElementRef?.current; } function setScrollEventListeners() { // sync scroll positions on init stickyScrollerHandler(); // mutually synchronize positions virtualScrollerEl.onscroll = stickyScrollerHandler; stickyScrollerEl.onscroll = throttleEvent(virtualScrollerHandler); } function syncScrollerAndPaginationVisibility(isVisible = false) { syncScrollerVisibility(isVisible); syncStickyPagination(isVisible); } function syncScrollerVisibility(isVisible = false) { if (isVisible) { stickyScrollerEl && setElementVisibility(stickyScrollerEl, true); virtualScrollerEl.classList.add(INVISIBLE_WEBKIT_SCROLLBAR_CLASS); } else { stickyScrollerEl && setElementVisibility(stickyScrollerEl, false); virtualScrollerEl.classList.remove(INVISIBLE_WEBKIT_SCROLLBAR_CLASS); } } function syncScrollerAndDataGridWidths() { const stickyContentEl = stickyScrollerEl?.firstElementChild as HTMLElement; const virtualContentEl = virtualScrollerEl?.firstElementChild as HTMLElement; if (stickyContentEl && virtualContentEl) { stickyScrollerEl.style.width = virtualScrollerEl.style.width; stickyContentEl.style.width = virtualContentEl.style.width; if (stickyPaginationEl) { stickyPaginationEl.style.width = virtualScrollerEl.style.width; } } } function syncStickyPagination(isVisible = false) { if (!stickyPaginationEl) return; if (isVisible) { stickyScrollerEl.style.bottom = `${stickyPaginationHeight}px`; stickyPaginationEl.classList.add(STICKY_PAGINATION_CONTAINER_INNER_CLASS); } else { stickyPaginationEl.classList.remove(STICKY_PAGINATION_CONTAINER_INNER_CLASS); } } };