@versini/ui-datagrid 0.3.0 → 0.3.2

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.
Files changed (32) hide show
  1. package/dist/DataGrid/DataGrid.js +33 -24
  2. package/dist/DataGrid/DataGridContext.js +1 -1
  3. package/dist/DataGrid/index.js +1 -1
  4. package/dist/DataGrid/utilities.d.ts +12 -8
  5. package/dist/DataGrid/utilities.js +18 -18
  6. package/dist/DataGridAnimated/AnimatedWrapper.js +1 -1
  7. package/dist/DataGridAnimated/index.js +1 -1
  8. package/dist/DataGridAnimated/useAnimatedHeight.js +1 -1
  9. package/dist/DataGridBody/DataGridBody.js +3 -3
  10. package/dist/DataGridBody/index.js +1 -1
  11. package/dist/DataGridCell/DataGridCell.js +8 -6
  12. package/dist/DataGridCell/index.js +1 -1
  13. package/dist/DataGridCellSort/DataGridCellSort.js +2 -2
  14. package/dist/DataGridCellSort/index.js +1 -1
  15. package/dist/DataGridConstants/DataGridConstants.js +1 -1
  16. package/dist/DataGridConstants/index.js +1 -1
  17. package/dist/DataGridFooter/DataGridFooter.d.ts +3 -2
  18. package/dist/DataGridFooter/DataGridFooter.js +33 -9
  19. package/dist/DataGridFooter/index.js +1 -1
  20. package/dist/DataGridHeader/DataGridHeader.d.ts +8 -7
  21. package/dist/DataGridHeader/DataGridHeader.js +39 -15
  22. package/dist/DataGridHeader/index.js +1 -1
  23. package/dist/DataGridInfinite/InfiniteScrollMarker.js +1 -1
  24. package/dist/DataGridInfinite/index.js +1 -1
  25. package/dist/DataGridInfinite/useInfiniteScroll.d.ts +5 -5
  26. package/dist/DataGridInfinite/useInfiniteScroll.js +3 -3
  27. package/dist/DataGridRow/DataGridRow.js +15 -14
  28. package/dist/DataGridRow/index.js +1 -1
  29. package/dist/DataGridSorting/index.js +1 -1
  30. package/dist/DataGridSorting/sortingUtils.js +1 -1
  31. package/dist/common/utilities.js +1 -1
  32. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -29,16 +29,22 @@ import { getDataGridClasses } from "./utilities.js";
29
29
  * DataGrid (main component)
30
30
  * ========================================================================== */ const DataGrid = ({ className, wrapperClassName, children, mode = "system", compact = false, stickyHeader = false, stickyFooter = false, blurEffect = BlurEffects.NONE, maxHeight, disabled = false, columns, ...rest })=>{
31
31
  /**
32
- * Track registered header/footer components via context registration.
33
- * Uses counter-based tracking to properly handle multiple instances.
34
- * Components register themselves when they mount, regardless of nesting depth.
35
- * This replaces the previous displayName-based child inspection approach.
32
+ * Track registered header/footer components via context registration. Uses
33
+ * counter-based tracking to properly handle multiple instances. Components
34
+ * register themselves when they mount, regardless of nesting depth. This
35
+ * replaces the previous displayName-based child inspection approach.
36
36
  */ const [headerCount, setHeaderCount] = useState(0);
37
37
  const [footerCount, setFooterCount] = useState(0);
38
38
  /**
39
- * Registration callbacks with stable references.
40
- * Called by DataGridHeader/DataGridFooter on mount/unmount.
41
- * Uses increment/decrement to handle multiple instances correctly.
39
+ * Track measured heights of header/footer for dynamic padding. Reported by
40
+ * DataGridHeader/Footer via ResizeObserver. This replaces the brittle
41
+ * hard-coded Tailwind padding classes.
42
+ */ const [headerHeight, setHeaderHeight] = useState(0);
43
+ const [footerHeight, setFooterHeight] = useState(0);
44
+ /**
45
+ * Registration callbacks with stable references. Called by
46
+ * DataGridHeader/DataGridFooter on mount/unmount. Uses increment/decrement to
47
+ * handle multiple instances correctly.
42
48
  */ const registerHeader = useCallback(()=>setHeaderCount((c)=>c + 1), []);
43
49
  const unregisterHeader = useCallback(()=>setHeaderCount((c)=>c - 1), []);
44
50
  const registerFooter = useCallback(()=>setFooterCount((c)=>c + 1), []);
@@ -46,23 +52,18 @@ import { getDataGridClasses } from "./utilities.js";
46
52
  const hasRegisteredHeader = headerCount > 0;
47
53
  const hasRegisteredFooter = footerCount > 0;
48
54
  /**
49
- * Only apply sticky behavior if both the prop is true AND the
50
- * corresponding component exists. This prevents adding padding/styles
51
- * for non-existent headers/footers.
55
+ * Only apply sticky behavior if both the prop is true AND the corresponding
56
+ * component exists. This prevents adding padding/styles for non-existent
57
+ * headers/footers.
52
58
  */ const effectiveStickyHeader = stickyHeader && hasRegisteredHeader;
53
59
  const effectiveStickyFooter = stickyFooter && hasRegisteredFooter;
54
60
  /**
55
- * State to hold the caption ID registered by DataGridHeader.
56
- * Used for aria-labelledby on the grid element for accessibility.
57
- * Also used to determine if a caption exists (for padding calculations).
61
+ * State to hold the caption ID registered by DataGridHeader. Used for
62
+ * aria-labelledby on the grid element for accessibility.
58
63
  */ const [captionId, setCaptionId] = useState(undefined);
59
64
  const handleSetCaptionId = useCallback((id)=>{
60
65
  setCaptionId(id);
61
66
  }, []);
62
- /**
63
- * Determine if caption exists based on registered captionId.
64
- * This replaces the previous child inspection approach.
65
- */ const hasCaption = Boolean(captionId);
66
67
  const classes = useMemo(()=>getDataGridClasses({
67
68
  mode,
68
69
  className,
@@ -70,7 +71,6 @@ import { getDataGridClasses } from "./utilities.js";
70
71
  stickyHeader: effectiveStickyHeader,
71
72
  stickyFooter: effectiveStickyFooter,
72
73
  disabled,
73
- hasCaption,
74
74
  hasColumns: Boolean(columns)
75
75
  }), [
76
76
  mode,
@@ -79,7 +79,6 @@ import { getDataGridClasses } from "./utilities.js";
79
79
  effectiveStickyHeader,
80
80
  effectiveStickyFooter,
81
81
  disabled,
82
- hasCaption,
83
82
  columns
84
83
  ]);
85
84
  const contextValue = useMemo(()=>({
@@ -93,7 +92,9 @@ import { getDataGridClasses } from "./utilities.js";
93
92
  registerHeader,
94
93
  unregisterHeader,
95
94
  registerFooter,
96
- unregisterFooter
95
+ unregisterFooter,
96
+ setHeaderHeight,
97
+ setFooterHeight
97
98
  }), [
98
99
  mode,
99
100
  compact,
@@ -110,14 +111,22 @@ import { getDataGridClasses } from "./utilities.js";
110
111
  const wrapperStyle = maxHeight ? {
111
112
  maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight
112
113
  } : undefined;
114
+ /**
115
+ * Dynamic padding for scrollable content based on measured header/footer
116
+ * heights. This replaces the brittle hard-coded Tailwind padding classes.
117
+ */ const scrollableContentStyle = {
118
+ ...wrapperStyle,
119
+ paddingTop: effectiveStickyHeader ? headerHeight : undefined,
120
+ paddingBottom: effectiveStickyFooter ? footerHeight : undefined
121
+ };
113
122
  /**
114
123
  * When sticky header/footer is enabled, use Panel-like structure: - Outer
115
124
  * wrapper has overflow-hidden - Scrollable content area in the middle with
116
125
  * padding - Header/footer are absolutely positioned.
117
126
  */ const hasSticky = effectiveStickyHeader || effectiveStickyFooter;
118
127
  /**
119
- * When columns are provided, apply grid-template-columns at the grid level
120
- * so all rows can use subgrid to inherit the same column sizing.
128
+ * When columns are provided, apply grid-template-columns at the grid level so
129
+ * all rows can use subgrid to inherit the same column sizing.
121
130
  */ const gridStyle = columns ? {
122
131
  gridTemplateColumns: columns.join(" ")
123
132
  } : undefined;
@@ -158,7 +167,7 @@ import { getDataGridClasses } from "./utilities.js";
158
167
  style: wrapperStyle,
159
168
  children: hasSticky ? /*#__PURE__*/ jsx("div", {
160
169
  className: classes.scrollableContent,
161
- style: wrapperStyle,
170
+ style: scrollableContentStyle,
162
171
  children: gridContent
163
172
  }) : gridContent
164
173
  })
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -3,18 +3,18 @@ import { type ThemeMode } from "../DataGridConstants/DataGridConstants";
3
3
  * Generates classes for the main DataGrid wrapper and grid. When sticky
4
4
  * header/footer is enabled:
5
5
  * - Outer wrapper has overflow-hidden
6
- * - Scrollable area is a separate inner container with padding for header/footer
6
+ * - Scrollable area is a separate inner container with dynamic padding
7
7
  * - Header/footer use position:absolute to overlay scrollbar
8
- * - Padding is auto-calculated based on whether caption is present
8
+ * - Padding is dynamically set via inline styles based on measured heights
9
9
  *
10
10
  * When columns prop is provided, the grid uses CSS Grid layout with subgrid
11
11
  * support for proper column alignment across all rows.
12
+ *
12
13
  */
13
- export declare const getDataGridClasses: ({ mode, className, wrapperClassName, stickyHeader, stickyFooter, disabled, hasCaption, hasColumns, }: {
14
+ export declare const getDataGridClasses: ({ mode, className, wrapperClassName, stickyHeader, stickyFooter, disabled, hasColumns, }: {
14
15
  mode: ThemeMode;
15
16
  className?: string;
16
17
  disabled?: boolean;
17
- hasCaption?: boolean;
18
18
  hasColumns?: boolean;
19
19
  stickyFooter?: boolean;
20
20
  stickyHeader?: boolean;
@@ -24,12 +24,16 @@ export declare const getDataGridClasses: ({ mode, className, wrapperClassName, s
24
24
  inner: string;
25
25
  spinnerWrapper: string;
26
26
  spinner: string;
27
+ /**
28
+ * Outer wrapper - overflow-hidden when sticky, like Panel's outerWrapper.
29
+ * Uses flex layout when sticky to allow scrollableContent to respect
30
+ * max-height.
31
+ */
27
32
  wrapper: string;
28
33
  /**
29
- * Scrollable content area with padding for absolute-positioned header/footer.
30
- * Header height varies based on whether caption is present:
31
- * - pt-10 (40px): header row only
32
- * - pt-19 (76px): header row + caption
34
+ * Scrollable content area for absolute-positioned header/footer. Padding is
35
+ * applied via inline styles based on measured header/footer heights,
36
+ * eliminating the need for hard-coded values that break when styles change.
33
37
  */
34
38
  scrollableContent: string;
35
39
  /**
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -35,13 +35,14 @@ import clsx from "clsx";
35
35
  * Generates classes for the main DataGrid wrapper and grid. When sticky
36
36
  * header/footer is enabled:
37
37
  * - Outer wrapper has overflow-hidden
38
- * - Scrollable area is a separate inner container with padding for header/footer
38
+ * - Scrollable area is a separate inner container with dynamic padding
39
39
  * - Header/footer use position:absolute to overlay scrollbar
40
- * - Padding is auto-calculated based on whether caption is present
40
+ * - Padding is dynamically set via inline styles based on measured heights
41
41
  *
42
42
  * When columns prop is provided, the grid uses CSS Grid layout with subgrid
43
43
  * support for proper column alignment across all rows.
44
- */ const getDataGridClasses = ({ mode, className, wrapperClassName, stickyHeader, stickyFooter, disabled, hasCaption, hasColumns })=>{
44
+ *
45
+ */ const getDataGridClasses = ({ mode, className, wrapperClassName, stickyHeader, stickyFooter, disabled, hasColumns })=>{
45
46
  const overlayClasses = disabled ? getOverlayClasses({
46
47
  mode
47
48
  }) : null;
@@ -51,9 +52,11 @@ import clsx from "clsx";
51
52
  inner: overlayClasses?.inner ?? "",
52
53
  spinnerWrapper: overlayClasses?.spinnerWrapper ?? "",
53
54
  spinner: overlayClasses?.spinner ?? "",
54
- // Outer wrapper - overflow-hidden when sticky, like Panel's outerWrapper.
55
- // Uses flex layout when sticky to allow scrollableContent to respect max-height.
56
- wrapper: clsx("not-prose relative w-full rounded-lg shadow-md", {
55
+ /**
56
+ * Outer wrapper - overflow-hidden when sticky, like Panel's outerWrapper.
57
+ * Uses flex layout when sticky to allow scrollableContent to respect
58
+ * max-height.
59
+ */ wrapper: clsx("not-prose relative w-full rounded-lg shadow-md", {
57
60
  "overflow-x-auto": !hasSticky && !disabled,
58
61
  "overflow-hidden flex flex-col": hasSticky || disabled,
59
62
  "bg-surface-darker": mode === "dark" || mode === "system",
@@ -66,17 +69,14 @@ import clsx from "clsx";
66
69
  "text-copy-dark dark:text-copy-light": mode === "alt-system"
67
70
  }, wrapperClassName),
68
71
  /**
69
- * Scrollable content area with padding for absolute-positioned header/footer.
70
- * Header height varies based on whether caption is present:
71
- * - pt-10 (40px): header row only
72
- * - pt-19 (76px): header row + caption
73
- */ scrollableContent: clsx(// flex-1 + min-h-0 allows this to shrink within flex parent and enable scrolling.
74
- // min-h-0 overrides the default min-height:auto that prevents flex items from shrinking.
75
- "overflow-y-auto overflow-x-hidden rounded-[inherit] flex-1 min-h-0", {
76
- "pt-10": stickyHeader && !hasCaption,
77
- "pt-19": stickyHeader && hasCaption,
78
- "pb-10": stickyFooter
79
- }),
72
+ * Scrollable content area for absolute-positioned header/footer. Padding is
73
+ * applied via inline styles based on measured header/footer heights,
74
+ * eliminating the need for hard-coded values that break when styles change.
75
+ */ scrollableContent: clsx(/**
76
+ * flex-1 + min-h-0 allows this to shrink within flex parent and enable
77
+ * scrolling. min-h-0 overrides the default min-height:auto that prevents
78
+ * flex items from shrinking.
79
+ */ "overflow-y-auto overflow-x-hidden rounded-[inherit] flex-1 min-h-0"),
80
80
  /**
81
81
  * When columns are provided, use CSS Grid so rows can use subgrid for
82
82
  * consistent column alignment. Otherwise, use flexbox.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -27,8 +27,8 @@ import { CellWrapper } from "../DataGridConstants/index.js";
27
27
  return /*#__PURE__*/ jsx(DataGridContext.Consumer, {
28
28
  children: (ctx)=>{
29
29
  /**
30
- * When columns are provided, use display:contents so the body
31
- * doesn't interfere with the grid flow. Rows will use subgrid.
30
+ * When columns are provided, use display:contents so the body doesn't
31
+ * interfere with the grid flow. Rows will use subgrid.
32
32
  */ const bodyClass = ctx.columns ? clsx("contents", className) : clsx("flex flex-col", className);
33
33
  return /*#__PURE__*/ jsx(DataGridContext.Provider, {
34
34
  value: {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -42,9 +42,10 @@ const getCellClasses = ({ cellWrapper, className, compact, align, active, mode,
42
42
  "before:bg-table-active-light": active && mode === "light",
43
43
  "before:bg-table-active-dark dark:before:bg-table-active-light": active && mode === "system",
44
44
  "before:bg-table-active-light dark:before:bg-table-active-dark": active && mode === "alt-system"
45
- }, // Vertical borders.
46
- // self-stretch ensures border spans full row height in grid layout.
47
- {
45
+ }, /**
46
+ * Vertical borders. self-stretch ensures border spans full row height in grid
47
+ * layout.
48
+ */ {
48
49
  "self-stretch": borderLeft || borderRight,
49
50
  "border-l border-l-table-dark": borderLeft && mode === "dark",
50
51
  "border-l border-l-table-light": borderLeft && mode === "light",
@@ -58,7 +59,8 @@ const getCellClasses = ({ cellWrapper, className, compact, align, active, mode,
58
59
  return mainClasses;
59
60
  };
60
61
  /**
61
- * Returns the appropriate ARIA role for the cell based on the cell wrapper type.
62
+ * Returns the appropriate ARIA role for the cell based on the cell wrapper
63
+ * type.
62
64
  */ const getCellRole = (cellWrapper)=>{
63
65
  if (cellWrapper === CellWrapper.HEADER) {
64
66
  return "columnheader";
@@ -81,7 +83,7 @@ const getCellClasses = ({ cellWrapper, className, compact, align, active, mode,
81
83
  borderRight
82
84
  });
83
85
  const role = getCellRole(cellWrapper);
84
- // Apply grid-column span for colSpan > 1
86
+ // Apply grid-column span for colSpan > 1.
85
87
  const cellStyle = colSpan && colSpan > 1 ? {
86
88
  ...style,
87
89
  gridColumn: `span ${colSpan}`
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -76,7 +76,7 @@ import { getCellClasses } from "../DataGridCell/DataGridCell.js";
76
76
  compact,
77
77
  align
78
78
  });
79
- // Flex container for alignment of button within the cell
79
+ // Flex container for alignment of button within the cell.
80
80
  const contentClasses = clsx("flex", {
81
81
  "justify-start": align === "left" || !align,
82
82
  "justify-center": align === "center",
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -3,8 +3,9 @@ import { type BlurEffect, type ThemeMode } from "../DataGridConstants/DataGridCo
3
3
  /**
4
4
  * Generates classes for the DataGridFooter.
5
5
  *
6
- * When columns are provided (subgrid mode), uses display:contents so the
7
- * footer doesn't break the parent grid flow.
6
+ * When columns are provided (subgrid mode), uses display:contents so the footer
7
+ * doesn't break the parent grid flow.
8
+ *
8
9
  */
9
10
  export declare const getFooterClasses: ({ className, stickyFooter, mode, blurEffect, hasColumns, }: {
10
11
  mode: ThemeMode;
@@ -1,11 +1,11 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
6
6
  import { jsx } from "react/jsx-runtime";
7
7
  import clsx from "clsx";
8
- import { useEffect } from "react";
8
+ import { useEffect, useLayoutEffect, useRef } from "react";
9
9
  import { getHeaderFooterBackgroundClasses, getStickyBlurClasses } from "../common/utilities.js";
10
10
  import { DataGridContext } from "../DataGrid/DataGridContext.js";
11
11
  import { BlurEffects, CellWrapper } from "../DataGridConstants/DataGridConstants.js";
@@ -32,8 +32,9 @@ import { BlurEffects, CellWrapper } from "../DataGridConstants/DataGridConstants
32
32
  /**
33
33
  * Generates classes for the DataGridFooter.
34
34
  *
35
- * When columns are provided (subgrid mode), uses display:contents so the
36
- * footer doesn't break the parent grid flow.
35
+ * When columns are provided (subgrid mode), uses display:contents so the footer
36
+ * doesn't break the parent grid flow.
37
+ *
37
38
  */ const getFooterClasses = ({ className, stickyFooter, mode, blurEffect, hasColumns })=>{
38
39
  /**
39
40
  * When columns are provided and not sticky, use display:contents so the
@@ -74,13 +75,15 @@ import { BlurEffects, CellWrapper } from "../DataGridConstants/DataGridConstants
74
75
  };
75
76
  DataGridFooter.displayName = "DataGridFooter";
76
77
  /**
77
- * Inner component to handle the useEffect for registering footer.
78
- * Separated to avoid hooks inside Consumer render prop.
78
+ * Inner component to handle the useEffect for registering footer. Separated to
79
+ * avoid hooks inside Consumer render prop.
79
80
  */ const DataGridFooterInner = ({ className, ctx, children, ...rest })=>{
80
81
  const hasColumns = Boolean(ctx.columns);
81
- // Register this footer with the parent DataGrid on mount.
82
- // This enables sticky footer behavior regardless of nesting depth.
83
- useEffect(()=>{
82
+ const footerRef = useRef(null);
83
+ /**
84
+ * Register this footer with the parent DataGrid on mount. This enables sticky
85
+ * footer behavior regardless of nesting depth.
86
+ */ useEffect(()=>{
84
87
  ctx.registerFooter?.();
85
88
  return ()=>{
86
89
  ctx.unregisterFooter?.();
@@ -89,12 +92,33 @@ DataGridFooter.displayName = "DataGridFooter";
89
92
  ctx.registerFooter,
90
93
  ctx.unregisterFooter
91
94
  ]);
95
+ /**
96
+ * Measure and report footer height for dynamic padding calculation. Uses
97
+ * ResizeObserver to handle dynamic content changes.
98
+ */ useLayoutEffect(()=>{
99
+ const element = footerRef.current;
100
+ if (!element || !ctx.stickyFooter || !ctx.setFooterHeight) {
101
+ return;
102
+ }
103
+ const observer = new ResizeObserver((entries)=>{
104
+ const height = entries[0]?.borderBoxSize?.[0]?.blockSize;
105
+ if (height !== undefined) {
106
+ ctx.setFooterHeight?.(height);
107
+ }
108
+ });
109
+ observer.observe(element);
110
+ return ()=>observer.disconnect();
111
+ }, [
112
+ ctx.stickyFooter,
113
+ ctx.setFooterHeight
114
+ ]);
92
115
  return /*#__PURE__*/ jsx(DataGridContext.Provider, {
93
116
  value: {
94
117
  ...ctx,
95
118
  cellWrapper: CellWrapper.FOOTER
96
119
  },
97
120
  children: /*#__PURE__*/ jsx("div", {
121
+ ref: footerRef,
98
122
  role: "rowgroup",
99
123
  className: getFooterClasses({
100
124
  className,
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -3,12 +3,13 @@ import { type BlurEffect, type ThemeMode } from "../DataGridConstants/DataGridCo
3
3
  /**
4
4
  * Generates classes for the DataGridHeader wrapper.
5
5
  *
6
- * When stickyHeader is true, uses position:absolute so the header overlays
7
- * the scrollbar area. The parent DataGrid auto-detects caption presence and
8
- * applies appropriate padding to the scrollable content.
6
+ * When stickyHeader is true, uses position:absolute so the header overlays the
7
+ * scrollbar area. The parent DataGrid auto-detects caption presence and applies
8
+ * appropriate padding to the scrollable content.
9
+ *
10
+ * When columns are provided (subgrid mode) and not sticky, uses
11
+ * display:contents so the header doesn't break the parent grid flow.
9
12
  *
10
- * When columns are provided (subgrid mode) and not sticky, uses display:contents
11
- * so the header doesn't break the parent grid flow.
12
13
  */
13
14
  export declare const getHeaderClasses: ({ className, stickyHeader, mode, blurEffect, hasColumns, }: {
14
15
  mode: ThemeMode;
@@ -18,8 +19,8 @@ export declare const getHeaderClasses: ({ className, stickyHeader, mode, blurEff
18
19
  stickyHeader?: boolean;
19
20
  }) => string;
20
21
  /**
21
- * Generates classes for the caption element inside DataGridHeader.
22
- * When hasColumns is true (subgrid mode), adds col-span-full to span all columns.
22
+ * Generates classes for the caption element inside DataGridHeader. When
23
+ * hasColumns is true (subgrid mode), adds col-span-full to span all columns.
23
24
  */
24
25
  export declare const getCaptionClasses: ({ captionClassName, hasColumns, }: {
25
26
  captionClassName?: string;
@@ -1,11 +1,11 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import clsx from "clsx";
8
- import { useEffect, useId } from "react";
8
+ import { useEffect, useId, useLayoutEffect, useRef } from "react";
9
9
  import { getHeaderFooterBackgroundClasses, getHeaderFooterCopyClasses, getStickyBlurClasses } from "../common/utilities.js";
10
10
  import { DataGridContext } from "../DataGrid/DataGridContext.js";
11
11
  import { BlurEffects, CellWrapper } from "../DataGridConstants/DataGridConstants.js";
@@ -32,12 +32,13 @@ import { BlurEffects, CellWrapper } from "../DataGridConstants/DataGridConstants
32
32
  /**
33
33
  * Generates classes for the DataGridHeader wrapper.
34
34
  *
35
- * When stickyHeader is true, uses position:absolute so the header overlays
36
- * the scrollbar area. The parent DataGrid auto-detects caption presence and
37
- * applies appropriate padding to the scrollable content.
35
+ * When stickyHeader is true, uses position:absolute so the header overlays the
36
+ * scrollbar area. The parent DataGrid auto-detects caption presence and applies
37
+ * appropriate padding to the scrollable content.
38
+ *
39
+ * When columns are provided (subgrid mode) and not sticky, uses
40
+ * display:contents so the header doesn't break the parent grid flow.
38
41
  *
39
- * When columns are provided (subgrid mode) and not sticky, uses display:contents
40
- * so the header doesn't break the parent grid flow.
41
42
  */ const getHeaderClasses = ({ className, stickyHeader, mode, blurEffect, hasColumns })=>{
42
43
  const hasBlur = Boolean(blurEffect && blurEffect !== BlurEffects.NONE);
43
44
  /**
@@ -53,7 +54,7 @@ import { BlurEffects, CellWrapper } from "../DataGridConstants/DataGridConstants
53
54
  }), className);
54
55
  }
55
56
  return clsx("flex flex-col", {
56
- // Absolute positioning to overlay scrollbar, matching footer behavior
57
+ // Absolute positioning to overlay scrollbar, matching footer behavior.
57
58
  "absolute left-0 right-0 z-20 top-0 rounded-t-lg": stickyHeader
58
59
  }, getHeaderFooterBackgroundClasses({
59
60
  mode,
@@ -66,10 +67,10 @@ import { BlurEffects, CellWrapper } from "../DataGridConstants/DataGridConstants
66
67
  }), className);
67
68
  };
68
69
  /**
69
- * Generates classes for the caption element inside DataGridHeader.
70
- * When hasColumns is true (subgrid mode), adds col-span-full to span all columns.
70
+ * Generates classes for the caption element inside DataGridHeader. When
71
+ * hasColumns is true (subgrid mode), adds col-span-full to span all columns.
71
72
  */ const getCaptionClasses = ({ captionClassName, hasColumns })=>{
72
- return clsx("py-2 text-sm text-center font-bold", // In subgrid mode, caption needs to span all columns
73
+ return clsx("py-2 text-sm text-center font-bold", // In subgrid mode, caption needs to span all columns.
73
74
  hasColumns && "col-span-full", captionClassName);
74
75
  };
75
76
  /* =============================================================================
@@ -94,9 +95,11 @@ DataGridHeader.displayName = "DataGridHeader";
94
95
  * Separated to avoid hooks inside Consumer render prop.
95
96
  */ const DataGridHeaderInner = ({ caption, captionClassName, captionId, className, ctx, children, ...rest })=>{
96
97
  const hasColumns = Boolean(ctx.columns);
97
- // Register this header with the parent DataGrid on mount.
98
- // This enables sticky header behavior regardless of nesting depth.
99
- useEffect(()=>{
98
+ const headerRef = useRef(null);
99
+ /**
100
+ * Register this header with the parent DataGrid on mount. This enables sticky
101
+ * header behavior regardless of nesting depth.
102
+ */ useEffect(()=>{
100
103
  ctx.registerHeader?.();
101
104
  return ()=>{
102
105
  ctx.unregisterHeader?.();
@@ -105,7 +108,7 @@ DataGridHeader.displayName = "DataGridHeader";
105
108
  ctx.registerHeader,
106
109
  ctx.unregisterHeader
107
110
  ]);
108
- // Register the caption ID with the parent DataGrid for aria-labelledby
111
+ // Register the caption ID with the parent DataGrid for aria-labelledby.
109
112
  useEffect(()=>{
110
113
  if (caption && ctx.setCaptionId) {
111
114
  ctx.setCaptionId(captionId);
@@ -120,12 +123,33 @@ DataGridHeader.displayName = "DataGridHeader";
120
123
  captionId,
121
124
  ctx.setCaptionId
122
125
  ]);
126
+ /**
127
+ * Measure and report header height for dynamic padding calculation. Uses
128
+ * ResizeObserver to handle dynamic content changes (text wrapping, etc.).
129
+ */ useLayoutEffect(()=>{
130
+ const element = headerRef.current;
131
+ if (!element || !ctx.stickyHeader || !ctx.setHeaderHeight) {
132
+ return;
133
+ }
134
+ const observer = new ResizeObserver((entries)=>{
135
+ const height = entries[0]?.borderBoxSize?.[0]?.blockSize;
136
+ if (height !== undefined) {
137
+ ctx.setHeaderHeight?.(height);
138
+ }
139
+ });
140
+ observer.observe(element);
141
+ return ()=>observer.disconnect();
142
+ }, [
143
+ ctx.stickyHeader,
144
+ ctx.setHeaderHeight
145
+ ]);
123
146
  return /*#__PURE__*/ jsx(DataGridContext.Provider, {
124
147
  value: {
125
148
  ...ctx,
126
149
  cellWrapper: CellWrapper.HEADER
127
150
  },
128
151
  children: /*#__PURE__*/ jsxs("div", {
152
+ ref: headerRef,
129
153
  role: "rowgroup",
130
154
  className: getHeaderClasses({
131
155
  className,
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Finds the nearest scrollable ancestor of an element.
3
- * Returns null if no scrollable ancestor is found (uses viewport).
2
+ * Finds the nearest scrollable ancestor of an element. Returns null if no
3
+ * scrollable ancestor is found (uses viewport).
4
4
  */
5
5
  export declare function findScrollableAncestor(element: HTMLElement): Element | null;
6
6
  export type UseInfiniteScrollOptions = {
@@ -34,9 +34,9 @@ export type UseInfiniteScrollOptions = {
34
34
  */
35
35
  onLoadMore?: (newVisibleCount: number) => void;
36
36
  /**
37
- * The scroll container element for IntersectionObserver root.
38
- * Required when the DataGrid is inside a scrollable container (e.g., with maxHeight).
39
- * Pass null to use the viewport as root.
37
+ * The scroll container element for IntersectionObserver root. Required when
38
+ * the DataGrid is inside a scrollable container (e.g., with maxHeight). Pass
39
+ * null to use the viewport as root.
40
40
  */
41
41
  scrollContainer?: Element | null;
42
42
  };
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -13,8 +13,8 @@ const DEFAULT_BATCH_SIZE = 20;
13
13
  const DEFAULT_THRESHOLD = 5;
14
14
  const DEFAULT_ROOT_MARGIN = "20px";
15
15
  /**
16
- * Finds the nearest scrollable ancestor of an element.
17
- * Returns null if no scrollable ancestor is found (uses viewport).
16
+ * Finds the nearest scrollable ancestor of an element. Returns null if no
17
+ * scrollable ancestor is found (uses viewport).
18
18
  */ function findScrollableAncestor(element) {
19
19
  let parent = element.parentElement;
20
20
  while(parent){
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -26,7 +26,7 @@ import { CellWrapper } from "../DataGridConstants/index.js";
26
26
 
27
27
 
28
28
  const getRowClasses = ({ mode, className, cellWrapper })=>{
29
- // Always use CSS Grid for proper column alignment, with vertical centering
29
+ // Always use CSS Grid for proper column alignment, with vertical centering.
30
30
  const layoutClass = "grid items-center";
31
31
  if (cellWrapper === CellWrapper.HEADER || cellWrapper === CellWrapper.FOOTER) {
32
32
  /**
@@ -52,43 +52,44 @@ const getRowClasses = ({ mode, className, cellWrapper })=>{
52
52
  /* =============================================================================
53
53
  * DataGridRow
54
54
  * ========================================================================== */ const DataGridRow = ({ className, children, style: userStyle, ...rest })=>{
55
- // Count the number of direct children to determine column count
55
+ // Count the number of direct children to determine column count.
56
56
  const columnCount = react.Children.count(children);
57
57
  return /*#__PURE__*/ jsx(DataGridContext.Consumer, {
58
58
  children: ({ mode, cellWrapper, stickyHeader, stickyFooter, columns })=>{
59
59
  /**
60
- * Determine if this row is inside a sticky header/footer.
61
- * Sticky elements are absolutely positioned and outside the main grid
62
- * flow, so they can't use subgrid.
60
+ * Determine if this row is inside a sticky header/footer. Sticky elements
61
+ * are absolutely positioned and outside the main grid flow, so they can't
62
+ * use subgrid.
63
63
  */ const isInStickyHeader = cellWrapper === CellWrapper.HEADER && stickyHeader;
64
64
  const isInStickyFooter = cellWrapper === CellWrapper.FOOTER && stickyFooter;
65
65
  const isInStickyContext = isInStickyHeader || isInStickyFooter;
66
66
  /**
67
- * When columns are provided AND the row is inside the grid flow
68
- * (not in a sticky header/footer), use CSS subgrid to inherit the
69
- * column sizing from the parent grid.
67
+ * When columns are provided AND the row is inside the grid flow (not in a
68
+ * sticky header/footer), use CSS subgrid to inherit the column sizing from
69
+ * the parent grid.
70
70
  *
71
71
  * For sticky headers/footers, use the explicit column template since
72
72
  * they're absolutely positioned outside the main grid.
73
73
  *
74
- * When columns are not provided, fall back to the original behavior
75
- * where each row defines its own equal-width columns.
74
+ * When columns are not provided, fall back to the original behavior where
75
+ * each row defines its own equal-width columns.
76
+ *
76
77
  */ let rowStyle;
77
78
  if (columns) {
78
79
  if (isInStickyContext) {
79
- // Sticky elements can't use subgrid, use explicit template
80
+ // Sticky elements can't use subgrid, use explicit template.
80
81
  rowStyle = {
81
82
  gridTemplateColumns: columns.join(" ")
82
83
  };
83
84
  } else {
84
- // Normal flow: use subgrid to inherit from parent grid
85
+ // Normal flow: use subgrid to inherit from parent grid.
85
86
  rowStyle = {
86
87
  gridColumn: "1 / -1",
87
88
  gridTemplateColumns: "subgrid"
88
89
  };
89
90
  }
90
91
  } else {
91
- // No columns prop: use equal-width columns
92
+ // No columns prop: use equal-width columns.
92
93
  rowStyle = {
93
94
  gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`
94
95
  };
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.0
2
+ @versini/ui-datagrid v0.3.2
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versini/ui-datagrid",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "publishConfig": {
@@ -94,5 +94,5 @@
94
94
  "sideEffects": [
95
95
  "**/*.css"
96
96
  ],
97
- "gitHead": "f67b8a86233049086793d0b19d160c25fbfc0828"
97
+ "gitHead": "598551d562037d7496d877be9a03b1ee8e687de5"
98
98
  }