UNPKG

@grafana/ui

Version:
389 lines (386 loc) • 13.8 kB
import { useState, useMemo, useLayoutEffect, useRef, useCallback, useEffect } from 'react'; import { formattedValueToString, compareArrayValues, ReducerID, FieldType, reduceField } from '@grafana/data'; import { TABLE } from './constants.mjs'; import { getDisplayName, processNestedTableRows, getColumnTypes, applySort, buildHeaderHeightMeasurers, getRowHeight, buildCellHeightMeasurers, IS_SAFARI_26, computeColWidths } from './utils.mjs'; "use strict"; const getDisplayedValue = (row, key, fields) => { const field = fields.find((field2) => getDisplayName(field2) === key); if (!field || !field.display) { return ""; } const displayedValue = formattedValueToString(field.display(row[key])); return displayedValue; }; function useFilteredRows(rows, fields, { hasNestedFrames }) { const [filter, setFilter] = useState({}); const filterValues = useMemo(() => Object.entries(filter), [filter]); const crossFilterOrder = useMemo( () => Array.from(new Set(filterValues.map(([key]) => key))), [filterValues] ); const [filteredRows, crossFilterRows] = useMemo(() => { const crossFilterRows2 = {}; const filterRows = (row) => { var _a; for (const [key, value] of filterValues) { const displayedValue = getDisplayedValue(row, key, fields); if (!value.filteredSet.has(displayedValue)) { return false; } crossFilterRows2[key] = (_a = crossFilterRows2[key]) != null ? _a : []; crossFilterRows2[key].push(row); } return true; }; const filteredRows2 = hasNestedFrames ? processNestedTableRows(rows, (parents) => parents.filter(filterRows)) : rows.filter(filterRows); return [filteredRows2, crossFilterRows2]; }, [filterValues, rows, fields, hasNestedFrames]); return { rows: filteredRows, filter, setFilter, crossFilterOrder, crossFilterRows }; } function useSortedRows(rows, fields, { initialSortBy, hasNestedFrames }) { const initialSortColumns = useMemo( () => { var _a; return (_a = initialSortBy == null ? void 0 : initialSortBy.flatMap(({ displayName, desc }) => { if (!fields.some((f) => getDisplayName(f) === displayName)) { return []; } return [ { columnKey: displayName, direction: desc ? "DESC" : "ASC" } ]; })) != null ? _a : []; }, [] // eslint-disable-line react-hooks/exhaustive-deps ); const [sortColumns, setSortColumns] = useState(initialSortColumns); const columnTypes = useMemo(() => getColumnTypes(fields), [fields]); const sortedRows = useMemo( () => applySort(rows, fields, sortColumns, columnTypes, hasNestedFrames), [rows, fields, sortColumns, hasNestedFrames, columnTypes] ); return { rows: sortedRows, sortColumns, setSortColumns }; } const PAGINATION_HEIGHT = 38; function usePaginatedRows(rows, { height, width, headerHeight, footerHeight, rowHeight, enabled }) { const [page, setPage] = useState(0); const numRows = rows.length; const avgRowHeight = useMemo(() => { if (!enabled) { return 0; } if (typeof rowHeight === "number") { return rowHeight; } if (typeof rowHeight === "string") { return TABLE.MAX_CELL_HEIGHT; } return rows.slice(0, 100).reduce((avg, row, _, { length }) => avg + rowHeight(row) / length, 0); }, [rows, rowHeight, enabled]); const smallPagination = useMemo(() => enabled && width < TABLE.PAGINATION_LIMIT, [enabled, width]); const { numPages, rowsPerPage, pageRangeStart, pageRangeEnd } = useMemo(() => { if (!enabled) { return { numPages: 0, rowsPerPage: 0, pageRangeStart: 1, pageRangeEnd: numRows }; } const rowAreaHeight = height - headerHeight - footerHeight - PAGINATION_HEIGHT; const heightPerRow = Math.floor(rowAreaHeight / (avgRowHeight || 1)); let rowsPerPage2 = heightPerRow > 1 ? heightPerRow : 1; const pageRangeStart2 = page * rowsPerPage2 + 1; let pageRangeEnd2 = pageRangeStart2 + rowsPerPage2 - 1; if (pageRangeEnd2 > numRows) { pageRangeEnd2 = numRows; } const numPages2 = Math.ceil(numRows / rowsPerPage2); return { numPages: numPages2, rowsPerPage: rowsPerPage2, pageRangeStart: pageRangeStart2, pageRangeEnd: pageRangeEnd2 }; }, [height, headerHeight, footerHeight, avgRowHeight, enabled, numRows, page]); useLayoutEffect(() => { if (!enabled) { return; } if (page > numPages) { setPage(numPages - 1); } }, [numPages, enabled, page, setPage]); const paginatedRows = useMemo(() => { if (!enabled) { return rows; } const pageOffset = page * rowsPerPage; return rows.slice(pageOffset, pageOffset + rowsPerPage); }, [page, rowsPerPage, rows, enabled]); return { rows: paginatedRows, page: enabled ? page : -1, setPage, numPages, rowsPerPage, pageRangeStart, pageRangeEnd, smallPagination }; } const ICON_WIDTH = 16; const ICON_GAP = 4; function useHeaderHeight({ fields, enabled, columnWidths, sortColumns, typographyCtx, showTypeIcons = false }) { const perIconSpace = ICON_WIDTH + ICON_GAP; const measurers = useMemo(() => buildHeaderHeightMeasurers(fields, typographyCtx), [fields, typographyCtx]); const columnAvailableWidths = useMemo( () => columnWidths.map((c, idx) => { var _a, _b; if (idx >= fields.length) { return 0; } let width = c - 2 * TABLE.CELL_PADDING - TABLE.BORDER_RIGHT; const field = fields[idx]; if ((_b = (_a = field.config) == null ? void 0 : _a.custom) == null ? void 0 : _b.filterable) { width -= perIconSpace; } if (sortColumns.some((col) => col.columnKey === getDisplayName(field))) { width -= perIconSpace; } if (showTypeIcons) { width -= perIconSpace; } return Math.floor(width) - 1; }), [fields, columnWidths, sortColumns, showTypeIcons, perIconSpace] ); const headerHeight = useMemo(() => { if (!enabled) { return 0; } return getRowHeight( fields, -1, columnAvailableWidths, TABLE.HEADER_HEIGHT, measurers, TABLE.LINE_HEIGHT, TABLE.CELL_PADDING ); }, [fields, enabled, columnAvailableWidths, measurers]); return headerHeight; } function useRowHeight({ columnWidths, fields, hasNestedFrames, defaultHeight, expandedRows, typographyCtx, maxHeight }) { const measurers = useMemo( () => buildCellHeightMeasurers(fields, typographyCtx, maxHeight), [fields, typographyCtx, maxHeight] ); const hasWrappedCols = useMemo(() => { var _a; return (_a = measurers == null ? void 0 : measurers.length) != null ? _a : 0 > 0; }, [measurers]); const colWidths = useMemo(() => { const columnWidthAffordance = 2 * TABLE.CELL_PADDING + TABLE.BORDER_RIGHT; return columnWidths.map((c) => c - columnWidthAffordance); }, [columnWidths]); const rowHeight = useMemo(() => { if (!hasNestedFrames && !hasWrappedCols || typeof defaultHeight === "string") { return defaultHeight; } const cache = Array(fields[0].values.length); return (row) => { var _a, _b, _c, _d, _e; if (row.__depth > 0) { if (!expandedRows.has(row.__index)) { return 0; } const rowCount = (_b = (_a = row.data) == null ? void 0 : _a.length) != null ? _b : 0; if (rowCount === 0) { return TABLE.NESTED_NO_DATA_HEIGHT + TABLE.CELL_PADDING * 2; } const nestedHeaderHeight = ((_e = (_d = (_c = row.data) == null ? void 0 : _c.meta) == null ? void 0 : _d.custom) == null ? void 0 : _e.noHeader) ? 0 : defaultHeight; return defaultHeight * rowCount + nestedHeaderHeight + TABLE.CELL_PADDING * 2; } let result = cache[row.__index]; if (!result) { result = cache[row.__index] = getRowHeight(fields, row.__index, colWidths, defaultHeight, measurers); } return result; }; }, [hasNestedFrames, hasWrappedCols, defaultHeight, fields, colWidths, measurers, expandedRows]); return rowHeight; } const INITIAL_COL_RESIZE_STATE = Object.freeze({ columnKey: void 0, width: 0 }); function useColumnResize(onColumnResize = () => { }) { const colResizeState = useRef({ ...INITIAL_COL_RESIZE_STATE }); const pointerIsDown = useRef(false); useLayoutEffect(() => { function pointerDown(_event) { pointerIsDown.current = true; } function pointerUp(_event) { pointerIsDown.current = false; } window.addEventListener("pointerdown", pointerDown); window.addEventListener("pointerup", pointerUp); return () => { window.removeEventListener("pointerdown", pointerDown); window.removeEventListener("pointerup", pointerUp); }; }); const dispatchEvent = useCallback(() => { if (colResizeState.current.columnKey) { onColumnResize(colResizeState.current.columnKey, Math.floor(colResizeState.current.width)); colResizeState.current = { ...INITIAL_COL_RESIZE_STATE }; } window.removeEventListener("click", dispatchEvent, { capture: true }); }, [onColumnResize]); const dataGridResizeHandler = useCallback( (column, width) => { if (!colResizeState.current.columnKey) { window.addEventListener("click", dispatchEvent, { capture: true }); } colResizeState.current.columnKey = column.key; colResizeState.current.width = width; if (!pointerIsDown.current) { dispatchEvent(); } }, [dispatchEvent] ); return dataGridResizeHandler; } function useScrollbarWidth(ref, height) { const [scrollbarWidth, setScrollbarWidth] = useState(0); useLayoutEffect(() => { var _a; const el = (_a = ref.current) == null ? void 0 : _a.element; if (!el || IS_SAFARI_26) { return; } const updateScrollbarDimensions = () => { setScrollbarWidth(el.offsetWidth - el.clientWidth); }; updateScrollbarDimensions(); const resizeObserver = new ResizeObserver(updateScrollbarDimensions); resizeObserver.observe(el); return () => { resizeObserver.disconnect(); }; }, [ref, height]); return scrollbarWidth; } const numIsEqual = (a, b) => a === b; function useColWidths(visibleFields, availableWidth, frozenColumns) { const [widths, setWidths] = useState(computeColWidths(visibleFields, availableWidth)); useEffect(() => { const newWidths = computeColWidths(visibleFields, availableWidth); if (!compareArrayValues(widths, newWidths, numIsEqual)) { setWidths(newWidths); } }, [availableWidth, widths, visibleFields]); const numFrozenColsFullyInView = useMemo(() => { if (!frozenColumns || frozenColumns <= 0) { return -1; } const fullyVisibleCols = widths.reduce( ([count, remainingWidth], nextWidth) => { if (remainingWidth - nextWidth >= 0) { return [count + 1, remainingWidth - nextWidth]; } return [count, 0]; }, [0, availableWidth] )[0]; return Math.min(fullyVisibleCols, frozenColumns); }, [widths, availableWidth, frozenColumns]); return [widths, numFrozenColsFullyInView]; } const isReducer = (maybeReducer) => maybeReducer in ReducerID; const nonMathReducers = /* @__PURE__ */ new Set([ ReducerID.allValues, ReducerID.changeCount, ReducerID.count, ReducerID.countAll, ReducerID.distinctCount, ReducerID.first, ReducerID.firstNotNull, ReducerID.last, ReducerID.lastNotNull, ReducerID.uniqueValues ]); const isNonMathReducer = (reducer) => isReducer(reducer) && nonMathReducers.has(reducer); const noFormattingReducers = /* @__PURE__ */ new Set([ReducerID.count, ReducerID.countAll]); const shouldReducerSkipFormatting = (reducer) => isReducer(reducer) && noFormattingReducers.has(reducer); const useReducerEntries = (field, rows, displayName, colIdx) => { return useMemo(() => { var _a, _b, _c; const reducers = (_c = (_b = (_a = field.config.custom) == null ? void 0 : _a.footer) == null ? void 0 : _b.reducers) != null ? _c : []; if (reducers.length === 0 || field.type !== FieldType.number && reducers.every((reducerId) => !isNonMathReducer(reducerId))) { return []; } const newState = { lastProcessedRowCount: 0, ...field.state || {} // Preserve any existing state properties }; field.state = newState; const currentRowCount = rows.length; const lastRowCount = newState.lastProcessedRowCount; if (lastRowCount !== currentRowCount) { if (newState.calcs) { delete newState.calcs; } newState.lastProcessedRowCount = currentRowCount; } const results = reduceField({ field: { ...field, values: rows.map((row) => row[displayName]) }, reducers }); return reducers.map((reducerId) => { if (results[reducerId] === void 0 || // For non-number fields, only show special count reducers field.type !== FieldType.number && !isNonMathReducer(reducerId) || // for countAll, only show the reducer in the first column reducerId === ReducerID.countAll && colIdx !== 0) { return [reducerId, null]; } const value = results[reducerId]; let result = null; if (!shouldReducerSkipFormatting(reducerId) && field.display) { result = formattedValueToString(field.display(value)); } else if (value != null) { result = String(value); } return [reducerId, result]; }); }, [field, rows, displayName, colIdx]); }; export { useColWidths, useColumnResize, useFilteredRows, useHeaderHeight, usePaginatedRows, useReducerEntries, useRowHeight, useScrollbarWidth, useSortedRows }; //# sourceMappingURL=hooks.mjs.map