@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.
- package/dist/DataGrid/DataGrid.js +33 -24
- package/dist/DataGrid/DataGridContext.js +1 -1
- package/dist/DataGrid/index.js +1 -1
- package/dist/DataGrid/utilities.d.ts +12 -8
- package/dist/DataGrid/utilities.js +18 -18
- package/dist/DataGridAnimated/AnimatedWrapper.js +1 -1
- package/dist/DataGridAnimated/index.js +1 -1
- package/dist/DataGridAnimated/useAnimatedHeight.js +1 -1
- package/dist/DataGridBody/DataGridBody.js +3 -3
- package/dist/DataGridBody/index.js +1 -1
- package/dist/DataGridCell/DataGridCell.js +8 -6
- package/dist/DataGridCell/index.js +1 -1
- package/dist/DataGridCellSort/DataGridCellSort.js +2 -2
- package/dist/DataGridCellSort/index.js +1 -1
- package/dist/DataGridConstants/DataGridConstants.js +1 -1
- package/dist/DataGridConstants/index.js +1 -1
- package/dist/DataGridFooter/DataGridFooter.d.ts +3 -2
- package/dist/DataGridFooter/DataGridFooter.js +33 -9
- package/dist/DataGridFooter/index.js +1 -1
- package/dist/DataGridHeader/DataGridHeader.d.ts +8 -7
- package/dist/DataGridHeader/DataGridHeader.js +39 -15
- package/dist/DataGridHeader/index.js +1 -1
- package/dist/DataGridInfinite/InfiniteScrollMarker.js +1 -1
- package/dist/DataGridInfinite/index.js +1 -1
- package/dist/DataGridInfinite/useInfiniteScroll.d.ts +5 -5
- package/dist/DataGridInfinite/useInfiniteScroll.js +3 -3
- package/dist/DataGridRow/DataGridRow.js +15 -14
- package/dist/DataGridRow/index.js +1 -1
- package/dist/DataGridSorting/index.js +1 -1
- package/dist/DataGridSorting/sortingUtils.js +1 -1
- package/dist/common/utilities.js +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v0.3.
|
|
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
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
-
*
|
|
51
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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:
|
|
170
|
+
style: scrollableContentStyle,
|
|
162
171
|
children: gridContent
|
|
163
172
|
}) : gridContent
|
|
164
173
|
})
|
package/dist/DataGrid/index.js
CHANGED
|
@@ -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
|
|
6
|
+
* - Scrollable area is a separate inner container with dynamic padding
|
|
7
7
|
* - Header/footer use position:absolute to overlay scrollbar
|
|
8
|
-
* - Padding is
|
|
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,
|
|
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
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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.
|
|
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
|
|
38
|
+
* - Scrollable area is a separate inner container with dynamic padding
|
|
39
39
|
* - Header/footer use position:absolute to overlay scrollbar
|
|
40
|
-
* - Padding is
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
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
|
-
*
|
|
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.
|
|
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
|
-
},
|
|
46
|
-
|
|
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
|
|
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.
|
|
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",
|
|
@@ -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
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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,
|
|
@@ -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
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
37
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Finds the nearest scrollable ancestor of an element.
|
|
3
|
-
*
|
|
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
|
-
*
|
|
39
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
62
|
-
*
|
|
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
|
-
*
|
|
69
|
-
*
|
|
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
|
-
*
|
|
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
|
};
|
package/dist/common/utilities.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versini/ui-datagrid",
|
|
3
|
-
"version": "0.3.
|
|
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": "
|
|
97
|
+
"gitHead": "598551d562037d7496d877be9a03b1ee8e687de5"
|
|
98
98
|
}
|