@sheetxl/grid-react 0.7.21 → 0.7.23-beta.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.
Files changed (172) hide show
  1. package/README.md +502 -2
  2. package/browser/cjs/index.cjs +3 -12
  3. package/browser/esm/index.mjs +5797 -1867
  4. package/licenses/dependencies.txt +143 -41
  5. package/node/esm/index.mjs +4951 -1861
  6. package/package.json +7 -6
  7. package/types/index.d.ts +2 -1497
  8. package/types/src/cell/CellLayer.d.ts +70 -0
  9. package/types/src/cell/CellLayer.d.ts.map +1 -0
  10. package/types/src/cell/CellRenderer.d.ts +42 -0
  11. package/types/src/cell/CellRenderer.d.ts.map +1 -0
  12. package/types/src/cell/DefaultCellRenderer.d.ts +6 -0
  13. package/types/src/cell/DefaultCellRenderer.d.ts.map +1 -0
  14. package/types/src/cell/GridlineLayer.d.ts +38 -0
  15. package/types/src/cell/GridlineLayer.d.ts.map +1 -0
  16. package/types/src/cell/Types.d.ts +12 -0
  17. package/types/src/cell/Types.d.ts.map +1 -0
  18. package/types/src/cell/_CellModel.d.ts +82 -0
  19. package/types/src/cell/_CellModel.d.ts.map +1 -0
  20. package/types/src/cell/index.d.ts +6 -0
  21. package/types/src/cell/index.d.ts.map +1 -0
  22. package/types/src/editor/DefaultEditable.d.ts +121 -0
  23. package/types/src/editor/DefaultEditable.d.ts.map +1 -0
  24. package/types/src/editor/EditSession.d.ts +182 -0
  25. package/types/src/editor/EditSession.d.ts.map +1 -0
  26. package/types/src/editor/EditState.d.ts +61 -0
  27. package/types/src/editor/EditState.d.ts.map +1 -0
  28. package/types/src/editor/EditorDiv.d.ts +65 -0
  29. package/types/src/editor/EditorDiv.d.ts.map +1 -0
  30. package/types/src/editor/EditorInput.d.ts +88 -0
  31. package/types/src/editor/EditorInput.d.ts.map +1 -0
  32. package/types/src/editor/EditorLayer.d.ts +69 -0
  33. package/types/src/editor/EditorLayer.d.ts.map +1 -0
  34. package/types/src/editor/EditorPlacement.d.ts +38 -0
  35. package/types/src/editor/EditorPlacement.d.ts.map +1 -0
  36. package/types/src/editor/index.d.ts +10 -0
  37. package/types/src/editor/index.d.ts.map +1 -0
  38. package/types/src/editor/useEditorKeymap.d.ts +74 -0
  39. package/types/src/editor/useEditorKeymap.d.ts.map +1 -0
  40. package/types/src/grid/Grid.d.ts +16 -0
  41. package/types/src/grid/Grid.d.ts.map +1 -0
  42. package/types/src/grid/IGrid.d.ts +93 -0
  43. package/types/src/grid/IGrid.d.ts.map +1 -0
  44. package/types/src/grid/index.d.ts +3 -0
  45. package/types/src/grid/index.d.ts.map +1 -0
  46. package/types/src/header/HeaderDividerAnchor.d.ts +120 -0
  47. package/types/src/header/HeaderDividerAnchor.d.ts.map +1 -0
  48. package/types/src/header/index.d.ts +3 -0
  49. package/types/src/header/index.d.ts.map +1 -0
  50. package/types/src/header/useHeaderSelectionPointer.d.ts +122 -0
  51. package/types/src/header/useHeaderSelectionPointer.d.ts.map +1 -0
  52. package/types/src/index.d.ts +13 -0
  53. package/types/src/index.d.ts.map +1 -0
  54. package/types/src/layer/HoverTipLayer.d.ts +74 -0
  55. package/types/src/layer/HoverTipLayer.d.ts.map +1 -0
  56. package/types/src/layer/Layer.d.ts +92 -0
  57. package/types/src/layer/Layer.d.ts.map +1 -0
  58. package/types/src/layer/LayerTypes.d.ts +47 -0
  59. package/types/src/layer/LayerTypes.d.ts.map +1 -0
  60. package/types/src/layer/RangePrompt.d.ts +42 -0
  61. package/types/src/layer/RangePrompt.d.ts.map +1 -0
  62. package/types/src/layer/Stage.d.ts +147 -0
  63. package/types/src/layer/Stage.d.ts.map +1 -0
  64. package/types/src/layer/StickyAnchor.d.ts +83 -0
  65. package/types/src/layer/StickyAnchor.d.ts.map +1 -0
  66. package/types/src/layer/StickyFloat.d.ts +65 -0
  67. package/types/src/layer/StickyFloat.d.ts.map +1 -0
  68. package/types/src/layer/VirtualDiv.d.ts +59 -0
  69. package/types/src/layer/VirtualDiv.d.ts.map +1 -0
  70. package/types/src/layer/_Utils.d.ts +37 -0
  71. package/types/src/layer/_Utils.d.ts.map +1 -0
  72. package/types/src/layer/index.d.ts +11 -0
  73. package/types/src/layer/index.d.ts.map +1 -0
  74. package/types/src/layer/useGridAnchor.d.ts +157 -0
  75. package/types/src/layer/useGridAnchor.d.ts.map +1 -0
  76. package/types/src/layer/useHoverIntent.d.ts +49 -0
  77. package/types/src/layer/useHoverIntent.d.ts.map +1 -0
  78. package/types/src/layer/useLayerResponder.d.ts +29 -0
  79. package/types/src/layer/useLayerResponder.d.ts.map +1 -0
  80. package/types/src/layer/usePlacement.d.ts +18 -0
  81. package/types/src/layer/usePlacement.d.ts.map +1 -0
  82. package/types/src/layer/useRangeHover.d.ts +35 -0
  83. package/types/src/layer/useRangeHover.d.ts.map +1 -0
  84. package/types/src/list/CheckboxList.d.ts +63 -0
  85. package/types/src/list/CheckboxList.d.ts.map +1 -0
  86. package/types/src/list/List.d.ts +55 -0
  87. package/types/src/list/List.d.ts.map +1 -0
  88. package/types/src/list/index.d.ts +3 -0
  89. package/types/src/list/index.d.ts.map +1 -0
  90. package/types/src/metrics/Types.d.ts +23 -0
  91. package/types/src/metrics/Types.d.ts.map +1 -0
  92. package/types/src/metrics/index.d.ts +3 -0
  93. package/types/src/metrics/index.d.ts.map +1 -0
  94. package/types/src/metrics/useGridMetrics.d.ts +67 -0
  95. package/types/src/metrics/useGridMetrics.d.ts.map +1 -0
  96. package/types/src/range/DragHandle.d.ts +129 -0
  97. package/types/src/range/DragHandle.d.ts.map +1 -0
  98. package/types/src/range/Draggable.d.ts +86 -0
  99. package/types/src/range/Draggable.d.ts.map +1 -0
  100. package/types/src/range/FillHandle.d.ts +54 -0
  101. package/types/src/range/FillHandle.d.ts.map +1 -0
  102. package/types/src/range/RangeRect.d.ts +36 -0
  103. package/types/src/range/RangeRect.d.ts.map +1 -0
  104. package/types/src/range/RangeResizer.d.ts +68 -0
  105. package/types/src/range/RangeResizer.d.ts.map +1 -0
  106. package/types/src/range/RangeStyle.d.ts +8 -0
  107. package/types/src/range/RangeStyle.d.ts.map +1 -0
  108. package/types/src/range/index.d.ts +13 -0
  109. package/types/src/range/index.d.ts.map +1 -0
  110. package/types/src/selection/DefaultSelection.d.ts +59 -0
  111. package/types/src/selection/DefaultSelection.d.ts.map +1 -0
  112. package/types/src/selection/ICellAnchor.d.ts +21 -0
  113. package/types/src/selection/ICellAnchor.d.ts.map +1 -0
  114. package/types/src/selection/ISelection.d.ts +65 -0
  115. package/types/src/selection/ISelection.d.ts.map +1 -0
  116. package/types/src/selection/RangesPickerLayer.d.ts +128 -0
  117. package/types/src/selection/RangesPickerLayer.d.ts.map +1 -0
  118. package/types/src/selection/SelectionLayer.d.ts +179 -0
  119. package/types/src/selection/SelectionLayer.d.ts.map +1 -0
  120. package/types/src/selection/SelectionStyle.d.ts +28 -0
  121. package/types/src/selection/SelectionStyle.d.ts.map +1 -0
  122. package/types/src/selection/SelectionTouchGrips.d.ts +29 -0
  123. package/types/src/selection/SelectionTouchGrips.d.ts.map +1 -0
  124. package/types/src/selection/SelectionUtils.d.ts +2 -0
  125. package/types/src/selection/SelectionUtils.d.ts.map +1 -0
  126. package/types/src/selection/Types.d.ts +67 -0
  127. package/types/src/selection/Types.d.ts.map +1 -0
  128. package/types/src/selection/_SelectionNavigation.d.ts +6 -0
  129. package/types/src/selection/_SelectionNavigation.d.ts.map +1 -0
  130. package/types/src/selection/_SelectionUtils.d.ts +7 -0
  131. package/types/src/selection/_SelectionUtils.d.ts.map +1 -0
  132. package/types/src/selection/index.d.ts +12 -0
  133. package/types/src/selection/index.d.ts.map +1 -0
  134. package/types/src/selection/useDragCells.d.ts +218 -0
  135. package/types/src/selection/useDragCells.d.ts.map +1 -0
  136. package/types/src/selection/useSelectionPointer.d.ts +70 -0
  137. package/types/src/selection/useSelectionPointer.d.ts.map +1 -0
  138. package/types/src/surface/GridPane.d.ts +85 -0
  139. package/types/src/surface/GridPane.d.ts.map +1 -0
  140. package/types/src/surface/GridPaneProvider.d.ts +37 -0
  141. package/types/src/surface/GridPaneProvider.d.ts.map +1 -0
  142. package/types/src/surface/GridSurface.d.ts +11 -0
  143. package/types/src/surface/GridSurface.d.ts.map +1 -0
  144. package/types/src/surface/IGridPane.d.ts +288 -0
  145. package/types/src/surface/IGridPane.d.ts.map +1 -0
  146. package/types/src/surface/IGridSurface.d.ts +141 -0
  147. package/types/src/surface/IGridSurface.d.ts.map +1 -0
  148. package/types/src/surface/SurfaceStateProvider.d.ts +50 -0
  149. package/types/src/surface/SurfaceStateProvider.d.ts.map +1 -0
  150. package/types/src/surface/_GridUtils.d.ts +37 -0
  151. package/types/src/surface/_GridUtils.d.ts.map +1 -0
  152. package/types/src/surface/index.d.ts +7 -0
  153. package/types/src/surface/index.d.ts.map +1 -0
  154. package/types/src/surface/useGridPane.d.ts +31 -0
  155. package/types/src/surface/useGridPane.d.ts.map +1 -0
  156. package/types/src/surface/useSurfaceState.d.ts +22 -0
  157. package/types/src/surface/useSurfaceState.d.ts.map +1 -0
  158. package/types/src/svg/LineSVG.d.ts +13 -0
  159. package/types/src/svg/LineSVG.d.ts.map +1 -0
  160. package/types/src/svg/RectangleSVG.d.ts +114 -0
  161. package/types/src/svg/RectangleSVG.d.ts.map +1 -0
  162. package/types/src/svg/ShapeBounds.d.ts +15 -0
  163. package/types/src/svg/ShapeBounds.d.ts.map +1 -0
  164. package/types/src/svg/Types.d.ts +37 -0
  165. package/types/src/svg/Types.d.ts.map +1 -0
  166. package/types/src/svg/index.d.ts +9 -0
  167. package/types/src/svg/index.d.ts.map +1 -0
  168. package/types/src/theme/GridTheme.d.ts +56 -0
  169. package/types/src/theme/GridTheme.d.ts.map +1 -0
  170. package/types/src/theme/index.d.ts +2 -0
  171. package/types/src/theme/index.d.ts.map +1 -0
  172. package/node/assets/style.css +0 -1
package/README.md CHANGED
@@ -6,9 +6,509 @@
6
6
  </a>
7
7
  </p>
8
8
 
9
- An advanced standalone data-grid component available for all [SheetXL](https://www.sheetxl.com) projects.
9
+ A high-performance, virtualized grid component built for React. Part of the [SheetXL](https://www.sheetxl.com) ecosystem.
10
10
 
11
- This package will generally not be used by itself but rather as a dependency of other packages.
11
+ ## Quick Start
12
+
13
+ Create a grid with just a few lines:
14
+
15
+ ```tsx
16
+ import { Grid } from '@sheetxl/grid-react';
17
+
18
+ <Grid
19
+ rowCount={1000}
20
+ columnCount={100}
21
+ getRowHeight={24} // a number = uniform size (or a (index) => number)
22
+ getColumnWidth={100}
23
+ showGridLines
24
+ renderCells={(props) => (
25
+ <div key={props.key} style={props.style}>
26
+ {props.value}
27
+ </div>
28
+ )}
29
+ />
30
+ ```
31
+
32
+ That's it — a virtualized grid that efficiently handles trillions of cells.
33
+
34
+ `renderCells` receives a `CellRendererProps`: `{ cell, range, bounds, value, style, pane }` (plus a
35
+ `key`). `style` is already positioned/sized for the cell — spread it onto your element. By default a
36
+ rendered cell is `pointer-events: none` so clicks fall through to the grid; override per cell to make
37
+ it interactive.
38
+
39
+ ## Why another grid?
40
+
41
+ Most data grids are **configuration-driven**. You describe the grid through a large surface of
42
+ column definitions, feature flags, and renderer callbacks; selection, editing, grouping, notes are
43
+ each a built-in property you switch on. Anything the library didn't anticipate means bending its
44
+ plugin API or forking it.
45
+
46
+ Grid-React starts from a different premise. A grid is a **virtualized coordinate space**, and
47
+ everything placed in it — cells, selections, highlights, notes, widgets — is an ordinary React
48
+ component positioned along the grid's metrics. There's no master list of properties to learn or
49
+ outgrow: you compose the features you need from small, single-purpose pieces, and your own components
50
+ sit right next to the built-in ones.
51
+
52
+ The payoff is a small core with an open surface. The grid owns the genuinely hard parts —
53
+ virtualization, scroll, zoom, freeze panes, device-pixel alignment — and stays out of the way of
54
+ *what* you draw. The mechanism that makes this work is a pair of primitives, below.
55
+
56
+ ## The whole model: two primitives
57
+
58
+ Everything in the grid — the built-in cells, gridlines, and selection included — is built from two
59
+ primitives, and only two:
60
+
61
+ > **A `VirtualDiv` is a div.** Everything you know about divs applies — pointer events, focus,
62
+ > refs, CSS, children. The grid adds exactly one thing: its **bounds are driven by the grid
63
+ > metrics**. Place it at a `range` (cell coordinates) or `bounds` (pixels), and scroll, zoom,
64
+ > freeze panes, device-pixel alignment, and off-screen culling are handled for you.
65
+ >
66
+ > **A `Layer` is an invisible, view-sized div — the boundary of one z-slot.** It never paints and
67
+ > is never a hit target; what it contributes is **containment**: everything you host inside it —
68
+ > VirtualDivs, plain elements, third-party widgets — belongs to its slot, and the slot orders both
69
+ > painting *and* event dispatch. Its own handlers are the slot's **tap**: the outermost handlers of
70
+ > its subtree, and grid-wide ears at its slot on every other layer's turn.
71
+
72
+ If you can hold those two sentences, you can reason out the whole system. Everything else in this
73
+ package is a convenience composed from them.
74
+
75
+ ```tsx
76
+ import { Grid, Layer, VirtualDiv, RangeRect } from '@sheetxl/grid-react';
77
+
78
+ <Grid
79
+ rowCount={1000}
80
+ columnCount={100}
81
+ getRowHeight={24}
82
+ getColumnWidth={100}
83
+ showGridLines
84
+ renderCells={renderCell}
85
+ >
86
+ {/* a layer of decorations + a widget, above the cells */}
87
+ <Layer zIndex={1}>
88
+ <RangeRect range={{ colStart: 2, rowStart: 2, colEnd: 5, rowEnd: 6 }}
89
+ stroke="#1976d2" alignWidth={2} />
90
+ <VirtualDiv range={{ colStart: 6, rowStart: 2, colEnd: 9, rowEnd: 5 }}
91
+ style={{ border: '2px solid #1976d2' }}>
92
+ A note
93
+ </VirtualDiv>
94
+ </Layer>
95
+ </Grid>
96
+ ```
97
+
98
+ Each piece is just a React element with full access to hooks, state, and context — placement,
99
+ virtualization, freeze-pane clipping, and event routing all "just work."
100
+
101
+ - **Declarative** — composed as `children`, like any React tree.
102
+ - **Automatic virtualization** — only visible content renders; off-screen VirtualDivs cull.
103
+ - **Freeze panes built-in** — correct clipping and coordinate conversion by default.
104
+ - **Flexible** — mix the built-in pieces with your own.
105
+
106
+ ## `Grid` vs `GridSurface`
107
+
108
+ There are two entry points:
109
+
110
+ - **`Grid`** — batteries-included (what most apps use). It adds cells (`renderCells`), gridlines
111
+ (`showGridLines`), and a content cache to the engine below. Layout convenience lives here:
112
+ `getRowHeight` / `getColumnWidth` (a number, or `(index) => number`).
113
+ - **`GridSurface`** — the pure engine. It knows nothing about cells or gridlines; you compose
114
+ *everything* from the primitives (`Grid` is a thin wrapper that does exactly that). Use it
115
+ directly when you want full control. It is metrics-only — pass an `IGridMetrics` (no
116
+ `getRowHeight`).
117
+
118
+ Both take their **layers as `children`**. `Grid` additionally takes `renderBelow` (content under
119
+ the cells) and `renderBorders`.
120
+
121
+ The grid's children are its **stack of Layers**, bottom → top — like the layers panel of a design
122
+ tool (Photoshop, Figma): a layer spans the whole canvas; the *objects on it* (VirtualDivs) have
123
+ positions. This is a **closed contract, both ways**: a grid's children can only be `Layer`s, and a
124
+ `Layer` can only appear there — never nested inside another `Layer` or a `VirtualDiv`
125
+ (dev-enforced). A composite feature (a selection, an editor, a table overlay) is a component that
126
+ renders exactly **one `Layer`** at its root. The stack is therefore always **flat**: one list, one
127
+ order, one place to reason about z.
128
+
129
+ ## `VirtualDiv` — a div, placed by the grid
130
+
131
+ A `<div>` whose bounds are driven by the grid metrics. All standard div props work (`className`,
132
+ `style`, `onClick`, `onPointerDown`, `tabIndex`, `ref`, …) and mean what they always mean.
133
+
134
+ ```tsx
135
+ <VirtualDiv range={{ colStart: 5, rowStart: 10, colEnd: 8, rowEnd: 15 }} /> // cell coordinates
136
+ <VirtualDiv bounds={{ x: 500, y: 1000, width: 200, height: 100 }} /> // grid-space pixels
137
+ <VirtualDiv fill /> // the whole view
138
+ ```
139
+
140
+ Placement is exactly one of:
141
+
142
+ - **`range`** — cell coordinates, resolved via the current pane. Culls when off-screen.
143
+ - **`bounds`** — CSS pixels from the grid origin. Culls when off-screen.
144
+ - **`fill`** — sized to the view, never culled. For full-view content: a background wash, a canvas
145
+ host, a pointer-capture surface, a container that needs real CSS (`overflow`, `opacity`,
146
+ `filter`) applied to a whole group.
147
+
148
+ Under the hood it is a **single** transform-placed div — no wrappers, no per-instance overhead —
149
+ with its edges snapped to the device-pixel grid so content stays crisp and adjacent boxes share
150
+ seam-free boundaries.
151
+
152
+ Useful extras:
153
+
154
+ - `coverGridlines` — extend the box by 1 device px so it covers its gridline pixels (like a cell fill).
155
+ - `alignWidth` — render an aligned **border** (instead of a fill) that lines up with the selection rectangle.
156
+ - `clipInsets` — keep the box mounted N px past the viewport edge (for borders/shadows that overhang).
157
+ - `keepMounted` — stay mounted (parked off-screen) instead of culling when scrolled away (e.g. an
158
+ editor whose input must never unmount).
159
+
160
+ A **decoration** is not a separate concept — it's a VirtualDiv you set `pointer-events: none` on,
161
+ exactly as you would any div:
162
+
163
+ ```tsx
164
+ {/* a highlight: never a hit target, clicks fall through to the grid */}
165
+ <VirtualDiv range={range} coverGridlines
166
+ style={{ pointerEvents: 'none', background: 'rgba(255,235,59,0.4)' }} />
167
+ ```
168
+
169
+ > **Huge ranges:** a VirtualDiv sized to a whole column/row would be millions of pixels tall —
170
+ > a giant compositing layer. For outlines/fills over arbitrary ranges use
171
+ > [`RangeRect`](#rangerect--crisp-vector-outlines--fills), which internally keeps its box
172
+ > view-sized and draws the shape at the range's true coordinates.
173
+
174
+ ## `Layer` — the boundary of a z-slot
175
+
176
+ A `Layer` renders one **invisible, view-sized div** — never painted, never a hit target
177
+ (`pointer-events:none`; children opt back in). Its DOM presence is what gives everything inside it
178
+ its **layer membership**, and with it:
179
+
180
+ - **z-slot** — the layer's position in the grid's stack. It orders painting (its children paint
181
+ after lower layers' children) *and* event dispatch (its subtree — and its tap — get their turn
182
+ before lower layers). One number, both axes, so what you see on top is what gets the event
183
+ first.
184
+ - **event tap** — the layer's own handlers (`onPointerMove`, `onKeyDown`, …): the outermost
185
+ handlers of its own subtree, and grid-wide ears at its slot on every other layer's turn — heard
186
+ whatever is under the pointer. Plain React handlers; consume with `event.stopPropagation()`.
187
+ **`event.target` is the real DOM element under the pointer** — enough to anchor a tooltip or
188
+ scope a context menu from a layer that draws nothing (see
189
+ [the element contract](#the-element-contract)).
190
+
191
+ ```tsx
192
+ const pane = useGridPane();
193
+
194
+ {/* a grid-wide hover tracker: draws nothing, just ears at its slot */}
195
+ <Layer onPointerMove={(e) => setHovered(pane.getCellFromPointer(e.clientX, e.clientY))} />
196
+
197
+ {/* a composite: one z-slot, one gesture, several visuals */}
198
+ <Layer zIndex={100}
199
+ onPointerDown={startDrag} onPointerMove={updateDrag} onPointerUp={endDrag}>
200
+ <RangeRect range={selection} stroke="var(--accent)" alignWidth={2} />
201
+ <VirtualDiv range={handleRange} style={{ cursor: 'grab' }} />
202
+ </Layer>
203
+ ```
204
+
205
+ Inside a Layer, `useLayer()` reads the slot:
206
+
207
+ ```tsx
208
+ const { zIndex, pane } = useLayer();
209
+ ```
210
+
211
+ `zIndex` is the layer's **effective order** in the stack (declaration order with explicit-`zIndex`
212
+ overrides folded in) — composites use it to register commands / responders at their layer's real
213
+ position. `pane` is the enclosing grid pane (`useGridPane()` also works anywhere). The Layer's
214
+ boundary div is internal (it never paints and is never a hit target) — a widget that needs a DOM
215
+ node is a `VirtualDiv`, and takes a regular React ref.
216
+
217
+ ### The four laws
218
+
219
+ 1. **z belongs to Layers.** VirtualDivs stack by DOM order *within* their Layer and do not set CSS
220
+ `z-index`. To restack something, move it (or its Layer) in the stack. This is what keeps paint
221
+ order and dispatch order the same thing.
222
+ 2. **A Layer never paints and is never a hit target.** Its div exists to *contain*, not to show:
223
+ `pointer-events:none`, no visuals. If a group needs a real container — `overflow` clipping,
224
+ group `opacity`/`filter`, a scroll-carrying transform, a paint target — put a `<VirtualDiv
225
+ fill>` inside the Layer. The container is then *visible in the model* instead of smuggled in.
226
+ 3. **One ordered bubble — layers on top hear events first, keys and pointers alike.** An event
227
+ visits the stack top-down: each layer's turn runs its own subtree exactly as React does (your
228
+ inner elements, then the layer's tap as the boundary), then the next layer down, then the grid's
229
+ own behavior, then the command system. The layer that owns the event's target gets its turn *in
230
+ order* — not early. `stopPropagation()` is the **only** consume signal, for every event type.
231
+ (Targeted events — `enter`/`leave`, `focus`/`blur` — keep plain per-element DOM semantics; keys
232
+ go to taps in the same top-down order, and to the focused element at its layer's turn.)
233
+ 4. **The stack is flat.** Layers exist only as direct children of the grid; a Layer's z-slot is
234
+ simply its position in that one list (with explicit `zIndex` overrides folded in).
235
+
236
+ ## Events
237
+
238
+ One law covers every event, key and pointer alike — law 3, **one ordered bubble: layers on top
239
+ hear events first**. The event visits the stack top-down; each layer's **turn** is "standard child
240
+ eventing" inside its own boundary — your inner elements dispatch exactly as normal React (a
241
+ `<button>` inside your VirtualDiv behaves untouched), ending at the layer's own tap handlers — and
242
+ if nobody `stopPropagation()`s, the next layer down gets its turn. The layer that owns the event's
243
+ target is *not* special: it gets its turn at its slot, after every layer above it. Whatever nothing
244
+ consumed falls to the grid's own behavior (selection / hover / context menu — just the bottom of
245
+ the stack), and for keys, on to the command system (the *terminal* handler of the same bubble).
246
+
247
+ This holds for **keys** too: a layer above a focused editor hears keystrokes first — an
248
+ autocomplete tip can observe them (build suggestions, don't stop them) or steal them
249
+ (`stopPropagation`) — and the focused input receives them at its own layer's turn, none the wiser.
250
+
251
+ `stopPropagation()` is the **single consume signal** — the same one you already use on any div:
252
+
253
+ ```tsx
254
+ {/* should block the grid: say so, exactly like on a plain page */}
255
+ <VirtualDiv range={range}>
256
+ <button onClick={(e) => { e.stopPropagation(); doThing(); }}>Click me</button>
257
+ </VirtualDiv>
258
+
259
+ {/* should NOT block the grid: just don't stop it */}
260
+ <VirtualDiv range={range} onPointerMove={trackHover} />
261
+
262
+ {/* decoration: never a hit target at all */}
263
+ <VirtualDiv range={range} style={{ pointerEvents: 'none', background: 'rgba(0,0,0,0.06)' }} />
264
+ ```
265
+
266
+ Consuming is per event type and is your handler's own one-line decision — which gives fine-grained
267
+ behavior for free: a link cell stops its `click`, while its right-click (no handler, nothing
268
+ stopped) still reaches the grid's context menu.
269
+
270
+ | you write | behavior |
271
+ | --- | --- |
272
+ | `<VirtualDiv>` + handlers | hittable; handlers fire **at its layer's turn**; the event continues down the stack unless you `stopPropagation()` |
273
+ | `<VirtualDiv style={{ pointerEvents: 'none' }}>` | transparent — never a target; falls straight through |
274
+ | `<Layer onX={…}>` | the slot's tap — the outermost handler of its own subtree, and grid-wide ears at its slot on every other layer's turn |
275
+
276
+ Targeted events keep plain per-element DOM semantics on your elements — `onPointerEnter/Leave`,
277
+ `onFocus/Blur` (and keys go to the focused element, at its layer's turn) — since they are defined
278
+ per element, not per position.
279
+
280
+ Rule of thumb: a positioned, interactive *thing* → `VirtualDiv` (it **is** the target); a
281
+ grid-wide *listener* → `Layer` (it **observes**, in z order).
282
+
283
+ ### The element contract
284
+
285
+ The events a tap hears are real: **`event.target` is the actual element under the pointer**, and
286
+ `pane.getCellFromPointer(e.clientX, e.clientY)` resolves the cell — enough to power tooltips,
287
+ context menus, hover tracking, and drag gestures from a layer that draws nothing. One consequence
288
+ of walk dispatch: on *other layers' turns* `event.currentTarget` is the grid's surface, not your
289
+ element — a gesture that captures the pointer captures on its **own ref** (pointer capture is
290
+ honored: a captured element owns its stream for the rest of the drag). And because the Layer's
291
+ boundary div gives every element inside it its layer membership, this works for anything you host —
292
+ plain elements and third-party widgets included:
293
+
294
+ ```tsx
295
+ const pane = useGridPane();
296
+ <Layer
297
+ onPointerMove={(e) => setHovered(pane.getCellFromPointer(e.clientX, e.clientY))}
298
+ onContextMenu={(e) => openMenuFor(e.target as HTMLElement)}
299
+ />
300
+ ```
301
+
302
+ ### Focus & keyboard
303
+
304
+ The **Stage is the grid's surface** — focus rests on it, and that's what cell navigation uses.
305
+ Programmatically, `gridRef.current.focus()` (or `pane.focus()`) focuses the surface. A `VirtualDiv`
306
+ that should take keyboard focus opts in with `tabIndex` — regular div focus semantics: when it's
307
+ focused it receives keys first (and may `stopPropagation` to keep them from the grid); when the
308
+ surface is focused, keys reach the grid and it navigates as usual.
309
+
310
+ Keys are positionless, so their walk skips hit-testing: **every** layer is offered the key, top of
311
+ z first, then the command system terminates the bubble. There is **no** "is the target an input?"
312
+ second guess — a widget that wants a key (e.g. a cell editor in edit mode) calls `stopPropagation`;
313
+ one that doesn't (ready mode) lets it bubble on. That's what makes the primitives first-class: drop
314
+ a `Layer` with visuals inside **and** an `onKeyDown` **and** contributed commands, and it slots
315
+ into the same bubble with no wiring.
316
+
317
+ ## Stacking
318
+
319
+ Layers stack by **declaration order** — last is on top, like DOM siblings. A layer's `zIndex` prop
320
+ overrides that. Visual stacking and event order stay coupled — the layer you see on top is offered
321
+ the event first. Within a layer, children stack by DOM order (law 1).
322
+
323
+ | Layer band | zIndex | Examples |
324
+ | --- | --- | --- |
325
+ | Backgrounds | < 0 | fills, highlights, hover states |
326
+ | Cells & gridlines | **0** | cell content *(default)* |
327
+ | Annotations | 1–99 | borders, badges, validation markers |
328
+ | Interactive | 100+ | editors, drag handles, movables |
329
+
330
+ > Pop-ups that must float above everything (menus, tooltips) generally **portal** out of the grid
331
+ > rather than ride an in-grid z-band — see [Anchored floats](#anchored-floats).
332
+
333
+ ## Conveniences
334
+
335
+ Everything else in the package is composed from the two primitives. You could write each of these
336
+ yourself; they're shipped because they encode non-obvious geometry.
337
+
338
+ ### `RangeRect` — crisp vector outlines & fills
339
+
340
+ The common decoration: one device-aligned outline/fill at a range. The stroke lines up **exactly**
341
+ with gridlines and the selection rectangle at any device-pixel ratio:
342
+
343
+ ```tsx
344
+ <RangeRect
345
+ range={{ colStart: 2, rowStart: 2, colEnd: 5, rowEnd: 6 }}
346
+ stroke="#1976d2"
347
+ alignWidth={2} // device px; even = grows outward, odd = centered
348
+ fill="rgba(25,118,210,0.1)"
349
+ strokeDasharray="3 2" // optional: marching-ants style
350
+ />
351
+ ```
352
+
353
+ Composition: a **view-sized, pointer-transparent `VirtualDiv`** hosting a `RectangleSVG` drawn at
354
+ the range's true coordinates (clamped to the view). The box is view-sized on purpose — a range-sized
355
+ box would be enormous for whole-column ranges, and an SVG whose box moves can't animate its stroke
356
+ (`animatedTransition` glides the path inside a stable coordinate space instead). `RectangleSVG` /
357
+ `LineSVG` are available for composing your own shapes the same way.
358
+
359
+ ### Anchored floats
360
+
361
+ Floats attach content to a grid location but **escape grid space** — they portal out of the grid's
362
+ clipping and stacking (tooltips, toolbars, prompts). They're built *above* the primitives (an
363
+ anchor resolver + Floating UI), not from them:
364
+
365
+ - **`StickyAnchor`** — resolves a grid anchor (range / cell / edge / point) to a live position;
366
+ children render inline (clipping with the pane) or self-position via render-prop.
367
+ - **`StickyFloat`** — the portal flavor: floats content beside the anchor with flip/shift/clamp.
368
+ - **`HoverTipLayer`** — hover-intent tips: a `Layer` tap probing the hovered cell + a `StickyFloat`.
369
+ - **`RangePrompt`** — a persistent prompt pinned to a range, with an out-of-view "go to" affordance.
370
+
371
+ Naming convention: a component ending in `-Layer` roots a `Layer` (drop it in as a grid child);
372
+ anything else composes *inside* one.
373
+
374
+ ### Range handles & gestures
375
+
376
+ The controlled interaction pieces a selection-style feature composes — each takes a `range` in and
377
+ reports the result range out through `onDrag*` callbacks:
378
+
379
+ - **`Draggable`** — makes its children movable (whole-rectangle or border-ring grip); translate by
380
+ cell deltas with edge auto-scroll.
381
+ - **`RangeResizer`** — an outlined range with corner/edge grips; Excel-style flip-through resizing.
382
+ - **`FillHandle`** — the fill corner: drag to extend along one axis, with a dashed preview.
383
+ - **`DragHandle`** — the generic corner/edge grip primitive the above are built from.
384
+
385
+ ### Built-in feature layers
386
+
387
+ Complete, drop-in layers (each roots its own `Layer` — stack them like any other grid child):
388
+
389
+ - **`CellLayer`** / **`GridlineLayer`** — the cells and gridlines `Grid` composes for you; use them
390
+ directly on a `GridSurface` when you own the stack.
391
+ - **`SelectionLayer`** — pointer + keyboard selection over an `ISelection` model, with move grip and
392
+ fill handle slots.
393
+ - **`EditorLayer`** — an always-mounted inline cell editor (swappable input).
394
+ - **`RangesPickerLayer`** — multi-rectangle range picking (formula references, chart ranges).
395
+ - **`List`** — a virtualized list built from the same engine (a one-column grid + selection).
396
+
397
+ ## Using the pane context
398
+
399
+ Inside any layer or VirtualDiv, reach the live pane with `useGridPane()`:
400
+
401
+ ```tsx
402
+ import { useGridPane } from '@sheetxl/grid-react';
403
+
404
+ function MyOverlay() {
405
+ const pane = useGridPane();
406
+ const visible = pane.getVisibleRange();
407
+ const bounds = pane.getBoundsFromRange({ colStart: 0, rowStart: 0, colEnd: 5, rowEnd: 5 });
408
+ return <VirtualDiv bounds={bounds} style={{ outline: '1px solid green' }} />;
409
+ }
410
+ ```
411
+
412
+ Key `IGridPane` methods:
413
+
414
+ - **Queries** — `getVisibleRange()`, `getZoom()`, `getPaneBounds()`, `getAnchor()`
415
+ - **Coordinates** — `getBoundsFromRange(range)`, `getCellFromPointer(x, y)`, `toClipBounds(bounds, insets?)`
416
+ - **Interaction** — `focus(options?)`, `setPointerCapture(id)`, `getElement()`, `addReactEventListener(type, listener)`
417
+
418
+ ## Architecture
419
+
420
+ ```scss
421
+ Grid (cells + gridlines + scrollbars + headers)
422
+ └─ GridSurface (the pure engine)
423
+ └─ GridPane (a viewport section — one per frozen region)
424
+ └─ Stage (the per-pane surface: event root + focus + stack container)
425
+ └─ Layers (gridlines, cells, and yours — each a z-slot of VirtualDivs, bottom → top)
426
+ ```
427
+
428
+ - **`Grid`** — batteries-included wrapper; composes cells + gridlines as layers onto the surface.
429
+ - **`GridSurface`** — the core virtualized engine; everything is built from the two primitives.
430
+ - **`GridPane`** — internal viewport section; created automatically for freeze panes. Reach it with
431
+ `useGridPane()`.
432
+ - **`Stage`** — the per-pane surface element. It holds focus and is the root every event converges
433
+ to. You rarely touch it directly.
434
+
435
+ ## Freeze panes
436
+
437
+ Freeze rows/columns to keep headers in view while scrolling:
438
+
439
+ ```tsx
440
+ <Grid freezeTop={1} freezeLeft={1} renderCells={renderCell} />
441
+ ```
442
+
443
+ Layers and VirtualDivs render correctly in every frozen region automatically — no manual handling.
444
+
445
+ ## Hidden rows / columns
446
+
447
+ For the simple case, return `true` from `hiddenCellAt` to skip a cell:
448
+
449
+ ```tsx
450
+ <Grid hiddenCellAt={(cell) => cell.rowIndex === 5} renderCells={renderCell} />
451
+ ```
452
+
453
+ For row/column-level hiding (used by virtualization), `hiddenRowsAt(index, forward)` /
454
+ `hiddenColumnsAt(index, forward)` return the **next visible index** in the given direction.
455
+
456
+ ## Performance
457
+
458
+ - Keep `renderCells` **pure and fast**; avoid `useState`/`useEffect` inside it; `React.memo` complex cells; return `null` for empty cells.
459
+ - For sparse data, supply `getVisibleCells(pane)` to return only the cells that have data.
460
+ - Minimize layers; prefer declaration order over `zIndex`; mark decorations `pointer-events: none`.
461
+ - For outlines/fills over **huge ranges** (whole rows/columns), use `RangeRect` — never a
462
+ range-sized `VirtualDiv`.
463
+
464
+ ```tsx
465
+ const Cell = memo(({ style, value }: CellRendererProps) => {
466
+ if (!value) return null;
467
+ return <div style={style}>{value}</div>;
468
+ });
469
+ ```
470
+
471
+ ## Coordinate systems
472
+
473
+ | System | Units | Example |
474
+ | --- | --- | --- |
475
+ | **Cell** | row/column indices | `{ rowIndex: 5, colIndex: 3 }` |
476
+ | **Range** | cell spans | `{ colStart: 0, rowStart: 0, colEnd: 3, rowEnd: 5 }` |
477
+ | **Bounds** | CSS pixels (grid space) + size | `{ x: 100, y: 200, width: 300, height: 50 }` |
478
+
479
+ Convert with `IGridPane`: `getBoundsFromRange(range)`, `getCellFromPointer(x, y)`, `toClipBounds(bounds)`.
480
+
481
+ ## API reference
482
+
483
+ ### `Grid` props
484
+
485
+ **Required:** `rowCount`, `columnCount`, `renderCells`.
486
+
487
+ **Layout:** `getRowHeight?: number | (index) => number`, `getColumnWidth?: number | (index) => number`,
488
+ `freezeTop?`, `freezeLeft?`.
489
+
490
+ **Display:** `showGridLines?: boolean`, `gridLineColor?`.
491
+
492
+ **Layers:** `children` (above the cells), `renderBelow?` (below the cells), `renderBorders?`.
493
+
494
+ **Data:** `getVisibleCells?(pane)`, `hiddenCellAt?(cell)`, `hiddenRowsAt?(index, forward)`,
495
+ `hiddenColumnsAt?(index, forward)`.
496
+
497
+ **Events:** `onViewportChange?`, `onFirstRender?(grid)`.
498
+
499
+ `GridSurface` takes the same props **minus** the cell/gridline conveniences (no `renderCells`,
500
+ `getRowHeight`, `showGridLines`) and **plus** `metrics?: IGridMetrics`.
501
+
502
+ See the [full API reference](https://api.sheetxl.com) for complete types.
503
+
504
+ ## Learn more
505
+
506
+ - [Documentation](https://sheetxl.com/docs)
507
+ - [API Reference](https://api.sheetxl.com)
508
+ - [Examples & Demos](https://sheetxl.com/docs/demos)
509
+ - [GitHub Issues](https://github.com/sheetxl/sheetxl/issues) · [Discord](https://discord.gg/NTKdwUgK9p)
510
+
511
+ ---
12
512
 
13
513
  ## 🔗 Learn More
14
514