From 134228dc0cbcd473adb3e545646dd84cc8f790ff Mon Sep 17 00:00:00 2001 From: novykh Date: Thu, 25 Jun 2026 11:05:55 +0300 Subject: [PATCH 1/3] Add auto resize column. --- src/components/table/body/columnFlex.js | 5 ++ src/components/table/body/header/cell.js | 9 +-- .../table/body/header/resizeHandler.js | 75 +++++++++++++++++-- src/components/table/body/row.js | 13 ++-- .../actions/columnVisibility/columnsMenu.js | 5 +- src/components/table/table.js | 1 + 6 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 src/components/table/body/columnFlex.js diff --git a/src/components/table/body/columnFlex.js b/src/components/table/body/columnFlex.js new file mode 100644 index 000000000..5034d27be --- /dev/null +++ b/src/components/table/body/columnFlex.js @@ -0,0 +1,5 @@ +export default (column, header, hasExplicitSize) => + (column.columnDef.notFlex || column.getCanResize()) && + (!column.columnDef.fullWidth || hasExplicitSize) + ? false + : header.colSpan diff --git a/src/components/table/body/header/cell.js b/src/components/table/body/header/cell.js index 3f83b32ff..cd5062232 100644 --- a/src/components/table/body/header/cell.js +++ b/src/components/table/body/header/cell.js @@ -5,6 +5,7 @@ import Flex from "@/components/templates/flex" import { Text } from "@/components/typography" import { useTableState } from "../../provider" import ResizeHandler from "./resizeHandler" +import getColumnFlex from "../columnFlex" import Sorting, { SortIconContainer } from "./sorting" import Info from "./info" import Filter from "./filter" @@ -73,11 +74,7 @@ const BodyHeaderCell = ({ s + h.column.getSize(), 0) : column.getSize()}px`} height={{ min: "45px" }} position="relative" @@ -125,7 +122,7 @@ const BodyHeaderCell = ({ - + {children} ) diff --git a/src/components/table/body/header/resizeHandler.js b/src/components/table/body/header/resizeHandler.js index fca349170..ac4fc12b1 100644 --- a/src/components/table/body/header/resizeHandler.js +++ b/src/components/table/body/header/resizeHandler.js @@ -1,10 +1,15 @@ -import React from "react" +import React, { useRef } from "react" +import { flushSync } from "react-dom" import styled from "styled-components" import Flex from "@/components/templates/flex" import { useTableState } from "../../provider" const rerenderSelector = state => state.columnSizingInfo?.deltaPercentage +const AUTO_FIT_PADDING = 8 + +const AUTO_FIT_MAX_RATIO = 0.5 + export const Handler = styled(Flex).attrs({ position: "absolute", top: "2px", @@ -12,16 +17,75 @@ export const Handler = styled(Flex).attrs({ bottom: "2px", })`` -const ResizeHandler = ({ header, table }) => { +const measureNaturalWidth = (nodes, forceNowrap) => { + const probe = document.createElement("div") + probe.style.cssText = + "position:absolute;visibility:hidden;left:-9999px;top:0;width:auto;white-space:nowrap;" + document.body.appendChild(probe) + + let max = 0 + nodes.forEach(node => { + const clone = node.cloneNode(true) + clone.style.width = "auto" + clone.style.maxWidth = "none" + if (forceNowrap) { + clone.style.whiteSpace = "nowrap" + clone.style.flexWrap = "nowrap" + clone.querySelectorAll("*").forEach(el => { + el.style.whiteSpace = "nowrap" + el.style.flexWrap = "nowrap" + }) + } + probe.appendChild(clone) + max = Math.max(max, clone.scrollWidth) + probe.removeChild(clone) + }) + + document.body.removeChild(probe) + return max +} + +const ResizeHandler = ({ header, table, testPrefix = "" }) => { useTableState(rerenderSelector) + const ref = useRef() if (!header.column.getCanResize()) return null - const resizingProps = header.column.getIsResizing() + const { column } = header + + const resizingProps = column.getIsResizing() ? { transform: `translateX(${table.getState().columnSizingInfo.deltaOffset}px)` } : {} + const handleResizeStart = e => { + if (column.columnDef.fullWidth && table.getState().columnSizing?.[column.id] == null) { + const width = ref.current?.parentElement?.getBoundingClientRect().width + if (width) flushSync(() => table.setColumnSizing(old => ({ ...old, [column.id]: width }))) + } + header.getResizeHandler()(e) + } + + const handleAutoFit = () => { + const headerCell = ref.current?.parentElement + if (!headerCell) return + + const container = headerCell.closest(`[data-testid="netdata-table${testPrefix}"]`) + const cells = container + ? [...container.querySelectorAll('[data-testid^="netdata-table-cell-"]')].filter( + el => el.getAttribute("data-testid") === `netdata-table-cell-${column.id}${testPrefix}` + ) + : [] + + const max = measureNaturalWidth([headerCell, ...cells], !column.columnDef.fullWidth) + if (!max) return + + const limit = container ? container.clientWidth * AUTO_FIT_MAX_RATIO : Infinity + const width = Math.min(Math.ceil(max) + AUTO_FIT_PADDING, limit) + table.setColumnSizing(old => ({ ...old, [column.id]: width })) + } + return ( { cursor: "col-resize", ...resizingProps, }} - onMouseDown={header.getResizeHandler()} - onTouchStart={header.getResizeHandler()} + onMouseDown={handleResizeStart} + onTouchStart={handleResizeStart} + onDoubleClick={handleAutoFit} /> ) } diff --git a/src/components/table/body/row.js b/src/components/table/body/row.js index 9b406cac7..cf63849c3 100644 --- a/src/components/table/body/row.js +++ b/src/components/table/body/row.js @@ -3,8 +3,9 @@ import styled from "styled-components" import Flex from "@/components/templates/flex" import { getColor } from "@/theme" import { useTableState } from "../provider" +import getColumnFlex from "./columnFlex" -const CellGroup = ({ cell, row, header, testPrefix, coloredSortedColumn }) => { +const CellGroup = ({ cell, row, table, header, testPrefix, coloredSortedColumn }) => { const { column } = cell const tableMeta = useMemo( @@ -32,11 +33,7 @@ const CellGroup = ({ cell, row, header, testPrefix, coloredSortedColumn }) => { return ( ({ columnVisibility: state.columnVisibility, selectedRows: state.selectedRows, allColumns: state.allColumns?.length, + visibleColumns: state.visibleColumns, }) const StyledRow = styled(Flex)` @@ -147,6 +145,7 @@ export default memo( { { rowsById: table.getRowModel().rowsById, selectedRows: table.getSelectedRowModel().flatRows, allColumns: table?.getAllLeafColumns?.(), + visibleColumns: table?.getVisibleLeafColumns?.().map(column => column.id), }) }, [table.getState()]) From 2c59c52f8bd2a5d52e7b35c69226d0872a42ebf2 Mon Sep 17 00:00:00 2001 From: novykh Date: Tue, 30 Jun 2026 17:17:04 +0300 Subject: [PATCH 2/3] Add autosizer. --- .../table/body/header/resizeHandler.js | 67 +++++++++++++------ 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/src/components/table/body/header/resizeHandler.js b/src/components/table/body/header/resizeHandler.js index ac4fc12b1..a52243b85 100644 --- a/src/components/table/body/header/resizeHandler.js +++ b/src/components/table/body/header/resizeHandler.js @@ -10,6 +10,17 @@ const AUTO_FIT_PADDING = 8 const AUTO_FIT_MAX_RATIO = 0.5 +const naturalWidthStyles = { + width: "max-content", + minWidth: "max-content", + maxWidth: "none", + whiteSpace: "nowrap", + overflow: "visible", + textOverflow: "clip", + flexWrap: "nowrap", + flexShrink: "0", +} + export const Handler = styled(Flex).attrs({ position: "absolute", top: "2px", @@ -19,29 +30,47 @@ export const Handler = styled(Flex).attrs({ const measureNaturalWidth = (nodes, forceNowrap) => { const probe = document.createElement("div") - probe.style.cssText = - "position:absolute;visibility:hidden;left:-9999px;top:0;width:auto;white-space:nowrap;" + probe.style.cssText = [ + "position:absolute", + "visibility:hidden", + "pointer-events:none", + "left:-9999px", + "top:0", + "width:max-content", + "min-width:max-content", + "max-width:none", + "white-space:nowrap", + ].join(";") document.body.appendChild(probe) let max = 0 - nodes.forEach(node => { - const clone = node.cloneNode(true) - clone.style.width = "auto" - clone.style.maxWidth = "none" - if (forceNowrap) { - clone.style.whiteSpace = "nowrap" - clone.style.flexWrap = "nowrap" - clone.querySelectorAll("*").forEach(el => { - el.style.whiteSpace = "nowrap" - el.style.flexWrap = "nowrap" - }) - } - probe.appendChild(clone) - max = Math.max(max, clone.scrollWidth) - probe.removeChild(clone) - }) + try { + nodes.forEach(node => { + const clone = node.cloneNode(true) + clone.style.width = "max-content" + clone.style.minWidth = "max-content" + clone.style.maxWidth = "none" + + const cs = getComputedStyle(node) + clone.style.fontFamily = cs.fontFamily + clone.style.fontSize = cs.fontSize + clone.style.fontWeight = cs.fontWeight + clone.style.fontStyle = cs.fontStyle + clone.style.letterSpacing = cs.letterSpacing + + if (forceNowrap) { + clone.querySelectorAll("*").forEach(el => Object.assign(el.style, naturalWidthStyles)) + Object.assign(clone.style, naturalWidthStyles) + } + + probe.appendChild(clone) + max = Math.max(max, clone.getBoundingClientRect().width, clone.scrollWidth) + probe.removeChild(clone) + }) + } finally { + document.body.removeChild(probe) + } - document.body.removeChild(probe) return max } From 4d6a62d26973027837860bc0055b604ae801b6c9 Mon Sep 17 00:00:00 2001 From: novykh Date: Tue, 30 Jun 2026 17:17:58 +0300 Subject: [PATCH 3/3] v5.4.17 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40c443583..68e3d6ee9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@netdata/netdata-ui", - "version": "5.4.16", + "version": "5.4.17", "description": "netdata UI kit", "main": "dist/index.js", "module": "dist/es6/index.js",