import React, { useCallback, useEffect, useMemo } from 'react' import { getStyleWidthValue } from './utils' import { type CellStyle } from './TableTypes' import { MIN_HEADER_HEIGHT } from './styles' // tableObjectId => rowId => height type RowHeightsMap = Record> // tableObjectId => width type FixedColumnWidthsMap = Record type TableContextState = { hoveringRowKey: string | null rowHeights: RowHeightsMap fixedColumnWidths: FixedColumnWidthsMap } type TableContextValue = { tableState: TableContextState setTableState: React.Dispatch> } const defaultTableContextState: TableContextState = { hoveringRowKey: null, rowHeights: {}, fixedColumnWidths: {}, } const defaultTableContextValue = { tableState: defaultTableContextState, setTableState: () => {}, } const TableContext = React.createContext( defaultTableContextValue ) const useTableContext = () => React.useContext(TableContext) export const TableContextProvider: React.FC = props => { const { children } = props const [tableState, setTableState] = React.useState( defaultTableContextState ) const tableContextValue: TableContextValue = { tableState, setTableState } return ( {children} ) } const getTableRowKey = (tableObjectId: string, rowId: string | number) => `${tableObjectId}--${rowId}` export const useContextRowHovering = ( tableObjectId: string, rowId: string | number ) => { const { tableState, setTableState } = useTableContext() const hoveringRowKey = getTableRowKey(tableObjectId, rowId) const isHovering = tableState.hoveringRowKey === hoveringRowKey const setHovering = useCallback( () => setTableState(state => ({ ...state, hoveringRowKey })), [hoveringRowKey] ) const clearHovering = useCallback( () => setTableState(state => ({ ...state, hoveringRowKey: null })), [] ) return { isHovering, setHovering, clearHovering } } export const useContextTableRowHeight = ( tableObjectId: string, rowId: string | number, minHeight: number ) => { const { tableState, setTableState } = useTableContext() const rowHeight = tableState.rowHeights?.[tableObjectId]?.[rowId] ?? minHeight const setRowHeight = useCallback( (newHeight: number) => { setTableState(state => ({ ...state, rowHeights: { ...state.rowHeights, [tableObjectId]: { ...state.rowHeights[tableObjectId], [rowId]: newHeight, }, }, })) }, [tableObjectId, rowId] ) const onRowHeightChanged = useCallback( (newHeight: number) => { if (newHeight > minHeight) { setRowHeight(newHeight) } }, [tableObjectId, rowId, rowHeight] ) const clearHeightFromContext = useCallback(() => { setTableState(state => { delete state.rowHeights?.[tableObjectId]?.[rowId] return state }) }, []) // Clean up row height when component unmounts useEffect(() => () => clearHeightFromContext(), [tableObjectId, rowId]) return { rowHeight, onRowHeightChanged } } const HEADER_ROW_ID = 'header' export const useContextHeaderHeight = (tableObjectId: string) => { const { rowHeight, onRowHeightChanged } = useContextTableRowHeight( tableObjectId, HEADER_ROW_ID, MIN_HEADER_HEIGHT ) return { headerHeight: rowHeight, onHeaderHeightChanged: onRowHeightChanged } } export const useContextFixedColumnWidthUpdate = (tableObjectId: string) => { const { tableState, setTableState } = useTableContext() const fixedColumnWidth = tableState.fixedColumnWidths?.[tableObjectId] ?? 0 const setFixedColumnWidth = useCallback( (newWidth: number) => { setTableState(state => ({ ...state, fixedColumnWidths: { ...state.fixedColumnWidths, [tableObjectId]: newWidth, }, })) }, [tableObjectId] ) const clearFixedColumnWidth = useCallback(() => { setTableState(state => { delete state.fixedColumnWidths?.[tableObjectId] return state }) }, []) // Clean up state on unmount useEffect(() => () => clearFixedColumnWidth(), []) return { fixedColumnWidth, setFixedColumnWidth } } export const useContextFixedColumnWidth = (tableObjectId: string) => { const { tableState } = useTableContext() const fixedColumnWidth = tableState.fixedColumnWidths?.[tableObjectId] ?? 0 return fixedColumnWidth } export const useContextTableMinWidth = ( tableObjectId: string, tableWidth: number, columns: T[] ) => { const fixedContentWidth = useContextFixedColumnWidth(tableObjectId) const tableMinWidthWithFixed: number = useMemo( () => columns.reduce((acc, col, index) => { if (index === 0 && fixedContentWidth) { return acc } const width = getStyleWidthValue(col.style) return acc + width }, fixedContentWidth ?? 0), [columns, tableObjectId, fixedContentWidth] ) return Math.max(tableWidth, tableMinWidthWithFixed) }