@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.js CHANGED
@@ -1955,6 +1955,36 @@ import React25, {
1955
1955
  } from "react";
1956
1956
  import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
1957
1957
 
1958
+ // src/libs/text-measurer.ts
1959
+ var TextMeasurer = class {
1960
+ constructor(font) {
1961
+ const canvas = document.createElement("canvas");
1962
+ this.ctx = canvas.getContext("2d");
1963
+ if (this.ctx && font) this.ctx.font = font;
1964
+ }
1965
+ setFont(font) {
1966
+ if (this.ctx) this.ctx.font = font;
1967
+ return this;
1968
+ }
1969
+ setFontFromElement(el) {
1970
+ if (this.ctx) this.ctx.font = getComputedStyle(el).font;
1971
+ return this;
1972
+ }
1973
+ measureText(text) {
1974
+ if (!this.ctx) return 0;
1975
+ return this.ctx.measureText(text).width;
1976
+ }
1977
+ measureMaxWidth(values) {
1978
+ if (!this.ctx) return 0;
1979
+ let max = 0;
1980
+ for (let i = 0; i < values.length; i++) {
1981
+ const w = this.ctx.measureText(values[i]).width;
1982
+ if (w > max) max = w;
1983
+ }
1984
+ return max;
1985
+ }
1986
+ };
1987
+
1958
1988
  // src/components/DataTable/utils.ts
1959
1989
  function extractFieldsFromGroupingModel(items) {
1960
1990
  const fields = /* @__PURE__ */ new Set();
@@ -2072,10 +2102,84 @@ function calculateColumnGroups(columnGroupingModel, columns, visibleFields) {
2072
2102
  const correctedMaxLevel = filteredGroupsByLevel.length > 0 ? filteredGroupsByLevel.length - 1 : -1;
2073
2103
  return { groups: filteredGroupsByLevel, maxLevel: correctedMaxLevel, fieldsInGroupingModel };
2074
2104
  }
2105
+ function parsePxValue(value) {
2106
+ if (!value) return null;
2107
+ const trimmed = value.trim();
2108
+ if (trimmed.endsWith("px")) {
2109
+ const num = parseFloat(trimmed);
2110
+ return isNaN(num) ? null : num;
2111
+ }
2112
+ return null;
2113
+ }
2075
2114
  function getTextAlign(props) {
2076
2115
  return !props.editMode && ["number", "date", "currency"].includes(props.type || "") ? "end" : "start";
2077
2116
  }
2078
2117
  var numberFormatter = (value) => "Intl" in window ? new Intl.NumberFormat().format(value) : value;
2118
+ function computeHeaderWidth(headerEl) {
2119
+ const thStyle = getComputedStyle(headerEl);
2120
+ const paddingX = parseFloat(thStyle.paddingLeft) + parseFloat(thStyle.paddingRight);
2121
+ const borderX = parseFloat(thStyle.borderLeftWidth) + parseFloat(thStyle.borderRightWidth);
2122
+ const stack = headerEl.firstElementChild;
2123
+ if (!stack) return paddingX;
2124
+ const stackStyle = getComputedStyle(stack);
2125
+ const gap = parseFloat(stackStyle.gap) || parseFloat(stackStyle.columnGap) || 0;
2126
+ let totalChildWidth = 0;
2127
+ let visibleChildCount = 0;
2128
+ for (const child of Array.from(stack.children)) {
2129
+ const el = child;
2130
+ if (!el.offsetWidth && !el.offsetHeight) continue;
2131
+ visibleChildCount++;
2132
+ const textEl = el.querySelector?.('[data-slot="header-text"]') || (el.dataset?.slot === "header-text" ? el : null);
2133
+ if (textEl) {
2134
+ const htmlEl = textEl;
2135
+ const isMultiLine = getComputedStyle(htmlEl).display === "-webkit-box";
2136
+ if (isMultiLine) {
2137
+ const measurer = new TextMeasurer();
2138
+ measurer.setFontFromElement(htmlEl);
2139
+ totalChildWidth += measurer.measureText(htmlEl.textContent || "");
2140
+ } else {
2141
+ totalChildWidth += htmlEl.scrollWidth;
2142
+ }
2143
+ } else {
2144
+ totalChildWidth += el.offsetWidth;
2145
+ }
2146
+ }
2147
+ const totalGaps = visibleChildCount > 1 ? (visibleChildCount - 1) * gap : 0;
2148
+ return totalChildWidth + totalGaps + paddingX + borderX;
2149
+ }
2150
+ function computeBodyWidth(headerEl, table, field, dataInPage) {
2151
+ const headId = headerEl.id;
2152
+ const sampleTd = headId ? table.querySelector(`tbody td[headers="${CSS.escape(headId)}"]`) : null;
2153
+ const styleSource = sampleTd || headerEl;
2154
+ const tdStyle = getComputedStyle(styleSource);
2155
+ const bodyPaddingX = parseFloat(tdStyle.paddingLeft) + parseFloat(tdStyle.paddingRight);
2156
+ const bodyBorderX = parseFloat(tdStyle.borderLeftWidth) + parseFloat(tdStyle.borderRightWidth);
2157
+ const measurer = new TextMeasurer();
2158
+ measurer.setFont(tdStyle.font);
2159
+ const texts = [];
2160
+ for (let i = 0; i < dataInPage.length; i++) {
2161
+ const val = dataInPage[i][field];
2162
+ texts.push(val == null ? "" : String(val));
2163
+ }
2164
+ const maxTextWidth = measurer.measureMaxWidth(texts);
2165
+ return maxTextWidth + bodyPaddingX + bodyBorderX;
2166
+ }
2167
+ function computeAutoFitWidth(params) {
2168
+ const { headerEl, field, dataInPage } = params;
2169
+ const table = headerEl.closest("table");
2170
+ if (!table) return null;
2171
+ const headerWidth = computeHeaderWidth(headerEl);
2172
+ const bodyWidth = computeBodyWidth(headerEl, table, field, dataInPage);
2173
+ let finalWidth = Math.ceil(Math.max(headerWidth, bodyWidth));
2174
+ const thStyle = getComputedStyle(headerEl);
2175
+ const resolvedMin = thStyle.minWidth;
2176
+ const resolvedMax = thStyle.maxWidth;
2177
+ const minPx = resolvedMin !== "none" ? parseFloat(resolvedMin) : NaN;
2178
+ const maxPx = resolvedMax !== "none" ? parseFloat(resolvedMax) : NaN;
2179
+ if (!isNaN(minPx) && minPx > 0) finalWidth = Math.max(finalWidth, minPx);
2180
+ if (!isNaN(maxPx) && maxPx > 0) finalWidth = Math.min(finalWidth, maxPx);
2181
+ return finalWidth;
2182
+ }
2079
2183
 
2080
2184
  // src/components/DataTable/styled.tsx
2081
2185
  import React17 from "react";
@@ -2186,7 +2290,7 @@ var StyledTd = styled8("td")(({ theme }) => ({
2186
2290
  var MotionSortIcon = motion17(SortIcon);
2187
2291
  var DefaultLoadingOverlay = () => /* @__PURE__ */ React17.createElement(LinearProgress, { value: 8, variant: "plain" });
2188
2292
  var DefaultNoRowsOverlay = () => /* @__PURE__ */ React17.createElement(Typography3, { level: "body-sm", textColor: "text.tertiary" }, "No rows");
2189
- var Resizer = (ref, targetRef = ref, onResizeStateChange) => /* @__PURE__ */ React17.createElement(
2293
+ var Resizer = (ref, targetRef = ref, onResizeStateChange, onAutoFit) => /* @__PURE__ */ React17.createElement(
2190
2294
  Box_default,
2191
2295
  {
2192
2296
  sx: {
@@ -2194,24 +2298,67 @@ var Resizer = (ref, targetRef = ref, onResizeStateChange) => /* @__PURE__ */ Rea
2194
2298
  top: 0,
2195
2299
  right: 0,
2196
2300
  bottom: 0,
2197
- width: "4px",
2198
- cursor: "col-resize"
2301
+ width: "7px",
2302
+ cursor: "col-resize",
2303
+ "&::after": {
2304
+ content: '""',
2305
+ position: "absolute",
2306
+ top: 0,
2307
+ bottom: 0,
2308
+ right: 0,
2309
+ width: "2px",
2310
+ bgcolor: "transparent",
2311
+ transition: "background-color 150ms ease"
2312
+ },
2313
+ "&:hover::after": {
2314
+ bgcolor: "primary.300"
2315
+ },
2316
+ "&[data-resizing]::after": {
2317
+ bgcolor: "primary.500"
2318
+ }
2199
2319
  },
2200
2320
  onClick: (e) => e.stopPropagation(),
2321
+ onDoubleClick: (e) => {
2322
+ e.stopPropagation();
2323
+ e.preventDefault();
2324
+ onAutoFit?.();
2325
+ },
2201
2326
  onMouseDown: (e) => {
2327
+ if (e.detail >= 2) return;
2328
+ const resizerEl = e.currentTarget;
2329
+ resizerEl.dataset.resizing = "";
2202
2330
  const initialX = e.clientX;
2203
2331
  const initialWidth = ref.current?.getBoundingClientRect().width;
2204
- onResizeStateChange?.(true);
2332
+ let activated = false;
2333
+ const DRAG_THRESHOLD = 3;
2334
+ const thStyle = ref.current ? getComputedStyle(ref.current) : null;
2335
+ const minW = thStyle ? parseFloat(thStyle.minWidth) : NaN;
2336
+ const maxW = thStyle ? parseFloat(thStyle.maxWidth) : NaN;
2205
2337
  const onMouseMove = (e2) => {
2206
- if (initialWidth && initialX) {
2207
- ref.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2208
- targetRef.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2338
+ if (!initialWidth) return;
2339
+ const delta = e2.clientX - initialX;
2340
+ if (!activated) {
2341
+ if (Math.abs(delta) < DRAG_THRESHOLD) return;
2342
+ activated = true;
2343
+ onResizeStateChange?.(true);
2344
+ }
2345
+ if (!ref.current || !targetRef.current) {
2346
+ onMouseUp();
2347
+ return;
2209
2348
  }
2349
+ let newWidth = initialWidth + delta;
2350
+ if (!isNaN(minW) && minW > 0) newWidth = Math.max(newWidth, minW);
2351
+ if (!isNaN(maxW) && maxW > 0) newWidth = Math.min(newWidth, maxW);
2352
+ ref.current.style.width = `${newWidth}px`;
2353
+ targetRef.current.style.width = `${newWidth}px`;
2210
2354
  };
2211
2355
  const onMouseUp = () => {
2356
+ resizerEl.removeAttribute("data-resizing");
2212
2357
  document.removeEventListener("mousemove", onMouseMove);
2213
2358
  document.removeEventListener("mouseup", onMouseUp);
2214
- requestAnimationFrame(() => onResizeStateChange?.(false));
2359
+ if (activated) {
2360
+ requestAnimationFrame(() => onResizeStateChange?.(false));
2361
+ }
2215
2362
  };
2216
2363
  document.addEventListener("mousemove", onMouseMove);
2217
2364
  document.addEventListener("mouseup", onMouseUp);
@@ -2793,7 +2940,11 @@ function InfoSign(props) {
2793
2940
  var InfoSign_default = InfoSign;
2794
2941
 
2795
2942
  // src/components/DataTable/components.tsx
2796
- var TextEllipsis = ({ children, lineClamp }) => {
2943
+ var TextEllipsis = ({
2944
+ children,
2945
+ lineClamp,
2946
+ ...rest
2947
+ }) => {
2797
2948
  const textRef = useRef5(null);
2798
2949
  const [showTooltip, setShowTooltip] = useState8(false);
2799
2950
  useLayoutEffect(() => {
@@ -2808,7 +2959,7 @@ var TextEllipsis = ({ children, lineClamp }) => {
2808
2959
  ro.observe(element);
2809
2960
  return () => ro.disconnect();
2810
2961
  }, [children, lineClamp]);
2811
- return /* @__PURE__ */ React22.createElement(Tooltip_default, { title: showTooltip ? children : "", placement: "top" }, /* @__PURE__ */ React22.createElement(EllipsisDiv, { ref: textRef, lineClamp }, children));
2962
+ return /* @__PURE__ */ React22.createElement(Tooltip_default, { title: showTooltip ? children : "", placement: "top" }, /* @__PURE__ */ React22.createElement(EllipsisDiv, { ref: textRef, lineClamp, ...rest }, children));
2812
2963
  };
2813
2964
  var CellTextEllipsis = ({ children }) => {
2814
2965
  const textRef = useRef5(null);
@@ -2860,7 +3011,8 @@ var HeadCell = (props) => {
2860
3011
  headerRef,
2861
3012
  tableColRef,
2862
3013
  headerClassName,
2863
- headerLineClamp
3014
+ headerLineClamp,
3015
+ onAutoFit
2864
3016
  } = props;
2865
3017
  const theme = useTheme();
2866
3018
  const ref = headerRef;
@@ -2877,10 +3029,15 @@ var HeadCell = (props) => {
2877
3029
  );
2878
3030
  const isResizingRef = useRef5(false);
2879
3031
  const resizer = useMemo8(
2880
- () => resizable ?? true ? Resizer(ref, colRef, (isResizing) => {
2881
- isResizingRef.current = isResizing;
2882
- }) : null,
2883
- [resizable, ref, colRef]
3032
+ () => resizable ?? true ? Resizer(
3033
+ ref,
3034
+ colRef,
3035
+ (isResizing) => {
3036
+ isResizingRef.current = isResizing;
3037
+ },
3038
+ onAutoFit ? () => onAutoFit(field) : void 0
3039
+ ) : null,
3040
+ [resizable, ref, colRef, onAutoFit, field]
2884
3041
  );
2885
3042
  const style = useMemo8(
2886
3043
  () => ({
@@ -2968,7 +3125,7 @@ var HeadCell = (props) => {
2968
3125
  initial: "initial",
2969
3126
  className: computedHeaderClassName
2970
3127
  },
2971
- /* @__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),
3128
+ /* @__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),
2972
3129
  resizer
2973
3130
  );
2974
3131
  };
@@ -3262,7 +3419,8 @@ function useDataTableRenderer({
3262
3419
  isRowSelectable,
3263
3420
  columnGroupingModel,
3264
3421
  columnVisibilityModel,
3265
- onColumnVisibilityModelChange
3422
+ onColumnVisibilityModelChange,
3423
+ checkboxSelection
3266
3424
  }) {
3267
3425
  if (pinnedColumns && columnGroupingModel) {
3268
3426
  throw new Error(
@@ -3295,6 +3453,14 @@ function useDataTableRenderer({
3295
3453
  [reorderedColumns, visibilityModel]
3296
3454
  );
3297
3455
  const visibleFieldSet = useMemo9(() => new Set(visibleColumns.map((c) => c.field)), [visibleColumns]);
3456
+ const tableMinWidth = useMemo9(() => {
3457
+ const DEFAULT_MIN = 50;
3458
+ let total = checkboxSelection ? 40 : 0;
3459
+ for (const col of visibleColumns) {
3460
+ total += parsePxValue(col.minWidth) ?? parsePxValue(col.width) ?? DEFAULT_MIN;
3461
+ }
3462
+ return total;
3463
+ }, [visibleColumns, checkboxSelection]);
3298
3464
  const allColumnsByField = useMemo9(
3299
3465
  () => reorderedColumns.reduce(
3300
3466
  (acc, curr) => ({
@@ -3515,6 +3681,25 @@ function useDataTableRenderer({
3515
3681
  prevRowsRef.current = _rows;
3516
3682
  }
3517
3683
  }, [_rows]);
3684
+ const handleAutoFit = useCallback10(
3685
+ (field) => {
3686
+ const colDef = visibleColumnsByField[field];
3687
+ if (!colDef?.headerRef.current) return;
3688
+ const column = allColumnsByField[field];
3689
+ const columnType = column && "type" in column ? column.type : void 0;
3690
+ if (columnType === "actions") return;
3691
+ const optimalWidth = computeAutoFitWidth({
3692
+ headerEl: colDef.headerRef.current,
3693
+ field,
3694
+ dataInPage
3695
+ });
3696
+ if (optimalWidth == null) return;
3697
+ const widthPx = `${optimalWidth}px`;
3698
+ colDef.headerRef.current.style.width = widthPx;
3699
+ if (colDef.tableColRef.current) colDef.tableColRef.current.style.width = widthPx;
3700
+ },
3701
+ [visibleColumnsByField, allColumnsByField, dataInPage]
3702
+ );
3518
3703
  return {
3519
3704
  rowCount,
3520
3705
  selectableRowCount,
@@ -3526,6 +3711,7 @@ function useDataTableRenderer({
3526
3711
  BodyRow,
3527
3712
  dataInPage,
3528
3713
  handleSortChange,
3714
+ handleAutoFit,
3529
3715
  isAllSelected,
3530
3716
  isTotalSelected,
3531
3717
  isSelectedRow: useCallback10((model) => selectedModelSet.has(model), [selectedModelSet]),
@@ -3575,6 +3761,7 @@ function useDataTableRenderer({
3575
3761
  ]
3576
3762
  ),
3577
3763
  columns,
3764
+ tableMinWidth,
3578
3765
  processedColumnGroups,
3579
3766
  onTotalSelect: useCallback10(() => {
3580
3767
  const selectableRows = rows.filter((row, i) => {
@@ -3956,6 +4143,7 @@ function Component(props, apiRef) {
3956
4143
  pageSize,
3957
4144
  onPaginationModelChange,
3958
4145
  handleSortChange,
4146
+ handleAutoFit,
3959
4147
  dataInPage,
3960
4148
  isTotalSelected,
3961
4149
  focusedRowId,
@@ -3963,6 +4151,7 @@ function Component(props, apiRef) {
3963
4151
  onTotalSelect,
3964
4152
  HeadCell: HeadCell2,
3965
4153
  BodyRow: BodyRow2,
4154
+ tableMinWidth,
3966
4155
  // For keyboard selection
3967
4156
  selectionAnchor,
3968
4157
  setSelectionAnchor
@@ -4152,7 +4341,7 @@ function Component(props, apiRef) {
4152
4341
  ref: parentRef,
4153
4342
  ...backgroundProps
4154
4343
  },
4155
- /* @__PURE__ */ React25.createElement(Table, { ...innerProps }, /* @__PURE__ */ React25.createElement("colgroup", null, checkboxSelection && /* @__PURE__ */ React25.createElement(
4344
+ /* @__PURE__ */ React25.createElement(Table, { ...innerProps, sx: { ...innerProps.sx, minWidth: tableMinWidth } }, /* @__PURE__ */ React25.createElement("colgroup", null, checkboxSelection && /* @__PURE__ */ React25.createElement(
4156
4345
  "col",
4157
4346
  {
4158
4347
  style: {
@@ -4165,7 +4354,8 @@ function Component(props, apiRef) {
4165
4354
  ref: c.tableColRef,
4166
4355
  key: `${c.field.toString()}_${c.width}`,
4167
4356
  style: {
4168
- width: c.width
4357
+ width: c.width,
4358
+ minWidth: c.minWidth ?? "50px"
4169
4359
  }
4170
4360
  }
4171
4361
  ))), /* @__PURE__ */ React25.createElement("thead", null, processedColumnGroups && processedColumnGroups.groups.length > 0 && processedColumnGroups.groups.map((levelGroups, level) => /* @__PURE__ */ React25.createElement("tr", { key: `group-level-${level}` }, checkboxSelection && level === 0 && /* @__PURE__ */ React25.createElement(
@@ -4234,6 +4424,7 @@ function Component(props, apiRef) {
4234
4424
  stickyHeader: props.stickyHeader,
4235
4425
  editMode: !!c.isCellEditable,
4236
4426
  onSortChange: handleSortChange,
4427
+ onAutoFit: handleAutoFit,
4237
4428
  ...c
4238
4429
  }
4239
4430
  )
@@ -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
+ }