@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.
- package/README.md +32 -11
- package/dist/GridMenus.svelte +34 -4
- package/dist/SvGrid.controller.svelte.d.ts +33 -5
- package/dist/SvGrid.controller.svelte.js +397 -58
- package/dist/SvGrid.css +200 -2
- package/dist/SvGrid.svelte +350 -40
- package/dist/SvGrid.types.d.ts +433 -7
- package/dist/SvGridDropdown.svelte +20 -0
- package/dist/SvGridDropdown.svelte.d.ts +12 -0
- package/dist/SvModal.svelte +22 -0
- package/dist/SvModal.svelte.d.ts +7 -0
- package/dist/a11y/dismissable.d.ts +10 -0
- package/dist/a11y/dismissable.js +24 -2
- package/dist/build-api.js +92 -37
- package/dist/cdn/{GridMenus-BL7ZgQvU.js → GridMenus-C-NtjlrJ.js} +155 -146
- package/dist/cdn/GridMenus-TssHej3q.js +644 -0
- package/dist/cdn/{SvChartMenu-FBSMINA6.js → SvChartMenu-BFSTpvB-.js} +1 -1
- package/dist/cdn/{SvChartMenu-Bl6PBbkT.js → SvChartMenu-BZ1Iwv0y.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-DQwt4UAs.js → SvDateTimePicker-D9u_cOf8.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-vNU6bZ-q.js → SvDateTimePicker-s_6IUpI7.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-B1p-vCK5.js → SvGridCellEditor-BlyCSp0q.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-D_0q4xAu.js → SvGridCellEditor-ClRRA9lW.js} +1 -1
- package/dist/cdn/{SvGridChart-DzLkSwCH.js → SvGridChart-B4EV8U6t.js} +4 -4
- package/dist/cdn/{SvGridChart-C3EWAZaM.js → SvGridChart-DsmwTyZF.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-CfII62sZ.js → SvGridChartBuilder-cHloHobN.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-BE2T1ykB.js → SvGridChartBuilder-vOl7C0Za.js} +4 -4
- package/dist/cdn/{SvGridChartPanel-xf4sWVTo.js → SvGridChartPanel-AcqF-Ll5.js} +5 -5
- package/dist/cdn/{SvGridChartPanel-CCX5_Wgd.js → SvGridChartPanel-biMtSBPA.js} +5 -5
- package/dist/cdn/{SvGridChartView-DYwabQWj.js → SvGridChartView-Dx_GmZuL.js} +2 -2
- package/dist/cdn/{SvGridChartView-CfuXmY5I.js → SvGridChartView-I3rDHXAq.js} +2 -2
- package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-BWeM3lMe.js} +46 -46
- package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CJ6QqTxu.js} +67 -67
- package/dist/cdn/{SvModal-CIZWCcad.js → SvModal-ClxGmpAc.js} +187 -186
- package/dist/cdn/{SvModal-BAE-pjZX.js → SvModal-DhB_VMFR.js} +148 -147
- package/dist/cdn/{cell-formatting-C2Hf5gqd.js → cell-formatting-DVVKOSI9.js} +1 -1
- package/dist/cdn/{chart-Dz7SqMXH.js → chart-DEZxwC0i.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CUbf2R4i.js → chart-panel-messages-D12XcTZJ.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CmNrMdsr.js → chart-panel-messages-DguS7gC5.js} +1 -1
- package/dist/cdn/{chart-summary-BJW_tg_X.js → chart-summary-CMAXBGY-.js} +1 -1
- package/dist/cdn/{chart-trend-CaN9mDEV.js → chart-trend-BkEp61Gj.js} +1 -1
- package/dist/cdn/{dismissable-DAHetSNk.js → dismissable-Brc4EZU1.js} +5 -1
- package/dist/cdn/{export-format-CUDLy2yn.js → export-format-YzdwBww2.js} +1 -1
- package/dist/cdn/{row-resize-niQCp040.js → row-resize-DtOS1KG6.js} +41 -32
- package/dist/cdn/server-block-cache-DIqb3VZn.js +241 -0
- package/dist/cdn/{src-V1uu8iE9.js → src-CgoeSM8k.js} +5339 -4753
- package/dist/cdn/{src-BKhZ6eXd.js → src-DfwxB1TH.js} +9817 -9231
- package/dist/cdn/svgrid.js +28 -27
- package/dist/cdn/svgrid.svelte-external.js +28 -27
- package/dist/cdn/validate-AZoD8BoR.js +80 -0
- package/dist/cell-formatting.js +18 -1
- package/dist/cell-render.js +25 -6
- package/dist/clipboard.d.ts +2 -2
- package/dist/clipboard.js +72 -23
- package/dist/columns.js +7 -2
- package/dist/conditional-formatting.d.ts +2 -0
- package/dist/conditional-formatting.js +2 -0
- package/dist/core.d.ts +22 -6
- package/dist/core.js +401 -38
- package/dist/editing.js +34 -0
- package/dist/gantt-view.svelte.d.ts +24 -0
- package/dist/gantt-view.svelte.js +13 -0
- package/dist/grid-icons.d.ts +2 -2
- package/dist/grid-icons.js +2 -2
- package/dist/grid-messages.d.ts +10 -0
- package/dist/grid-messages.js +9 -0
- package/dist/headless.d.ts +5 -0
- package/dist/headless.js +12 -0
- package/dist/history.d.ts +21 -0
- package/dist/history.js +34 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -3
- package/dist/keyboard-handlers.js +24 -1
- package/dist/keyboard.js +4 -0
- package/dist/row-drag-touch.d.ts +1 -1
- package/dist/row-drag.js +35 -1
- package/dist/row-model.d.ts +139 -0
- package/dist/row-model.js +16 -0
- package/dist/row-resize.js +22 -1
- package/dist/selection.d.ts +2 -1
- package/dist/selection.js +31 -3
- package/dist/server-block-cache.d.ts +219 -0
- package/dist/server-block-cache.js +539 -0
- package/dist/server-data-source.d.ts +274 -8
- package/dist/server-data-source.js +229 -20
- package/dist/server.d.ts +13 -0
- package/dist/server.js +13 -0
- package/dist/sparkline.js +7 -5
- package/dist/svgrid-wrapper.types.d.ts +33 -0
- package/dist/validate.d.ts +4 -0
- package/dist/validate.js +29 -3
- package/package.json +11 -1
- package/src/GridMenus.svelte +34 -4
- package/src/SvGrid.controller.svelte.ts +397 -61
- package/src/SvGrid.css +200 -2
- package/src/SvGrid.svelte +350 -40
- package/src/SvGrid.types.ts +453 -7
- package/src/SvGridDropdown.svelte +20 -0
- package/src/SvModal.svelte +22 -0
- package/src/SvModal.test.ts +30 -1
- package/src/a11y/dismissable.test.ts +10 -1
- package/src/a11y/dismissable.ts +22 -2
- package/src/build-api.ts +96 -31
- package/src/cell-formatting.test.ts +17 -0
- package/src/cell-formatting.ts +18 -1
- package/src/cell-render.test.ts +24 -2
- package/src/cell-render.ts +24 -8
- package/src/clipboard.test.ts +107 -0
- package/src/clipboard.ts +62 -23
- package/src/columns.test.ts +4 -4
- package/src/columns.ts +7 -2
- package/src/conditional-formatting.test.ts +8 -0
- package/src/conditional-formatting.ts +3 -0
- package/src/core.rowmodel-cache.test.ts +51 -0
- package/src/core.tick-repair.test.ts +287 -0
- package/src/core.ts +407 -38
- package/src/editing.test.ts +52 -0
- package/src/editing.ts +33 -0
- package/src/gantt-stub.test.svelte +38 -0
- package/src/gantt-view.svelte.ts +35 -0
- package/src/grid-icons.ts +2 -2
- package/src/grid-messages.test.ts +7 -0
- package/src/grid-messages.ts +22 -0
- package/src/headless.ts +24 -0
- package/src/history.test.ts +49 -1
- package/src/history.ts +48 -2
- package/src/icon-seam.test.ts +2 -4
- package/src/index.ts +38 -10
- package/src/keyboard-handlers.coverage.test.ts +65 -0
- package/src/keyboard-handlers.ts +26 -1
- package/src/keyboard.ts +3 -0
- package/src/row-drag-touch.ts +2 -2
- package/src/row-drag.test.ts +51 -0
- package/src/row-drag.ts +35 -2
- package/src/row-model.ts +152 -0
- package/src/row-resize.ts +24 -1
- package/src/selection.test.ts +3 -0
- package/src/selection.ts +33 -3
- package/src/server-block-cache.test.ts +672 -0
- package/src/server-block-cache.ts +690 -0
- package/src/server-data-source.infinite.test.ts +405 -0
- package/src/server-data-source.test.ts +19 -0
- package/src/server-data-source.ts +496 -29
- package/src/server.ts +49 -0
- package/src/sparkline.test.ts +7 -0
- package/src/sparkline.ts +6 -4
- package/src/svgrid-wrapper.types.ts +32 -0
- package/src/svgrid.api.test.ts +21 -0
- package/src/svgrid.charting.test.ts +12 -4
- package/src/svgrid.context-menu.test.ts +12 -0
- package/src/svgrid.detail-rows.svelte.test.ts +187 -0
- package/src/svgrid.filter-menu-scroll.test.ts +9 -0
- package/src/svgrid.gantt-seam.test.ts +199 -0
- package/src/svgrid.live-update-paths.svelte.test.ts +184 -0
- package/src/svgrid.menu-scroll-close.test.ts +10 -0
- package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
- package/src/svgrid.row-model-seam.svelte.test.ts +305 -0
- package/src/svgrid.row-pinning.test.ts +15 -1
- package/src/svgrid.selection-bar-seam.test.ts +1 -1
- package/src/svgrid.sticky-groups.svelte.test.ts +145 -0
- package/src/svgrid.upsell-license.test.ts +8 -5
- package/src/test-setup.ts +12 -0
- package/src/transaction.test.ts +58 -0
- package/src/validate.test.ts +29 -0
- package/src/validate.ts +34 -3
- package/dist/SvGroupCell.svelte +0 -141
- package/dist/SvGroupCell.svelte.d.ts +0 -49
- package/dist/SvRowGroupPanel.svelte +0 -186
- package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
- package/dist/cdn/GridMenus-7kbpnnBW.js +0 -635
- package/dist/cdn/validate-_CDJzgIo.js +0 -75
- package/dist/server-group-model.d.ts +0 -98
- package/dist/server-group-model.js +0 -263
- package/src/SvGroupCell.svelte +0 -141
- package/src/SvRowGroupPanel.svelte +0 -186
- package/src/server-group-model.test.ts +0 -294
- package/src/server-group-model.ts +0 -370
package/src/row-drag.test.ts
CHANGED
|
@@ -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 =
|
|
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);
|
package/src/row-model.ts
ADDED
|
@@ -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
|
-
|
|
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
|
package/src/selection.test.ts
CHANGED
|
@@ -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
|
-
|
|
33
|
-
|
|
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,
|