Skip to content

Feat/resize columns - #350

Draft
mir4a wants to merge 48 commits into
nick-keller:masterfrom
mir4a:feat/resize-columns
Draft

Feat/resize columns#350
mir4a wants to merge 48 commits into
nick-keller:masterfrom
mir4a:feat/resize-columns

Conversation

@mir4a

@mir4a mir4a commented Apr 29, 2024

Copy link
Copy Markdown

This PR adds possibility to resize columns with with two callbacks: for tracking resize (dragging) and resize end useful for storing which columns were resized. Stored values then can be passed as a property so that after reloading the page the table will restore touched column widths.

What is done:

  • columns are resizable
  • callback for dragging
  • callback for resize end
  • accept initial values for columns
  • re-calculate table's height when horizontal scroll appears
  • prevent column collapsing when resized. Partially done by setting min (resizable) width to 40px
  • adjust the rest column widths to fit available space so that the right border of the table doesn't look broken

If you would like to test it out right away, you can try my fork published on npm: npm i @mir4a/react-datasheet-grid@4.12.0-alpha.15 --save-exact.

@netlify

netlify Bot commented Apr 29, 2024

Copy link
Copy Markdown

Deploy Preview for react-datasheet-grid canceled.

Name Link
🔨 Latest commit ede1a05
🔍 Latest deploy log https://app.netlify.com/sites/react-datasheet-grid/deploys/664a0f293e64900008787685

Comment thread src/style.css Outdated

.dsg-resize-handle:hover {
cursor: col-resize;
background: rgba(0, 0, 250, 0.75);

@maxkuzmin maxkuzmin May 16, 2024

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

it should use --dsg-selection-border-color var instead of hardcoded color

> Type: `({ widths: Array<number | undefined>) => void`<br />
> Default: `void`

If provided, the grid will became resizable. This callback is called when the user resizes a column.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think we need to have additional boolean flag which enables resizing. One would use resizing without need for handling it.

Called when the selection changes. Called with null when the grid is blurred.

### onColumnsResize
> Type: `({ widths: Array<number | undefined>) => void`<br />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

closing } bracer is missed here

@tdonkena

Copy link
Copy Markdown

@nick-keller Can this get reviewed and merged please, this has been open for 2 months

rodrigoescandon added a commit to rodrigoescandon/react-datasheet-grid that referenced this pull request Aug 5, 2026
Root cause of the earlier JS-driven approaches (98f4543, f3168a2): any
scroll-event-driven positioning -- even one that bypasses React and writes
a CSS var straight to the DOM -- still runs on the main thread in response
to a dispatched 'scroll' event. On macOS momentum/fast-wheel scrolling the
compositor paints frames faster than the main thread can dispatch and
handle those events, so the sticky-left cells' visual position could
render a frame (or several) behind the actual scrollLeft, producing
visible drift/vanish/snap-back under fast scrolling.

Fix: make sticky-left cells (gutter + pinned data columns) real in-flow
`position: sticky` elements again, pinned by the browser's compositor with
no JS involved in the scroll path at all -- categorically immune to the
main-thread-lag failure mode, not just less prone to it.

This requires solving the original blocker from PR nick-keller#386: .dsg-row is a
plain block container, and in-flow sticky cells (unlike ordinary cells,
which are position: absolute and therefore out of flow) stack vertically
under block layout -- one row-height per sticky cell -- which is what
broke PR nick-keller#386 for N > 1 pinned columns in the first place.

Fix layout instead of abandoning sticky:

1. src/style.css: `.dsg-row { display: flex }`. Ordinary cells are
   position: absolute and therefore not flex items at all -- completely
   unaffected. The in-flow sticky cells (gutter, sticky-left, sticky-right)
   become flex items on one horizontal line instead of stacking.

2. src/components/Grid.tsx: fixed a latent DOM-order bug in the column
   virtualizer's rangeExtractor. It force-includes sticky column indices
   via repeated `result.unshift(stickyCol)` in an ascending loop, which
   builds the array in DESCENDING order (each unshift lands in front of
   the last) whenever those columns fall outside the naturally-virtualized
   range (i.e. once scrolled past them). That was harmless when sticky-left
   cells were position: absolute (DOM order didn't affect visual position,
   see f3168a2) but would render pinned columns in reverse visual order
   under flex. Now collects the missing indices and unshifts them together
   to preserate ascending order.

3. src/style.css: sticky cells get `left: col.start` (unchanged, still set
   inline per-cell in Cell.tsx) plus `flex: none` to keep their exact
   measured width regardless of available flex-line space. col.start for a
   sticky column is the cumulative width of columns 0..i-1, which -- since
   the sticky set is always the contiguous range starting at 0 -- is
   exactly the cumulative width of the *prior sticky columns*, precisely
   what `position: sticky`'s `left` needs.

4. src/style.css: removed `.dsg-cell-sticky-right`'s
   `transform: translateY(-100%)`. That hack compensated for exactly two
   in-flow children (gutter, sticky-right) stacking vertically under block
   layout; under flex, in-flow children lay out horizontally on one line,
   so the vertical stacking it compensated for no longer happens and the
   transform would now be actively wrong.

5. src/components/Grid.tsx, src/style.css: removed the --dsg-sticky-x
   custom-property/transform mechanism (and the onScroll handler that
   drove it) entirely -- no longer needed now that positioning is native
   CSS sticky.

6. src/style.css: gave `.dsg-cell-sticky-left` its own
   border-right/border-bottom (using --dsg-border-color) instead of
   relying on the shared box-shadow trick ordinary cells use to draw grid
   lines -- that trick depends on the two cells' edges staying pixel-
   aligned as the grid scrolls, which doesn't hold for a sticky cell with
   an opaque background sitting over a non-sticky neighbor. This was
   previously patched only in the consumer app's CSS
   (base-de-datos-grid/packages/payload-grid-view); folded the same fix
   into the fork so the engine is correct standalone.

Verified in the consumer app (base-de-datos-grid, Directorio, 5391 rows,
stickyLeftColumnNumber={2}): gutter + open-record arrow + Nombre stay
rock-solid pinned and opaque through rapid back-and-forth horizontal
scrolling, scrolling to the far right, and vertical scrolling while
horizontally offset (rows 51-74 checked) -- both light and dark themes.
Column resize (PR nick-keller#350) still works and correctly reflows the sticky
layout. npm test: 100/100 (one paste.test.tsx case flaked once under full-
suite timing pressure -- an act()-wrapping warning unrelated to this
change -- and passed both in isolation and on a full-suite rerun).

- src/components/Grid.tsx: rangeExtractor DOM-order fix; dropped
  handleScroll/--dsg-sticky-x wiring, onScroll passed through directly.
- src/style.css: .dsg-row display: flex; .dsg-cell-sticky-left/-right/
  -gutter position: sticky with flex: none; sticky-left border-right/
  -bottom; removed --dsg-sticky-x var and sticky-right's translateY hack.
- dist/: rebuilt (npm run build).
- package.json: version -> 4.11.6-grid.4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

5 participants