Column Resizing
Column Resizing
TanStack Table's Column Resizing (React) Guide is about columnResizeMode,
columnResizeDirection, header.getResizeHandler(), and the transient
columnSizing drag state. None of that is ported. Resizing a column by
dragging its edge is a DOM interaction: it needs mouse and touch events,
element positions, and a pointer capture, and this package touches no DOM. So
there is no resize feature here, no resize state slice, no config option, no
column option, and no query.
What is ported is everything a resize writes to. A column's width is state,
and that state is Column Sizing. A drag is your own
Html.Events wiring that ends in one
setColumnSize call. This page shows
that wiring.
Writing the drag yourself
A mouse-driven resize is three messages and one piece of your own model.
Hold the drag in your model. You need the column being resized, the pointer position the drag started at, and the width it started at. Everything else is arithmetic.
type alias Drag =
{ columnId : String
, startX : Float
, startWidth : Float
}
type alias Model =
{ state : Table.State
, drag : Maybe Drag
}
type Msg
= ResizeStarted String Float
| ResizeMoved Float
| ResizeEnded
Start the drag on mousedown. The handle is a small absolutely
positioned element on the trailing edge of the header cell. The decoder reads
clientX off the event:
resizeHandle : String -> Html Msg
resizeHandle columnId =
div
[ style "position" "absolute"
, style "right" "0"
, style "top" "0"
, style "height" "100%"
, style "width" "5px"
, style "cursor" "col-resize"
, Html.Events.on "mousedown" (Decode.map (ResizeStarted columnId) clientX)
]
[]
clientX : Decode.Decoder Float
clientX =
Decode.field "clientX" Decode.float
Read the starting width from the table.
getColumnSize is the width the
column is currently rendering at, whether it comes from State.columnSizing,
the column's own size, or the table default:
currentWidth : Table.State -> String -> Float
currentWidth state columnId =
Table.allColumns config
|> List.filter (\column -> Table.columnId column == columnId)
|> List.head
|> Maybe.map (Table.getColumnSize config state)
|> Maybe.withDefault 0
Follow the pointer with a subscription. Listening on the document rather than on the handle is what keeps the drag alive when the pointer leaves the 5-pixel handle, which it does immediately:
subscriptions : Model -> Sub Msg
subscriptions model =
case model.drag of
Nothing ->
Sub.none
Just _ ->
Sub.batch
[ Browser.Events.onMouseMove (Decode.map ResizeMoved clientX)
, Browser.Events.onMouseUp (Decode.succeed ResizeEnded)
]
Commit the new width in update. The whole interaction reduces to
startWidth + (currentX - startX):
update : Msg -> Model -> Model
update msg model =
case ( msg, model.drag ) of
( ResizeStarted columnId x, _ ) ->
{ model
| drag =
Just
{ columnId = columnId
, startX = x
, startWidth = currentWidth model.state columnId
}
}
( ResizeMoved x, Just drag ) ->
{ model
| state =
Table.setColumnSize drag.columnId
(drag.startWidth + x - drag.startX)
model.state
}
( ResizeMoved _, Nothing ) ->
model
( ResizeEnded, _ ) ->
{ model | drag = Nothing }
You do not have to clamp the result. getColumnSize clamps between the
column's minSize and maxSize on read, so a drag past either bound is
stored as written and rendered at the bound. Set those bounds with
withMinSize and
withMaxSize.
For touch support, add Html.Events.on "touchstart" and
Html.Events.on "touchmove" handlers reading touches[0].clientX, or use a
port to a pointerdown / pointermove listener, which handles mouse, touch,
and pen in one path.
Resize mode
TanStack's columnResizeMode chooses when column.getSize() starts returning
the new width: "onChange" updates it on every drag frame, "onEnd" only
when the drag finishes. Here that is not an option, it is where you put the
setColumnSize call:
| TanStack mode | What you write |
|---|---|
"onChange" |
Call setColumnSize in the ResizeMoved branch, as above. The table re-renders at every frame. |
"onEnd" |
Keep the running delta in your Drag record, render a preview line from it, and call setColumnSize once in the ResizeEnded branch. |
"onEnd" exists in TanStack mainly to avoid re-rendering a large React table
on every frame. Elm's virtual DOM diff is cheap enough that "onChange" is a
reasonable default, but a table with expensive cells will still be smoother
with the "onEnd" shape.
Resize direction
TanStack's columnResizeDirection flips the sign of the delta for
right-to-left layouts. There is no such option here; in an RTL layout,
subtract the delta instead of adding it.
Not covered
Everything else on TanStack's page depends on the resize feature existing:
header.getResizeHandler(),column.getCanResize(), andcolumn.getIsResizing(). The package produces no event handlers and tracks no drag, so write the handler above and read "is this column being resized?" off your ownDrag.- The
columnResizingstate object (deltaOffset,deltaPercentage,isResizingColumn,startOffset,startSize,columnSizingStart) andtable.setColumnResizing. YourDragrecord is that state, holding only the fields you actually use. enableColumnResizingand the per-columnenableResizing. Decide whether to render a handle in your own view code.- The advanced performance section, which is about CSS variables, React memoization, and keeping a drag off React's render path. None of it transfers.
Example
The column widths this page writes are the ones the Column Sizing example renders, with min and max clamping visible. There is no drag example: the drag is DOM code, not table code.