@ceed/cds 1.27.1 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2091,6 +2091,36 @@ var CurrencyInput_default = CurrencyInput;
2091
2091
  var import_react28 = __toESM(require("react"));
2092
2092
  var import_react_virtual2 = require("@tanstack/react-virtual");
2093
2093
 
2094
+ // src/libs/text-measurer.ts
2095
+ var TextMeasurer = class {
2096
+ constructor(font) {
2097
+ const canvas = document.createElement("canvas");
2098
+ this.ctx = canvas.getContext("2d");
2099
+ if (this.ctx && font) this.ctx.font = font;
2100
+ }
2101
+ setFont(font) {
2102
+ if (this.ctx) this.ctx.font = font;
2103
+ return this;
2104
+ }
2105
+ setFontFromElement(el) {
2106
+ if (this.ctx) this.ctx.font = getComputedStyle(el).font;
2107
+ return this;
2108
+ }
2109
+ measureText(text) {
2110
+ if (!this.ctx) return 0;
2111
+ return this.ctx.measureText(text).width;
2112
+ }
2113
+ measureMaxWidth(values) {
2114
+ if (!this.ctx) return 0;
2115
+ let max = 0;
2116
+ for (let i = 0; i < values.length; i++) {
2117
+ const w = this.ctx.measureText(values[i]).width;
2118
+ if (w > max) max = w;
2119
+ }
2120
+ return max;
2121
+ }
2122
+ };
2123
+
2094
2124
  // src/components/DataTable/utils.ts
2095
2125
  function extractFieldsFromGroupingModel(items) {
2096
2126
  const fields = /* @__PURE__ */ new Set();
@@ -2212,6 +2242,71 @@ function getTextAlign(props) {
2212
2242
  return !props.editMode && ["number", "date", "currency"].includes(props.type || "") ? "end" : "start";
2213
2243
  }
2214
2244
  var numberFormatter = (value) => "Intl" in window ? new Intl.NumberFormat().format(value) : value;
2245
+ function computeHeaderWidth(headerEl) {
2246
+ const thStyle = getComputedStyle(headerEl);
2247
+ const paddingX = parseFloat(thStyle.paddingLeft) + parseFloat(thStyle.paddingRight);
2248
+ const borderX = parseFloat(thStyle.borderLeftWidth) + parseFloat(thStyle.borderRightWidth);
2249
+ const stack = headerEl.firstElementChild;
2250
+ if (!stack) return paddingX;
2251
+ const stackStyle = getComputedStyle(stack);
2252
+ const gap = parseFloat(stackStyle.gap) || parseFloat(stackStyle.columnGap) || 0;
2253
+ let totalChildWidth = 0;
2254
+ let visibleChildCount = 0;
2255
+ for (const child of Array.from(stack.children)) {
2256
+ const el = child;
2257
+ if (!el.offsetWidth && !el.offsetHeight) continue;
2258
+ visibleChildCount++;
2259
+ const textEl = el.querySelector?.('[data-slot="header-text"]') || (el.dataset?.slot === "header-text" ? el : null);
2260
+ if (textEl) {
2261
+ const htmlEl = textEl;
2262
+ const isMultiLine = getComputedStyle(htmlEl).display === "-webkit-box";
2263
+ if (isMultiLine) {
2264
+ const measurer = new TextMeasurer();
2265
+ measurer.setFontFromElement(htmlEl);
2266
+ totalChildWidth += measurer.measureText(htmlEl.textContent || "");
2267
+ } else {
2268
+ totalChildWidth += htmlEl.scrollWidth;
2269
+ }
2270
+ } else {
2271
+ totalChildWidth += el.offsetWidth;
2272
+ }
2273
+ }
2274
+ const totalGaps = visibleChildCount > 1 ? (visibleChildCount - 1) * gap : 0;
2275
+ return totalChildWidth + totalGaps + paddingX + borderX;
2276
+ }
2277
+ function computeBodyWidth(headerEl, table, field, dataInPage) {
2278
+ const headId = headerEl.id;
2279
+ const sampleTd = headId ? table.querySelector(`tbody td[headers="${CSS.escape(headId)}"]`) : null;
2280
+ const styleSource = sampleTd || headerEl;
2281
+ const tdStyle = getComputedStyle(styleSource);
2282
+ const bodyPaddingX = parseFloat(tdStyle.paddingLeft) + parseFloat(tdStyle.paddingRight);
2283
+ const bodyBorderX = parseFloat(tdStyle.borderLeftWidth) + parseFloat(tdStyle.borderRightWidth);
2284
+ const measurer = new TextMeasurer();
2285
+ measurer.setFont(tdStyle.font);
2286
+ const texts = [];
2287
+ for (let i = 0; i < dataInPage.length; i++) {
2288
+ const val = dataInPage[i][field];
2289
+ texts.push(val == null ? "" : String(val));
2290
+ }
2291
+ const maxTextWidth = measurer.measureMaxWidth(texts);
2292
+ return maxTextWidth + bodyPaddingX + bodyBorderX;
2293
+ }
2294
+ function computeAutoFitWidth(params) {
2295
+ const { headerEl, field, dataInPage } = params;
2296
+ const table = headerEl.closest("table");
2297
+ if (!table) return null;
2298
+ const headerWidth = computeHeaderWidth(headerEl);
2299
+ const bodyWidth = computeBodyWidth(headerEl, table, field, dataInPage);
2300
+ let finalWidth = Math.ceil(Math.max(headerWidth, bodyWidth));
2301
+ const thStyle = getComputedStyle(headerEl);
2302
+ const resolvedMin = thStyle.minWidth;
2303
+ const resolvedMax = thStyle.maxWidth;
2304
+ const minPx = resolvedMin !== "none" ? parseFloat(resolvedMin) : NaN;
2305
+ const maxPx = resolvedMax !== "none" ? parseFloat(resolvedMax) : NaN;
2306
+ if (!isNaN(minPx) && minPx > 0) finalWidth = Math.max(finalWidth, minPx);
2307
+ if (!isNaN(maxPx) && maxPx > 0) finalWidth = Math.min(finalWidth, maxPx);
2308
+ return finalWidth;
2309
+ }
2215
2310
 
2216
2311
  // src/components/DataTable/styled.tsx
2217
2312
  var import_react19 = __toESM(require("react"));
@@ -2322,7 +2417,7 @@ var StyledTd = (0, import_joy25.styled)("td")(({ theme }) => ({
2322
2417
  var MotionSortIcon = (0, import_framer_motion17.motion)(import_ArrowUpwardRounded.default);
2323
2418
  var DefaultLoadingOverlay = () => /* @__PURE__ */ import_react19.default.createElement(import_joy25.LinearProgress, { value: 8, variant: "plain" });
2324
2419
  var DefaultNoRowsOverlay = () => /* @__PURE__ */ import_react19.default.createElement(import_joy25.Typography, { level: "body-sm", textColor: "text.tertiary" }, "No rows");
2325
- var Resizer = (ref, targetRef = ref, onResizeStateChange) => /* @__PURE__ */ import_react19.default.createElement(
2420
+ var Resizer = (ref, targetRef = ref, onResizeStateChange, onAutoFit) => /* @__PURE__ */ import_react19.default.createElement(
2326
2421
  Box_default,
2327
2422
  {
2328
2423
  sx: {
@@ -2330,24 +2425,67 @@ var Resizer = (ref, targetRef = ref, onResizeStateChange) => /* @__PURE__ */ imp
2330
2425
  top: 0,
2331
2426
  right: 0,
2332
2427
  bottom: 0,
2333
- width: "4px",
2334
- cursor: "col-resize"
2428
+ width: "7px",
2429
+ cursor: "col-resize",
2430
+ "&::after": {
2431
+ content: '""',
2432
+ position: "absolute",
2433
+ top: 0,
2434
+ bottom: 0,
2435
+ right: 0,
2436
+ width: "2px",
2437
+ bgcolor: "transparent",
2438
+ transition: "background-color 150ms ease"
2439
+ },
2440
+ "&:hover::after": {
2441
+ bgcolor: "primary.300"
2442
+ },
2443
+ "&[data-resizing]::after": {
2444
+ bgcolor: "primary.500"
2445
+ }
2335
2446
  },
2336
2447
  onClick: (e) => e.stopPropagation(),
2448
+ onDoubleClick: (e) => {
2449
+ e.stopPropagation();
2450
+ e.preventDefault();
2451
+ onAutoFit?.();
2452
+ },
2337
2453
  onMouseDown: (e) => {
2454
+ if (e.detail >= 2) return;
2455
+ const resizerEl = e.currentTarget;
2456
+ resizerEl.dataset.resizing = "";
2338
2457
  const initialX = e.clientX;
2339
2458
  const initialWidth = ref.current?.getBoundingClientRect().width;
2340
- onResizeStateChange?.(true);
2459
+ let activated = false;
2460
+ const DRAG_THRESHOLD = 3;
2461
+ const thStyle = ref.current ? getComputedStyle(ref.current) : null;
2462
+ const minW = thStyle ? parseFloat(thStyle.minWidth) : NaN;
2463
+ const maxW = thStyle ? parseFloat(thStyle.maxWidth) : NaN;
2341
2464
  const onMouseMove = (e2) => {
2342
- if (initialWidth && initialX) {
2343
- ref.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2344
- targetRef.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2465
+ if (!initialWidth) return;
2466
+ const delta = e2.clientX - initialX;
2467
+ if (!activated) {
2468
+ if (Math.abs(delta) < DRAG_THRESHOLD) return;
2469
+ activated = true;
2470
+ onResizeStateChange?.(true);
2345
2471
  }
2472
+ if (!ref.current || !targetRef.current) {
2473
+ onMouseUp();
2474
+ return;
2475
+ }
2476
+ let newWidth = initialWidth + delta;
2477
+ if (!isNaN(minW) && minW > 0) newWidth = Math.max(newWidth, minW);
2478
+ if (!isNaN(maxW) && maxW > 0) newWidth = Math.min(newWidth, maxW);
2479
+ ref.current.style.width = `${newWidth}px`;
2480
+ targetRef.current.style.width = `${newWidth}px`;
2346
2481
  };
2347
2482
  const onMouseUp = () => {
2483
+ resizerEl.removeAttribute("data-resizing");
2348
2484
  document.removeEventListener("mousemove", onMouseMove);
2349
2485
  document.removeEventListener("mouseup", onMouseUp);
2350
- requestAnimationFrame(() => onResizeStateChange?.(false));
2486
+ if (activated) {
2487
+ requestAnimationFrame(() => onResizeStateChange?.(false));
2488
+ }
2351
2489
  };
2352
2490
  document.addEventListener("mousemove", onMouseMove);
2353
2491
  document.addEventListener("mouseup", onMouseUp);
@@ -2920,7 +3058,11 @@ function InfoSign(props) {
2920
3058
  var InfoSign_default = InfoSign;
2921
3059
 
2922
3060
  // src/components/DataTable/components.tsx
2923
- var TextEllipsis = ({ children, lineClamp }) => {
3061
+ var TextEllipsis = ({
3062
+ children,
3063
+ lineClamp,
3064
+ ...rest
3065
+ }) => {
2924
3066
  const textRef = (0, import_react24.useRef)(null);
2925
3067
  const [showTooltip, setShowTooltip] = (0, import_react24.useState)(false);
2926
3068
  (0, import_react24.useLayoutEffect)(() => {
@@ -2935,7 +3077,7 @@ var TextEllipsis = ({ children, lineClamp }) => {
2935
3077
  ro.observe(element);
2936
3078
  return () => ro.disconnect();
2937
3079
  }, [children, lineClamp]);
2938
- return /* @__PURE__ */ import_react24.default.createElement(Tooltip_default, { title: showTooltip ? children : "", placement: "top" }, /* @__PURE__ */ import_react24.default.createElement(EllipsisDiv, { ref: textRef, lineClamp }, children));
3080
+ return /* @__PURE__ */ import_react24.default.createElement(Tooltip_default, { title: showTooltip ? children : "", placement: "top" }, /* @__PURE__ */ import_react24.default.createElement(EllipsisDiv, { ref: textRef, lineClamp, ...rest }, children));
2939
3081
  };
2940
3082
  var CellTextEllipsis = ({ children }) => {
2941
3083
  const textRef = (0, import_react24.useRef)(null);
@@ -2987,11 +3129,16 @@ var HeadCell = (props) => {
2987
3129
  headerRef,
2988
3130
  tableColRef,
2989
3131
  headerClassName,
2990
- headerLineClamp
3132
+ headerLineClamp,
3133
+ onAutoFit
2991
3134
  } = props;
2992
3135
  const theme = (0, import_joy32.useTheme)();
2993
3136
  const ref = headerRef;
2994
3137
  const colRef = tableColRef;
3138
+ const localRef = (0, import_react24.useRef)(null);
3139
+ (0, import_react24.useLayoutEffect)(() => {
3140
+ ref.current = localRef.current;
3141
+ }, [ref]);
2995
3142
  const [isHovered, setIsHovered] = (0, import_react24.useState)(false);
2996
3143
  const sortable = type === "actions" ? false : _sortable;
2997
3144
  const headId = (0, import_react24.useMemo)(
@@ -3000,10 +3147,15 @@ var HeadCell = (props) => {
3000
3147
  );
3001
3148
  const isResizingRef = (0, import_react24.useRef)(false);
3002
3149
  const resizer = (0, import_react24.useMemo)(
3003
- () => resizable ?? true ? Resizer(ref, colRef, (isResizing) => {
3004
- isResizingRef.current = isResizing;
3005
- }) : null,
3006
- [resizable, ref, colRef]
3150
+ () => resizable ?? true ? Resizer(
3151
+ ref,
3152
+ colRef,
3153
+ (isResizing) => {
3154
+ isResizingRef.current = isResizing;
3155
+ },
3156
+ onAutoFit ? () => onAutoFit(field) : void 0
3157
+ ) : null,
3158
+ [resizable, ref, colRef, onAutoFit, field]
3007
3159
  );
3008
3160
  const style = (0, import_react24.useMemo)(
3009
3161
  () => ({
@@ -3074,7 +3226,7 @@ var HeadCell = (props) => {
3074
3226
  id: headId,
3075
3227
  "aria-label": headerName ?? field,
3076
3228
  "aria-sort": sort ? { asc: "ascending", desc: "descending" }[sort] : "none",
3077
- ref,
3229
+ ref: localRef,
3078
3230
  key: field,
3079
3231
  style,
3080
3232
  onClick: (0, import_react24.useCallback)(
@@ -3091,7 +3243,7 @@ var HeadCell = (props) => {
3091
3243
  initial: "initial",
3092
3244
  className: computedHeaderClassName
3093
3245
  },
3094
- /* @__PURE__ */ import_react24.default.createElement(Stack_default, { gap: 1, direction: "row", justifyContent: textAlign, alignItems: "center", sx: { minWidth: 0 } }, textAlign === "end" && sortIcon, textAlign === "end" && infoSign, /* @__PURE__ */ import_react24.default.createElement(TextEllipsis, { lineClamp: headerLineClamp }, headerName ?? field, editMode && required && /* @__PURE__ */ import_react24.default.createElement(Asterisk, null, "*")), textAlign === "start" && infoSign, textAlign === "start" && sortIcon),
3246
+ /* @__PURE__ */ import_react24.default.createElement(Stack_default, { gap: 1, direction: "row", justifyContent: textAlign, alignItems: "center", sx: { minWidth: 0 } }, textAlign === "end" && sortIcon, textAlign === "end" && infoSign, /* @__PURE__ */ import_react24.default.createElement(TextEllipsis, { lineClamp: headerLineClamp, "data-slot": "header-text" }, headerName ?? field, editMode && required && /* @__PURE__ */ import_react24.default.createElement(Asterisk, null, "*")), textAlign === "start" && infoSign, textAlign === "start" && sortIcon),
3095
3247
  resizer
3096
3248
  );
3097
3249
  };
@@ -3638,6 +3790,25 @@ function useDataTableRenderer({
3638
3790
  prevRowsRef.current = _rows;
3639
3791
  }
3640
3792
  }, [_rows]);
3793
+ const handleAutoFit = (0, import_react25.useCallback)(
3794
+ (field) => {
3795
+ const colDef = visibleColumnsByField[field];
3796
+ if (!colDef?.headerRef.current) return;
3797
+ const column = allColumnsByField[field];
3798
+ const columnType = column && "type" in column ? column.type : void 0;
3799
+ if (columnType === "actions") return;
3800
+ const optimalWidth = computeAutoFitWidth({
3801
+ headerEl: colDef.headerRef.current,
3802
+ field,
3803
+ dataInPage
3804
+ });
3805
+ if (optimalWidth == null) return;
3806
+ const widthPx = `${optimalWidth}px`;
3807
+ colDef.headerRef.current.style.width = widthPx;
3808
+ if (colDef.tableColRef.current) colDef.tableColRef.current.style.width = widthPx;
3809
+ },
3810
+ [visibleColumnsByField, allColumnsByField, dataInPage]
3811
+ );
3641
3812
  return {
3642
3813
  rowCount,
3643
3814
  selectableRowCount,
@@ -3649,6 +3820,7 @@ function useDataTableRenderer({
3649
3820
  BodyRow,
3650
3821
  dataInPage,
3651
3822
  handleSortChange,
3823
+ handleAutoFit,
3652
3824
  isAllSelected,
3653
3825
  isTotalSelected,
3654
3826
  isSelectedRow: (0, import_react25.useCallback)((model) => selectedModelSet.has(model), [selectedModelSet]),
@@ -4079,6 +4251,7 @@ function Component(props, apiRef) {
4079
4251
  pageSize,
4080
4252
  onPaginationModelChange,
4081
4253
  handleSortChange,
4254
+ handleAutoFit,
4082
4255
  dataInPage,
4083
4256
  isTotalSelected,
4084
4257
  focusedRowId,
@@ -4357,6 +4530,7 @@ function Component(props, apiRef) {
4357
4530
  stickyHeader: props.stickyHeader,
4358
4531
  editMode: !!c.isCellEditable,
4359
4532
  onSortChange: handleSortChange,
4533
+ onAutoFit: handleAutoFit,
4360
4534
  ...c
4361
4535
  }
4362
4536
  )
package/dist/index.js CHANGED
@@ -1965,6 +1965,36 @@ import React25, {
1965
1965
  } from "react";
1966
1966
  import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
1967
1967
 
1968
+ // src/libs/text-measurer.ts
1969
+ var TextMeasurer = class {
1970
+ constructor(font) {
1971
+ const canvas = document.createElement("canvas");
1972
+ this.ctx = canvas.getContext("2d");
1973
+ if (this.ctx && font) this.ctx.font = font;
1974
+ }
1975
+ setFont(font) {
1976
+ if (this.ctx) this.ctx.font = font;
1977
+ return this;
1978
+ }
1979
+ setFontFromElement(el) {
1980
+ if (this.ctx) this.ctx.font = getComputedStyle(el).font;
1981
+ return this;
1982
+ }
1983
+ measureText(text) {
1984
+ if (!this.ctx) return 0;
1985
+ return this.ctx.measureText(text).width;
1986
+ }
1987
+ measureMaxWidth(values) {
1988
+ if (!this.ctx) return 0;
1989
+ let max = 0;
1990
+ for (let i = 0; i < values.length; i++) {
1991
+ const w = this.ctx.measureText(values[i]).width;
1992
+ if (w > max) max = w;
1993
+ }
1994
+ return max;
1995
+ }
1996
+ };
1997
+
1968
1998
  // src/components/DataTable/utils.ts
1969
1999
  function extractFieldsFromGroupingModel(items) {
1970
2000
  const fields = /* @__PURE__ */ new Set();
@@ -2086,6 +2116,71 @@ function getTextAlign(props) {
2086
2116
  return !props.editMode && ["number", "date", "currency"].includes(props.type || "") ? "end" : "start";
2087
2117
  }
2088
2118
  var numberFormatter = (value) => "Intl" in window ? new Intl.NumberFormat().format(value) : value;
2119
+ function computeHeaderWidth(headerEl) {
2120
+ const thStyle = getComputedStyle(headerEl);
2121
+ const paddingX = parseFloat(thStyle.paddingLeft) + parseFloat(thStyle.paddingRight);
2122
+ const borderX = parseFloat(thStyle.borderLeftWidth) + parseFloat(thStyle.borderRightWidth);
2123
+ const stack = headerEl.firstElementChild;
2124
+ if (!stack) return paddingX;
2125
+ const stackStyle = getComputedStyle(stack);
2126
+ const gap = parseFloat(stackStyle.gap) || parseFloat(stackStyle.columnGap) || 0;
2127
+ let totalChildWidth = 0;
2128
+ let visibleChildCount = 0;
2129
+ for (const child of Array.from(stack.children)) {
2130
+ const el = child;
2131
+ if (!el.offsetWidth && !el.offsetHeight) continue;
2132
+ visibleChildCount++;
2133
+ const textEl = el.querySelector?.('[data-slot="header-text"]') || (el.dataset?.slot === "header-text" ? el : null);
2134
+ if (textEl) {
2135
+ const htmlEl = textEl;
2136
+ const isMultiLine = getComputedStyle(htmlEl).display === "-webkit-box";
2137
+ if (isMultiLine) {
2138
+ const measurer = new TextMeasurer();
2139
+ measurer.setFontFromElement(htmlEl);
2140
+ totalChildWidth += measurer.measureText(htmlEl.textContent || "");
2141
+ } else {
2142
+ totalChildWidth += htmlEl.scrollWidth;
2143
+ }
2144
+ } else {
2145
+ totalChildWidth += el.offsetWidth;
2146
+ }
2147
+ }
2148
+ const totalGaps = visibleChildCount > 1 ? (visibleChildCount - 1) * gap : 0;
2149
+ return totalChildWidth + totalGaps + paddingX + borderX;
2150
+ }
2151
+ function computeBodyWidth(headerEl, table, field, dataInPage) {
2152
+ const headId = headerEl.id;
2153
+ const sampleTd = headId ? table.querySelector(`tbody td[headers="${CSS.escape(headId)}"]`) : null;
2154
+ const styleSource = sampleTd || headerEl;
2155
+ const tdStyle = getComputedStyle(styleSource);
2156
+ const bodyPaddingX = parseFloat(tdStyle.paddingLeft) + parseFloat(tdStyle.paddingRight);
2157
+ const bodyBorderX = parseFloat(tdStyle.borderLeftWidth) + parseFloat(tdStyle.borderRightWidth);
2158
+ const measurer = new TextMeasurer();
2159
+ measurer.setFont(tdStyle.font);
2160
+ const texts = [];
2161
+ for (let i = 0; i < dataInPage.length; i++) {
2162
+ const val = dataInPage[i][field];
2163
+ texts.push(val == null ? "" : String(val));
2164
+ }
2165
+ const maxTextWidth = measurer.measureMaxWidth(texts);
2166
+ return maxTextWidth + bodyPaddingX + bodyBorderX;
2167
+ }
2168
+ function computeAutoFitWidth(params) {
2169
+ const { headerEl, field, dataInPage } = params;
2170
+ const table = headerEl.closest("table");
2171
+ if (!table) return null;
2172
+ const headerWidth = computeHeaderWidth(headerEl);
2173
+ const bodyWidth = computeBodyWidth(headerEl, table, field, dataInPage);
2174
+ let finalWidth = Math.ceil(Math.max(headerWidth, bodyWidth));
2175
+ const thStyle = getComputedStyle(headerEl);
2176
+ const resolvedMin = thStyle.minWidth;
2177
+ const resolvedMax = thStyle.maxWidth;
2178
+ const minPx = resolvedMin !== "none" ? parseFloat(resolvedMin) : NaN;
2179
+ const maxPx = resolvedMax !== "none" ? parseFloat(resolvedMax) : NaN;
2180
+ if (!isNaN(minPx) && minPx > 0) finalWidth = Math.max(finalWidth, minPx);
2181
+ if (!isNaN(maxPx) && maxPx > 0) finalWidth = Math.min(finalWidth, maxPx);
2182
+ return finalWidth;
2183
+ }
2089
2184
 
2090
2185
  // src/components/DataTable/styled.tsx
2091
2186
  import React17 from "react";
@@ -2196,7 +2291,7 @@ var StyledTd = styled8("td")(({ theme }) => ({
2196
2291
  var MotionSortIcon = motion17(SortIcon);
2197
2292
  var DefaultLoadingOverlay = () => /* @__PURE__ */ React17.createElement(LinearProgress, { value: 8, variant: "plain" });
2198
2293
  var DefaultNoRowsOverlay = () => /* @__PURE__ */ React17.createElement(Typography3, { level: "body-sm", textColor: "text.tertiary" }, "No rows");
2199
- var Resizer = (ref, targetRef = ref, onResizeStateChange) => /* @__PURE__ */ React17.createElement(
2294
+ var Resizer = (ref, targetRef = ref, onResizeStateChange, onAutoFit) => /* @__PURE__ */ React17.createElement(
2200
2295
  Box_default,
2201
2296
  {
2202
2297
  sx: {
@@ -2204,24 +2299,67 @@ var Resizer = (ref, targetRef = ref, onResizeStateChange) => /* @__PURE__ */ Rea
2204
2299
  top: 0,
2205
2300
  right: 0,
2206
2301
  bottom: 0,
2207
- width: "4px",
2208
- cursor: "col-resize"
2302
+ width: "7px",
2303
+ cursor: "col-resize",
2304
+ "&::after": {
2305
+ content: '""',
2306
+ position: "absolute",
2307
+ top: 0,
2308
+ bottom: 0,
2309
+ right: 0,
2310
+ width: "2px",
2311
+ bgcolor: "transparent",
2312
+ transition: "background-color 150ms ease"
2313
+ },
2314
+ "&:hover::after": {
2315
+ bgcolor: "primary.300"
2316
+ },
2317
+ "&[data-resizing]::after": {
2318
+ bgcolor: "primary.500"
2319
+ }
2209
2320
  },
2210
2321
  onClick: (e) => e.stopPropagation(),
2322
+ onDoubleClick: (e) => {
2323
+ e.stopPropagation();
2324
+ e.preventDefault();
2325
+ onAutoFit?.();
2326
+ },
2211
2327
  onMouseDown: (e) => {
2328
+ if (e.detail >= 2) return;
2329
+ const resizerEl = e.currentTarget;
2330
+ resizerEl.dataset.resizing = "";
2212
2331
  const initialX = e.clientX;
2213
2332
  const initialWidth = ref.current?.getBoundingClientRect().width;
2214
- onResizeStateChange?.(true);
2333
+ let activated = false;
2334
+ const DRAG_THRESHOLD = 3;
2335
+ const thStyle = ref.current ? getComputedStyle(ref.current) : null;
2336
+ const minW = thStyle ? parseFloat(thStyle.minWidth) : NaN;
2337
+ const maxW = thStyle ? parseFloat(thStyle.maxWidth) : NaN;
2215
2338
  const onMouseMove = (e2) => {
2216
- if (initialWidth && initialX) {
2217
- ref.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2218
- targetRef.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2339
+ if (!initialWidth) return;
2340
+ const delta = e2.clientX - initialX;
2341
+ if (!activated) {
2342
+ if (Math.abs(delta) < DRAG_THRESHOLD) return;
2343
+ activated = true;
2344
+ onResizeStateChange?.(true);
2219
2345
  }
2346
+ if (!ref.current || !targetRef.current) {
2347
+ onMouseUp();
2348
+ return;
2349
+ }
2350
+ let newWidth = initialWidth + delta;
2351
+ if (!isNaN(minW) && minW > 0) newWidth = Math.max(newWidth, minW);
2352
+ if (!isNaN(maxW) && maxW > 0) newWidth = Math.min(newWidth, maxW);
2353
+ ref.current.style.width = `${newWidth}px`;
2354
+ targetRef.current.style.width = `${newWidth}px`;
2220
2355
  };
2221
2356
  const onMouseUp = () => {
2357
+ resizerEl.removeAttribute("data-resizing");
2222
2358
  document.removeEventListener("mousemove", onMouseMove);
2223
2359
  document.removeEventListener("mouseup", onMouseUp);
2224
- requestAnimationFrame(() => onResizeStateChange?.(false));
2360
+ if (activated) {
2361
+ requestAnimationFrame(() => onResizeStateChange?.(false));
2362
+ }
2225
2363
  };
2226
2364
  document.addEventListener("mousemove", onMouseMove);
2227
2365
  document.addEventListener("mouseup", onMouseUp);
@@ -2803,7 +2941,11 @@ function InfoSign(props) {
2803
2941
  var InfoSign_default = InfoSign;
2804
2942
 
2805
2943
  // src/components/DataTable/components.tsx
2806
- var TextEllipsis = ({ children, lineClamp }) => {
2944
+ var TextEllipsis = ({
2945
+ children,
2946
+ lineClamp,
2947
+ ...rest
2948
+ }) => {
2807
2949
  const textRef = useRef5(null);
2808
2950
  const [showTooltip, setShowTooltip] = useState8(false);
2809
2951
  useLayoutEffect(() => {
@@ -2818,7 +2960,7 @@ var TextEllipsis = ({ children, lineClamp }) => {
2818
2960
  ro.observe(element);
2819
2961
  return () => ro.disconnect();
2820
2962
  }, [children, lineClamp]);
2821
- return /* @__PURE__ */ React22.createElement(Tooltip_default, { title: showTooltip ? children : "", placement: "top" }, /* @__PURE__ */ React22.createElement(EllipsisDiv, { ref: textRef, lineClamp }, children));
2963
+ return /* @__PURE__ */ React22.createElement(Tooltip_default, { title: showTooltip ? children : "", placement: "top" }, /* @__PURE__ */ React22.createElement(EllipsisDiv, { ref: textRef, lineClamp, ...rest }, children));
2822
2964
  };
2823
2965
  var CellTextEllipsis = ({ children }) => {
2824
2966
  const textRef = useRef5(null);
@@ -2870,11 +3012,16 @@ var HeadCell = (props) => {
2870
3012
  headerRef,
2871
3013
  tableColRef,
2872
3014
  headerClassName,
2873
- headerLineClamp
3015
+ headerLineClamp,
3016
+ onAutoFit
2874
3017
  } = props;
2875
3018
  const theme = useTheme();
2876
3019
  const ref = headerRef;
2877
3020
  const colRef = tableColRef;
3021
+ const localRef = useRef5(null);
3022
+ useLayoutEffect(() => {
3023
+ ref.current = localRef.current;
3024
+ }, [ref]);
2878
3025
  const [isHovered, setIsHovered] = useState8(false);
2879
3026
  const sortable = type === "actions" ? false : _sortable;
2880
3027
  const headId = useMemo8(
@@ -2883,10 +3030,15 @@ var HeadCell = (props) => {
2883
3030
  );
2884
3031
  const isResizingRef = useRef5(false);
2885
3032
  const resizer = useMemo8(
2886
- () => resizable ?? true ? Resizer(ref, colRef, (isResizing) => {
2887
- isResizingRef.current = isResizing;
2888
- }) : null,
2889
- [resizable, ref, colRef]
3033
+ () => resizable ?? true ? Resizer(
3034
+ ref,
3035
+ colRef,
3036
+ (isResizing) => {
3037
+ isResizingRef.current = isResizing;
3038
+ },
3039
+ onAutoFit ? () => onAutoFit(field) : void 0
3040
+ ) : null,
3041
+ [resizable, ref, colRef, onAutoFit, field]
2890
3042
  );
2891
3043
  const style = useMemo8(
2892
3044
  () => ({
@@ -2957,7 +3109,7 @@ var HeadCell = (props) => {
2957
3109
  id: headId,
2958
3110
  "aria-label": headerName ?? field,
2959
3111
  "aria-sort": sort ? { asc: "ascending", desc: "descending" }[sort] : "none",
2960
- ref,
3112
+ ref: localRef,
2961
3113
  key: field,
2962
3114
  style,
2963
3115
  onClick: useCallback9(
@@ -2974,7 +3126,7 @@ var HeadCell = (props) => {
2974
3126
  initial: "initial",
2975
3127
  className: computedHeaderClassName
2976
3128
  },
2977
- /* @__PURE__ */ React22.createElement(Stack_default, { gap: 1, direction: "row", justifyContent: textAlign, alignItems: "center", sx: { minWidth: 0 } }, textAlign === "end" && sortIcon, textAlign === "end" && infoSign, /* @__PURE__ */ React22.createElement(TextEllipsis, { lineClamp: headerLineClamp }, headerName ?? field, editMode && required && /* @__PURE__ */ React22.createElement(Asterisk, null, "*")), textAlign === "start" && infoSign, textAlign === "start" && sortIcon),
3129
+ /* @__PURE__ */ React22.createElement(Stack_default, { gap: 1, direction: "row", justifyContent: textAlign, alignItems: "center", sx: { minWidth: 0 } }, textAlign === "end" && sortIcon, textAlign === "end" && infoSign, /* @__PURE__ */ React22.createElement(TextEllipsis, { lineClamp: headerLineClamp, "data-slot": "header-text" }, headerName ?? field, editMode && required && /* @__PURE__ */ React22.createElement(Asterisk, null, "*")), textAlign === "start" && infoSign, textAlign === "start" && sortIcon),
2978
3130
  resizer
2979
3131
  );
2980
3132
  };
@@ -3521,6 +3673,25 @@ function useDataTableRenderer({
3521
3673
  prevRowsRef.current = _rows;
3522
3674
  }
3523
3675
  }, [_rows]);
3676
+ const handleAutoFit = useCallback10(
3677
+ (field) => {
3678
+ const colDef = visibleColumnsByField[field];
3679
+ if (!colDef?.headerRef.current) return;
3680
+ const column = allColumnsByField[field];
3681
+ const columnType = column && "type" in column ? column.type : void 0;
3682
+ if (columnType === "actions") return;
3683
+ const optimalWidth = computeAutoFitWidth({
3684
+ headerEl: colDef.headerRef.current,
3685
+ field,
3686
+ dataInPage
3687
+ });
3688
+ if (optimalWidth == null) return;
3689
+ const widthPx = `${optimalWidth}px`;
3690
+ colDef.headerRef.current.style.width = widthPx;
3691
+ if (colDef.tableColRef.current) colDef.tableColRef.current.style.width = widthPx;
3692
+ },
3693
+ [visibleColumnsByField, allColumnsByField, dataInPage]
3694
+ );
3524
3695
  return {
3525
3696
  rowCount,
3526
3697
  selectableRowCount,
@@ -3532,6 +3703,7 @@ function useDataTableRenderer({
3532
3703
  BodyRow,
3533
3704
  dataInPage,
3534
3705
  handleSortChange,
3706
+ handleAutoFit,
3535
3707
  isAllSelected,
3536
3708
  isTotalSelected,
3537
3709
  isSelectedRow: useCallback10((model) => selectedModelSet.has(model), [selectedModelSet]),
@@ -3962,6 +4134,7 @@ function Component(props, apiRef) {
3962
4134
  pageSize,
3963
4135
  onPaginationModelChange,
3964
4136
  handleSortChange,
4137
+ handleAutoFit,
3965
4138
  dataInPage,
3966
4139
  isTotalSelected,
3967
4140
  focusedRowId,
@@ -4240,6 +4413,7 @@ function Component(props, apiRef) {
4240
4413
  stickyHeader: props.stickyHeader,
4241
4414
  editMode: !!c.isCellEditable,
4242
4415
  onSortChange: handleSortChange,
4416
+ onAutoFit: handleAutoFit,
4243
4417
  ...c
4244
4418
  }
4245
4419
  )
@@ -0,0 +1,8 @@
1
+ export declare class TextMeasurer {
2
+ private ctx;
3
+ constructor(font?: string);
4
+ setFont(font: string): this;
5
+ setFontFromElement(el: Element): this;
6
+ measureText(text: string): number;
7
+ measureMaxWidth(values: string[]): number;
8
+ }