Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/HighTable/Scroller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCallback, useContext, useMemo } from 'react'

import { CellNavigationContext } from '../../contexts/CellNavigationContext.js'
import { ScrollContext } from '../../contexts/ScrollContext.js'
import { useSetViewportSize } from '../../contexts/ViewportSizeContext.js'
import { SetViewportSizeContext } from '../../contexts/ViewportSizeContext.js'
import styles from '../../HighTable.module.css'

interface Props {
Expand All @@ -13,7 +13,7 @@ interface Props {

export default function Scroller({ children }: Props) {
/** Callback to set the current viewport size */
const setViewportSize = useSetViewportSize()
const setViewportSize = useContext(SetViewportSizeContext)
const { goToCurrentCell } = useContext(CellNavigationContext)
const { canvasHeight, sliceTop, setScrollTop, setScrollTo } = useContext(ScrollContext)

Expand Down
14 changes: 7 additions & 7 deletions src/components/HighTable/Slice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCallback, useContext, useMemo } from 'react'

import { CellNavigationContext } from '../../contexts/CellNavigationContext.js'
import { ColumnsVisibilityContext } from '../../contexts/ColumnsVisibilityContext.js'
import { useData, useDataVersion, useNumRows } from '../../contexts/DataContext.js'
import { DataFrameMethodsContext, DataVersionContext, NumRowsContext } from '../../contexts/DataContext.js'
import { OrderByContext } from '../../contexts/OrderByContext.js'
import { ScrollContext } from '../../contexts/ScrollContext.js'
import { SelectionContext } from '../../contexts/SelectionContext.js'
Expand Down Expand Up @@ -34,10 +34,10 @@ export default function Slice({
const { visibleColumnsParameters: columnsParameters } = useContext(ColumnsVisibilityContext)
const { renderedRowsStart, renderedRowsEnd } = useContext(ScrollContext)
/** A version number that increments whenever a data frame is updated or resolved (the key remains the same). */
const version = useDataVersion()
const version = useContext(DataVersionContext)
/** The actual number of rows in the data frame */
const numRows = useNumRows()
const data = useData()
const numRows = useContext(NumRowsContext)
const dataFrameMethods = useContext(DataFrameMethodsContext)

// Fetch the required cells if needed (visible + overscan)
// it's a side-effect.
Expand Down Expand Up @@ -144,9 +144,9 @@ export default function Slice({

const canMeasureColumn: Record<string, boolean> = {}
const rowContents = rows.map((row) => {
const rowNumber = data.getRowNumber({ row, orderBy })?.value
const rowNumber = dataFrameMethods.getRowNumber({ row, orderBy })?.value
const cells = (columnsParameters ?? []).map(({ name: column, index: originalColumnIndex, className }) => {
const cell = data.getCell({ row, column, orderBy })
const cell = dataFrameMethods.getCell({ row, column, orderBy })
canMeasureColumn[column] ||= cell !== undefined
return { columnIndex: originalColumnIndex, cell, className }
})
Expand All @@ -161,7 +161,7 @@ export default function Slice({
canMeasureColumn,
version,
}
}, [data, columnsParameters, renderedRowsStart, renderedRowsEnd, orderBy, version])
}, [dataFrameMethods, columnsParameters, renderedRowsStart, renderedRowsEnd, orderBy, version])

// don't render table if the data frame has no visible columns
// (it can have zero rows, but must have at least one visible column)
Expand Down
10 changes: 5 additions & 5 deletions src/components/HighTable/Wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { CSSProperties, ReactNode } from 'react'
import { type CSSProperties, type ReactNode, useContext } from 'react'

import { useNumRows } from '../../contexts/DataContext.js'
import { NumRowsContext } from '../../contexts/DataContext.js'
import { PortalContainerContext } from '../../contexts/PortalContainerContext.js'
import { useHeaderHeight } from '../../contexts/TableCornerSizeContext.js'
import { TableCornerHeightContext } from '../../contexts/TableCornerSizeContext.js'
import styles from '../../HighTable.module.css'
import { useHTMLElement } from '../../hooks/useHTMLElement.js'
import type { HighTableProps } from '../../types.js'
Expand All @@ -14,9 +14,9 @@ type Props = Pick<HighTableProps, 'className' | 'maxRowNumber' | 'styled'> & {

export default function Wrapper({ children, className, maxRowNumber, styled }: Props) {
/** Number of rows in the data frame */
const numRows = useNumRows()
const numRows = useContext(NumRowsContext)
/** Height of the header, used to set a CSS variable for row height calculation in the cells */
const headerHeight = useHeaderHeight()
const headerHeight = useContext(TableCornerHeightContext)

// reserve space for at least 3 characters
const numCharacters = Math.max((maxRowNumber ?? numRows).toLocaleString('en-US').length, 3)
Expand Down
6 changes: 3 additions & 3 deletions src/components/TableCorner/TableCorner.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ChangeEvent, CSSProperties, KeyboardEvent, ReactNode } from 'react'
import { useCallback, useEffect, useRef } from 'react'
import { useCallback, useContext, useEffect, useRef } from 'react'

import { useSetTableCornerSize } from '../../contexts/TableCornerSizeContext.js'
import { SetTableCornerSizeContext } from '../../contexts/TableCornerSizeContext.js'
import { useCellFocus } from '../../hooks/useCellFocus.js'

interface Props {
Expand All @@ -16,7 +16,7 @@ interface Props {

export default function TableCorner({ children, checked, onCheckboxPress, pendingSelectionGesture, style, ariaColIndex, ariaRowIndex }: Props) {
const { tabIndex, navigateToCell, focusIfNeeded } = useCellFocus({ ariaColIndex, ariaRowIndex })
const setTableCornerSize = useSetTableCornerSize()
const setTableCornerSize = useContext(SetTableCornerSizeContext)

// Focus the cell if needed. We use an effect, as it acts on the DOM element after render.
const ref = useRef<HTMLTableCellElement | null>(null)
Expand Down
46 changes: 11 additions & 35 deletions src/contexts/DataContext.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,23 @@
import { createContext, useContext } from 'react'
import { createContext } from 'react'

import type { ColumnDescriptor, DataFrame } from '../helpers/dataframe/types.js'

/**
* The data frame, limited to the getRowNumber, getCell, and fetch methods.
*
* The methods might change over time, without the data frame instance changing.
*/
export type DataFrameMethods = Pick<DataFrame, 'getRowNumber' | 'getCell' | 'fetch'>
export type DataFrameWithoutMethods = Omit<DataFrame, 'getRowNumber' | 'getCell' | 'fetch'>

export const DataKeyContext = createContext<number>(0)
export const DataVersionContext = createContext<number>(0)
export const NumRowsContext = createContext<number>(0)
export const ColumnDescriptorsContext = createContext<Pick<ColumnDescriptor, 'name' | 'sortable'>[]>([])
export const NumColumnsContext = createContext<number>(0)
export const ExclusiveSortContext = createContext<boolean>(false)
export const DataContext = createContext<DataFrameMethods | undefined>(undefined)

export const DataFrameMethodsContext = createContext<DataFrameMethods>({
getRowNumber: () => undefined,
getCell: () => undefined,
})
// the data key is only used in tests
export function useDataKey() {
return useContext(DataKeyContext)
}

export function useDataVersion() {
return useContext(DataVersionContext)
}

export function useNumRows() {
return useContext(NumRowsContext)
}

export function useColumnDescriptors() {
return useContext(ColumnDescriptorsContext)
}

export function useNumColumns() {
return useContext(NumColumnsContext)
}

export function useExclusiveSort() {
return useContext(ExclusiveSortContext)
}

export function useData(): DataFrameMethods {
const data = useContext(DataContext)
if (data === undefined) {
throw new Error('useData must be used within a DataContext.Provider with a valid DataFrameMethods value')
}
return data
}
export const DataKeyContext = createContext<number>(0)
17 changes: 3 additions & 14 deletions src/contexts/TableCornerSizeContext.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import { createContext, useContext } from 'react'
import { createContext } from 'react'

import { rowHeight } from '../helpers/constants.js'

type SetTableCornerSizeContextType = (element: HTMLElement) => void

export const TableCornerHeightContext = createContext<number | undefined>(undefined)
export const defaultTableCornerHeight = rowHeight
export const TableCornerHeightContext = createContext<number>(defaultTableCornerHeight)
export const TableCornerWidthContext = createContext<number | undefined>(undefined)
export const SetTableCornerSizeContext = createContext<SetTableCornerSizeContextType | undefined>(undefined)

export function useTableCornerWidth() {
return useContext(TableCornerWidthContext)
}

export function useHeaderHeight() {
return useContext(TableCornerHeightContext) ?? rowHeight
}

export function useSetTableCornerSize() {
return useContext(SetTableCornerSizeContext)
}
14 changes: 1 addition & 13 deletions src/contexts/ViewportSizeContext.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import { createContext, useContext } from 'react'
import { createContext } from 'react'

type SetViewportSizeContextType = (element: HTMLElement) => void

export const ViewportHeightContext = createContext<number | undefined>(undefined)
export const ViewportWidthContext = createContext<number | undefined>(undefined)
export const SetViewportSizeContext = createContext<SetViewportSizeContextType | undefined>(undefined)

export function useViewportWidth() {
return useContext(ViewportWidthContext)
}

export function useViewportHeight() {
return useContext(ViewportHeightContext)
}

export function useSetViewportSize() {
return useContext(SetViewportSizeContext)
}
12 changes: 6 additions & 6 deletions src/hooks/useFetchCells.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useContext, useEffect, useEffectEvent, useMemo } from 'react'

import { ColumnsVisibilityContext } from '../contexts/ColumnsVisibilityContext.js'
import { useData, useNumRows } from '../contexts/DataContext.js'
import { DataFrameMethodsContext, NumRowsContext } from '../contexts/DataContext.js'
import { OrderByContext } from '../contexts/OrderByContext.js'
import { ScrollContext } from '../contexts/ScrollContext.js'
import { defaultOverscan } from '../helpers/constants.js'
Expand All @@ -16,8 +16,8 @@ export function useFetchCells({ overscan = defaultOverscan, onError }: Props) {
const { visibleRowsStart, visibleRowsEnd } = useContext(ScrollContext)
const { visibleColumnsParameters } = useContext(ColumnsVisibilityContext)
const orderBy = useContext(OrderByContext)
const data = useData()
const numRows = useNumRows()
const dataFrameMethods = useContext(DataFrameMethodsContext)
const numRows = useContext(NumRowsContext)

const fetchedRowsStart = useMemo(() => {
if (visibleRowsStart === undefined) return undefined
Expand All @@ -44,13 +44,13 @@ export function useFetchCells({ overscan = defaultOverscan, onError }: Props) {
// Keep this inside an effect so we don't update state
// or perform side-effects during render, for example when calling onError.
useEffect(() => {
if (data.fetch === undefined || fetchedRowsStart === undefined || fetchedRowsEnd === undefined) return
if (dataFrameMethods.fetch === undefined || fetchedRowsStart === undefined || fetchedRowsEnd === undefined) return

// Create an AbortController per fetch and clean it up on dependency changes.
const abortController = new AbortController()

// Launch the data fetch. The promise is not awaited here, but it will be aborted if any dependency changes.
data.fetch({
dataFrameMethods.fetch({
rowStart: fetchedRowsStart,
rowEnd: fetchedRowsEnd,
columns: columnNames,
Expand All @@ -66,5 +66,5 @@ export function useFetchCells({ overscan = defaultOverscan, onError }: Props) {
return () => {
abortController.abort()
}
}, [data, fetchedRowsStart, fetchedRowsEnd, columnNames, orderBy])
}, [dataFrameMethods, fetchedRowsStart, fetchedRowsEnd, columnNames, orderBy])
}
4 changes: 2 additions & 2 deletions src/providers/CellNavigationProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCallback, useContext, useEffect, useMemo, useReducer } from 'react'
import type { FocusAction, FocusState, MoveCellAction } from '../contexts/CellNavigationContext.js'
import { CellNavigationContext } from '../contexts/CellNavigationContext.js'
import { ColumnsVisibilityContext } from '../contexts/ColumnsVisibilityContext.js'
import { useNumRows } from '../contexts/DataContext.js'
import { NumRowsContext } from '../contexts/DataContext.js'
import { defaultNumRowsPerPage } from '../helpers/constants.js'
import { useInputState } from '../hooks/useInputState.js'
import type { HighTableProps } from '../types.js'
Expand Down Expand Up @@ -55,7 +55,7 @@ export function CellNavigationProvider({
}: CellNavigationProviderProps) {
const [focusState, focusDispatch] = useReducer(reducer, focus, initializeFocusState)
/** The actual number of rows in the data frame */
const numDataRows = useNumRows()
const numDataRows = useContext(NumRowsContext)

const notifyChange = useCallback(() => {
focusDispatch({ type: 'START' })
Expand Down
6 changes: 3 additions & 3 deletions src/providers/ColumnParametersProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type ReactNode, useMemo } from 'react'
import { type ReactNode, useContext, useMemo } from 'react'

import { type ColumnParameters, ColumnParametersContext, SortableColumnsContext } from '../contexts/ColumnParametersContext.js'
import { useColumnDescriptors } from '../contexts/DataContext.js'
import { ColumnDescriptorsContext } from '../contexts/DataContext.js'
import type { HighTableProps } from '../types.js'

type Props = Pick<HighTableProps, 'columnConfiguration'> & {
Expand All @@ -15,7 +15,7 @@ type Props = Pick<HighTableProps, 'columnConfiguration'> & {
* It merges the column descriptors from the data frame with the user-provided configuration.
*/
export function ColumnParametersProvider({ columnConfiguration, children }: Props) {
const columnDescriptors = useColumnDescriptors()
const columnDescriptors = useContext(ColumnDescriptorsContext)

// A column is sortable iif it's marked as sortable in the column descriptors from the data frame. The user configuration can't change that.
const sortableColumns = useMemo(() => {
Expand Down
12 changes: 6 additions & 6 deletions src/providers/ColumnWidthsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'r

import { ColumnParametersContext } from '../contexts/ColumnParametersContext.js'
import { ColumnWidthsContext } from '../contexts/ColumnWidthsContext.js'
import { useNumColumns } from '../contexts/DataContext.js'
import { useTableCornerWidth } from '../contexts/TableCornerSizeContext.js'
import { useViewportWidth } from '../contexts/ViewportSizeContext.js'
import { NumColumnsContext } from '../contexts/DataContext.js'
import { TableCornerWidthContext } from '../contexts/TableCornerSizeContext.js'
import { ViewportWidthContext } from '../contexts/ViewportSizeContext.js'
import { cellStyle } from '../helpers/width.js'
import { useLocalStorageState } from '../hooks/useLocalStorageState.js'

Expand Down Expand Up @@ -73,11 +73,11 @@ interface ColumnWidthsProviderProps {
*/
export function ColumnWidthsProvider({ children, localStorageKey, minWidth }: ColumnWidthsProviderProps) {
/** Current viewport width (used to compute the maximum total width) */
const viewportWidth = useViewportWidth()
const viewportWidth = useContext(ViewportWidthContext)
/** Current table corner width (used to compute the maximum total width) */
const tableCornerWidth = useTableCornerWidth()
const tableCornerWidth = useContext(TableCornerWidthContext)
/** Number of columns (used to initialize the widths array, and compute the widths) */
const numColumns = useNumColumns()
const numColumns = useContext(NumColumnsContext)

// Number of columns
if (!Number.isInteger(numColumns) || numColumns < 0) {
Expand Down
6 changes: 3 additions & 3 deletions src/providers/DataProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type ReactNode, useEffect, useState } from 'react'

import type { DataFrameWithoutMethods } from '../contexts/DataContext.js'
import { ColumnDescriptorsContext, DataContext, DataKeyContext, DataVersionContext, ExclusiveSortContext, NumColumnsContext, NumRowsContext } from '../contexts/DataContext.js'
import { ColumnDescriptorsContext, DataFrameMethodsContext, DataKeyContext, DataVersionContext, ExclusiveSortContext, NumColumnsContext, NumRowsContext } from '../contexts/DataContext.js'
import type { HighTableProps } from '../types.js'

// Assign stable numeric ids to data instances without triggering state
Expand Down Expand Up @@ -33,11 +33,11 @@ export function DataProvider({ children, data }: Props) {
return (
// The data key context is only used in tests
<DataKeyContext.Provider value={key}>
<DataContext.Provider value={data}>
<DataFrameMethodsContext.Provider value={data}>
<KeyedDataProvider data={data} key={key}>
{children}
</KeyedDataProvider>
</DataContext.Provider>
</DataFrameMethodsContext.Provider>
</DataKeyContext.Provider>
)
}
Expand Down
4 changes: 2 additions & 2 deletions src/providers/OrderByProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type ReactNode, useContext, useMemo } from 'react'

import { SortableColumnsContext } from '../contexts/ColumnParametersContext.js'
import { useExclusiveSort } from '../contexts/DataContext.js'
import { ExclusiveSortContext } from '../contexts/DataContext.js'
import { OrderByContext, SortInfoAndActionsByColumnContext } from '../contexts/OrderByContext.js'
import { type OrderBy, toggleColumn, toggleColumnExclusive } from '../helpers/sort.js'
import { useInputState } from '../hooks/useInputState.js'
Expand All @@ -24,7 +24,7 @@ type Props = Pick<HighTableProps, 'orderBy' | 'onOrderByChange'> & {
* The context value is memoized and won't change unless the orderBy or the sortable columns change, to avoid unnecessary re-renders of the consumers.
*/
export function OrderByProvider({ children, orderBy: controlledOrderBy, onOrderByChange }: Props) {
const exclusiveSort = useExclusiveSort()
const exclusiveSort = useContext(ExclusiveSortContext)
const sortableColumns = useContext(SortableColumnsContext)

const [orderBy, setOrderBy] = useInputState<OrderBy>({
Expand Down
12 changes: 6 additions & 6 deletions src/providers/ScrollProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { type ReactNode, useCallback, useContext, useEffect, useMemo, useReducer, useState } from 'react'

import { CellNavigationContext } from '../contexts/CellNavigationContext.js'
import { useNumRows } from '../contexts/DataContext.js'
import { NumRowsContext } from '../contexts/DataContext.js'
import { ScrollContext } from '../contexts/ScrollContext.js'
import { useHeaderHeight } from '../contexts/TableCornerSizeContext.js'
import { useViewportHeight } from '../contexts/ViewportSizeContext.js'
import { TableCornerHeightContext } from '../contexts/TableCornerSizeContext.js'
import { ViewportHeightContext } from '../contexts/ViewportSizeContext.js'
import { defaultPadding, maxElementHeight, rowHeight } from '../helpers/constants.js'
import { computeDerivedValues, createScale, getScrollActionForRow, initializeScrollState, scrollReducer } from '../helpers/scroll.js'
import type { HighTableProps } from '../types.js'
Expand All @@ -20,11 +20,11 @@ type ScrollProviderProps = Pick<HighTableProps, 'padding'> & {
export function ScrollProvider({ children, padding = defaultPadding }: ScrollProviderProps) {
const [{ scale, scrollTop, scrollTopAnchor, localOffset }, dispatch] = useReducer(scrollReducer, undefined, initializeScrollState)
const { cellPosition, focusState, focusDispatch } = useContext(CellNavigationContext)
const clientHeight = useViewportHeight()
const clientHeight = useContext(ViewportHeightContext)
/** Height of the header row, in pixels */
const headerHeight = useHeaderHeight()
const headerHeight = useContext(TableCornerHeightContext)
/** The actual number of rows in the data frame */
const numRows = useNumRows()
const numRows = useContext(NumRowsContext)

const [scrollTo, setScrollTo] = useState<HTMLElement['scrollTo'] | undefined>(undefined)
const setScrollTop = useCallback((scrollTop: number) => {
Expand Down
Loading