Architecture
Gridla is one package with three layers and hard boundaries between them: the core solves layouts as plain data, the interaction layer turns gestures into solver calls without knowing any framework, and each adapter binds that layer to one rendering model.
Core, interaction, adapters
gridla/interaction is a public entry. It holds everything the React adapter used to own that was not React: the store, the controller (controlled and uncontrolled sync, projection, onLayoutChange reasons, the gesture API), pointer and keyboard handling over a minimal event shape, the transfer scope with its resting-rect hit-testing, and size observation. Every adapter listed under Adapters is a thin binding over it, so a bug fix or a new gesture lands once and reaches every framework; the Write your own adapter section shows the four functions a new binding needs. The React sections below describe that adapter specifically; the other adapters map the same pieces onto their framework's idioms.
Core modules
Rules the core follows: every input is a plain object and every output is a new plain object; no function reads or writes anything outside its arguments; no DOM, timers, or globals; results are deterministic for the same inputs. These rules are what make the solvers testable with fixtures and property tests, and what let the same code run in the browser, on the server, and in a worker.
React adapter
The adapter adds exactly what a browser needs: measurement, pointer and keyboard orchestration, state, and headless positioning.
- Store. A minimal external store (
createStore) holdsGridState: the source layout, the measured size, the rendered (projected) layout, the gesture in progress, the cursor-tracked rect, the solver preview, selection, and the transferring flag. Components subscribe to slices withuseSyncExternalStore, so a drag rerenders only the items whose view changed. - Provider.
GridProvidercreates the store, projects the source onto the measured size, exposesGridActions(programmatic operations that go through the solvers) and an internal gesture API, and reports changes throughonLayoutChangeandonCommit. Controlled and uncontrolled modes differ only in where the source layout lives. - Interaction.
useGridInteractionturns pointer and keyboard events on the canvas element into gesture calls. It finds items and handles by data attributes (GRID_DATA), applies the drag threshold, axis lock, and snap bypass, and delegates the math to the core. - Components.
GridCanvasmeasures and wires;GridItempositions and exposes handles;GridPreviewOutlinedraws the landing rect. - Transfer scope.
GridTransferScoperegisters providers and, during a drag, previews and commits drops in whichever registered canvas is under the pointer.
Data flow during a drag
pointerdownon a drag handle selects the item and records a pending press. Once the pointer travels pastdragThreshold,beginMovesnapshots the item's rect and the grab offset.- Every
pointermoveconverts the client point to canvas-local pixels, applies Shift axis lock, and callsupdateMove(pointer, { snap }). - The provider runs
moveItemon the rendered layout and stores the result aspreview(only when accepted; a rejected step keeps the last accepted preview).activeRecttracks the cursor independently so the dragged item can follow the pointer while siblings show the solved positions. useGridItemViewselects each item's rect frompreview.layout(falling back to the rendered layout), andGridItemwrites it totransform.pointerupcallscommit: the preview layout becomes the new source (or is handed toonLayoutChangewhen controlled),onCommitfires, and the gesture state clears.Escapeorpointercancelcallscancelinstead.
Transfers insert a step between 2 and 3: the scope asks the target provider to previewIncoming, and on release commitIncoming on the target followed by completeOutgoing on the source.
Nested layouts
Nesting is a core concern, not an adapter one. flattenLayout produces root-relative rects and per-container rendered layouts; an application that renders trees keeps one provider per container (or one custom controller over the flat layout) and uses the coordinate conversions in nested to move between them. The studio example is built this way.