@ceed/ads 1.28.2 → 1.29.1

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
@@ -2089,6 +2089,36 @@ var CurrencyInput_default = CurrencyInput;
2089
2089
  var import_react28 = __toESM(require("react"));
2090
2090
  var import_react_virtual2 = require("@tanstack/react-virtual");
2091
2091
 
2092
+ // src/libs/text-measurer.ts
2093
+ var TextMeasurer = class {
2094
+ constructor(font) {
2095
+ const canvas = document.createElement("canvas");
2096
+ this.ctx = canvas.getContext("2d");
2097
+ if (this.ctx && font) this.ctx.font = font;
2098
+ }
2099
+ setFont(font) {
2100
+ if (this.ctx) this.ctx.font = font;
2101
+ return this;
2102
+ }
2103
+ setFontFromElement(el) {
2104
+ if (this.ctx) this.ctx.font = getComputedStyle(el).font;
2105
+ return this;
2106
+ }
2107
+ measureText(text) {
2108
+ if (!this.ctx) return 0;
2109
+ return this.ctx.measureText(text).width;
2110
+ }
2111
+ measureMaxWidth(values) {
2112
+ if (!this.ctx) return 0;
2113
+ let max = 0;
2114
+ for (let i = 0; i < values.length; i++) {
2115
+ const w = this.ctx.measureText(values[i]).width;
2116
+ if (w > max) max = w;
2117
+ }
2118
+ return max;
2119
+ }
2120
+ };
2121
+
2092
2122
  // src/components/DataTable/utils.ts
2093
2123
  function extractFieldsFromGroupingModel(items) {
2094
2124
  const fields = /* @__PURE__ */ new Set();
@@ -2206,10 +2236,84 @@ function calculateColumnGroups(columnGroupingModel, columns, visibleFields) {
2206
2236
  const correctedMaxLevel = filteredGroupsByLevel.length > 0 ? filteredGroupsByLevel.length - 1 : -1;
2207
2237
  return { groups: filteredGroupsByLevel, maxLevel: correctedMaxLevel, fieldsInGroupingModel };
2208
2238
  }
2239
+ function parsePxValue(value) {
2240
+ if (!value) return null;
2241
+ const trimmed = value.trim();
2242
+ if (trimmed.endsWith("px")) {
2243
+ const num = parseFloat(trimmed);
2244
+ return isNaN(num) ? null : num;
2245
+ }
2246
+ return null;
2247
+ }
2209
2248
  function getTextAlign(props) {
2210
2249
  return !props.editMode && ["number", "date", "currency"].includes(props.type || "") ? "end" : "start";
2211
2250
  }
2212
2251
  var numberFormatter = (value) => "Intl" in window ? new Intl.NumberFormat().format(value) : value;
2252
+ function computeHeaderWidth(headerEl) {
2253
+ const thStyle = getComputedStyle(headerEl);
2254
+ const paddingX = parseFloat(thStyle.paddingLeft) + parseFloat(thStyle.paddingRight);
2255
+ const borderX = parseFloat(thStyle.borderLeftWidth) + parseFloat(thStyle.borderRightWidth);
2256
+ const stack = headerEl.firstElementChild;
2257
+ if (!stack) return paddingX;
2258
+ const stackStyle = getComputedStyle(stack);
2259
+ const gap = parseFloat(stackStyle.gap) || parseFloat(stackStyle.columnGap) || 0;
2260
+ let totalChildWidth = 0;
2261
+ let visibleChildCount = 0;
2262
+ for (const child of Array.from(stack.children)) {
2263
+ const el = child;
2264
+ if (!el.offsetWidth && !el.offsetHeight) continue;
2265
+ visibleChildCount++;
2266
+ const textEl = el.querySelector?.('[data-slot="header-text"]') || (el.dataset?.slot === "header-text" ? el : null);
2267
+ if (textEl) {
2268
+ const htmlEl = textEl;
2269
+ const isMultiLine = getComputedStyle(htmlEl).display === "-webkit-box";
2270
+ if (isMultiLine) {
2271
+ const measurer = new TextMeasurer();
2272
+ measurer.setFontFromElement(htmlEl);
2273
+ totalChildWidth += measurer.measureText(htmlEl.textContent || "");
2274
+ } else {
2275
+ totalChildWidth += htmlEl.scrollWidth;
2276
+ }
2277
+ } else {
2278
+ totalChildWidth += el.offsetWidth;
2279
+ }
2280
+ }
2281
+ const totalGaps = visibleChildCount > 1 ? (visibleChildCount - 1) * gap : 0;
2282
+ return totalChildWidth + totalGaps + paddingX + borderX;
2283
+ }
2284
+ function computeBodyWidth(headerEl, table, field, dataInPage) {
2285
+ const headId = headerEl.id;
2286
+ const sampleTd = headId ? table.querySelector(`tbody td[headers="${CSS.escape(headId)}"]`) : null;
2287
+ const styleSource = sampleTd || headerEl;
2288
+ const tdStyle = getComputedStyle(styleSource);
2289
+ const bodyPaddingX = parseFloat(tdStyle.paddingLeft) + parseFloat(tdStyle.paddingRight);
2290
+ const bodyBorderX = parseFloat(tdStyle.borderLeftWidth) + parseFloat(tdStyle.borderRightWidth);
2291
+ const measurer = new TextMeasurer();
2292
+ measurer.setFont(tdStyle.font);
2293
+ const texts = [];
2294
+ for (let i = 0; i < dataInPage.length; i++) {
2295
+ const val = dataInPage[i][field];
2296
+ texts.push(val == null ? "" : String(val));
2297
+ }
2298
+ const maxTextWidth = measurer.measureMaxWidth(texts);
2299
+ return maxTextWidth + bodyPaddingX + bodyBorderX;
2300
+ }
2301
+ function computeAutoFitWidth(params) {
2302
+ const { headerEl, field, dataInPage } = params;
2303
+ const table = headerEl.closest("table");
2304
+ if (!table) return null;
2305
+ const headerWidth = computeHeaderWidth(headerEl);
2306
+ const bodyWidth = computeBodyWidth(headerEl, table, field, dataInPage);
2307
+ let finalWidth = Math.ceil(Math.max(headerWidth, bodyWidth));
2308
+ const thStyle = getComputedStyle(headerEl);
2309
+ const resolvedMin = thStyle.minWidth;
2310
+ const resolvedMax = thStyle.maxWidth;
2311
+ const minPx = resolvedMin !== "none" ? parseFloat(resolvedMin) : NaN;
2312
+ const maxPx = resolvedMax !== "none" ? parseFloat(resolvedMax) : NaN;
2313
+ if (!isNaN(minPx) && minPx > 0) finalWidth = Math.max(finalWidth, minPx);
2314
+ if (!isNaN(maxPx) && maxPx > 0) finalWidth = Math.min(finalWidth, maxPx);
2315
+ return finalWidth;
2316
+ }
2213
2317
 
2214
2318
  // src/components/DataTable/styled.tsx
2215
2319
  var import_react19 = __toESM(require("react"));
@@ -2320,7 +2424,7 @@ var StyledTd = (0, import_joy25.styled)("td")(({ theme }) => ({
2320
2424
  var MotionSortIcon = (0, import_framer_motion17.motion)(import_ArrowUpwardRounded.default);
2321
2425
  var DefaultLoadingOverlay = () => /* @__PURE__ */ import_react19.default.createElement(import_joy25.LinearProgress, { value: 8, variant: "plain" });
2322
2426
  var DefaultNoRowsOverlay = () => /* @__PURE__ */ import_react19.default.createElement(import_joy25.Typography, { level: "body-sm", textColor: "text.tertiary" }, "No rows");
2323
- var Resizer = (ref, targetRef = ref, onResizeStateChange) => /* @__PURE__ */ import_react19.default.createElement(
2427
+ var Resizer = (ref, targetRef = ref, onResizeStateChange, onAutoFit) => /* @__PURE__ */ import_react19.default.createElement(
2324
2428
  Box_default,
2325
2429
  {
2326
2430
  sx: {
@@ -2328,24 +2432,67 @@ var Resizer = (ref, targetRef = ref, onResizeStateChange) => /* @__PURE__ */ imp
2328
2432
  top: 0,
2329
2433
  right: 0,
2330
2434
  bottom: 0,
2331
- width: "4px",
2332
- cursor: "col-resize"
2435
+ width: "7px",
2436
+ cursor: "col-resize",
2437
+ "&::after": {
2438
+ content: '""',
2439
+ position: "absolute",
2440
+ top: 0,
2441
+ bottom: 0,
2442
+ right: 0,
2443
+ width: "2px",
2444
+ bgcolor: "transparent",
2445
+ transition: "background-color 150ms ease"
2446
+ },
2447
+ "&:hover::after": {
2448
+ bgcolor: "primary.300"
2449
+ },
2450
+ "&[data-resizing]::after": {
2451
+ bgcolor: "primary.500"
2452
+ }
2333
2453
  },
2334
2454
  onClick: (e) => e.stopPropagation(),
2455
+ onDoubleClick: (e) => {
2456
+ e.stopPropagation();
2457
+ e.preventDefault();
2458
+ onAutoFit?.();
2459
+ },
2335
2460
  onMouseDown: (e) => {
2461
+ if (e.detail >= 2) return;
2462
+ const resizerEl = e.currentTarget;
2463
+ resizerEl.dataset.resizing = "";
2336
2464
  const initialX = e.clientX;
2337
2465
  const initialWidth = ref.current?.getBoundingClientRect().width;
2338
- onResizeStateChange?.(true);
2466
+ let activated = false;
2467
+ const DRAG_THRESHOLD = 3;
2468
+ const thStyle = ref.current ? getComputedStyle(ref.current) : null;
2469
+ const minW = thStyle ? parseFloat(thStyle.minWidth) : NaN;
2470
+ const maxW = thStyle ? parseFloat(thStyle.maxWidth) : NaN;
2339
2471
  const onMouseMove = (e2) => {
2340
- if (initialWidth && initialX) {
2341
- ref.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2342
- targetRef.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2472
+ if (!initialWidth) return;
2473
+ const delta = e2.clientX - initialX;
2474
+ if (!activated) {
2475
+ if (Math.abs(delta) < DRAG_THRESHOLD) return;
2476
+ activated = true;
2477
+ onResizeStateChange?.(true);
2478
+ }
2479
+ if (!ref.current || !targetRef.current) {
2480
+ onMouseUp();
2481
+ return;
2343
2482
  }
2483
+ let newWidth = initialWidth + delta;
2484
+ if (!isNaN(minW) && minW > 0) newWidth = Math.max(newWidth, minW);
2485
+ if (!isNaN(maxW) && maxW > 0) newWidth = Math.min(newWidth, maxW);
2486
+ ref.current.style.width = `${newWidth}px`;
2487
+ targetRef.current.style.width = `${newWidth}px`;
2344
2488
  };
2345
2489
  const onMouseUp = () => {
2490
+ resizerEl.removeAttribute("data-resizing");
2346
2491
  document.removeEventListener("mousemove", onMouseMove);
2347
2492
  document.removeEventListener("mouseup", onMouseUp);
2348
- requestAnimationFrame(() => onResizeStateChange?.(false));
2493
+ if (activated) {
2494
+ requestAnimationFrame(() => onResizeStateChange?.(false));
2495
+ }
2349
2496
  };
2350
2497
  document.addEventListener("mousemove", onMouseMove);
2351
2498
  document.addEventListener("mouseup", onMouseUp);
@@ -2918,7 +3065,11 @@ function InfoSign(props) {
2918
3065
  var InfoSign_default = InfoSign;
2919
3066
 
2920
3067
  // src/components/DataTable/components.tsx
2921
- var TextEllipsis = ({ children, lineClamp }) => {
3068
+ var TextEllipsis = ({
3069
+ children,
3070
+ lineClamp,
3071
+ ...rest
3072
+ }) => {
2922
3073
  const textRef = (0, import_react24.useRef)(null);
2923
3074
  const [showTooltip, setShowTooltip] = (0, import_react24.useState)(false);
2924
3075
  (0, import_react24.useLayoutEffect)(() => {
@@ -2933,7 +3084,7 @@ var TextEllipsis = ({ children, lineClamp }) => {
2933
3084
  ro.observe(element);
2934
3085
  return () => ro.disconnect();
2935
3086
  }, [children, lineClamp]);
2936
- return /* @__PURE__ */ import_react24.default.createElement(Tooltip_default, { title: showTooltip ? children : "", placement: "top" }, /* @__PURE__ */ import_react24.default.createElement(EllipsisDiv, { ref: textRef, lineClamp }, children));
3087
+ return /* @__PURE__ */ import_react24.default.createElement(Tooltip_default, { title: showTooltip ? children : "", placement: "top" }, /* @__PURE__ */ import_react24.default.createElement(EllipsisDiv, { ref: textRef, lineClamp, ...rest }, children));
2937
3088
  };
2938
3089
  var CellTextEllipsis = ({ children }) => {
2939
3090
  const textRef = (0, import_react24.useRef)(null);
@@ -2985,7 +3136,8 @@ var HeadCell = (props) => {
2985
3136
  headerRef,
2986
3137
  tableColRef,
2987
3138
  headerClassName,
2988
- headerLineClamp
3139
+ headerLineClamp,
3140
+ onAutoFit
2989
3141
  } = props;
2990
3142
  const theme = (0, import_joy32.useTheme)();
2991
3143
  const ref = headerRef;
@@ -3002,10 +3154,15 @@ var HeadCell = (props) => {
3002
3154
  );
3003
3155
  const isResizingRef = (0, import_react24.useRef)(false);
3004
3156
  const resizer = (0, import_react24.useMemo)(
3005
- () => resizable ?? true ? Resizer(ref, colRef, (isResizing) => {
3006
- isResizingRef.current = isResizing;
3007
- }) : null,
3008
- [resizable, ref, colRef]
3157
+ () => resizable ?? true ? Resizer(
3158
+ ref,
3159
+ colRef,
3160
+ (isResizing) => {
3161
+ isResizingRef.current = isResizing;
3162
+ },
3163
+ onAutoFit ? () => onAutoFit(field) : void 0
3164
+ ) : null,
3165
+ [resizable, ref, colRef, onAutoFit, field]
3009
3166
  );
3010
3167
  const style = (0, import_react24.useMemo)(
3011
3168
  () => ({
@@ -3093,7 +3250,7 @@ var HeadCell = (props) => {
3093
3250
  initial: "initial",
3094
3251
  className: computedHeaderClassName
3095
3252
  },
3096
- /* @__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),
3253
+ /* @__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),
3097
3254
  resizer
3098
3255
  );
3099
3256
  };
@@ -3387,7 +3544,8 @@ function useDataTableRenderer({
3387
3544
  isRowSelectable,
3388
3545
  columnGroupingModel,
3389
3546
  columnVisibilityModel,
3390
- onColumnVisibilityModelChange
3547
+ onColumnVisibilityModelChange,
3548
+ checkboxSelection
3391
3549
  }) {
3392
3550
  if (pinnedColumns && columnGroupingModel) {
3393
3551
  throw new Error(
@@ -3420,6 +3578,14 @@ function useDataTableRenderer({
3420
3578
  [reorderedColumns, visibilityModel]
3421
3579
  );
3422
3580
  const visibleFieldSet = (0, import_react25.useMemo)(() => new Set(visibleColumns.map((c) => c.field)), [visibleColumns]);
3581
+ const tableMinWidth = (0, import_react25.useMemo)(() => {
3582
+ const DEFAULT_MIN = 50;
3583
+ let total = checkboxSelection ? 40 : 0;
3584
+ for (const col of visibleColumns) {
3585
+ total += parsePxValue(col.minWidth) ?? parsePxValue(col.width) ?? DEFAULT_MIN;
3586
+ }
3587
+ return total;
3588
+ }, [visibleColumns, checkboxSelection]);
3423
3589
  const allColumnsByField = (0, import_react25.useMemo)(
3424
3590
  () => reorderedColumns.reduce(
3425
3591
  (acc, curr) => ({
@@ -3640,6 +3806,25 @@ function useDataTableRenderer({
3640
3806
  prevRowsRef.current = _rows;
3641
3807
  }
3642
3808
  }, [_rows]);
3809
+ const handleAutoFit = (0, import_react25.useCallback)(
3810
+ (field) => {
3811
+ const colDef = visibleColumnsByField[field];
3812
+ if (!colDef?.headerRef.current) return;
3813
+ const column = allColumnsByField[field];
3814
+ const columnType = column && "type" in column ? column.type : void 0;
3815
+ if (columnType === "actions") return;
3816
+ const optimalWidth = computeAutoFitWidth({
3817
+ headerEl: colDef.headerRef.current,
3818
+ field,
3819
+ dataInPage
3820
+ });
3821
+ if (optimalWidth == null) return;
3822
+ const widthPx = `${optimalWidth}px`;
3823
+ colDef.headerRef.current.style.width = widthPx;
3824
+ if (colDef.tableColRef.current) colDef.tableColRef.current.style.width = widthPx;
3825
+ },
3826
+ [visibleColumnsByField, allColumnsByField, dataInPage]
3827
+ );
3643
3828
  return {
3644
3829
  rowCount,
3645
3830
  selectableRowCount,
@@ -3651,6 +3836,7 @@ function useDataTableRenderer({
3651
3836
  BodyRow,
3652
3837
  dataInPage,
3653
3838
  handleSortChange,
3839
+ handleAutoFit,
3654
3840
  isAllSelected,
3655
3841
  isTotalSelected,
3656
3842
  isSelectedRow: (0, import_react25.useCallback)((model) => selectedModelSet.has(model), [selectedModelSet]),
@@ -3700,6 +3886,7 @@ function useDataTableRenderer({
3700
3886
  ]
3701
3887
  ),
3702
3888
  columns,
3889
+ tableMinWidth,
3703
3890
  processedColumnGroups,
3704
3891
  onTotalSelect: (0, import_react25.useCallback)(() => {
3705
3892
  const selectableRows = rows.filter((row, i) => {
@@ -4081,6 +4268,7 @@ function Component(props, apiRef) {
4081
4268
  pageSize,
4082
4269
  onPaginationModelChange,
4083
4270
  handleSortChange,
4271
+ handleAutoFit,
4084
4272
  dataInPage,
4085
4273
  isTotalSelected,
4086
4274
  focusedRowId,
@@ -4088,6 +4276,7 @@ function Component(props, apiRef) {
4088
4276
  onTotalSelect,
4089
4277
  HeadCell: HeadCell2,
4090
4278
  BodyRow: BodyRow2,
4279
+ tableMinWidth,
4091
4280
  // For keyboard selection
4092
4281
  selectionAnchor,
4093
4282
  setSelectionAnchor
@@ -4277,7 +4466,7 @@ function Component(props, apiRef) {
4277
4466
  ref: parentRef,
4278
4467
  ...backgroundProps
4279
4468
  },
4280
- /* @__PURE__ */ import_react28.default.createElement(Table, { ...innerProps }, /* @__PURE__ */ import_react28.default.createElement("colgroup", null, checkboxSelection && /* @__PURE__ */ import_react28.default.createElement(
4469
+ /* @__PURE__ */ import_react28.default.createElement(Table, { ...innerProps, sx: { ...innerProps.sx, minWidth: tableMinWidth } }, /* @__PURE__ */ import_react28.default.createElement("colgroup", null, checkboxSelection && /* @__PURE__ */ import_react28.default.createElement(
4281
4470
  "col",
4282
4471
  {
4283
4472
  style: {
@@ -4290,7 +4479,8 @@ function Component(props, apiRef) {
4290
4479
  ref: c.tableColRef,
4291
4480
  key: `${c.field.toString()}_${c.width}`,
4292
4481
  style: {
4293
- width: c.width
4482
+ width: c.width,
4483
+ minWidth: c.minWidth ?? "50px"
4294
4484
  }
4295
4485
  }
4296
4486
  ))), /* @__PURE__ */ import_react28.default.createElement("thead", null, processedColumnGroups && processedColumnGroups.groups.length > 0 && processedColumnGroups.groups.map((levelGroups, level) => /* @__PURE__ */ import_react28.default.createElement("tr", { key: `group-level-${level}` }, checkboxSelection && level === 0 && /* @__PURE__ */ import_react28.default.createElement(
@@ -4359,6 +4549,7 @@ function Component(props, apiRef) {
4359
4549
  stickyHeader: props.stickyHeader,
4360
4550
  editMode: !!c.isCellEditable,
4361
4551
  onSortChange: handleSortChange,
4552
+ onAutoFit: handleAutoFit,
4362
4553
  ...c
4363
4554
  }
4364
4555
  )