UNPKG

@blueprintjs/table

Version:

Scalable interactive table component

278 lines (252 loc) 10.2 kB
/* * Copyright 2017 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { type Region, RegionCardinality, Regions } from "../../regions"; import type { TableProps } from "../../tableProps"; import { type CellCoordinates, type FocusedCell, type FocusedCellCoordinates, type FocusedRegion, type FocusedRow, FocusMode, } from "../cellTypes"; import * as Errors from "../errors"; /** * Returns the inferred focus mode from the table props. This prefers the new focus mode API, falling back to the * deprecated enableFocusedCell API if that is not provided. */ export function getFocusModeFromProps(props: TableProps): FocusMode | undefined { // eslint-disable-next-line @typescript-eslint/no-deprecated const { enableFocusedCell, focusMode } = props; return focusMode ?? getFocusModeFromEnabled(enableFocusedCell); } function getFocusModeFromEnabled(enableFocusedCell = false): FocusMode | undefined { return enableFocusedCell ? FocusMode.CELL : undefined; } /** * Returns the inferred focused region from the table props. This prefers the new focus mode API, falling back to the * deprecated API if a focused region is not provided. */ export function getFocusedRegionFromProps(props: TableProps): FocusedRegion | undefined { // eslint-disable-next-line @typescript-eslint/no-deprecated const { focusedRegion, focusedCell } = props; return focusedRegion ?? getFocusedCellFromCoordinates(focusedCell); } export function getFocusedCellFromCoordinates( focusedCell: FocusedCellCoordinates | undefined, ): FocusedCell | undefined { return focusedCell != null ? { type: FocusMode.CELL, ...focusedCell } : undefined; } /** * Returns the `focusedSelectionIndex` if both the focused region and that * property are defined, or the last index of `selectedRegions` otherwise. If * `selectedRegions` is empty, the function always returns `undefined`. */ export function getFocusedOrLastSelectedIndex(selectedRegions: Region[], focusedRegion?: FocusedRegion) { if (selectedRegions.length === 0) { return undefined; } else if (focusedRegion != null) { return focusedRegion.focusSelectionIndex; } else { return selectedRegions.length - 1; } } /** * Returns the proper focused region for the given set of initial conditions. */ export function getInitialFocusedRegion( focusMode: FocusMode | undefined, focusedRegionFromProps: FocusedRegion | undefined, focusedRegionFromState: FocusedRegion | undefined, selectedRegions: Region[], ): FocusedRegion | undefined { return validateFocusedRegion( focusMode, getInitialFocusedCell(focusedRegionFromProps, focusedRegionFromState, selectedRegions), ); } function getInitialFocusedCell( focusedRegionFromProps: FocusedRegion | undefined, focusedRegionFromState: FocusedRegion | undefined, selectedRegions: Region[], ): FocusedRegion { return focusedRegionFromProps ?? focusedRegionFromState ?? getInitialFocusedCellFromSelection(selectedRegions); } function getInitialFocusedCellFromSelection(selectedRegions: Region[]): FocusedRegion { if (selectedRegions.length === 0) { return { col: 0, focusSelectionIndex: 0, row: 0, type: FocusMode.CELL }; } const lastIndex = selectedRegions.length - 1; // focus the top-left cell of the last selection return { ...Regions.getFocusCellCoordinatesFromRegion(selectedRegions[lastIndex]), focusSelectionIndex: lastIndex, type: FocusMode.CELL, }; } /** * Returns a focused region that matches the given focus mode if possible. If such a conversion is not possible, * returns undefined instead. */ export function validateFocusedRegion( focusMode: FocusMode | undefined, focusedRegion: FocusedRegion, ): FocusedRegion | undefined { if (focusMode == null) { return undefined; } if (focusedRegion.type === focusMode) { return focusedRegion; } if (focusedRegion.type === FocusMode.CELL && focusMode === FocusMode.ROW) { return { focusSelectionIndex: focusedRegion.focusSelectionIndex, row: focusedRegion.row, type: focusMode, }; } if (focusedRegion.type === FocusMode.ROW && focusMode === FocusMode.CELL) { return { col: 0, focusSelectionIndex: focusedRegion.focusSelectionIndex, row: focusedRegion.row, type: focusMode, }; } return undefined; } /** * Returns `true` if the focused region is located along the top boundary of the * provided region, or `false` otherwise. */ export function isFocusAtRegionTop(region: Region, focusedRegion: FocusedRegion) { return region.rows != null && focusedRegion.row === region.rows[0]; } /** * Returns `true` if the focused region is located along the bottom boundary of * the provided region, or `false` otherwise. */ export function isFocusAtRegionBottom(region: Region, focusedRegion: FocusedRegion) { return region.rows != null && focusedRegion.row === region.rows[1]; } /** * Returns `true` if the focused region is located along the left boundary of the * provided region, or `false` otherwise. */ export function isFocusAtRegionLeft(region: Region, focusedRegion: FocusedRegion) { return region.cols != null && getFocusedColumn(focusedRegion) === region.cols[0]; } /** * Returns `true` if the focused region is located along the right boundary of the * provided region, or `false` otherwise. */ export function isFocusAtRegionRight(region: Region, focusedRegion: FocusedRegion) { return region.cols != null && getFocusedColumn(focusedRegion) === region.cols[1]; } /** * Returns the column associated with this region, if there is one. */ export function getFocusedColumn(focusedRegion: FocusedRegion): number | undefined { switch (focusedRegion.type) { case FocusMode.CELL: return focusedRegion.col; case FocusMode.ROW: return undefined; } } /** * Returns a new focused region object in the given focus mode that includes a focusSelectionIndex property. */ export function toFocusedRegion( focusMode: FocusMode.CELL, cellCoords: CellCoordinates, focusSelectionIndex?: number, ): FocusedCell; export function toFocusedRegion( focusMode: FocusMode.ROW, cellCoords: CellCoordinates, focusSelectionIndex?: number, ): FocusedRow; export function toFocusedRegion( focusMode: FocusMode | undefined, cellCoords: CellCoordinates, focusSelectionIndex?: number, ): FocusedRegion | undefined; export function toFocusedRegion( focusMode: FocusMode | undefined, cellCoords: CellCoordinates, focusSelectionIndex: number = 0, ): FocusedRegion | undefined { switch (focusMode) { case FocusMode.CELL: return { type: FocusMode.CELL, ...cellCoords, focusSelectionIndex }; case FocusMode.ROW: return { focusSelectionIndex, row: cellCoords.row, type: FocusMode.ROW }; case undefined: return undefined; } } /** * Expands an existing region to new region based on the current focused region. * The focused region is an invariant and should not move as a result of this * operation. This function is used, for instance, to expand a selected region * on shift+click. */ export function expandFocusedRegion(focusedRegion: FocusedRegion, newRegion: Region) { switch (Regions.getRegionCardinality(newRegion)) { case RegionCardinality.FULL_COLUMNS: { const [indexStart, indexEnd] = getExpandedRegionIndices(focusedRegion, newRegion, "col", "cols"); return Regions.column(indexStart, indexEnd); } case RegionCardinality.FULL_ROWS: { const [indexStart, indexEnd] = getExpandedRegionIndices(focusedRegion, newRegion, "row", "rows"); return Regions.row(indexStart, indexEnd); } case RegionCardinality.CELLS: const [rowIndexStart, rowIndexEnd] = getExpandedRegionIndices(focusedRegion, newRegion, "row", "rows"); const [colIndexStart, colIndexEnd] = getExpandedRegionIndices(focusedRegion, newRegion, "col", "cols"); return Regions.cell(rowIndexStart, colIndexStart, rowIndexEnd, colIndexEnd); default: // i.e. `case RegionCardinality.FULL_TABLE:` return Regions.table(); } } function getExpandedRegionIndices( focusedRegion: FocusedRegion, newRegion: Region, focusedCellDimension: "row" | "col", regionDimension: "rows" | "cols", ) { const sourceIndex = focusedCellDimension === "row" ? focusedRegion.row : getFocusedColumn(focusedRegion) ?? 0; // THIS IS QUESTIONABLE AT BEST const [destinationIndex, destinationIndexEnd] = newRegion[regionDimension]!; if (destinationIndex !== destinationIndexEnd) { if (regionDimension === "rows") { throw new Error(Errors.TABLE_EXPAND_FOCUSED_REGION_MULTI_ROW_REGION); } else if (regionDimension === "cols") { throw new Error(Errors.TABLE_EXPAND_FOCUSED_REGION_MULTI_COLUMN_REGION); } } return sourceIndex <= destinationIndex ? [sourceIndex, destinationIndex] : [destinationIndex, sourceIndex]; } export function areFocusedRegionsEqual(left: FocusedRegion, right: FocusedRegion) { if (left.type === FocusMode.CELL && right.type === FocusMode.CELL) { return left.row === right.row && left.col === right.col; } else if (left.type === FocusMode.ROW && right.type === FocusMode.ROW) { return left.row === right.row; } else { return false; } }