@ceed/cds 1.27.2 → 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);
2471
+ }
2472
+ if (!ref.current || !targetRef.current) {
2473
+ onMouseUp();
2474
+ return;
2345
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,7 +3129,8 @@ 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;
@@ -3004,10 +3147,15 @@ var HeadCell = (props) => {
3004
3147
  );
3005
3148
  const isResizingRef = (0, import_react24.useRef)(false);
3006
3149
  const resizer = (0, import_react24.useMemo)(
3007
- () => resizable ?? true ? Resizer(ref, colRef, (isResizing) => {
3008
- isResizingRef.current = isResizing;
3009
- }) : null,
3010
- [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]
3011
3159
  );
3012
3160
  const style = (0, import_react24.useMemo)(
3013
3161
  () => ({
@@ -3095,7 +3243,7 @@ var HeadCell = (props) => {
3095
3243
  initial: "initial",
3096
3244
  className: computedHeaderClassName
3097
3245
  },
3098
- /* @__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),
3099
3247
  resizer
3100
3248
  );
3101
3249
  };
@@ -3642,6 +3790,25 @@ function useDataTableRenderer({
3642
3790
  prevRowsRef.current = _rows;
3643
3791
  }
3644
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
+ );
3645
3812
  return {
3646
3813
  rowCount,
3647
3814
  selectableRowCount,
@@ -3653,6 +3820,7 @@ function useDataTableRenderer({
3653
3820
  BodyRow,
3654
3821
  dataInPage,
3655
3822
  handleSortChange,
3823
+ handleAutoFit,
3656
3824
  isAllSelected,
3657
3825
  isTotalSelected,
3658
3826
  isSelectedRow: (0, import_react25.useCallback)((model) => selectedModelSet.has(model), [selectedModelSet]),
@@ -4083,6 +4251,7 @@ function Component(props, apiRef) {
4083
4251
  pageSize,
4084
4252
  onPaginationModelChange,
4085
4253
  handleSortChange,
4254
+ handleAutoFit,
4086
4255
  dataInPage,
4087
4256
  isTotalSelected,
4088
4257
  focusedRowId,
@@ -4361,6 +4530,7 @@ function Component(props, apiRef) {
4361
4530
  stickyHeader: props.stickyHeader,
4362
4531
  editMode: !!c.isCellEditable,
4363
4532
  onSortChange: handleSortChange,
4533
+ onAutoFit: handleAutoFit,
4364
4534
  ...c
4365
4535
  }
4366
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);
2345
+ }
2346
+ if (!ref.current || !targetRef.current) {
2347
+ onMouseUp();
2348
+ return;
2219
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,7 +3012,8 @@ 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;
@@ -2887,10 +3030,15 @@ var HeadCell = (props) => {
2887
3030
  );
2888
3031
  const isResizingRef = useRef5(false);
2889
3032
  const resizer = useMemo8(
2890
- () => resizable ?? true ? Resizer(ref, colRef, (isResizing) => {
2891
- isResizingRef.current = isResizing;
2892
- }) : null,
2893
- [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]
2894
3042
  );
2895
3043
  const style = useMemo8(
2896
3044
  () => ({
@@ -2978,7 +3126,7 @@ var HeadCell = (props) => {
2978
3126
  initial: "initial",
2979
3127
  className: computedHeaderClassName
2980
3128
  },
2981
- /* @__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),
2982
3130
  resizer
2983
3131
  );
2984
3132
  };
@@ -3525,6 +3673,25 @@ function useDataTableRenderer({
3525
3673
  prevRowsRef.current = _rows;
3526
3674
  }
3527
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
+ );
3528
3695
  return {
3529
3696
  rowCount,
3530
3697
  selectableRowCount,
@@ -3536,6 +3703,7 @@ function useDataTableRenderer({
3536
3703
  BodyRow,
3537
3704
  dataInPage,
3538
3705
  handleSortChange,
3706
+ handleAutoFit,
3539
3707
  isAllSelected,
3540
3708
  isTotalSelected,
3541
3709
  isSelectedRow: useCallback10((model) => selectedModelSet.has(model), [selectedModelSet]),
@@ -3966,6 +4134,7 @@ function Component(props, apiRef) {
3966
4134
  pageSize,
3967
4135
  onPaginationModelChange,
3968
4136
  handleSortChange,
4137
+ handleAutoFit,
3969
4138
  dataInPage,
3970
4139
  isTotalSelected,
3971
4140
  focusedRowId,
@@ -4244,6 +4413,7 @@ function Component(props, apiRef) {
4244
4413
  stickyHeader: props.stickyHeader,
4245
4414
  editMode: !!c.isCellEditable,
4246
4415
  onSortChange: handleSortChange,
4416
+ onAutoFit: handleAutoFit,
4247
4417
  ...c
4248
4418
  }
4249
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
+ }