@svgrid/grid 3.0.3 → 3.0.4
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 +2 -1
- package/dist/GridMenus.svelte +44 -2
- package/dist/SvChartMenu.svelte +69 -0
- package/dist/SvChartMenu.svelte.d.ts +11 -0
- package/dist/SvChartPanes.svelte +113 -0
- package/dist/SvChartPanes.svelte.d.ts +20 -0
- package/dist/SvGrid.controller.svelte.d.ts +110 -39
- package/dist/SvGrid.controller.svelte.js +655 -200
- package/dist/SvGrid.css +105 -2
- package/dist/SvGrid.svelte +386 -514
- package/dist/SvGrid.types.d.ts +210 -9
- package/dist/SvGridCellEditor.svelte +17 -0
- package/dist/SvGridChart.svelte +2257 -329
- package/dist/SvGridChart.svelte.d.ts +6 -106
- package/dist/SvGridChart.types.d.ts +338 -0
- package/dist/SvGridChart.types.js +1 -0
- package/dist/SvGridChartBuilder.svelte +323 -0
- package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
- package/dist/SvGridChartPanel.svelte +221 -275
- package/dist/SvGridChartPickers.svelte +417 -0
- package/dist/SvGridChartPickers.svelte.d.ts +70 -0
- package/dist/SvGridChartView.svelte +13 -14
- package/dist/SvModal.svelte +7 -1
- package/dist/SvModal.svelte.d.ts +5 -0
- package/dist/ai.d.ts +40 -8
- package/dist/ai.js +135 -24
- package/dist/aria.d.ts +11 -0
- package/dist/build-api.js +108 -22
- package/dist/cdn/GridMenus-7kbpnnBW.js +635 -0
- package/dist/cdn/GridMenus-BL7ZgQvU.js +636 -0
- package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
- package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
- package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
- package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
- package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
- package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
- package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
- package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
- package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
- package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
- package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
- package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
- package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
- package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
- package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
- package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
- package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
- package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
- package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
- package/dist/cdn/SvModal-CIZWCcad.js +397 -0
- package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
- package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
- package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
- package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
- package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
- package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
- package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
- package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
- package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
- package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
- package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
- package/dist/cdn/dismissable-DAHetSNk.js +44 -0
- package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
- package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
- package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
- package/dist/cdn/{src-D6XvA1Ij.js → src-BKhZ6eXd.js} +10227 -9787
- package/dist/cdn/{src-Bugf5XlI.js → src-V1uu8iE9.js} +6568 -6128
- package/dist/cdn/svgrid.js +26 -15
- package/dist/cdn/svgrid.svelte-external.js +26 -15
- package/dist/chart-axes.d.ts +53 -0
- package/dist/chart-axes.js +351 -0
- package/dist/chart-cartesian.d.ts +88 -0
- package/dist/chart-cartesian.js +1862 -0
- package/dist/chart-decimate.d.ts +51 -0
- package/dist/chart-decimate.js +199 -0
- package/dist/chart-export-pdf.d.ts +47 -0
- package/dist/chart-export-pdf.js +187 -0
- package/dist/chart-export.d.ts +2 -1
- package/dist/chart-export.js +18 -3
- package/dist/chart-financial.d.ts +91 -0
- package/dist/chart-financial.js +175 -0
- package/dist/chart-flow.d.ts +10 -0
- package/dist/chart-flow.js +298 -0
- package/dist/chart-format.d.ts +18 -0
- package/dist/chart-format.js +65 -0
- package/dist/chart-grid.d.ts +8 -0
- package/dist/chart-grid.js +190 -0
- package/dist/chart-hierarchy.d.ts +17 -0
- package/dist/chart-hierarchy.js +146 -0
- package/dist/chart-indicators.d.ts +115 -0
- package/dist/chart-indicators.js +389 -0
- package/dist/chart-messages.d.ts +95 -0
- package/dist/chart-messages.js +92 -0
- package/dist/chart-motion.d.ts +16 -0
- package/dist/chart-motion.js +141 -0
- package/dist/chart-panel-messages.d.ts +265 -0
- package/dist/chart-panel-messages.js +285 -0
- package/dist/chart-pivot.d.ts +87 -0
- package/dist/chart-pivot.js +156 -0
- package/dist/chart-polar.d.ts +25 -0
- package/dist/chart-polar.js +700 -0
- package/dist/chart-samples.d.ts +16 -0
- package/dist/chart-samples.js +153 -0
- package/dist/chart-scale.d.ts +113 -0
- package/dist/chart-scale.js +405 -0
- package/dist/chart-stats.d.ts +111 -0
- package/dist/chart-stats.js +419 -0
- package/dist/chart-stream.d.ts +45 -0
- package/dist/chart-stream.js +91 -0
- package/dist/chart-summary.d.ts +27 -0
- package/dist/chart-summary.js +205 -0
- package/dist/chart-sync.svelte.d.ts +32 -0
- package/dist/chart-sync.svelte.js +23 -0
- package/dist/chart-table.d.ts +35 -0
- package/dist/chart-table.js +165 -0
- package/dist/chart-trend.d.ts +19 -0
- package/dist/chart-trend.js +61 -0
- package/dist/chart-types.d.ts +1456 -0
- package/dist/chart-types.js +6 -0
- package/dist/chart-validate.d.ts +43 -0
- package/dist/chart-validate.js +272 -0
- package/dist/chart-zoom.d.ts +40 -0
- package/dist/chart-zoom.js +145 -0
- package/dist/chart.d.ts +186 -865
- package/dist/chart.js +460 -2204
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +137 -18
- package/dist/column-resize.d.ts +3 -0
- package/dist/column-resize.js +4 -1
- package/dist/columns.js +3 -0
- package/dist/command-context.d.ts +19 -0
- package/dist/command-context.js +116 -0
- package/dist/core.d.ts +7 -0
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.d.ts +2 -8
- package/dist/editing.js +262 -33
- package/dist/fill-patterns.d.ts +0 -5
- package/dist/fill-patterns.js +56 -2
- package/dist/grid-messages.d.ts +10 -3
- package/dist/grid-messages.js +5 -0
- package/dist/history.d.ts +83 -0
- package/dist/history.js +133 -0
- package/dist/index.d.ts +24 -6
- package/dist/index.js +20 -2
- package/dist/keyboard-handlers.js +88 -31
- package/dist/keyboard.d.ts +55 -0
- package/dist/keyboard.js +132 -0
- package/dist/menus.d.ts +3 -1
- package/dist/menus.js +7 -2
- package/dist/merges.d.ts +97 -0
- package/dist/merges.js +147 -0
- package/dist/row-resize.d.ts +3 -0
- package/dist/row-resize.js +14 -0
- package/dist/scroll-sync.d.ts +1 -0
- package/dist/scroll-sync.js +24 -0
- package/dist/selection.d.ts +16 -0
- package/dist/selection.js +108 -16
- package/dist/shortcut-registry.d.ts +145 -0
- package/dist/shortcut-registry.js +44 -0
- package/dist/svgrid-wrapper.types.d.ts +213 -22
- package/dist/virtualization/virtualizer.js +38 -17
- package/package.json +6 -1
- package/src/GridMenus.svelte +44 -2
- package/src/SvChartMenu.svelte +69 -0
- package/src/SvChartPanes.svelte +113 -0
- package/src/SvChartPanes.test.ts +66 -0
- package/src/SvGrid.controller.svelte.ts +629 -172
- package/src/SvGrid.css +105 -2
- package/src/SvGrid.svelte +386 -514
- package/src/SvGrid.types.ts +188 -8
- package/src/SvGridCellEditor.svelte +17 -0
- package/src/SvGridChart.svelte +2257 -329
- package/src/SvGridChart.test.ts +1419 -1
- package/src/SvGridChart.types.ts +336 -0
- package/src/SvGridChartBuilder.svelte +323 -0
- package/src/SvGridChartPanel.svelte +221 -275
- package/src/SvGridChartPickers.svelte +417 -0
- package/src/SvGridChartView.svelte +13 -14
- package/src/SvModal.svelte +7 -1
- package/src/SvModal.test.ts +9 -0
- package/src/ai.test.ts +57 -2
- package/src/ai.ts +166 -22
- package/src/aria.d.ts +11 -0
- package/src/build-api.coverage.test.ts +184 -0
- package/src/build-api.ts +107 -18
- package/src/chart-axes.test.ts +573 -0
- package/src/chart-axes.ts +342 -0
- package/src/chart-cartesian.ts +1863 -0
- package/src/chart-decimate.test.ts +249 -0
- package/src/chart-decimate.ts +186 -0
- package/src/chart-export-pdf.test.ts +91 -0
- package/src/chart-export-pdf.ts +207 -0
- package/src/chart-export.test.ts +14 -0
- package/src/chart-export.ts +13 -3
- package/src/chart-financial.ts +208 -0
- package/src/chart-flow.ts +286 -0
- package/src/chart-format.ts +67 -0
- package/src/chart-grid.ts +179 -0
- package/src/chart-hierarchy.ts +135 -0
- package/src/chart-indicators.test.ts +443 -0
- package/src/chart-indicators.ts +384 -0
- package/src/chart-messages.test.ts +21 -0
- package/src/chart-messages.ts +184 -0
- package/src/chart-motion.test.ts +71 -0
- package/src/chart-motion.ts +148 -0
- package/src/chart-panel-messages.test.ts +29 -0
- package/src/chart-panel-messages.ts +550 -0
- package/src/chart-pivot.test.ts +54 -0
- package/src/chart-pivot.ts +225 -0
- package/src/chart-polar.ts +674 -0
- package/src/chart-samples.ts +106 -0
- package/src/chart-scale.ts +394 -0
- package/src/chart-series-types.test.ts +714 -0
- package/src/chart-stats.ts +409 -0
- package/src/chart-stream.test.ts +75 -0
- package/src/chart-stream.ts +114 -0
- package/src/chart-summary.test.ts +90 -0
- package/src/chart-summary.ts +197 -0
- package/src/chart-sync.svelte.ts +41 -0
- package/src/chart-table.test.ts +76 -0
- package/src/chart-table.ts +178 -0
- package/src/chart-trend.ts +47 -0
- package/src/chart-types.ts +1309 -0
- package/src/chart-validate.test.ts +100 -0
- package/src/chart-validate.ts +247 -0
- package/src/chart-zoom.test.ts +137 -0
- package/src/chart-zoom.ts +124 -0
- package/src/chart.coverage.test.ts +81 -0
- package/src/chart.test.ts +56 -3
- package/src/chart.ts +557 -2784
- package/src/clipboard.test.ts +151 -0
- package/src/clipboard.ts +145 -16
- package/src/column-resize.ts +7 -1
- package/src/columns.ts +2 -0
- package/src/command-context.test.ts +200 -0
- package/src/command-context.ts +137 -0
- package/src/core.ts +7 -0
- package/src/editing.test.ts +281 -3
- package/src/editing.ts +256 -38
- package/src/fill-patterns.test.ts +35 -0
- package/src/fill-patterns.ts +56 -2
- package/src/grid-messages.ts +16 -3
- package/src/history.test.ts +196 -0
- package/src/history.ts +162 -0
- package/src/icon-seam.test.ts +3 -2
- package/src/index.ts +150 -2
- package/src/keyboard-handlers.coverage.test.ts +126 -6
- package/src/keyboard-handlers.ts +91 -30
- package/src/keyboard-shortcuts.seam.test.ts +200 -0
- package/src/keyboard.test.ts +124 -1
- package/src/keyboard.ts +142 -0
- package/src/menus.test.ts +26 -0
- package/src/menus.ts +10 -3
- package/src/merges.test.ts +112 -0
- package/src/merges.ts +168 -0
- package/src/pivot.grid.test.ts +40 -5
- package/src/row-resize.test.ts +44 -0
- package/src/row-resize.ts +17 -0
- package/src/scroll-sync.test.ts +42 -1
- package/src/scroll-sync.ts +25 -0
- package/src/selection.multi-range.test.ts +34 -0
- package/src/selection.test.ts +30 -2
- package/src/selection.ts +111 -19
- package/src/shortcut-registry.test.ts +97 -0
- package/src/shortcut-registry.ts +181 -0
- package/src/svgrid-wrapper.types.ts +184 -13
- package/src/svgrid.behavior.test.ts +135 -0
- package/src/svgrid.charting.test.ts +513 -3
- package/src/svgrid.context-menu.test.ts +31 -0
- package/src/svgrid.new-features.wrapper.test.ts +10 -3
- package/src/virtualization/virtualizer.ts +39 -17
- package/dist/cdn/GridMenus-CHOMnW-c.js +0 -602
- package/dist/cdn/GridMenus-DhdPF700.js +0 -603
- package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
- package/dist/cdn/SvGridChart-1PMSp2LZ.js +0 -1481
- package/dist/cdn/SvGridChart-D02avhyr.js +0 -1480
- package/dist/cdn/SvGridChartPanel-DJnViOv4.js +0 -834
- package/dist/cdn/SvGridChartPanel-vPTNerFZ.js +0 -810
- package/dist/cdn/SvGridChartView-C--HhdUo.js +0 -56
- package/dist/cdn/SvGridChartView-KyyFG-wY.js +0 -55
- package/dist/cdn/chart-DLg3_zTQ.js +0 -1652
package/src/scroll-sync.test.ts
CHANGED
|
@@ -203,6 +203,47 @@ describe('scheduleScrollSync / flushScheduledScrollSync', () => {
|
|
|
203
203
|
})
|
|
204
204
|
})
|
|
205
205
|
|
|
206
|
+
describe('syncScrollNow', () => {
|
|
207
|
+
let cancelled: number[] = []
|
|
208
|
+
beforeEach(() => {
|
|
209
|
+
cancelled = []
|
|
210
|
+
vi.stubGlobal('requestAnimationFrame', () => 42)
|
|
211
|
+
vi.stubGlobal('cancelAnimationFrame', (h: number) => {
|
|
212
|
+
cancelled.push(h)
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
afterEach(() => {
|
|
216
|
+
vi.unstubAllGlobals()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('flushes the virtualizers synchronously, without waiting for a frame', () => {
|
|
220
|
+
const { ctx, ss } = syncFor({
|
|
221
|
+
rowVirtualizationEnabled: true,
|
|
222
|
+
columnVirtualizationEnabled: true,
|
|
223
|
+
domToLogicalRowOffset: (n: number) => n * 2,
|
|
224
|
+
})
|
|
225
|
+
ss.syncScrollNow(30, 88)
|
|
226
|
+
expect(ctx.pendingScrollTop).toBe(30)
|
|
227
|
+
expect(ctx.pendingScrollLeft).toBe(88)
|
|
228
|
+
expect(ctx.scrollVersion).toBe(1)
|
|
229
|
+
expect(ctx.virtualizer.setScrollOffset).toHaveBeenCalledWith(60)
|
|
230
|
+
expect(ctx.columnVirtualizer.setHorizontalOffset).toHaveBeenCalledWith(88)
|
|
231
|
+
expect(ctx.scrollSyncRaf).toBeNull()
|
|
232
|
+
expect(cancelled).toEqual([])
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('cancels a frame that a scroll event already scheduled, so the position is not applied twice', () => {
|
|
236
|
+
const { ctx, ss } = syncFor({ rowVirtualizationEnabled: true })
|
|
237
|
+
ss.scheduleScrollSync(10, 0)
|
|
238
|
+
expect(ctx.scrollSyncRaf).toBe(42)
|
|
239
|
+
ss.syncScrollNow(64, 0)
|
|
240
|
+
expect(cancelled).toEqual([42])
|
|
241
|
+
expect(ctx.scrollSyncRaf).toBeNull()
|
|
242
|
+
expect(ctx.virtualizer.setScrollOffset).toHaveBeenCalledTimes(1)
|
|
243
|
+
expect(ctx.virtualizer.setScrollOffset).toHaveBeenCalledWith(64)
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
206
247
|
describe('onBodyScroll', () => {
|
|
207
248
|
beforeEach(() => {
|
|
208
249
|
// These tests assert what happens BEFORE the frame runs, so the stub just
|
|
@@ -321,7 +362,7 @@ describe('createScrollSync wiring', () => {
|
|
|
321
362
|
it('exposes the documented handler surface', () => {
|
|
322
363
|
const { ss } = syncFor()
|
|
323
364
|
expect(Object.keys(ss).sort()).toEqual(
|
|
324
|
-
['flushScheduledScrollSync', 'hideTooltip', 'onBodyScroll', 'scheduleScrollSync', 'showTooltipFor'],
|
|
365
|
+
['flushScheduledScrollSync', 'hideTooltip', 'onBodyScroll', 'scheduleScrollSync', 'showTooltipFor', 'syncScrollNow'],
|
|
325
366
|
)
|
|
326
367
|
})
|
|
327
368
|
})
|
package/src/scroll-sync.ts
CHANGED
|
@@ -66,6 +66,30 @@ export function createScrollSync<
|
|
|
66
66
|
ctx.scrollSyncRaf = requestAnimationFrame(flushScheduledScrollSync);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Sync the virtualizers to a scroll position NOW instead of on the next
|
|
71
|
+
* animation frame.
|
|
72
|
+
*
|
|
73
|
+
* For a scroll the grid made itself - keyboard navigation bringing the
|
|
74
|
+
* active cell into view - the position is already known, and going through
|
|
75
|
+
* the browser's `scroll` event and then the rAF above put the row-window
|
|
76
|
+
* update one frame behind the key press: the keydown's own flush painted
|
|
77
|
+
* the active cell on the OLD rows, and the rows only caught up on the next
|
|
78
|
+
* frame. Two painted frames per arrow key. Syncing inline folds the window
|
|
79
|
+
* update into the same flush as the active-cell change, so the key press
|
|
80
|
+
* paints once. The `scroll` event still fires afterwards and re-syncs to
|
|
81
|
+
* the same offset, which the virtualizers treat as a no-op.
|
|
82
|
+
*/
|
|
83
|
+
function syncScrollNow(scrollTop: number, scrollLeft: number) {
|
|
84
|
+
if (ctx.scrollSyncRaf !== null) {
|
|
85
|
+
cancelAnimationFrame(ctx.scrollSyncRaf);
|
|
86
|
+
ctx.scrollSyncRaf = null;
|
|
87
|
+
}
|
|
88
|
+
ctx.pendingScrollTop = scrollTop;
|
|
89
|
+
ctx.pendingScrollLeft = scrollLeft;
|
|
90
|
+
flushScheduledScrollSync();
|
|
91
|
+
}
|
|
92
|
+
|
|
69
93
|
function onBodyScroll(event: Event) {
|
|
70
94
|
const container = event.currentTarget as HTMLDivElement | null;
|
|
71
95
|
if (!container) return;
|
|
@@ -91,6 +115,7 @@ export function createScrollSync<
|
|
|
91
115
|
hideTooltip,
|
|
92
116
|
flushScheduledScrollSync,
|
|
93
117
|
scheduleScrollSync,
|
|
118
|
+
syncScrollNow,
|
|
94
119
|
onBodyScroll,
|
|
95
120
|
};
|
|
96
121
|
}
|
|
@@ -58,4 +58,38 @@ describe('multiple range selection', () => {
|
|
|
58
58
|
// outside every range
|
|
59
59
|
expect(s.getCellRangeEdges(2, 2)).toBeNull()
|
|
60
60
|
})
|
|
61
|
+
|
|
62
|
+
it('cellSelectionState folds active cell, range edges and fill handle into one value', () => {
|
|
63
|
+
const ctx = makeCtx()
|
|
64
|
+
ctx.activeCell = { rowIndex: 1, colIndex: 1, cellId: null }
|
|
65
|
+
ctx.fillHandleCell = { rowIndex: 1, colIndex: 1 }
|
|
66
|
+
const s = createSelection(ctx)
|
|
67
|
+
s.setSelection(0, 0)
|
|
68
|
+
s.extendSelection(1, 1)
|
|
69
|
+
|
|
70
|
+
// The focus corner: active, on the bottom-right edges, carries the handle.
|
|
71
|
+
expect(s.cellSelectionState(1, 1)).toEqual({
|
|
72
|
+
active: true,
|
|
73
|
+
edges: { top: false, bottom: true, left: false, right: true },
|
|
74
|
+
fillHandle: true,
|
|
75
|
+
})
|
|
76
|
+
// Another cell of the range: in the range, not active, no handle.
|
|
77
|
+
expect(s.cellSelectionState(0, 1)).toEqual({
|
|
78
|
+
active: false,
|
|
79
|
+
edges: { top: true, bottom: false, left: false, right: true },
|
|
80
|
+
fillHandle: false,
|
|
81
|
+
})
|
|
82
|
+
// A plain cell is `null`, not an all-false object, so the view can tell
|
|
83
|
+
// "nothing changed here" by identity.
|
|
84
|
+
expect(s.cellSelectionState(5, 5)).toBeNull()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('cellSelectionState reports the active cell even with no range anchored', () => {
|
|
88
|
+
const ctx = makeCtx()
|
|
89
|
+
ctx.activeCell = { rowIndex: 2, colIndex: 3, cellId: null }
|
|
90
|
+
ctx.fillHandleCell = null
|
|
91
|
+
const s = createSelection(ctx)
|
|
92
|
+
expect(s.cellSelectionState(2, 3)).toEqual({ active: true, edges: null, fillHandle: false })
|
|
93
|
+
expect(s.cellSelectionState(2, 4)).toBeNull()
|
|
94
|
+
})
|
|
61
95
|
})
|
package/src/selection.test.ts
CHANGED
|
@@ -75,6 +75,8 @@ function makeCtx(overrides: any = {}) {
|
|
|
75
75
|
// for plain drag-select. Its own behaviour is covered in move-cells.test.ts.
|
|
76
76
|
trackEdgeScroll: vi.fn(),
|
|
77
77
|
stopEdgeScroll: vi.fn(),
|
|
78
|
+
// Inline virtualizer sync after a programmatic scroll (scroll-sync.ts).
|
|
79
|
+
syncScrollNow: vi.fn(),
|
|
78
80
|
grid: {
|
|
79
81
|
setRowSelection,
|
|
80
82
|
setActiveCell,
|
|
@@ -367,6 +369,19 @@ describe('createSelection - onCellPointerDown', () => {
|
|
|
367
369
|
expect(ctx.selectionRange.anchor).toEqual({ rowIndex: 0, colIndex: 0 })
|
|
368
370
|
expect(ctx.selectionRange.focus).toEqual({ rowIndex: 1, colIndex: 1 })
|
|
369
371
|
expect(ctx.activeAtPointerDown).toEqual({ rowIndex: 0, colIndex: 0 })
|
|
372
|
+
// The active cell stays at the anchor, as in Excel.
|
|
373
|
+
expect(ctx.grid.setActiveCell).not.toHaveBeenCalled()
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('Shift+click on a grid never clicked makes the cell active', () => {
|
|
377
|
+
const ctx = makeCtx({
|
|
378
|
+
allRows: [makeDataRow('r0'), makeDataRow('r1')],
|
|
379
|
+
allColumns: [makeColumn('c0'), makeColumn('c1')],
|
|
380
|
+
activeCell: null,
|
|
381
|
+
})
|
|
382
|
+
const sel = createSelection(ctx)
|
|
383
|
+
sel.onCellPointerDown(1, 1, { button: 0, shiftKey: true } as any)
|
|
384
|
+
expect(ctx.grid.setActiveCell).toHaveBeenCalled()
|
|
370
385
|
})
|
|
371
386
|
})
|
|
372
387
|
|
|
@@ -378,7 +393,7 @@ describe('createSelection - onCellPointerEnter / drag', () => {
|
|
|
378
393
|
expect(ctx.grid.setActiveCell).not.toHaveBeenCalled()
|
|
379
394
|
})
|
|
380
395
|
|
|
381
|
-
it('extends selection and
|
|
396
|
+
it('extends the selection while dragging and leaves the active cell where the drag began', () => {
|
|
382
397
|
const ctx = makeCtx({
|
|
383
398
|
isDraggingSelection: true,
|
|
384
399
|
selectionRange: { anchor: { rowIndex: 0, colIndex: 0 }, focus: { rowIndex: 0, colIndex: 0 } },
|
|
@@ -388,7 +403,7 @@ describe('createSelection - onCellPointerEnter / drag', () => {
|
|
|
388
403
|
const sel = createSelection(ctx)
|
|
389
404
|
sel.onCellPointerEnter(1, 1)
|
|
390
405
|
expect(ctx.selectionRange.focus).toEqual({ rowIndex: 1, colIndex: 1 })
|
|
391
|
-
expect(ctx.grid.setActiveCell).toHaveBeenCalled()
|
|
406
|
+
expect(ctx.grid.setActiveCell).not.toHaveBeenCalled()
|
|
392
407
|
})
|
|
393
408
|
|
|
394
409
|
it('ignores group rows while dragging', () => {
|
|
@@ -649,6 +664,19 @@ describe('createSelection - onCellClick', () => {
|
|
|
649
664
|
expect(ctx.onCellDoubleClick).toHaveBeenCalledWith(0, 0)
|
|
650
665
|
})
|
|
651
666
|
|
|
667
|
+
it('leaves the cell alone on a second click when editOnSecondClick is off', () => {
|
|
668
|
+
// A sheet: clicking the active cell again is a click, not an edit.
|
|
669
|
+
const ctx = clickCtx({
|
|
670
|
+
editingEnabled: true,
|
|
671
|
+
activeAtPointerDown: { rowIndex: 0, colIndex: 0 },
|
|
672
|
+
props: { editOnSecondClick: false },
|
|
673
|
+
})
|
|
674
|
+
const sel = createSelection(ctx)
|
|
675
|
+
sel.onCellClick(0, 0)
|
|
676
|
+
expect(ctx.onCellDoubleClick).not.toHaveBeenCalled()
|
|
677
|
+
expect(ctx.editingCell).toBeNull()
|
|
678
|
+
})
|
|
679
|
+
|
|
652
680
|
it('clears editing when not re-clicking the active cell', () => {
|
|
653
681
|
const ctx = clickCtx({
|
|
654
682
|
editingEnabled: true,
|
package/src/selection.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getColumnBaseValue,
|
|
15
15
|
isGroupRow,
|
|
16
16
|
} from "./cell-values";
|
|
17
|
+
import { expandRectToMerges, originOf, endOf } from "./merges";
|
|
17
18
|
|
|
18
19
|
export function createSelection<
|
|
19
20
|
TFeatures extends TableFeatures = TableFeatures,
|
|
@@ -41,7 +42,18 @@ export function createSelection<
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
function setActiveCell(rowIndex: number, colIndex: number) {
|
|
45
|
+
// The active cell inside a merge is its origin: the one that draws, the
|
|
46
|
+
// one the formula bar shows, the one an edit writes.
|
|
47
|
+
({ rowIndex, colIndex } = originOf(ctx.mergeIndex, rowIndex, colIndex));
|
|
44
48
|
ctx.userHasActivatedCell = true;
|
|
49
|
+
// A click, an arrow, a jump: the Tab run is over. The Tab and Enter
|
|
50
|
+
// paths put the origin back after this call when it should live on.
|
|
51
|
+
// Activating the cell the cursor is already on (typing into it, F2, a
|
|
52
|
+
// double-click) is not a move and keeps the run.
|
|
53
|
+
const prev = ctx.grid.getState().activeCell;
|
|
54
|
+
if (!prev || prev.rowIndex !== rowIndex || prev.colIndex !== colIndex) {
|
|
55
|
+
ctx.tabRunOrigin = null;
|
|
56
|
+
}
|
|
45
57
|
ctx.grid.setActiveCell({
|
|
46
58
|
rowIndex,
|
|
47
59
|
colIndex,
|
|
@@ -64,8 +76,16 @@ export function createSelection<
|
|
|
64
76
|
if (!ctx.scrollContainer) return;
|
|
65
77
|
if (rowIndex < 0 || rowIndex >= ctx.allRows.length) return;
|
|
66
78
|
if (colIndex < 0 || colIndex >= ctx.allColumns.length) return;
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
const container = ctx.scrollContainer as HTMLElement;
|
|
80
|
+
const topBefore = container.scrollTop;
|
|
81
|
+
const leftBefore = container.scrollLeft;
|
|
82
|
+
// A frozen row is always in view; only the column may need to move.
|
|
83
|
+
const frozen: number = ctx.frozenRowCount ?? 0;
|
|
84
|
+
const frozenBand: number = ctx.frozenBandHeight ?? 0;
|
|
85
|
+
|
|
86
|
+
if (rowIndex < frozen) {
|
|
87
|
+
// Always in view; only the column below may need to move.
|
|
88
|
+
} else if (ctx.rowVirtualizationEnabled) {
|
|
69
89
|
// Prefer the browser's native `scrollIntoView({ block: 'nearest' })`
|
|
70
90
|
// when the target row is already mounted. It does Excel-style
|
|
71
91
|
// minimum-scroll, respects the sticky thead via the
|
|
@@ -116,8 +136,10 @@ export function createSelection<
|
|
|
116
136
|
const rowHeight = ctx.virtualizer.getSizeForIndex(rowIndex)
|
|
117
137
|
const rowBottom = rowTopScroll + rowHeight
|
|
118
138
|
let nextTop = currentTop
|
|
119
|
-
|
|
120
|
-
|
|
139
|
+
// The frozen band sits under the header, so a row is hidden until
|
|
140
|
+
// it clears both.
|
|
141
|
+
if (rowTopScroll < currentTop + headerHeight + frozenBand) {
|
|
142
|
+
nextTop = rowTopScroll - headerHeight - frozenBand
|
|
121
143
|
} else if (rowBottom > currentTop + clientHeight) {
|
|
122
144
|
nextTop = rowBottom - clientHeight
|
|
123
145
|
}
|
|
@@ -170,33 +192,66 @@ export function createSelection<
|
|
|
170
192
|
} else if (cellEnd > viewEnd) {
|
|
171
193
|
ctx.scrollContainer.scrollLeft = cellEnd - ctx.scrollContainer.clientWidth;
|
|
172
194
|
}
|
|
173
|
-
//
|
|
174
|
-
//
|
|
195
|
+
// The scroll position is known right here, so hand it to the
|
|
196
|
+
// virtualizers now rather than waiting for the `scroll` event + rAF:
|
|
197
|
+
// that put the new row window one painted frame behind the active-cell
|
|
198
|
+
// move (see `syncScrollNow`). Reading the position back is cheap - the
|
|
199
|
+
// scrollIntoView / scrollTop writes above already laid the tree out.
|
|
200
|
+
const topAfter = container.scrollTop;
|
|
201
|
+
const leftAfter = container.scrollLeft;
|
|
202
|
+
if (topAfter !== topBefore || leftAfter !== leftBefore) {
|
|
203
|
+
ctx.syncScrollNow(topAfter, leftAfter);
|
|
204
|
+
}
|
|
175
205
|
}
|
|
176
206
|
|
|
177
|
-
// Normalize a range to a rectangle, or null when incomplete.
|
|
207
|
+
// Normalize a range to a rectangle, or null when incomplete. A merge
|
|
208
|
+
// the rectangle touches is in it whole, as a spreadsheet's selection
|
|
209
|
+
// has it: a merged cell is one cell.
|
|
178
210
|
function rangeRect(range: SelectionRange | null) {
|
|
179
211
|
const a = range?.anchor;
|
|
180
212
|
const f = range?.focus;
|
|
181
213
|
if (!a || !f) return null;
|
|
182
|
-
return {
|
|
214
|
+
return expandRectToMerges(ctx.mergeIndex, {
|
|
183
215
|
minRow: Math.min(a.rowIndex, f.rowIndex),
|
|
184
216
|
maxRow: Math.max(a.rowIndex, f.rowIndex),
|
|
185
217
|
minCol: Math.min(a.colIndex, f.colIndex),
|
|
186
218
|
maxCol: Math.max(a.colIndex, f.colIndex),
|
|
187
|
-
};
|
|
219
|
+
});
|
|
188
220
|
}
|
|
189
221
|
|
|
222
|
+
type SelectionRect = { minRow: number; maxRow: number; minCol: number; maxCol: number };
|
|
223
|
+
|
|
224
|
+
/** The active range as a rectangle, or null while nothing is anchored. */
|
|
225
|
+
function activeRangeRect(): SelectionRect | null {
|
|
226
|
+
return rangeRect(ctx.selectionRange as SelectionRange);
|
|
227
|
+
}
|
|
228
|
+
// Memo for `getSelectionRects`, keyed on the identity of its two inputs.
|
|
229
|
+
// Both are replaced wholesale on every change and never mutated in place,
|
|
230
|
+
// so identity is a complete change check. Without it, every rendered
|
|
231
|
+
// cell's outline derived rebuilt this array on every arrow key - ~300
|
|
232
|
+
// arrays plus their rect objects per key press, for a result that is the
|
|
233
|
+
// same until the selection moves.
|
|
234
|
+
let rectsForRange: SelectionRange | null = null;
|
|
235
|
+
let rectsForRanges: SelectionRange[] | null = null;
|
|
236
|
+
let rectsMemo: SelectionRect[] = [];
|
|
237
|
+
|
|
190
238
|
// Every selected rectangle: the committed extra ranges plus the active one.
|
|
191
239
|
// Order matters for copy (added-order); the active range comes last.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
240
|
+
// Callers must treat the result as read-only.
|
|
241
|
+
function getSelectionRects(): SelectionRect[] {
|
|
242
|
+
const range = ctx.selectionRange as SelectionRange;
|
|
243
|
+
const ranges = (ctx.selectionRanges as SelectionRange[]) ?? [];
|
|
244
|
+
if (range === rectsForRange && ranges === rectsForRanges) return rectsMemo;
|
|
245
|
+
const rects: SelectionRect[] = [];
|
|
246
|
+
for (const r of ranges) {
|
|
195
247
|
const rect = rangeRect(r);
|
|
196
248
|
if (rect) rects.push(rect);
|
|
197
249
|
}
|
|
198
|
-
const active = rangeRect(
|
|
250
|
+
const active = rangeRect(range);
|
|
199
251
|
if (active) rects.push(active);
|
|
252
|
+
rectsForRange = range;
|
|
253
|
+
rectsForRanges = ranges;
|
|
254
|
+
rectsMemo = rects;
|
|
200
255
|
return rects;
|
|
201
256
|
}
|
|
202
257
|
|
|
@@ -259,6 +314,9 @@ export function createSelection<
|
|
|
259
314
|
// that work to return null every time.
|
|
260
315
|
if (!hasAnySelectionRect()) return null;
|
|
261
316
|
|
|
317
|
+
// A td inside a merge draws to the merge's far corner, so its bottom and
|
|
318
|
+
// right edges are the merge's, not the cell's.
|
|
319
|
+
const end = endOf(ctx.mergeIndex, rowIndex, colIndex);
|
|
262
320
|
for (const rect of getSelectionRects()) {
|
|
263
321
|
if (
|
|
264
322
|
rowIndex < rect.minRow ||
|
|
@@ -270,14 +328,40 @@ export function createSelection<
|
|
|
270
328
|
}
|
|
271
329
|
return {
|
|
272
330
|
top: rowIndex === rect.minRow,
|
|
273
|
-
bottom: rowIndex === rect.maxRow,
|
|
331
|
+
bottom: end.rowIndex === rect.maxRow,
|
|
274
332
|
left: colIndex === rect.minCol,
|
|
275
|
-
right: colIndex === rect.maxCol,
|
|
333
|
+
right: end.colIndex === rect.maxCol,
|
|
276
334
|
};
|
|
277
335
|
}
|
|
278
336
|
return null;
|
|
279
337
|
}
|
|
280
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Everything a cell's outline needs from the selection model, in ONE value:
|
|
341
|
+
* whether it is the active cell, which range edges it sits on (`edges` is
|
|
342
|
+
* null when it is in no range) and whether it carries the fill handle.
|
|
343
|
+
* `null` for a cell that is none of those - nearly every cell.
|
|
344
|
+
*
|
|
345
|
+
* One value on purpose. The view reads it through a single `{@const}`, so
|
|
346
|
+
* an active-cell move re-marks one derived per rendered cell instead of
|
|
347
|
+
* three (active flag, range edges, fill handle) plus the fill handle's
|
|
348
|
+
* `{#if}`. With ~300 cells rendered, that marking was the largest single
|
|
349
|
+
* cost of an arrow key that does not scroll. A plain cell returns the same
|
|
350
|
+
* `null` it returned before the move, so its attribute effect stays put.
|
|
351
|
+
*/
|
|
352
|
+
function cellSelectionState(rowIndex: number, colIndex: number) {
|
|
353
|
+
const a = ctx.activeCell;
|
|
354
|
+
const active = !!a && a.rowIndex === rowIndex && a.colIndex === colIndex;
|
|
355
|
+
const edges = getCellRangeEdges(rowIndex, colIndex);
|
|
356
|
+
const fh = ctx.fillHandleCell;
|
|
357
|
+
// The handle sits on the range's bottom-right cell; when that is inside
|
|
358
|
+
// a merge, the td that draws the merge carries it.
|
|
359
|
+
const end = fh ? endOf(ctx.mergeIndex, rowIndex, colIndex) : null;
|
|
360
|
+
const fillHandle = !!fh && !!end && fh.rowIndex === end.rowIndex && fh.colIndex === end.colIndex;
|
|
361
|
+
if (!active && !edges && !fillHandle) return null;
|
|
362
|
+
return { active, edges, fillHandle };
|
|
363
|
+
}
|
|
364
|
+
|
|
281
365
|
/**
|
|
282
366
|
* The rectangle a drag is currently promising, or null when no drag is in
|
|
283
367
|
* flight. A fill grows the source rectangle out to the pointer; a move
|
|
@@ -395,8 +479,12 @@ export function createSelection<
|
|
|
395
479
|
? { rowIndex: active.rowIndex, colIndex: active.colIndex }
|
|
396
480
|
: null;
|
|
397
481
|
if (event.shiftKey) {
|
|
482
|
+
// The range grows to the clicked cell and the active cell stays where
|
|
483
|
+
// it is, as in Excel: it is the cell the formula bar shows and the
|
|
484
|
+
// one the next keystroke edits. Used to jump to the clicked corner,
|
|
485
|
+
// so a Shift+click to select B2:D5 then typing wrote into D5.
|
|
398
486
|
extendSelection(rowIndex, colIndex);
|
|
399
|
-
setActiveCell(rowIndex, colIndex);
|
|
487
|
+
if (!active) setActiveCell(rowIndex, colIndex);
|
|
400
488
|
} else {
|
|
401
489
|
setActiveCell(rowIndex, colIndex);
|
|
402
490
|
// Ctrl/Cmd starts an ADDITIONAL range, keeping prior ranges highlighted.
|
|
@@ -414,8 +502,9 @@ export function createSelection<
|
|
|
414
502
|
if (!ctx.isDraggingSelection) return;
|
|
415
503
|
const row = ctx.allRows[rowIndex];
|
|
416
504
|
if (!row || isGroupRow(row)) return;
|
|
505
|
+
// The active cell is where the drag began; only the range's far corner
|
|
506
|
+
// follows the pointer.
|
|
417
507
|
extendSelection(rowIndex, colIndex);
|
|
418
|
-
setActiveCell(rowIndex, colIndex);
|
|
419
508
|
}
|
|
420
509
|
|
|
421
510
|
/** `event` is the window `pointerup` when this comes from the real gesture,
|
|
@@ -550,12 +639,13 @@ export function createSelection<
|
|
|
550
639
|
}
|
|
551
640
|
|
|
552
641
|
// onCellPointerDown already set active+selection for data cells. Only
|
|
553
|
-
// decide whether to enter edit mode (click on the previously-active cell
|
|
642
|
+
// decide whether to enter edit mode (click on the previously-active cell,
|
|
643
|
+
// unless the grid was told a second click is just a click).
|
|
554
644
|
const wasActive =
|
|
555
645
|
ctx.activeAtPointerDown !== null &&
|
|
556
646
|
ctx.activeAtPointerDown.rowIndex === rowIndex &&
|
|
557
647
|
ctx.activeAtPointerDown.colIndex === colIndex;
|
|
558
|
-
if (wasActive && ctx.editingEnabled) {
|
|
648
|
+
if (wasActive && ctx.editingEnabled && ctx.props.editOnSecondClick !== false) {
|
|
559
649
|
ctx.onCellDoubleClick(rowIndex, colIndex);
|
|
560
650
|
return;
|
|
561
651
|
}
|
|
@@ -603,6 +693,8 @@ export function createSelection<
|
|
|
603
693
|
extendSelection,
|
|
604
694
|
isCellInSelectedRange,
|
|
605
695
|
getCellRangeEdges,
|
|
696
|
+
activeRangeRect,
|
|
697
|
+
cellSelectionState,
|
|
606
698
|
fillMarqueeEdges,
|
|
607
699
|
getSelectionRects,
|
|
608
700
|
isInFillPreview,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach, vi } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
registerGridShortcuts,
|
|
4
|
+
runGridShortcuts,
|
|
5
|
+
hasGridShortcuts,
|
|
6
|
+
clearGridShortcuts,
|
|
7
|
+
type GridCommandContext,
|
|
8
|
+
} from './shortcut-registry'
|
|
9
|
+
|
|
10
|
+
// The context is a structural type over controller state; nothing in the
|
|
11
|
+
// registry reads it, so a cast of an empty object is enough here. The real
|
|
12
|
+
// shape is covered by command-context.test.ts.
|
|
13
|
+
const cmd = {} as GridCommandContext
|
|
14
|
+
const key = (init: KeyboardEventInit = {}) => new KeyboardEvent('keydown', init)
|
|
15
|
+
|
|
16
|
+
beforeEach(() => clearGridShortcuts())
|
|
17
|
+
|
|
18
|
+
describe('shortcut registry', () => {
|
|
19
|
+
it('reports nothing registered on a fresh grid', () => {
|
|
20
|
+
expect(hasGridShortcuts()).toBe(false)
|
|
21
|
+
expect(runGridShortcuts(key(), cmd)).toBe(false)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('runs a registered handler and reports the claim', () => {
|
|
25
|
+
const handler = vi.fn(() => true)
|
|
26
|
+
registerGridShortcuts(handler)
|
|
27
|
+
expect(hasGridShortcuts()).toBe(true)
|
|
28
|
+
expect(runGridShortcuts(key({ key: 'd' }), cmd)).toBe(true)
|
|
29
|
+
expect(handler).toHaveBeenCalledOnce()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('falls through when the handler declines', () => {
|
|
33
|
+
registerGridShortcuts(() => false)
|
|
34
|
+
expect(runGridShortcuts(key(), cmd)).toBe(false)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('stops at the first handler that claims', () => {
|
|
38
|
+
const order: string[] = []
|
|
39
|
+
registerGridShortcuts(() => { order.push('a'); return true })
|
|
40
|
+
registerGridShortcuts(() => { order.push('b'); return true })
|
|
41
|
+
runGridShortcuts(key(), cmd)
|
|
42
|
+
expect(order).toEqual(['a'])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('runs higher priority first regardless of registration order', () => {
|
|
46
|
+
const order: string[] = []
|
|
47
|
+
registerGridShortcuts(() => { order.push('low'); return false }, { priority: 1 })
|
|
48
|
+
registerGridShortcuts(() => { order.push('high'); return false }, { priority: 50 })
|
|
49
|
+
runGridShortcuts(key(), cmd)
|
|
50
|
+
expect(order).toEqual(['high', 'low'])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('keeps registration order for equal priorities', () => {
|
|
54
|
+
const order: string[] = []
|
|
55
|
+
registerGridShortcuts(() => { order.push('first'); return false })
|
|
56
|
+
registerGridShortcuts(() => { order.push('second'); return false })
|
|
57
|
+
runGridShortcuts(key(), cmd)
|
|
58
|
+
expect(order).toEqual(['first', 'second'])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('replaces rather than stacks when the same id registers twice', () => {
|
|
62
|
+
const first = vi.fn(() => false)
|
|
63
|
+
const second = vi.fn(() => false)
|
|
64
|
+
registerGridShortcuts(first, { id: 'sheet' })
|
|
65
|
+
registerGridShortcuts(second, { id: 'sheet' })
|
|
66
|
+
runGridShortcuts(key(), cmd)
|
|
67
|
+
expect(first).not.toHaveBeenCalled()
|
|
68
|
+
expect(second).toHaveBeenCalledOnce()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('unregisters through the returned disposer', () => {
|
|
72
|
+
const handler = vi.fn(() => true)
|
|
73
|
+
const off = registerGridShortcuts(handler)
|
|
74
|
+
off()
|
|
75
|
+
expect(hasGridShortcuts()).toBe(false)
|
|
76
|
+
expect(runGridShortcuts(key(), cmd)).toBe(false)
|
|
77
|
+
expect(handler).not.toHaveBeenCalled()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('leaves siblings alone when one disposer runs', () => {
|
|
81
|
+
const kept = vi.fn(() => false)
|
|
82
|
+
const off = registerGridShortcuts(() => false, { id: 'gone' })
|
|
83
|
+
registerGridShortcuts(kept, { id: 'kept' })
|
|
84
|
+
off()
|
|
85
|
+
runGridShortcuts(key(), cmd)
|
|
86
|
+
expect(hasGridShortcuts()).toBe(true)
|
|
87
|
+
expect(kept).toHaveBeenCalledOnce()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('hands the handler the event and the context it was called with', () => {
|
|
91
|
+
const handler = vi.fn(() => true)
|
|
92
|
+
registerGridShortcuts(handler)
|
|
93
|
+
const event = key({ key: 'ArrowDown', ctrlKey: true })
|
|
94
|
+
runGridShortcuts(event, cmd)
|
|
95
|
+
expect(handler).toHaveBeenCalledWith(event, cmd)
|
|
96
|
+
})
|
|
97
|
+
})
|