Skip to content

fix(table): floor fractional table width so columns don't overflow#10238

Open
ardittirana wants to merge 3 commits into
adobe:mainfrom
ardittirana:fix-tablelayout-fractional-width
Open

fix(table): floor fractional table width so columns don't overflow#10238
ardittirana wants to merge 3 commits into
adobe:mainfrom
ardittirana:fix-tablelayout-fractional-width

Conversation

@ardittirana

Copy link
Copy Markdown

Closes

Closes #9448

📣 Description

calculateColumnSizes distributes column widths and then calls cascadeRounding, whose contract (per its comment) is "given an array of floats that sum to an integer…". When the table width is fractional — e.g. a percentage-based width derived from the page size — that invariant no longer holds: the per-column target sizes sum to a non-integer, and the cascade rounds the total up (e.g. round(1000.5) = 1001). The columns then sum to more than the available width, producing an unexpected horizontal scrollbar even with overflow-x: clip.

🔎 Fix

Floor availableWidth once at the start of calculateColumnSizes so the rounding invariant holds. Integer table widths are unchanged (floor(n) === n); only fractional widths are corrected, and columns can no longer round past the container.

🧪 How verified

Added a regression test to packages/react-stately/test/table/TableUtils.test.js:
with tableWidth = 1000.5 and two 1fr columns, the result is [500, 500] (sum 1000 ≤ floor(1000.5)). On the current code it returns [500, 501] (sum 1001), so the test fails before this change and passes after. Existing TableUtils cases (integer widths) are unaffected.

calculateColumnSizes relies on cascadeRounding, which assumes the target column
sizes sum to an integer. With a fractional table width (e.g. a percentage-based
size) that invariant breaks and the rounded widths can exceed the available
width by 1px, producing an unexpected horizontal scrollbar. Flooring the
available width keeps the sum integral; integer widths are unchanged.

Adds a regression test to TableUtils. Fixes adobe#9448.
@ardittirana ardittirana force-pushed the fix-tablelayout-fractional-width branch from 7a60738 to cbe5c8c Compare June 21, 2026 13:05
@ardittirana ardittirana reopened this Jun 21, 2026
() => 150,
() => 50
);
expect(widths).toStrictEqual([500, 500]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is what we actually want, then there's a .5px gap in the last column and the edge of the table, which could lead to odd rendering artifacts on higher resolution monitors.

Did you try assigning the decimals to the last column? in this case, [500, 500.5] so that the sum matches the table width? We should be able to handle it inside cascade rounding.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — you're right that flooring leaves a sub-pixel gap at the table edge. I've switched to your approach: cascadeRounding now assigns the leftover fraction to the last column so the widths sum exactly to the available width, with no flooring.

For 1000.5 with [1fr, 1fr] it now returns [500, 500.5] (and [334, 333, 333.5] for three 1fr columns) — flush with the edge, no gap and no overflow. Integer widths are unchanged, since fpTotal === intTotal in that case and nothing is added.

Updated the test to assert the widths sum exactly to the table width. Pushed in 8b60847.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, it looks like there is a bug though. There are some failing tests that shouldn't be failing, such as react-aria/test/table/tableResizingTests.tsx:498:39

I can't recall if this was the reason it was assumed to be integers a long time ago.

Did you run the tests before committing and pushing?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, and thanks for catching it — that was my mistake. The earlier revision added the leftover fraction inside cascadeRounding via fpTotal - intTotal, and floating-point accumulation could leave a tiny residue on the last column even when the table width is an integer, which is what broke tableResizingTests (the widths were no longer whole numbers). Apologies for not running the full suite before pushing.

I've reworked it so cascadeRounding is back to its original integer-only form (so the integer assumption you mentioned is preserved exactly), and the fractional handling now lives in calculateColumnSizes: it distributes the floored whole-pixel width across the columns, then adds the leftover fraction to the last column. Results:

  • 1000.5 with [1fr, 1fr] -> [500, 500.5] (flush with the edge, no gap, no overflow)
  • integer widths stay whole numbers (e.g. 1000 -> [500, 500]), so the resize behavior is unchanged

I added a test asserting integer widths stay whole numbers to lock that in. Pushed in 063cc38.

Address review: instead of flooring the available width (which left a
sub-pixel gap between the last column and the table edge), assign the
leftover fraction to the last column inside cascadeRounding so the column
widths sum exactly to the available width. Integer widths are unchanged.
The previous revision added the fractional remainder inside cascadeRounding
using `fpTotal - intTotal`, but floating-point accumulation could leave a
tiny residue on the last column even for integer table widths, breaking
tests that assume whole-number widths (e.g. tableResizingTests).

Revert cascadeRounding to its integer-only form and instead split the
available width in calculateColumnSizes: distribute the floored (whole-pixel)
width across the columns, then add the leftover fraction to the last column.
Integer widths are now exactly whole numbers again, and a fractional width
(e.g. 1000.5 -> [500, 500.5]) keeps the columns flush with the table edge.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TableLayout: sizes are not rounding correctly

2 participants