@grafana/ui
Version:
Grafana Components Library
565 lines (560 loc) • 20.2 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var lodash = require('lodash');
var React = require('react');
var data = require('@grafana/data');
var constants = require('./constants.cjs');
var utils = require('./utils.cjs');
;
function useFilteredRows(rows, fields, hasNestedFrames) {
const [filter, setFilter] = React.useState({});
const filterResult = React.useMemo(
() => utils.applyFilter(rows, filter, fields, hasNestedFrames),
[rows, filter, fields, hasNestedFrames]
);
return { rows: filterResult.filteredRows, filter, setFilter, filterResult };
}
function useManagedSort({ sortByBehavior, setSortColumns, sortBy }) {
React.useEffect(() => {
if (sortByBehavior === "managed" && sortBy) {
setSortColumns(
sortBy.map(({ displayName, desc }) => ({
columnKey: displayName,
direction: desc === true ? "DESC" : "ASC"
}))
);
}
}, [setSortColumns, sortBy, sortByBehavior]);
}
function useSortedRows(rows, fields, nestedFields, { initialSortBy, hasNestedFrames }) {
const allFields = React.useMemo(() => [...fields, ...nestedFields], [fields, nestedFields]);
const initialSortColumns = React.useMemo(
() => {
var _a;
return (_a = initialSortBy == null ? void 0 : initialSortBy.flatMap(({ displayName, desc }) => {
if (!allFields.some((f) => utils.getDisplayName(f) === displayName)) {
return [];
}
return [
{
columnKey: displayName,
direction: desc ? "DESC" : "ASC"
}
];
})) != null ? _a : [];
},
[]
// eslint-disable-line react-hooks/exhaustive-deps
);
const [sortColumns, setSortColumns] = React.useState(initialSortColumns);
const columnTypes = React.useMemo(() => utils.getColumnTypes(fields), [fields]);
const sortedRows = React.useMemo(
() => utils.applySort(rows, fields, sortColumns, columnTypes, hasNestedFrames),
[rows, fields, sortColumns, columnTypes, hasNestedFrames]
);
return {
rows: sortedRows,
sortColumns,
setSortColumns
};
}
const PAGINATION_HEIGHT = 38;
function usePaginatedRows(rows, { height, width, headerHeight, footerHeight, rowHeight, enabled, hasNestedFrames }) {
const [page, setPage] = React.useState(0);
const numRows = React.useMemo(() => rows.filter((r) => r.__depth === 0).length, [rows]);
const avgRowHeight = React.useMemo(() => {
if (!enabled) {
return 0;
}
if (typeof rowHeight === "number") {
return rowHeight;
}
if (typeof rowHeight === "string") {
return constants.TABLE.MAX_CELL_HEIGHT;
}
let sum = 0;
let count = 0;
for (let i = 0; i < Math.min(100, rows.length); i++) {
const row = rows[i];
if (row.__depth > 0) {
continue;
}
sum += rowHeight(rows[i]);
count++;
}
return sum / count;
}, [rows, rowHeight, enabled]);
const smallPagination = React.useMemo(() => enabled && width < constants.TABLE.PAGINATION_LIMIT, [enabled, width]);
const { numPages, rowsPerPage, pageRangeStart, pageRangeEnd } = React.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]);
React.useLayoutEffect(() => {
if (!enabled) {
return;
}
if (page > numPages) {
setPage(numPages - 1);
}
}, [numPages, enabled, page, setPage]);
const paginatedRows = React.useMemo(() => {
if (!enabled) {
return rows;
}
const result = [];
const pageOffset = page * rowsPerPage;
let count = hasNestedFrames ? -1 * pageOffset : 0;
let i = hasNestedFrames ? 0 : pageOffset;
while (count <= rowsPerPage && i < rows.length) {
const currRow = rows[i];
i++;
if (currRow.__depth === 0) {
count++;
}
if (count >= 1 && count <= rowsPerPage) {
result.push(currRow);
}
}
return result;
}, [page, rowsPerPage, rows, enabled, hasNestedFrames]);
return {
rows: paginatedRows,
page: enabled ? page : -1,
numRows,
setPage,
numPages,
rowsPerPage,
pageRangeStart,
pageRangeEnd,
smallPagination
};
}
const useNestedRows = (rows, nestedData, hasNestedFrames, nestedFramesFieldName, filter, sortColumns, protoParserEnabled = false) => {
const frameToRecords = React.useMemo(() => {
if (!hasNestedFrames || !nestedFramesFieldName || !(nestedData == null ? void 0 : nestedData[0])) {
return;
}
return protoParserEnabled ? utils.compileFrameToRecordsV2(nestedData[0]) : utils.compileFrameToRecordsV1(nestedData[0], nestedFramesFieldName);
}, [hasNestedFrames, nestedFramesFieldName, nestedData, protoParserEnabled]);
return React.useMemo(() => {
const result = [];
if (!hasNestedFrames || !nestedFramesFieldName || !frameToRecords || !nestedData) {
return result;
}
for (const parentRow of rows) {
const nestedFrame = nestedData[parentRow.__index];
if (!nestedFrame) {
continue;
}
const rawRows = frameToRecords(nestedFrame, parentRow.__index);
const filterResult = utils.applyFilter(rawRows, filter, nestedFrame.fields, false, parentRow.__index);
const sortedRows = utils.applySort(
filterResult.filteredRows,
nestedFrame.fields,
sortColumns,
utils.getColumnTypes(nestedFrame.fields)
);
result[parentRow.__index] = { raw: rawRows, final: sortedRows, filterResult };
}
return result;
}, [hasNestedFrames, nestedFramesFieldName, rows, sortColumns, filter, frameToRecords, nestedData]);
};
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 = React.useMemo(() => utils.buildHeaderHeightMeasurers(fields, typographyCtx), [fields, typographyCtx]);
const columnAvailableWidths = React.useMemo(
() => columnWidths.map((c, idx) => {
var _a, _b;
if (idx >= fields.length) {
return 0;
}
let width = c - 2 * constants.TABLE.CELL_PADDING - constants.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 === utils.getDisplayName(field))) {
width -= perIconSpace;
}
if (showTypeIcons) {
width -= perIconSpace;
}
return Math.floor(width) - 1;
}),
[fields, columnWidths, sortColumns, showTypeIcons, perIconSpace]
);
const headerHeight = React.useMemo(() => {
if (!enabled) {
return 0;
}
return utils.getRowHeight(
fields,
{ __index: -1, __depth: 0 },
columnAvailableWidths,
constants.TABLE.HEADER_HEIGHT,
measurers,
constants.TABLE.LINE_HEIGHT,
constants.TABLE.CELL_PADDING
);
}, [fields, enabled, columnAvailableWidths, measurers]);
return headerHeight;
}
const getTrueColWidths = (cw) => cw.map((c) => c - (2 * constants.TABLE.CELL_PADDING + constants.TABLE.BORDER_RIGHT));
function useRowHeight({
columnWidths,
fields,
defaultHeight,
defaultNestedHeight,
typographyCtx,
maxHeight,
hasNestedFrames,
nestedData,
nestedRows,
nestedFields,
nestedColWidths,
visibleNestedRowCounts,
nestedFooterHeight = 0
}) {
var _a;
const nestedMeasurers = React.useMemo(
() => utils.buildCellHeightMeasurers(nestedFields, typographyCtx, maxHeight),
[nestedFields, typographyCtx, maxHeight]
);
const totalParentWidth = React.useMemo(() => columnWidths.reduce((acc, width) => acc + width, 0), [columnWidths]);
const totalNestedWidth = React.useMemo(() => nestedColWidths.reduce((acc, width) => acc + width, 0), [nestedColWidths]);
const nestedHasOverflow = React.useMemo(() => totalParentWidth < totalNestedWidth, [totalParentWidth, totalNestedWidth]);
const getNestedRowHeightWithCache = React.useMemo(() => {
var _a2;
if (typeof defaultNestedHeight === "string") {
return () => 0;
}
if (((_a2 = nestedMeasurers == null ? void 0 : nestedMeasurers.length) != null ? _a2 : 0) === 0) {
return () => defaultNestedHeight;
}
const nestedRowCache = visibleNestedRowCounts.map(
(count) => count == null ? void 0 : Array(count)
);
return (row) => {
if (row.__parentIndex == null) {
return 0;
}
const nestedRowCacheEntry = nestedRowCache[row.__parentIndex];
if (nestedRowCacheEntry == null) {
return 0;
}
const trueNestedColWidths = getTrueColWidths(nestedColWidths);
let result = nestedRowCacheEntry[row.__index];
if (result == null) {
result = nestedRowCacheEntry[row.__index] = utils.getRowHeight(
nestedFields,
row,
trueNestedColWidths,
defaultNestedHeight,
nestedMeasurers
);
}
return result;
};
}, [nestedFields, nestedColWidths, defaultNestedHeight, nestedMeasurers, visibleNestedRowCounts]);
const measurers = React.useMemo(
() => utils.buildCellHeightMeasurers(fields, typographyCtx, maxHeight),
[fields, typographyCtx, maxHeight]
);
const hasWrappedCols = ((_a = measurers == null ? void 0 : measurers.length) != null ? _a : 0) > 0;
const getRowHeightWithCache = React.useMemo(() => {
if (typeof defaultHeight === "string") {
return () => 0;
}
if (!hasWrappedCols) {
return () => defaultHeight;
}
const trueColWidths = getTrueColWidths(columnWidths);
const cache = Array(fields[0].values.length);
return (row) => {
let result = cache[row.__index];
if (result == null) {
result = cache[row.__index] = utils.getRowHeight(fields, row, trueColWidths, defaultHeight, measurers);
}
return result;
};
}, [fields, columnWidths, defaultHeight, measurers, hasWrappedCols]);
const rowHeight = React.useMemo(() => {
if (typeof defaultHeight === "string" || !(hasWrappedCols || hasNestedFrames)) {
return defaultHeight;
}
if (typeof defaultNestedHeight === "string") {
return defaultNestedHeight;
}
return (row) => {
var _a2, _b, _c;
if (row.__depth > 0) {
const visibleNestedRowCount = visibleNestedRowCounts[row.__index];
if (visibleNestedRowCount == null) {
return 0;
}
if (visibleNestedRowCount === 0) {
return constants.TABLE.NESTED_NO_DATA_HEIGHT + constants.TABLE.CELL_PADDING * 2 + nestedFooterHeight;
}
const nestedHeaderHeight = ((_c = (_b = (_a2 = nestedData == null ? void 0 : nestedData[row.__index]) == null ? void 0 : _a2.meta) == null ? void 0 : _b.custom) == null ? void 0 : _c.noHeader) ? 0 : defaultNestedHeight;
const nestedRowsHeight = nestedRows[row.__index].final.reduce(
(acc, row2) => acc + getNestedRowHeightWithCache(row2),
0
);
const scrollbarHeight = nestedHasOverflow ? constants.TABLE.SCROLLBAR_AFFORDANCE : 0;
return nestedRowsHeight + nestedHeaderHeight + nestedFooterHeight + constants.TABLE.CELL_PADDING * 2 + scrollbarHeight;
}
return row.__parentIndex != null ? getNestedRowHeightWithCache(row) : getRowHeightWithCache(row);
};
}, [
getNestedRowHeightWithCache,
getRowHeightWithCache,
defaultHeight,
defaultNestedHeight,
hasNestedFrames,
hasWrappedCols,
nestedFooterHeight,
nestedHasOverflow,
nestedRows,
nestedData,
visibleNestedRowCounts
]);
return rowHeight;
}
const INITIAL_COL_RESIZE_STATE = Object.freeze({ columnKey: void 0, width: 0 });
function useColumnResize(onColumnResize = () => {
}, fieldScope) {
const colResizeState = React.useRef({ ...INITIAL_COL_RESIZE_STATE });
const pointerIsDown = React.useRef(false);
React.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 = React.useCallback(() => {
if (colResizeState.current.columnKey) {
onColumnResize(
colResizeState.current.columnKey,
Math.floor(colResizeState.current.width),
colResizeState.current.fieldScope
);
colResizeState.current = { ...INITIAL_COL_RESIZE_STATE };
}
window.removeEventListener("click", dispatchEvent, { capture: true });
}, [onColumnResize]);
const dataGridResizeHandler = React.useCallback(
(column, width) => {
if (!colResizeState.current.columnKey) {
window.addEventListener("click", dispatchEvent, { capture: true });
}
colResizeState.current.columnKey = column.key;
colResizeState.current.width = width;
if (fieldScope) {
colResizeState.current.fieldScope = fieldScope;
}
if (!pointerIsDown.current) {
dispatchEvent();
}
},
[fieldScope, dispatchEvent]
);
return dataGridResizeHandler;
}
function useScrollbarWidth(ref, height) {
const [scrollbarWidth, setScrollbarWidth] = React.useState(0);
const updateScrollbarDimensions = lodash.debounce(() => {
var _a;
const el = (_a = ref.current) == null ? void 0 : _a.element;
if (el) {
setScrollbarWidth(el.offsetWidth - el.clientWidth);
}
}, 150);
React.useLayoutEffect(() => {
var _a;
const el = (_a = ref.current) == null ? void 0 : _a.element;
if (!el || utils.IS_SAFARI_26) {
return;
}
updateScrollbarDimensions();
const resizeObserver = new ResizeObserver(updateScrollbarDimensions);
resizeObserver.observe(el);
return () => {
resizeObserver.disconnect();
};
}, [ref, height, updateScrollbarDimensions]);
return scrollbarWidth;
}
function useNestedColWidths({
nestedVisibleFields,
availableWidth,
structureRev
}) {
const configuredWidths = React.useMemo(
() => utils.computeColWidths(nestedVisibleFields, availableWidth),
[nestedVisibleFields, availableWidth]
);
const [nestedFieldWidths, setNestedFieldWidths] = React.useState(() => configuredWidths);
React.useEffect(() => {
const newWidths = utils.computeColWidths(nestedVisibleFields, availableWidth);
let hasChanges = false;
if (nestedFieldWidths.length !== newWidths.length) {
hasChanges = true;
}
for (let i = 0; i < newWidths.length; i++) {
if (nestedFieldWidths[i] !== newWidths[i]) {
hasChanges = true;
break;
}
}
if (hasChanges) {
setNestedFieldWidths(newWidths);
}
}, [structureRev]);
const nestedColWidths = React.useMemo(
() => utils.buildNestedColumnWidthsMap(nestedVisibleFields, nestedFieldWidths),
[nestedVisibleFields, nestedFieldWidths]
);
const handleNestedColumnWidthsChange = React.useCallback(
(newColWidths) => {
setNestedFieldWidths(
nestedVisibleFields.map((f, idx) => {
const entry = newColWidths.get(utils.getDisplayName(f));
return entry != null ? entry.width : nestedFieldWidths[idx];
})
);
},
[nestedVisibleFields, nestedFieldWidths]
);
return { nestedFieldWidths, nestedColWidths, handleNestedColumnWidthsChange };
}
function useColWidths(visibleFields, availableWidth, frozenColumns, resetKey) {
const widths = React.useMemo(
() => utils.computeColWidths(visibleFields, availableWidth),
// Width override removals can mutate width config onto existing field objects.
// eslint-disable-next-line react-hooks/exhaustive-deps
[visibleFields, availableWidth, resetKey]
);
const numFrozenColsFullyInView = React.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 data.ReducerID;
const nonMathReducers = /* @__PURE__ */ new Set([
data.ReducerID.allValues,
data.ReducerID.changeCount,
data.ReducerID.count,
data.ReducerID.countAll,
data.ReducerID.distinctCount,
data.ReducerID.first,
data.ReducerID.firstNotNull,
data.ReducerID.last,
data.ReducerID.lastNotNull,
data.ReducerID.uniqueValues
]);
const isNonMathReducer = (reducer) => isReducer(reducer) && nonMathReducers.has(reducer);
const noFormattingReducers = /* @__PURE__ */ new Set([data.ReducerID.count, data.ReducerID.countAll]);
const shouldReducerSkipFormatting = (reducer) => isReducer(reducer) && noFormattingReducers.has(reducer);
const useReducerEntries = (field, rows, displayName, colIdx) => {
return React.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 !== data.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 = data.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 !== data.FieldType.number && !isNonMathReducer(reducerId) || // for countAll, only show the reducer in the first column
reducerId === data.ReducerID.countAll && colIdx !== 0) {
return [reducerId, null];
}
const value = results[reducerId];
let result = null;
if (!shouldReducerSkipFormatting(reducerId) && field.display) {
result = data.formattedValueToString(field.display(value));
} else if (value != null) {
result = String(value);
}
return [reducerId, result];
});
}, [field, rows, displayName, colIdx]);
};
exports.useColWidths = useColWidths;
exports.useColumnResize = useColumnResize;
exports.useFilteredRows = useFilteredRows;
exports.useHeaderHeight = useHeaderHeight;
exports.useManagedSort = useManagedSort;
exports.useNestedColWidths = useNestedColWidths;
exports.useNestedRows = useNestedRows;
exports.usePaginatedRows = usePaginatedRows;
exports.useReducerEntries = useReducerEntries;
exports.useRowHeight = useRowHeight;
exports.useScrollbarWidth = useScrollbarWidth;
exports.useSortedRows = useSortedRows;
//# sourceMappingURL=hooks.cjs.map