@svgrid/grid 3.0.4 → 3.0.6

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 (176) hide show
  1. package/README.md +32 -11
  2. package/dist/GridMenus.svelte +34 -4
  3. package/dist/SvGrid.controller.svelte.d.ts +33 -5
  4. package/dist/SvGrid.controller.svelte.js +397 -58
  5. package/dist/SvGrid.css +200 -2
  6. package/dist/SvGrid.svelte +350 -40
  7. package/dist/SvGrid.types.d.ts +433 -7
  8. package/dist/SvGridDropdown.svelte +20 -0
  9. package/dist/SvGridDropdown.svelte.d.ts +12 -0
  10. package/dist/SvModal.svelte +22 -0
  11. package/dist/SvModal.svelte.d.ts +7 -0
  12. package/dist/a11y/dismissable.d.ts +10 -0
  13. package/dist/a11y/dismissable.js +24 -2
  14. package/dist/build-api.js +92 -37
  15. package/dist/cdn/{GridMenus-BL7ZgQvU.js → GridMenus-C-NtjlrJ.js} +155 -146
  16. package/dist/cdn/GridMenus-TssHej3q.js +644 -0
  17. package/dist/cdn/{SvChartMenu-FBSMINA6.js → SvChartMenu-BFSTpvB-.js} +1 -1
  18. package/dist/cdn/{SvChartMenu-Bl6PBbkT.js → SvChartMenu-BZ1Iwv0y.js} +1 -1
  19. package/dist/cdn/{SvDateTimePicker-DQwt4UAs.js → SvDateTimePicker-D9u_cOf8.js} +1 -1
  20. package/dist/cdn/{SvDateTimePicker-vNU6bZ-q.js → SvDateTimePicker-s_6IUpI7.js} +1 -1
  21. package/dist/cdn/{SvGridCellEditor-B1p-vCK5.js → SvGridCellEditor-BlyCSp0q.js} +1 -1
  22. package/dist/cdn/{SvGridCellEditor-D_0q4xAu.js → SvGridCellEditor-ClRRA9lW.js} +1 -1
  23. package/dist/cdn/{SvGridChart-DzLkSwCH.js → SvGridChart-B4EV8U6t.js} +4 -4
  24. package/dist/cdn/{SvGridChart-C3EWAZaM.js → SvGridChart-DsmwTyZF.js} +4 -4
  25. package/dist/cdn/{SvGridChartBuilder-CfII62sZ.js → SvGridChartBuilder-cHloHobN.js} +4 -4
  26. package/dist/cdn/{SvGridChartBuilder-BE2T1ykB.js → SvGridChartBuilder-vOl7C0Za.js} +4 -4
  27. package/dist/cdn/{SvGridChartPanel-xf4sWVTo.js → SvGridChartPanel-AcqF-Ll5.js} +5 -5
  28. package/dist/cdn/{SvGridChartPanel-CCX5_Wgd.js → SvGridChartPanel-biMtSBPA.js} +5 -5
  29. package/dist/cdn/{SvGridChartView-DYwabQWj.js → SvGridChartView-Dx_GmZuL.js} +2 -2
  30. package/dist/cdn/{SvGridChartView-CfuXmY5I.js → SvGridChartView-I3rDHXAq.js} +2 -2
  31. package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-BWeM3lMe.js} +46 -46
  32. package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CJ6QqTxu.js} +67 -67
  33. package/dist/cdn/{SvModal-CIZWCcad.js → SvModal-ClxGmpAc.js} +187 -186
  34. package/dist/cdn/{SvModal-BAE-pjZX.js → SvModal-DhB_VMFR.js} +148 -147
  35. package/dist/cdn/{cell-formatting-C2Hf5gqd.js → cell-formatting-DVVKOSI9.js} +1 -1
  36. package/dist/cdn/{chart-Dz7SqMXH.js → chart-DEZxwC0i.js} +1 -1
  37. package/dist/cdn/{chart-panel-messages-CUbf2R4i.js → chart-panel-messages-D12XcTZJ.js} +1 -1
  38. package/dist/cdn/{chart-panel-messages-CmNrMdsr.js → chart-panel-messages-DguS7gC5.js} +1 -1
  39. package/dist/cdn/{chart-summary-BJW_tg_X.js → chart-summary-CMAXBGY-.js} +1 -1
  40. package/dist/cdn/{chart-trend-CaN9mDEV.js → chart-trend-BkEp61Gj.js} +1 -1
  41. package/dist/cdn/{dismissable-DAHetSNk.js → dismissable-Brc4EZU1.js} +5 -1
  42. package/dist/cdn/{export-format-CUDLy2yn.js → export-format-YzdwBww2.js} +1 -1
  43. package/dist/cdn/{row-resize-niQCp040.js → row-resize-DtOS1KG6.js} +41 -32
  44. package/dist/cdn/server-block-cache-DIqb3VZn.js +241 -0
  45. package/dist/cdn/{src-V1uu8iE9.js → src-CgoeSM8k.js} +5339 -4753
  46. package/dist/cdn/{src-BKhZ6eXd.js → src-DfwxB1TH.js} +9817 -9231
  47. package/dist/cdn/svgrid.js +28 -27
  48. package/dist/cdn/svgrid.svelte-external.js +28 -27
  49. package/dist/cdn/validate-AZoD8BoR.js +80 -0
  50. package/dist/cell-formatting.js +18 -1
  51. package/dist/cell-render.js +25 -6
  52. package/dist/clipboard.d.ts +2 -2
  53. package/dist/clipboard.js +72 -23
  54. package/dist/columns.js +7 -2
  55. package/dist/conditional-formatting.d.ts +2 -0
  56. package/dist/conditional-formatting.js +2 -0
  57. package/dist/core.d.ts +22 -6
  58. package/dist/core.js +401 -38
  59. package/dist/editing.js +34 -0
  60. package/dist/gantt-view.svelte.d.ts +24 -0
  61. package/dist/gantt-view.svelte.js +13 -0
  62. package/dist/grid-icons.d.ts +2 -2
  63. package/dist/grid-icons.js +2 -2
  64. package/dist/grid-messages.d.ts +10 -0
  65. package/dist/grid-messages.js +9 -0
  66. package/dist/headless.d.ts +5 -0
  67. package/dist/headless.js +12 -0
  68. package/dist/history.d.ts +21 -0
  69. package/dist/history.js +34 -4
  70. package/dist/index.d.ts +5 -4
  71. package/dist/index.js +3 -3
  72. package/dist/keyboard-handlers.js +24 -1
  73. package/dist/keyboard.js +4 -0
  74. package/dist/row-drag-touch.d.ts +1 -1
  75. package/dist/row-drag.js +35 -1
  76. package/dist/row-model.d.ts +139 -0
  77. package/dist/row-model.js +16 -0
  78. package/dist/row-resize.js +22 -1
  79. package/dist/selection.d.ts +2 -1
  80. package/dist/selection.js +31 -3
  81. package/dist/server-block-cache.d.ts +219 -0
  82. package/dist/server-block-cache.js +539 -0
  83. package/dist/server-data-source.d.ts +274 -8
  84. package/dist/server-data-source.js +229 -20
  85. package/dist/server.d.ts +13 -0
  86. package/dist/server.js +13 -0
  87. package/dist/sparkline.js +7 -5
  88. package/dist/svgrid-wrapper.types.d.ts +33 -0
  89. package/dist/validate.d.ts +4 -0
  90. package/dist/validate.js +29 -3
  91. package/package.json +11 -1
  92. package/src/GridMenus.svelte +34 -4
  93. package/src/SvGrid.controller.svelte.ts +397 -61
  94. package/src/SvGrid.css +200 -2
  95. package/src/SvGrid.svelte +350 -40
  96. package/src/SvGrid.types.ts +453 -7
  97. package/src/SvGridDropdown.svelte +20 -0
  98. package/src/SvModal.svelte +22 -0
  99. package/src/SvModal.test.ts +30 -1
  100. package/src/a11y/dismissable.test.ts +10 -1
  101. package/src/a11y/dismissable.ts +22 -2
  102. package/src/build-api.ts +96 -31
  103. package/src/cell-formatting.test.ts +17 -0
  104. package/src/cell-formatting.ts +18 -1
  105. package/src/cell-render.test.ts +24 -2
  106. package/src/cell-render.ts +24 -8
  107. package/src/clipboard.test.ts +107 -0
  108. package/src/clipboard.ts +62 -23
  109. package/src/columns.test.ts +4 -4
  110. package/src/columns.ts +7 -2
  111. package/src/conditional-formatting.test.ts +8 -0
  112. package/src/conditional-formatting.ts +3 -0
  113. package/src/core.rowmodel-cache.test.ts +51 -0
  114. package/src/core.tick-repair.test.ts +287 -0
  115. package/src/core.ts +407 -38
  116. package/src/editing.test.ts +52 -0
  117. package/src/editing.ts +33 -0
  118. package/src/gantt-stub.test.svelte +38 -0
  119. package/src/gantt-view.svelte.ts +35 -0
  120. package/src/grid-icons.ts +2 -2
  121. package/src/grid-messages.test.ts +7 -0
  122. package/src/grid-messages.ts +22 -0
  123. package/src/headless.ts +24 -0
  124. package/src/history.test.ts +49 -1
  125. package/src/history.ts +48 -2
  126. package/src/icon-seam.test.ts +2 -4
  127. package/src/index.ts +38 -10
  128. package/src/keyboard-handlers.coverage.test.ts +65 -0
  129. package/src/keyboard-handlers.ts +26 -1
  130. package/src/keyboard.ts +3 -0
  131. package/src/row-drag-touch.ts +2 -2
  132. package/src/row-drag.test.ts +51 -0
  133. package/src/row-drag.ts +35 -2
  134. package/src/row-model.ts +152 -0
  135. package/src/row-resize.ts +24 -1
  136. package/src/selection.test.ts +3 -0
  137. package/src/selection.ts +33 -3
  138. package/src/server-block-cache.test.ts +672 -0
  139. package/src/server-block-cache.ts +690 -0
  140. package/src/server-data-source.infinite.test.ts +405 -0
  141. package/src/server-data-source.test.ts +19 -0
  142. package/src/server-data-source.ts +496 -29
  143. package/src/server.ts +49 -0
  144. package/src/sparkline.test.ts +7 -0
  145. package/src/sparkline.ts +6 -4
  146. package/src/svgrid-wrapper.types.ts +32 -0
  147. package/src/svgrid.api.test.ts +21 -0
  148. package/src/svgrid.charting.test.ts +12 -4
  149. package/src/svgrid.context-menu.test.ts +12 -0
  150. package/src/svgrid.detail-rows.svelte.test.ts +187 -0
  151. package/src/svgrid.filter-menu-scroll.test.ts +9 -0
  152. package/src/svgrid.gantt-seam.test.ts +199 -0
  153. package/src/svgrid.live-update-paths.svelte.test.ts +184 -0
  154. package/src/svgrid.menu-scroll-close.test.ts +10 -0
  155. package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
  156. package/src/svgrid.row-model-seam.svelte.test.ts +305 -0
  157. package/src/svgrid.row-pinning.test.ts +15 -1
  158. package/src/svgrid.selection-bar-seam.test.ts +1 -1
  159. package/src/svgrid.sticky-groups.svelte.test.ts +145 -0
  160. package/src/svgrid.upsell-license.test.ts +8 -5
  161. package/src/test-setup.ts +12 -0
  162. package/src/transaction.test.ts +58 -0
  163. package/src/validate.test.ts +29 -0
  164. package/src/validate.ts +34 -3
  165. package/dist/SvGroupCell.svelte +0 -141
  166. package/dist/SvGroupCell.svelte.d.ts +0 -49
  167. package/dist/SvRowGroupPanel.svelte +0 -186
  168. package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
  169. package/dist/cdn/GridMenus-7kbpnnBW.js +0 -635
  170. package/dist/cdn/validate-_CDJzgIo.js +0 -75
  171. package/dist/server-group-model.d.ts +0 -98
  172. package/dist/server-group-model.js +0 -263
  173. package/src/SvGroupCell.svelte +0 -141
  174. package/src/SvRowGroupPanel.svelte +0 -186
  175. package/src/server-group-model.test.ts +0 -294
  176. package/src/server-group-model.ts +0 -370
@@ -73,6 +73,57 @@ describe('createRowDrag - same-grid reorder', () => {
73
73
  })
74
74
  })
75
75
 
76
+ describe('createRowDrag - a custom drop (onRowDrop)', () => {
77
+ it('reports the drop with its side and leaves the data alone', () => {
78
+ const rows: Task[] = [
79
+ { id: 1, title: 'a' },
80
+ { id: 2, title: 'b' },
81
+ { id: 3, title: 'c' },
82
+ ]
83
+ const onRowDrop = vi.fn()
84
+ const ctx = makeCtx(rows, { ...P, onRowDrop })
85
+ const h = createRowDrag(ctx)
86
+ h.onRowDragStart(fakeEvent(), 0) // drag 'a'
87
+ h.onRowDragOver(fakeEvent(35, 0, 40), 2) // low on c: after
88
+ expect(ctx.rowDropSide).toBe('after')
89
+ h.onRowDrop(fakeEvent(), 2)
90
+ expect(onRowDrop).toHaveBeenCalledWith({ row: rows[0], target: rows[2], targetIndex: 2, side: 'after' })
91
+ expect((ctx.internalData as Task[]).map((r) => r.title)).toEqual(['a', 'b', 'c'])
92
+ // The empty space below the rows reports no target.
93
+ h.onRowDragStart(fakeEvent(), 1)
94
+ h.onRowsContainerDrop(fakeEvent())
95
+ expect(onRowDrop).toHaveBeenLastCalledWith({ row: rows[1], target: null, targetIndex: null, side: 'after' })
96
+ })
97
+
98
+ it('offers "into" over the middle of a group row, and only then', () => {
99
+ const rows: Task[] = [
100
+ { id: 1, title: 'folder' },
101
+ { id: 2, title: 'file' },
102
+ ]
103
+ const ctx = makeCtx(rows, {
104
+ ...P,
105
+ onRowDrop: vi.fn(),
106
+ serverGroup: { isGroup: (r: Task) => r.title === 'folder', level: () => 0, onToggle: () => {} },
107
+ })
108
+ const h = createRowDrag(ctx)
109
+ h.onRowDragStart(fakeEvent(), 1)
110
+ h.onRowDragOver(fakeEvent(20, 0, 40), 0) // the middle of the folder
111
+ expect(ctx.rowDropSide).toBe('into')
112
+ h.onRowDragOver(fakeEvent(3, 0, 40), 0) // its top edge
113
+ expect(ctx.rowDropSide).toBe('before')
114
+ h.onRowDragOver(fakeEvent(19, 0, 40), 1) // the middle of a file: no nesting, just the halves
115
+ expect(ctx.rowDropSide).toBe('before')
116
+ h.onRowDragOver(fakeEvent(21, 0, 40), 1)
117
+ expect(ctx.rowDropSide).toBe('after')
118
+ // Without a custom handler the middle band does not exist.
119
+ const managed = makeCtx(rows, { ...P, serverGroup: ctx.props.serverGroup })
120
+ const hm = createRowDrag(managed)
121
+ hm.onRowDragStart(fakeEvent(), 1)
122
+ hm.onRowDragOver(fakeEvent(20, 0, 40), 0)
123
+ expect(managed.rowDropSide).toBe('after')
124
+ })
125
+ })
126
+
76
127
  describe('createRowDrag - grid-to-grid', () => {
77
128
  it('moves a row from source into target when groups match', () => {
78
129
  const a = makeCtx([{ id: 1, title: 'a1' }, { id: 2, title: 'a2' }], { ...P, rowDragGroup: 'g' })
package/src/row-drag.ts CHANGED
@@ -116,7 +116,25 @@ export function createRowDrag<TFeatures, TData>(ctx: any) {
116
116
  if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
117
117
  const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
118
118
  ctx.rowDropIndex = rowIndex;
119
- ctx.rowDropSide = e.clientY < rect.top + rect.height / 2 ? "before" : "after";
119
+ ctx.rowDropSide = sideAt(rowIndex, rect, e.clientY);
120
+ }
121
+
122
+ /**
123
+ * Which edge the pointer is on. A custom drop handler also gets the middle
124
+ * of a group or tree row as "into" (the row becomes its child); the
125
+ * managed reorder only knows before and after.
126
+ */
127
+ function sideAt(rowIndex: number, rect: DOMRect, y: number): "before" | "after" | "into" {
128
+ const rel = (y - rect.top) / Math.max(rect.height, 1);
129
+ if (ctx.props.onRowDrop && rel >= 0.25 && rel <= 0.75 && canNest(rowIndex)) return "into";
130
+ return rel < 0.5 ? "before" : "after";
131
+ }
132
+ function canNest(rowIndex: number): boolean {
133
+ const row = ctx.allRows[rowIndex];
134
+ if (!row) return false;
135
+ const sg = ctx.props.serverGroup;
136
+ if (sg) return !!sg.isGroup(row.original);
137
+ return typeof row.getCanExpand === "function" && row.getCanExpand();
120
138
  }
121
139
 
122
140
  function onRowDragLeave(rowIndex: number) {
@@ -147,7 +165,7 @@ export function createRowDrag<TFeatures, TData>(ctx: any) {
147
165
  * both land here, so the reorder, the cross-grid hand-off and the
148
166
  * `onRowDragEnd` payload have exactly one implementation.
149
167
  */
150
- function commitDropOnRow(rowIndex: number, side: "before" | "after") {
168
+ function commitDropOnRow(rowIndex: number, side: "before" | "after" | "into") {
151
169
  if (!canAccept() || !bus) return;
152
170
  const dragged = bus.row;
153
171
  const target = originalAt(rowIndex);
@@ -155,6 +173,15 @@ export function createRowDrag<TFeatures, TData>(ctx: any) {
155
173
  const sameGrid = sourceGridId === gridId;
156
174
  clearIndicators();
157
175
 
176
+ // A custom handler owns the drop: the grid reports it and leaves its
177
+ // data alone (a row model's rows are not the grid's to splice).
178
+ if (sameGrid && ctx.props.onRowDrop) {
179
+ bus = null;
180
+ ctx.props.onRowDrop({ row: dragged, target, targetIndex: rowIndex, side });
181
+ return;
182
+ }
183
+ if (side === "into") side = "after";
184
+
158
185
  const data = (ctx.internalData as unknown[]).slice();
159
186
  if (sameGrid) {
160
187
  const from = data.indexOf(dragged);
@@ -198,6 +225,12 @@ export function createRowDrag<TFeatures, TData>(ctx: any) {
198
225
  const sameGrid = sourceGridId === gridId;
199
226
  clearIndicators();
200
227
 
228
+ if (sameGrid && ctx.props.onRowDrop) {
229
+ bus = null;
230
+ ctx.props.onRowDrop({ row: dragged, target: null, targetIndex: null, side: "after" });
231
+ return;
232
+ }
233
+
201
234
  const data = (ctx.internalData as unknown[]).slice();
202
235
  if (sameGrid) {
203
236
  const from = data.indexOf(dragged);
@@ -0,0 +1,152 @@
1
+ /**
2
+ * `GridRowModel` - one prop instead of eleven.
3
+ *
4
+ * A grid backed by a server needs the same wiring every time: hand it the
5
+ * rows, tell it not to sort or filter locally, forward the sort and the filter
6
+ * to the backend, report which rows are on screen, say which rows have not
7
+ * loaded, and delegate selection. Written out by hand that is a dozen props
8
+ * and a filter-shape conversion that every app gets slightly differently:
9
+ *
10
+ * ```svelte
11
+ * <SvGrid data={s.rows} externalSort externalFilter loading={s.loading}
12
+ * onSortingChange={(x) => ctl.setSort(x)}
13
+ * onFiltersChange={(f) => ctl.setFilter({ global: f.global,
14
+ * columns: Object.fromEntries(f.columns.map((c) => [c.id, { ... }])) })}
15
+ * onVisibleRangeChange={(r) => ctl.setViewport(r.startIndex, r.endIndex)}
16
+ * ... />
17
+ * ```
18
+ *
19
+ * An object that implements this interface carries all of it, so the same grid
20
+ * becomes:
21
+ *
22
+ * ```svelte
23
+ * <SvGrid rowModel={ctl} {columns} />
24
+ * ```
25
+ *
26
+ * `createServerDataSource` returns one, and so does the Enterprise server row
27
+ * model - which is the point of the interface rather than a concrete type: the
28
+ * grid depends on the SHAPE, never on either implementation, and an app with
29
+ * its own fetching can satisfy it too.
30
+ *
31
+ * Every member except `subscribe` / `getRows` / `isLoading` is optional, and a
32
+ * prop written explicitly on `<SvGrid>` always wins over the model's answer -
33
+ * so adopting this is never all-or-nothing.
34
+ */
35
+ import type { FilterOperator } from './SvGrid.types'
36
+
37
+ /** The filter state as the grid reports it, straight from `onFiltersChange`. */
38
+ export type GridFilterState = {
39
+ global: string
40
+ columns: Array<{
41
+ id: string
42
+ operator: FilterOperator
43
+ value: string
44
+ valueTo?: string
45
+ selectedValues?: Array<string>
46
+ }>
47
+ }
48
+
49
+ /** The sort the grid hands a row model: column id and direction, in priority order. */
50
+ export type GridRowModelSort = Array<{ id: string; desc: boolean }>
51
+
52
+ /**
53
+ * What `<SvGrid rowModel>` consumes: rows, a loading flag, a change
54
+ * subscription, and whichever optional parts the model implements - sort,
55
+ * filter, viewport, placeholders, group accessors, selection, filter
56
+ * values, paging, pinned rows and pivot columns. `createServerDataSource`
57
+ * and the Enterprise `createServerRowModel` both satisfy it.
58
+ */
59
+ export type GridRowModel<TData> = {
60
+ /**
61
+ * Register a callback for "something changed, re-read me". Returns an
62
+ * unsubscribe function, which the grid calls on unmount.
63
+ */
64
+ subscribe(onChange: () => void): () => void
65
+ /** The rows to render, including any placeholders for unloaded ones. */
66
+ getRows(): ReadonlyArray<TData>
67
+ /** True while a fetch is in flight, for the loading overlay. */
68
+ isLoading(): boolean
69
+ /** Stable row id. Required once selection or editing is involved. */
70
+ getRowId?(row: TData, index: number): string
71
+ /** Sort on the server. Present means "do not sort locally". */
72
+ setSort?(sort: GridRowModelSort): void
73
+ /** Filter on the server. Present means "do not filter locally". */
74
+ setFilter?(filters: GridFilterState): void
75
+ /** Which rows are on screen, so a block-loading model knows what to fetch. */
76
+ setViewport?(startIndex: number, endIndex: number): void
77
+ /** Why a row has no data yet, or null when it does. */
78
+ rowPlaceholder?(row: TData, rowIndex: number): 'loading' | 'failed' | null
79
+ /** Retry the failed block a placeholder row belongs to. */
80
+ retryRow?(row: TData, rowIndex: number): void
81
+ /** Group / tree accessors, for the grid's treegrid keyboard and ARIA. */
82
+ group?: {
83
+ isGroup: (row: TData) => boolean
84
+ level: (row: TData) => number
85
+ expanded?: (row: TData) => boolean
86
+ onToggle: (row: TData) => void
87
+ /** Open or close the detail panel under a leaf. Ctrl+Enter on a row calls it when `renderDetailRow` is set, and so does the `showDetailToggle` chevron. */
88
+ toggleDetail?: (row: TData) => void
89
+ /** Whether the detail under a leaf is open: the chevron's direction. */
90
+ detailOpen?: (row: TData) => boolean
91
+ /** Whether a row can open a detail; the chevron is left out where this is false. */
92
+ hasDetail?: (row: TData) => boolean
93
+ }
94
+ /** Selection the grid does not own - see the `rowSelectionModel` prop. */
95
+ selection?: {
96
+ isSelected: (rowId: string, row: TData) => boolean
97
+ headerState: () => 'none' | 'some' | 'all'
98
+ toggle: (rowId: string, row: TData, next: boolean) => void
99
+ toggleAll: (next: boolean) => void
100
+ selectedCount?: () => number | null
101
+ bulkUpdate?: (patch: Record<string, unknown>) => Promise<number>
102
+ }
103
+ /** Distinct values for a column's set filter, fetched from the server. */
104
+ filterValues?(columnId: string): Promise<Array<string>>
105
+ /** Server-side paging. Omit it for infinite scrolling. */
106
+ pagination?: {
107
+ pageIndex: number
108
+ pageSize: number
109
+ rowCount: number
110
+ setPage(pageIndex: number): void
111
+ setPageSize(pageSize: number): void
112
+ /** Choices for the footer's page-size selector. */
113
+ pageSizes?: number[]
114
+ /**
115
+ * Ask the grid for as many rows as its body fits: it measures and calls
116
+ * `setPageSize` whenever the fit changes.
117
+ */
118
+ autoPageSize?: boolean
119
+ }
120
+ /** Rows to pin above / below the scrolling body - a grand total, say. */
121
+ readonly pinnedTopRows?: ReadonlyArray<TData>
122
+ readonly pinnedBottomRows?: ReadonlyArray<TData>
123
+ /**
124
+ * Columns that replace the grid's `columns` while the model is in pivot
125
+ * mode, or null. Read after every change notification.
126
+ */
127
+ readonly pivotResultColumns?: ReadonlyArray<unknown> | null
128
+ }
129
+
130
+ /**
131
+ * Turn the grid's filter payload into the column-keyed map a `ServerRequest`
132
+ * carries. Exported because a hand-written `GridRowModel` needs exactly this
133
+ * conversion, and copying it is how the two shapes drift apart.
134
+ */
135
+ export function toServerFilterColumns(
136
+ filters: GridFilterState,
137
+ ): Record<
138
+ string,
139
+ { operator: string; value: string; valueTo?: string; selectedValues?: Array<string> }
140
+ > {
141
+ return Object.fromEntries(
142
+ filters.columns.map((c) => [
143
+ c.id,
144
+ {
145
+ operator: c.operator,
146
+ value: c.value,
147
+ ...(c.valueTo !== undefined ? { valueTo: c.valueTo } : {}),
148
+ ...(c.selectedValues !== undefined ? { selectedValues: c.selectedValues } : {}),
149
+ },
150
+ ]),
151
+ )
152
+ }
package/src/row-resize.ts CHANGED
@@ -69,6 +69,20 @@ export function rowResize(node: HTMLElement, opts: RowResizeOptions) {
69
69
  /** The last press on a strip, for telling a double-click from two drags. */
70
70
  let lastPress: { rowIndex: number; at: number } | null = null
71
71
 
72
+ /**
73
+ * A focusable `role="separator"` is a splitter, and a splitter carries its
74
+ * position: axe's `aria-required-attr` fails one without `aria-valuenow`,
75
+ * and a screen reader reading the strip says nothing useful without it. The
76
+ * value is the row's height in pixels, between the action's min and max.
77
+ */
78
+ function setStripValue(strip: HTMLElement, height: number): void {
79
+ const min = current.min ?? 20
80
+ const max = current.max ?? 320
81
+ strip.setAttribute('aria-valuemin', String(min))
82
+ strip.setAttribute('aria-valuemax', String(max))
83
+ strip.setAttribute('aria-valuenow', String(Math.round(Math.max(min, Math.min(max, height || min)))))
84
+ }
85
+
72
86
  function rowIndexOf(tr: HTMLTableRowElement): number {
73
87
  const dataRow = tr.querySelector<HTMLElement>('[data-svgrid-row]')
74
88
  const val = dataRow?.dataset.svgridRow
@@ -81,6 +95,7 @@ export function rowResize(node: HTMLElement, opts: RowResizeOptions) {
81
95
  const max = current.max ?? 320
82
96
  const next = Math.max(min, Math.min(max, drag.startHeight + (e.clientY - drag.startY)))
83
97
  drag.trEl.style.height = `${Math.round(next)}px`
98
+ setStripValue(drag.strip, next)
84
99
  current.onResizeMove?.(drag.rowIndex, Math.round(next))
85
100
  }
86
101
  function onPointerUp(e: PointerEvent) {
@@ -124,6 +139,7 @@ export function rowResize(node: HTMLElement, opts: RowResizeOptions) {
124
139
  Math.max(min, Math.min(max, tr.getBoundingClientRect().height + delta)),
125
140
  )
126
141
  tr.style.height = `${next}px`
142
+ setStripValue(t, next)
127
143
  current.onResizeMove?.(rowIndex, next)
128
144
  current.onResize(rowIndex, next)
129
145
  }
@@ -189,7 +205,13 @@ export function rowResize(node: HTMLElement, opts: RowResizeOptions) {
189
205
  ? tr.querySelector<HTMLTableCellElement>('.sv-grid-cell')
190
206
  : null)
191
207
  if (!gutter) continue
192
- if (gutter.querySelector(`:scope > .${STRIP_CLASS}`)) continue
208
+ const already = gutter.querySelector<HTMLElement>(`:scope > .${STRIP_CLASS}`)
209
+ if (already) {
210
+ // The row may have been resized from outside the action (a saved
211
+ // height, a double-click autofit); keep the separator's value honest.
212
+ setStripValue(already, tr.getBoundingClientRect().height)
213
+ continue
214
+ }
193
215
  // Anchor the strip to the gutter cell + allow it to overflow
194
216
  // downward into the row gap so the hit-area straddles the
195
217
  // row boundary the way Excel does.
@@ -203,6 +225,7 @@ export function rowResize(node: HTMLElement, opts: RowResizeOptions) {
203
225
  // A separator used as an interactive splitter has to be focusable so
204
226
  // keyboard-only users can reach it and drive `onKeyDown` (#79).
205
227
  strip.tabIndex = 0
228
+ setStripValue(strip, tr.getBoundingClientRect().height)
206
229
  // Inline the geometry + pointer-events so the strip works even
207
230
  // before SvGrid.css is parsed; the hover tint still comes from
208
231
  // the stylesheet. We keep the strip fully inside the gutter
@@ -39,6 +39,9 @@ function makeCtx(overrides: any = {}) {
39
39
  allRows: [],
40
40
  allColumns: [],
41
41
  headerSelectionState: 'none',
42
+ // The real controller always supplies this; a fake that omits it would
43
+ // make every select-all path throw rather than assert.
44
+ placeholderStateOf: () => null,
42
45
  userHasActivatedCell: false,
43
46
  enableCellSelectionEffective: true,
44
47
  selectionRange: { anchor: null, focus: null },
package/src/selection.ts CHANGED
@@ -21,16 +21,45 @@ export function createSelection<
21
21
  TData extends RowData = RowData,
22
22
  >(ctx: any) {
23
23
  function isRowSelected(rowId: string) {
24
+ const model = ctx.props.rowSelectionModel;
25
+ if (model) {
26
+ const row = ctx.allRows.find((r: any) => r.id === rowId);
27
+ return row ? model.isSelected(rowId, row.original) : false;
28
+ }
24
29
  return Boolean(ctx.rowSelectionState[rowId]);
25
30
  }
26
31
 
27
32
  function toggleRowSelectionById(rowId: string) {
33
+ const model = ctx.props.rowSelectionModel;
34
+ if (model) {
35
+ const row = ctx.allRows.find((r: any) => r.id === rowId);
36
+ if (!row) return;
37
+ model.toggle(rowId, row.original, !model.isSelected(rowId, row.original));
38
+ return;
39
+ }
28
40
  ctx.grid.setRowSelection((prev: any) => ({ ...prev, [rowId]: !prev[rowId] }));
29
41
  }
30
42
 
31
43
  function toggleSelectAllRows() {
32
- const selectable = ctx.allRows.filter((row: any) => !isGroupRow(row));
33
- const select = ctx.headerSelectionState !== "all";
44
+ setSelectAllRows(ctx.headerSelectionState !== "all");
45
+ }
46
+
47
+ /**
48
+ * Select or clear every row, for the header checkbox and for
49
+ * `api.selectAllRows()`.
50
+ */
51
+ function setSelectAllRows(select: boolean) {
52
+ // An external model decides what "all" means - possibly a million rows
53
+ // the grid has never seen - so it gets the intent, not a row list.
54
+ if (ctx.props.rowSelectionModel) {
55
+ ctx.props.rowSelectionModel.toggleAll(select);
56
+ return;
57
+ }
58
+ // Placeholder rows are excluded: there is no row there to select yet,
59
+ // and ticking one would leave a selected id that never resolves.
60
+ const selectable = ctx.allRows.filter(
61
+ (row: any) => !isGroupRow(row) && !ctx.placeholderStateOf(row),
62
+ );
34
63
  ctx.grid.setRowSelection((prev: any) => {
35
64
  const next = { ...prev };
36
65
  for (const row of selectable) {
@@ -519,7 +548,7 @@ export function createSelection<
519
548
  ctx.stopEdgeScroll();
520
549
  // Also commit a fill-handle drag if one was in progress - the user
521
550
  // released the mouse, time to apply the pattern.
522
- if (ctx.fillDrag) ctx.onFillPointerUp();
551
+ if (ctx.fillDrag) ctx.onFillPointerUp(event);
523
552
  if (ctx.moveDrag) ctx.onMovePointerUp(event);
524
553
  }
525
554
 
@@ -687,6 +716,7 @@ export function createSelection<
687
716
  isRowSelected,
688
717
  toggleRowSelectionById,
689
718
  toggleSelectAllRows,
719
+ setSelectAllRows,
690
720
  setActiveCell,
691
721
  scrollActiveCellIntoView,
692
722
  setSelection,