@crispy-seed/data-table 0.14.0 → 0.14.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 +34 -8
- package/dist/index.js +34 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1668,6 +1668,11 @@ const ROOT_MARGIN = "160px";
|
|
|
1668
1668
|
const elementRef = React.useRef(null);
|
|
1669
1669
|
const [scrolledStart, setScrolledStart] = React.useState(false);
|
|
1670
1670
|
const [scrolledEnd, setScrolledEnd] = React.useState(false);
|
|
1671
|
+
// The scroll container's own content-box width (0 until first measured). The
|
|
1672
|
+
// styled layer reads it to cap how much of a narrow viewport the sticky-left
|
|
1673
|
+
// pinned group may occupy (`useEffectiveColumns`) -- without a cap, a pinned
|
|
1674
|
+
// group wider than the container covers every scrolling column for good.
|
|
1675
|
+
const [viewportWidth, setViewportWidth] = React.useState(0);
|
|
1671
1676
|
const [viewport, setViewport] = React.useState({
|
|
1672
1677
|
scrollTop: 0,
|
|
1673
1678
|
viewportHeight: 0
|
|
@@ -1688,6 +1693,7 @@ const ROOT_MARGIN = "160px";
|
|
|
1688
1693
|
viewportHeight
|
|
1689
1694
|
});
|
|
1690
1695
|
}
|
|
1696
|
+
setViewportWidth(element.clientWidth);
|
|
1691
1697
|
setScrolledStart(element.scrollLeft > 0);
|
|
1692
1698
|
// A 1px slop: sub-pixel layout can leave `scrollLeft + clientWidth` a fraction
|
|
1693
1699
|
// short of `scrollWidth` even when fully scrolled, which would otherwise leave
|
|
@@ -1697,16 +1703,16 @@ const ROOT_MARGIN = "160px";
|
|
|
1697
1703
|
virtualized,
|
|
1698
1704
|
rowHeight
|
|
1699
1705
|
]);
|
|
1700
|
-
// The container can resize without scrolling (window resize, a
|
|
1701
|
-
// changing height) -- the viewport height feeds the window
|
|
1706
|
+
// The container can resize without scrolling (window resize, rotating a phone, a
|
|
1707
|
+
// bounded parent changing height) -- the viewport height feeds the virtual window
|
|
1708
|
+
// and the width feeds the pinned-group cap, so re-measure then too.
|
|
1702
1709
|
React.useEffect(()=>{
|
|
1703
1710
|
const element = elementRef.current;
|
|
1704
|
-
if (!
|
|
1711
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
1705
1712
|
const observer = new ResizeObserver(()=>remeasure());
|
|
1706
1713
|
observer.observe(element);
|
|
1707
1714
|
return ()=>observer.disconnect();
|
|
1708
1715
|
}, [
|
|
1709
|
-
virtualized,
|
|
1710
1716
|
remeasure
|
|
1711
1717
|
]);
|
|
1712
1718
|
const scrollAreaRef = React.useCallback((node)=>{
|
|
@@ -1722,6 +1728,7 @@ const ROOT_MARGIN = "160px";
|
|
|
1722
1728
|
scrollAreaRef,
|
|
1723
1729
|
scrolledStart,
|
|
1724
1730
|
scrolledEnd,
|
|
1731
|
+
viewportWidth,
|
|
1725
1732
|
onScroll,
|
|
1726
1733
|
remeasure,
|
|
1727
1734
|
/** Row-virtualization state; `enabled: false` (the default) means render every row. */ virtual: {
|
|
@@ -3658,6 +3665,13 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3658
3665
|
});
|
|
3659
3666
|
}
|
|
3660
3667
|
|
|
3668
|
+
/** Share of the scroll container's width the sticky-left pinned group may occupy
|
|
3669
|
+
* before FURTHER pinned columns stop sticking and scroll like any other column. A
|
|
3670
|
+
* pinned group at or past the container's width pins every pixel of it: the
|
|
3671
|
+
* scrolling columns slide in underneath the sticky cells and can never be seen (a
|
|
3672
|
+
* 375px phone with Serial + one more pinned column showed nothing else, ever). The
|
|
3673
|
+
* FIRST pinned column always sticks regardless -- the entity/name column staying
|
|
3674
|
+
* frozen is the whole point of pinning. */ const MAX_PINNED_VIEWPORT_SHARE = 0.6;
|
|
3661
3675
|
/** Fallback width (px) used to compute a pinned column's cumulative sticky offset when
|
|
3662
3676
|
* it has no explicit `width` and hasn't been drag-resized -- pinning needs a STATIC
|
|
3663
3677
|
* number to put in an inline `insetInlineStart`, and `table-layout: fixed` only
|
|
@@ -3677,7 +3691,7 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3677
3691
|
* one and a header row built from the other drift out of sync under `table-layout:
|
|
3678
3692
|
* fixed`'s own column-to-cell matching.
|
|
3679
3693
|
*/ function useEffectiveColumns(columns, options) {
|
|
3680
|
-
const { visibleColumnOrder, getColumnLabel, getColumnWidth, pinnedColumnKeys } = useDataTableContext();
|
|
3694
|
+
const { visibleColumnOrder, getColumnLabel, getColumnWidth, pinnedColumnKeys, viewportWidth } = useDataTableContext();
|
|
3681
3695
|
const { selectable, stickyFirst } = options;
|
|
3682
3696
|
return React__namespace.useMemo(()=>{
|
|
3683
3697
|
const byKey = new Map(columns.map((column)=>[
|
|
@@ -3703,11 +3717,14 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3703
3717
|
const bPinned = pinnedSet.has(b) ? 0 : 1;
|
|
3704
3718
|
return aPinned - bPinned;
|
|
3705
3719
|
});
|
|
3720
|
+
// 0 = not measured yet (first paint / jsdom) -- no cap rather than a guess.
|
|
3721
|
+
const pinBudget = viewportWidth ? viewportWidth * MAX_PINNED_VIEWPORT_SHARE : Number.POSITIVE_INFINITY;
|
|
3706
3722
|
let cursor = selectable ? 40 : 0;
|
|
3707
3723
|
let pinnedCount = 0;
|
|
3724
|
+
let budgetSpent = false;
|
|
3708
3725
|
const entries = ordered.map((key)=>byKey.get(key)).filter((column)=>Boolean(column)).map((column)=>{
|
|
3709
3726
|
const rawWidth = getColumnWidth(column.key) ?? column.width;
|
|
3710
|
-
const
|
|
3727
|
+
const inPinnedSet = pinnedSet.has(column.key);
|
|
3711
3728
|
// Fix (0.10.4, screenshot-confirmed pinned-column overlay): a pinned column
|
|
3712
3729
|
// with no explicit/resized width used to keep `width` as `undefined` here,
|
|
3713
3730
|
// which `DataTableColGroup` (DataTable.tsx) renders as an AUTO `<col>` --
|
|
@@ -3727,7 +3744,15 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3727
3744
|
// without its own width now always renders at exactly
|
|
3728
3745
|
// `DEFAULT_PINNED_COLUMN_WIDTH`, matching what every other pinned column's
|
|
3729
3746
|
// offset already assumed it would be.
|
|
3730
|
-
const width =
|
|
3747
|
+
const width = inPinnedSet && rawWidth === undefined ? DEFAULT_PINNED_COLUMN_WIDTH : rawWidth;
|
|
3748
|
+
// Sticks only while the group still fits the pin budget (see
|
|
3749
|
+
// MAX_PINNED_VIEWPORT_SHARE); the first pinned column always sticks.
|
|
3750
|
+
// Once one pinned column overflows, every later one scrolls too -- a narrower
|
|
3751
|
+
// late column must not stick past a gap.
|
|
3752
|
+
if (inPinnedSet && pinnedCount > 0 && cursor + (width ?? DEFAULT_PINNED_COLUMN_WIDTH) > pinBudget) {
|
|
3753
|
+
budgetSpent = true;
|
|
3754
|
+
}
|
|
3755
|
+
const pinned = inPinnedSet && !budgetSpent;
|
|
3731
3756
|
const pinOffset = pinned ? cursor : 0;
|
|
3732
3757
|
if (pinned) {
|
|
3733
3758
|
cursor += width ?? DEFAULT_PINNED_COLUMN_WIDTH;
|
|
@@ -3766,7 +3791,8 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3766
3791
|
getColumnWidth,
|
|
3767
3792
|
pinnedColumnKeys,
|
|
3768
3793
|
selectable,
|
|
3769
|
-
stickyFirst
|
|
3794
|
+
stickyFirst,
|
|
3795
|
+
viewportWidth
|
|
3770
3796
|
]);
|
|
3771
3797
|
}
|
|
3772
3798
|
/** The 40px selection column's own sticky-left state -- `stickyFirst` (the table-wide
|
package/dist/index.js
CHANGED
|
@@ -1649,6 +1649,11 @@ const ROOT_MARGIN = "160px";
|
|
|
1649
1649
|
const elementRef = useRef(null);
|
|
1650
1650
|
const [scrolledStart, setScrolledStart] = useState(false);
|
|
1651
1651
|
const [scrolledEnd, setScrolledEnd] = useState(false);
|
|
1652
|
+
// The scroll container's own content-box width (0 until first measured). The
|
|
1653
|
+
// styled layer reads it to cap how much of a narrow viewport the sticky-left
|
|
1654
|
+
// pinned group may occupy (`useEffectiveColumns`) -- without a cap, a pinned
|
|
1655
|
+
// group wider than the container covers every scrolling column for good.
|
|
1656
|
+
const [viewportWidth, setViewportWidth] = useState(0);
|
|
1652
1657
|
const [viewport, setViewport] = useState({
|
|
1653
1658
|
scrollTop: 0,
|
|
1654
1659
|
viewportHeight: 0
|
|
@@ -1669,6 +1674,7 @@ const ROOT_MARGIN = "160px";
|
|
|
1669
1674
|
viewportHeight
|
|
1670
1675
|
});
|
|
1671
1676
|
}
|
|
1677
|
+
setViewportWidth(element.clientWidth);
|
|
1672
1678
|
setScrolledStart(element.scrollLeft > 0);
|
|
1673
1679
|
// A 1px slop: sub-pixel layout can leave `scrollLeft + clientWidth` a fraction
|
|
1674
1680
|
// short of `scrollWidth` even when fully scrolled, which would otherwise leave
|
|
@@ -1678,16 +1684,16 @@ const ROOT_MARGIN = "160px";
|
|
|
1678
1684
|
virtualized,
|
|
1679
1685
|
rowHeight
|
|
1680
1686
|
]);
|
|
1681
|
-
// The container can resize without scrolling (window resize, a
|
|
1682
|
-
// changing height) -- the viewport height feeds the window
|
|
1687
|
+
// The container can resize without scrolling (window resize, rotating a phone, a
|
|
1688
|
+
// bounded parent changing height) -- the viewport height feeds the virtual window
|
|
1689
|
+
// and the width feeds the pinned-group cap, so re-measure then too.
|
|
1683
1690
|
useEffect(()=>{
|
|
1684
1691
|
const element = elementRef.current;
|
|
1685
|
-
if (!
|
|
1692
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
1686
1693
|
const observer = new ResizeObserver(()=>remeasure());
|
|
1687
1694
|
observer.observe(element);
|
|
1688
1695
|
return ()=>observer.disconnect();
|
|
1689
1696
|
}, [
|
|
1690
|
-
virtualized,
|
|
1691
1697
|
remeasure
|
|
1692
1698
|
]);
|
|
1693
1699
|
const scrollAreaRef = useCallback((node)=>{
|
|
@@ -1703,6 +1709,7 @@ const ROOT_MARGIN = "160px";
|
|
|
1703
1709
|
scrollAreaRef,
|
|
1704
1710
|
scrolledStart,
|
|
1705
1711
|
scrolledEnd,
|
|
1712
|
+
viewportWidth,
|
|
1706
1713
|
onScroll,
|
|
1707
1714
|
remeasure,
|
|
1708
1715
|
/** Row-virtualization state; `enabled: false` (the default) means render every row. */ virtual: {
|
|
@@ -3639,6 +3646,13 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3639
3646
|
});
|
|
3640
3647
|
}
|
|
3641
3648
|
|
|
3649
|
+
/** Share of the scroll container's width the sticky-left pinned group may occupy
|
|
3650
|
+
* before FURTHER pinned columns stop sticking and scroll like any other column. A
|
|
3651
|
+
* pinned group at or past the container's width pins every pixel of it: the
|
|
3652
|
+
* scrolling columns slide in underneath the sticky cells and can never be seen (a
|
|
3653
|
+
* 375px phone with Serial + one more pinned column showed nothing else, ever). The
|
|
3654
|
+
* FIRST pinned column always sticks regardless -- the entity/name column staying
|
|
3655
|
+
* frozen is the whole point of pinning. */ const MAX_PINNED_VIEWPORT_SHARE = 0.6;
|
|
3642
3656
|
/** Fallback width (px) used to compute a pinned column's cumulative sticky offset when
|
|
3643
3657
|
* it has no explicit `width` and hasn't been drag-resized -- pinning needs a STATIC
|
|
3644
3658
|
* number to put in an inline `insetInlineStart`, and `table-layout: fixed` only
|
|
@@ -3658,7 +3672,7 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3658
3672
|
* one and a header row built from the other drift out of sync under `table-layout:
|
|
3659
3673
|
* fixed`'s own column-to-cell matching.
|
|
3660
3674
|
*/ function useEffectiveColumns(columns, options) {
|
|
3661
|
-
const { visibleColumnOrder, getColumnLabel, getColumnWidth, pinnedColumnKeys } = useDataTableContext();
|
|
3675
|
+
const { visibleColumnOrder, getColumnLabel, getColumnWidth, pinnedColumnKeys, viewportWidth } = useDataTableContext();
|
|
3662
3676
|
const { selectable, stickyFirst } = options;
|
|
3663
3677
|
return React.useMemo(()=>{
|
|
3664
3678
|
const byKey = new Map(columns.map((column)=>[
|
|
@@ -3684,11 +3698,14 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3684
3698
|
const bPinned = pinnedSet.has(b) ? 0 : 1;
|
|
3685
3699
|
return aPinned - bPinned;
|
|
3686
3700
|
});
|
|
3701
|
+
// 0 = not measured yet (first paint / jsdom) -- no cap rather than a guess.
|
|
3702
|
+
const pinBudget = viewportWidth ? viewportWidth * MAX_PINNED_VIEWPORT_SHARE : Number.POSITIVE_INFINITY;
|
|
3687
3703
|
let cursor = selectable ? 40 : 0;
|
|
3688
3704
|
let pinnedCount = 0;
|
|
3705
|
+
let budgetSpent = false;
|
|
3689
3706
|
const entries = ordered.map((key)=>byKey.get(key)).filter((column)=>Boolean(column)).map((column)=>{
|
|
3690
3707
|
const rawWidth = getColumnWidth(column.key) ?? column.width;
|
|
3691
|
-
const
|
|
3708
|
+
const inPinnedSet = pinnedSet.has(column.key);
|
|
3692
3709
|
// Fix (0.10.4, screenshot-confirmed pinned-column overlay): a pinned column
|
|
3693
3710
|
// with no explicit/resized width used to keep `width` as `undefined` here,
|
|
3694
3711
|
// which `DataTableColGroup` (DataTable.tsx) renders as an AUTO `<col>` --
|
|
@@ -3708,7 +3725,15 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3708
3725
|
// without its own width now always renders at exactly
|
|
3709
3726
|
// `DEFAULT_PINNED_COLUMN_WIDTH`, matching what every other pinned column's
|
|
3710
3727
|
// offset already assumed it would be.
|
|
3711
|
-
const width =
|
|
3728
|
+
const width = inPinnedSet && rawWidth === undefined ? DEFAULT_PINNED_COLUMN_WIDTH : rawWidth;
|
|
3729
|
+
// Sticks only while the group still fits the pin budget (see
|
|
3730
|
+
// MAX_PINNED_VIEWPORT_SHARE); the first pinned column always sticks.
|
|
3731
|
+
// Once one pinned column overflows, every later one scrolls too -- a narrower
|
|
3732
|
+
// late column must not stick past a gap.
|
|
3733
|
+
if (inPinnedSet && pinnedCount > 0 && cursor + (width ?? DEFAULT_PINNED_COLUMN_WIDTH) > pinBudget) {
|
|
3734
|
+
budgetSpent = true;
|
|
3735
|
+
}
|
|
3736
|
+
const pinned = inPinnedSet && !budgetSpent;
|
|
3712
3737
|
const pinOffset = pinned ? cursor : 0;
|
|
3713
3738
|
if (pinned) {
|
|
3714
3739
|
cursor += width ?? DEFAULT_PINNED_COLUMN_WIDTH;
|
|
@@ -3747,7 +3772,8 @@ function RatingCell({ value, maxRating = 5 }) {
|
|
|
3747
3772
|
getColumnWidth,
|
|
3748
3773
|
pinnedColumnKeys,
|
|
3749
3774
|
selectable,
|
|
3750
|
-
stickyFirst
|
|
3775
|
+
stickyFirst,
|
|
3776
|
+
viewportWidth
|
|
3751
3777
|
]);
|
|
3752
3778
|
}
|
|
3753
3779
|
/** The 40px selection column's own sticky-left state -- `stickyFirst` (the table-wide
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crispy-seed/data-table",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Self-contained, publishable build of seed-design's Attio-measured DataTable, ResizablePanel and RecordPreview — one install, one CSS import.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|