• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

glideapps / glide-data-grid / 15734690370

18 Jun 2025 01:49PM UTC coverage: 91.279% (-0.02%) from 91.299%
15734690370

Pull #1047

github

web-flow
Merge ab08cb2e9 into 62f422ab7
Pull Request #1047: Deselect and already selected column when clicked again

2922 of 3613 branches covered (80.87%)

13 of 20 new or added lines in 1 file covered. (65.0%)

63 existing lines in 1 file now uncovered.

17876 of 19584 relevant lines covered (91.28%)

3104.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.49
/packages/core/src/data-editor/data-editor.tsx
1
/* eslint-disable sonarjs/no-duplicate-string */
1✔
2
import * as React from "react";
1✔
3
import { assert, assertNever, maybe } from "../common/support.js";
1✔
4
import clamp from "lodash/clamp.js";
1✔
5
import uniq from "lodash/uniq.js";
1✔
6
import flatten from "lodash/flatten.js";
1✔
7
import range from "lodash/range.js";
1✔
8
import debounce from "lodash/debounce.js";
1✔
9
import {
1✔
10
    type EditableGridCell,
1✔
11
    type GridCell,
1✔
12
    GridCellKind,
1✔
13
    type GridSelection,
1✔
14
    isEditableGridCell,
1✔
15
    type Rectangle,
1✔
16
    isReadWriteCell,
1✔
17
    type InnerGridCell,
1✔
18
    InnerGridCellKind,
1✔
19
    CompactSelection,
1✔
20
    type Slice,
1✔
21
    isInnerOnlyCell,
1✔
22
    type ProvideEditorCallback,
1✔
23
    type GridColumn,
1✔
24
    isObjectEditorCallbackResult,
1✔
25
    type Item,
1✔
26
    type MarkerCell,
1✔
27
    type ValidatedGridCell,
1✔
28
    type ImageEditorType,
1✔
29
    type CustomCell,
1✔
30
    BooleanEmpty,
1✔
31
    BooleanIndeterminate,
1✔
32
    type FillHandleDirection,
1✔
33
    type EditListItem,
1✔
34
    type CellActiviationBehavior,
1✔
35
} from "../internal/data-grid/data-grid-types.js";
1✔
36
import DataGridSearch, { type DataGridSearchProps } from "../internal/data-grid-search/data-grid-search.js";
1✔
37
import { browserIsOSX } from "../common/browser-detect.js";
1✔
38
import {
1✔
39
    getDataEditorTheme,
1✔
40
    makeCSSStyle,
1✔
41
    type FullTheme,
1✔
42
    type Theme,
1✔
43
    ThemeContext,
1✔
44
    mergeAndRealizeTheme,
1✔
45
} from "../common/styles.js";
1✔
46
import type { DataGridRef } from "../internal/data-grid/data-grid.js";
1✔
47
import { getScrollBarWidth, useEventListener, whenDefined } from "../common/utils.js";
1✔
48
import {
1✔
49
    isGroupEqual,
1✔
50
    itemsAreEqual,
1✔
51
    itemIsInRect,
1✔
52
    gridSelectionHasItem,
1✔
53
    getFreezeTrailingHeight,
1✔
54
} from "../internal/data-grid/render/data-grid-lib.js";
1✔
55
import { GroupRename } from "./group-rename.js";
1✔
56
import { measureColumn, useColumnSizer } from "./use-column-sizer.js";
1✔
57
import { isHotkey } from "../common/is-hotkey.js";
1✔
58
import { type SelectionBlending, useSelectionBehavior } from "../internal/data-grid/use-selection-behavior.js";
1✔
59
import { useCellsForSelection } from "./use-cells-for-selection.js";
1✔
60
import { unquote, expandSelection, copyToClipboard, toggleBoolean } from "./data-editor-fns.js";
1✔
61
import { DataEditorContainer } from "../internal/data-editor-container/data-grid-container.js";
1✔
62
import { useAutoscroll } from "./use-autoscroll.js";
1✔
63
import type { CustomRenderer, CellRenderer, InternalCellRenderer } from "../cells/cell-types.js";
1✔
64
import { decodeHTML, type CopyBuffer } from "./copy-paste.js";
1✔
65
import { useRemAdjuster } from "./use-rem-adjuster.js";
1✔
66
import { withAlpha } from "../internal/data-grid/color-parser.js";
1✔
67
import { combineRects, getClosestRect, pointInRect } from "../common/math.js";
1✔
68
import {
1✔
69
    type HeaderClickedEventArgs,
1✔
70
    type GroupHeaderClickedEventArgs,
1✔
71
    type CellClickedEventArgs,
1✔
72
    type FillPatternEventArgs,
1✔
73
    type GridMouseEventArgs,
1✔
74
    groupHeaderKind,
1✔
75
    outOfBoundsKind,
1✔
76
    type GridMouseCellEventArgs,
1✔
77
    headerKind,
1✔
78
    type GridDragEventArgs,
1✔
79
    mouseEventArgsAreEqual,
1✔
80
    type GridKeyEventArgs,
1✔
81
} from "../internal/data-grid/event-args.js";
1✔
82
import { type Keybinds, useKeybindingsWithDefaults } from "./data-editor-keybindings.js";
1✔
83
import type { Highlight } from "../internal/data-grid/render/data-grid-render.cells.js";
1✔
84
import { useRowGroupingInner, type RowGroupingOptions } from "./row-grouping.js";
1✔
85
import { useRowGrouping } from "./row-grouping-api.js";
1✔
86
import { useInitialScrollOffset } from "./use-initial-scroll-offset.js";
1✔
87
import type { VisibleRegion } from "./visible-region.js";
1✔
88

1✔
89
const DataGridOverlayEditor = React.lazy(
1✔
90
    async () => await import("../internal/data-grid-overlay-editor/data-grid-overlay-editor.js")
1✔
91
);
1✔
92

1✔
93
// There must be a better way
1✔
94
let idCounter = 0;
1✔
95

1✔
96
export interface RowMarkerOptions {
1✔
97
    kind: "checkbox" | "number" | "clickable-number" | "checkbox-visible" | "both" | "none";
1✔
98
    checkboxStyle?: "circle" | "square";
1✔
99
    startIndex?: number;
1✔
100
    width?: number;
1✔
101
    theme?: Partial<Theme>;
1✔
102
    headerTheme?: Partial<Theme>;
1✔
103
    headerAlwaysVisible?: boolean;
1✔
104
}
1✔
105

1✔
106
interface MouseState {
1✔
107
    readonly previousSelection?: GridSelection;
1✔
108
    readonly fillHandle?: boolean;
1✔
109
}
1✔
110

1✔
111
type Props = Partial<
1✔
112
    Omit<
1✔
113
        DataGridSearchProps,
1✔
114
        | "accessibilityHeight"
1✔
115
        | "canvasRef"
1✔
116
        | "cellXOffset"
1✔
117
        | "cellYOffset"
1✔
118
        | "className"
1✔
119
        | "clientSize"
1✔
120
        | "columns"
1✔
121
        | "disabledRows"
1✔
122
        | "drawFocusRing"
1✔
123
        | "enableGroups"
1✔
124
        | "firstColAccessible"
1✔
125
        | "firstColSticky"
1✔
126
        | "freezeColumns"
1✔
127
        | "hasAppendRow"
1✔
128
        | "getCellContent"
1✔
129
        | "getCellRenderer"
1✔
130
        | "getCellsForSelection"
1✔
131
        | "getRowThemeOverride"
1✔
132
        | "gridRef"
1✔
133
        | "groupHeaderHeight"
1✔
134
        | "headerHeight"
1✔
135
        | "isFilling"
1✔
136
        | "isFocused"
1✔
137
        | "imageWindowLoader"
1✔
138
        | "lockColumns"
1✔
139
        | "maxColumnWidth"
1✔
140
        | "minColumnWidth"
1✔
141
        | "nonGrowWidth"
1✔
142
        | "onCanvasBlur"
1✔
143
        | "onCanvasFocused"
1✔
144
        | "onCellFocused"
1✔
145
        | "onContextMenu"
1✔
146
        | "onDragEnd"
1✔
147
        | "onMouseDown"
1✔
148
        | "onMouseMove"
1✔
149
        | "onMouseUp"
1✔
150
        | "onVisibleRegionChanged"
1✔
151
        | "rowHeight"
1✔
152
        | "rows"
1✔
153
        | "scrollRef"
1✔
154
        | "searchInputRef"
1✔
155
        | "selectedColumns"
1✔
156
        | "selection"
1✔
157
        | "theme"
1✔
158
        | "translateX"
1✔
159
        | "translateY"
1✔
160
        | "verticalBorder"
1✔
161
    >
1✔
162
>;
1✔
163

1✔
164
type EmitEvents = "copy" | "paste" | "delete" | "fill-right" | "fill-down";
1✔
165

1✔
166
function getSpanStops(cells: readonly (readonly GridCell[])[]): number[] {
5✔
167
    return uniq(
5✔
168
        flatten(
5✔
169
            flatten(cells)
5✔
170
                .filter(c => c.span !== undefined)
5✔
171
                .map(c => range((c.span?.[0] ?? 0) + 1, (c.span?.[1] ?? 0) + 1))
5✔
172
        )
5✔
173
    );
5✔
174
}
5✔
175

1✔
176
function shiftSelection(input: GridSelection, offset: number): GridSelection {
309✔
177
    if (input === undefined || offset === 0 || (input.columns.length === 0 && input.current === undefined))
309✔
178
        return input;
309✔
179

35✔
180
    return {
35✔
181
        current:
35✔
182
            input.current === undefined
35✔
183
                ? undefined
1✔
184
                : {
34✔
185
                      cell: [input.current.cell[0] + offset, input.current.cell[1]],
34✔
186
                      range: {
34✔
187
                          ...input.current.range,
34✔
188
                          x: input.current.range.x + offset,
34✔
189
                      },
34✔
190
                      rangeStack: input.current.rangeStack.map(r => ({
34✔
191
                          ...r,
×
192
                          x: r.x + offset,
×
193
                      })),
34✔
194
                  },
34✔
195
        rows: input.rows,
309✔
196
        columns: input.columns.offset(offset),
309✔
197
    };
309✔
198
}
309✔
199

1✔
200
/**
1✔
201
 * @category DataEditor
1✔
202
 */
1✔
203
export interface DataEditorProps extends Props, Pick<DataGridSearchProps, "imageWindowLoader"> {
1✔
204
    /** Emitted whenever the user has requested the deletion of the selection.
1✔
205
     * @group Editing
1✔
206
     */
1✔
207
    readonly onDelete?: (selection: GridSelection) => boolean | GridSelection;
1✔
208
    /** Emitted whenever a cell edit is completed.
1✔
209
     * @group Editing
1✔
210
     */
1✔
211
    readonly onCellEdited?: (cell: Item, newValue: EditableGridCell) => void;
1✔
212
    /** Emitted whenever a cell mutation is completed and provides all edits inbound as a single batch.
1✔
213
     * @group Editing
1✔
214
     */
1✔
215
    readonly onCellsEdited?: (newValues: readonly EditListItem[]) => boolean | void;
1✔
216
    /** Emitted whenever a row append operation is requested. Append location can be set in callback.
1✔
217
     * @group Editing
1✔
218
     */
1✔
219
    readonly onRowAppended?: () => Promise<"top" | "bottom" | number | undefined> | void;
1✔
220
    /** Emitted when a column header should show a context menu. Usually right click.
1✔
221
     * @group Events
1✔
222
     */
1✔
223
    readonly onHeaderClicked?: (colIndex: number, event: HeaderClickedEventArgs) => void;
1✔
224
    /** Emitted when a group header is clicked.
1✔
225
     * @group Events
1✔
226
     */
1✔
227
    readonly onGroupHeaderClicked?: (colIndex: number, event: GroupHeaderClickedEventArgs) => void;
1✔
228
    /** Emitted whe the user wishes to rename a group.
1✔
229
     * @group Events
1✔
230
     */
1✔
231
    readonly onGroupHeaderRenamed?: (groupName: string, newVal: string) => void;
1✔
232
    /** Emitted when a cell is clicked.
1✔
233
     * @group Events
1✔
234
     */
1✔
235
    readonly onCellClicked?: (cell: Item, event: CellClickedEventArgs) => void;
1✔
236
    /** Emitted when a cell is activated, by pressing Enter, Space or double clicking it.
1✔
237
     * @group Events
1✔
238
     */
1✔
239
    readonly onCellActivated?: (cell: Item) => void;
1✔
240

1✔
241
    /**
1✔
242
     * Emitted whenever the user initiats a pattern fill using the fill handle. This event provides both
1✔
243
     * a patternSource region and a fillDestination region, and can be prevented.
1✔
244
     * @group Editing
1✔
245
     */
1✔
246
    readonly onFillPattern?: (event: FillPatternEventArgs) => void;
1✔
247
    /** Emitted when editing has finished, regardless of data changing or not.
1✔
248
     * @group Editing
1✔
249
     */
1✔
250
    readonly onFinishedEditing?: (newValue: GridCell | undefined, movement: Item) => void;
1✔
251
    /** Emitted when a column header should show a context menu. Usually right click.
1✔
252
     * @group Events
1✔
253
     */
1✔
254
    readonly onHeaderContextMenu?: (colIndex: number, event: HeaderClickedEventArgs) => void;
1✔
255
    /** Emitted when a group header should show a context menu. Usually right click.
1✔
256
     * @group Events
1✔
257
     */
1✔
258
    readonly onGroupHeaderContextMenu?: (colIndex: number, event: GroupHeaderClickedEventArgs) => void;
1✔
259
    /** Emitted when a cell should show a context menu. Usually right click.
1✔
260
     * @group Events
1✔
261
     */
1✔
262
    readonly onCellContextMenu?: (cell: Item, event: CellClickedEventArgs) => void;
1✔
263
    /** Used for validating cell values during editing.
1✔
264
     * @group Editing
1✔
265
     * @param cell The cell which is being validated.
1✔
266
     * @param newValue The new value being proposed.
1✔
267
     * @param prevValue The previous value before the edit.
1✔
268
     * @returns A return of false indicates the value will not be accepted. A value of
1✔
269
     * true indicates the value will be accepted. Returning a new GridCell will immediately coerce the value to match.
1✔
270
     */
1✔
271
    readonly validateCell?: (
1✔
272
        cell: Item,
1✔
273
        newValue: EditableGridCell,
1✔
274
        prevValue: GridCell
1✔
275
    ) => boolean | ValidatedGridCell;
1✔
276

1✔
277
    /** The columns to display in the data grid.
1✔
278
     * @group Data
1✔
279
     */
1✔
280
    readonly columns: readonly GridColumn[];
1✔
281

1✔
282
    /** Controls the trailing row used to insert new data into the grid.
1✔
283
     * @group Editing
1✔
284
     */
1✔
285
    readonly trailingRowOptions?: {
1✔
286
        /** If the trailing row should be tinted */
1✔
287
        readonly tint?: boolean;
1✔
288
        /** A hint string displayed on hover. Usually something like "New row" */
1✔
289
        readonly hint?: string;
1✔
290
        /** When set to true, the trailing row is always visible. */
1✔
291
        readonly sticky?: boolean;
1✔
292
        /** The icon to use for the cell. Either a GridColumnIcon or a member of the passed headerIcons */
1✔
293
        readonly addIcon?: string;
1✔
294
        /** Overrides the column to focus when a new row is created. */
1✔
295
        readonly targetColumn?: number | GridColumn;
1✔
296
    };
1✔
297
    /** Controls the height of the header row
1✔
298
     * @defaultValue 36
1✔
299
     * @group Style
1✔
300
     */
1✔
301
    readonly headerHeight?: number;
1✔
302
    /** Controls the header of the group header row
1✔
303
     * @defaultValue `headerHeight`
1✔
304
     * @group Style
1✔
305
     */
1✔
306
    readonly groupHeaderHeight?: number;
1✔
307

1✔
308
    /**
1✔
309
     * The number of rows in the grid.
1✔
310
     * @group Data
1✔
311
     */
1✔
312
    readonly rows: number;
1✔
313

1✔
314
    /** Determines if row markers should be automatically added to the grid.
1✔
315
     * Interactive row markers allow the user to select a row.
1✔
316
     *
1✔
317
     * - "clickable-number" renders a number that can be clicked to
1✔
318
     *   select the row
1✔
319
     * - "both" causes the row marker to show up as a number but
1✔
320
     *   reveal a checkbox when the marker is hovered.
1✔
321
     *
1✔
322
     * @defaultValue `none`
1✔
323
     * @group Style
1✔
324
     */
1✔
325
    readonly rowMarkers?: RowMarkerOptions["kind"] | RowMarkerOptions;
1✔
326
    /**
1✔
327
     * Sets the width of row markers in pixels, if unset row markers will automatically size.
1✔
328
     * @group Style
1✔
329
     * @deprecated Use `rowMarkers` instead.
1✔
330
     */
1✔
331
    readonly rowMarkerWidth?: number;
1✔
332
    /** Changes the starting index for row markers.
1✔
333
     * @defaultValue 1
1✔
334
     * @group Style
1✔
335
     * @deprecated Use `rowMarkers` instead.
1✔
336
     */
1✔
337
    readonly rowMarkerStartIndex?: number;
1✔
338

1✔
339
    /** Changes the theme of the row marker column
1✔
340
     * @group Style
1✔
341
     * @deprecated Use `rowMarkers` instead.
1✔
342
     */
1✔
343
    readonly rowMarkerTheme?: Partial<Theme>;
1✔
344

1✔
345
    /** Sets the width of the data grid.
1✔
346
     * @group Style
1✔
347
     */
1✔
348
    readonly width?: number | string;
1✔
349
    /** Sets the height of the data grid.
1✔
350
     * @group Style
1✔
351
     */
1✔
352
    readonly height?: number | string;
1✔
353
    /** Custom classname for data grid wrapper.
1✔
354
     * @group Style
1✔
355
     */
1✔
356
    readonly className?: string;
1✔
357

1✔
358
    /** If set to `default`, `gridSelection` will be coerced to always include full spans.
1✔
359
     * @group Selection
1✔
360
     * @defaultValue `default`
1✔
361
     */
1✔
362
    readonly spanRangeBehavior?: "default" | "allowPartial";
1✔
363

1✔
364
    /** Controls which types of selections can exist at the same time in the grid. If selection blending is set to
1✔
365
     * exclusive, the grid will clear other types of selections when the exclusive selection is made. By default row,
1✔
366
     * column, and range selections are exclusive.
1✔
367
     * @group Selection
1✔
368
     * @defaultValue `exclusive`
1✔
369
     * */
1✔
370
    readonly rangeSelectionBlending?: SelectionBlending;
1✔
371
    /** {@inheritDoc rangeSelectionBlending}
1✔
372
     * @group Selection
1✔
373
     */
1✔
374
    readonly columnSelectionBlending?: SelectionBlending;
1✔
375
    /** {@inheritDoc rangeSelectionBlending}
1✔
376
     * @group Selection
1✔
377
     */
1✔
378
    readonly rowSelectionBlending?: SelectionBlending;
1✔
379
    /** Controls if multi-selection is allowed. If disabled, shift/ctrl/command clicking will work as if no modifiers
1✔
380
     * are pressed.
1✔
381
     *
1✔
382
     * When range select is set to cell, only one cell may be selected at a time. When set to rect one one rect at a
1✔
383
     * time. The multi variants allow for multiples of the rect or cell to be selected.
1✔
384
     * @group Selection
1✔
385
     * @defaultValue `rect`
1✔
386
     */
1✔
387
    readonly rangeSelect?: "none" | "cell" | "rect" | "multi-cell" | "multi-rect";
1✔
388
    /** {@inheritDoc rangeSelect}
1✔
389
     * @group Selection
1✔
390
     * @defaultValue `multi`
1✔
391
     */
1✔
392
    readonly columnSelect?: "none" | "single" | "multi";
1✔
393
    /** {@inheritDoc rangeSelect}
1✔
394
     * @group Selection
1✔
395
     * @defaultValue `multi`
1✔
396
     */
1✔
397
    readonly rowSelect?: "none" | "single" | "multi";
1✔
398

1✔
399
    /** Controls if range selection is allowed to span columns.
1✔
400
     * @group Selection
1✔
401
     * @defaultValue `true`
1✔
402
     */
1✔
403
    readonly rangeSelectionColumnSpanning?: boolean;
1✔
404

1✔
405
    /** Sets the initial scroll Y offset.
1✔
406
     * @see {@link scrollOffsetX}
1✔
407
     * @group Advanced
1✔
408
     */
1✔
409
    readonly scrollOffsetY?: number;
1✔
410
    /** Sets the initial scroll X offset
1✔
411
     * @see {@link scrollOffsetY}
1✔
412
     * @group Advanced
1✔
413
     */
1✔
414
    readonly scrollOffsetX?: number;
1✔
415

1✔
416
    /** Determins the height of each row.
1✔
417
     * @group Style
1✔
418
     * @defaultValue 34
1✔
419
     */
1✔
420
    readonly rowHeight?: DataGridSearchProps["rowHeight"];
1✔
421
    /** Fires whenever the mouse moves
1✔
422
     * @group Events
1✔
423
     * @param args
1✔
424
     */
1✔
425
    readonly onMouseMove?: DataGridSearchProps["onMouseMove"];
1✔
426

1✔
427
    /**
1✔
428
     * The minimum width a column can be resized to.
1✔
429
     * @defaultValue 50
1✔
430
     * @group Style
1✔
431
     */
1✔
432
    readonly minColumnWidth?: DataGridSearchProps["minColumnWidth"];
1✔
433
    /**
1✔
434
     * The maximum width a column can be resized to.
1✔
435
     * @defaultValue 500
1✔
436
     * @group Style
1✔
437
     */
1✔
438
    readonly maxColumnWidth?: DataGridSearchProps["maxColumnWidth"];
1✔
439
    /**
1✔
440
     * The maximum width a column can be automatically sized to.
1✔
441
     * @defaultValue `maxColumnWidth`
1✔
442
     * @group Style
1✔
443
     */
1✔
444
    readonly maxColumnAutoWidth?: number;
1✔
445

1✔
446
    /**
1✔
447
     * Used to provide an override to the default image editor for the data grid. `provideEditor` may be a better
1✔
448
     * choice for most people.
1✔
449
     * @group Advanced
1✔
450
     * */
1✔
451
    readonly imageEditorOverride?: ImageEditorType;
1✔
452
    /**
1✔
453
     * If specified, it will be used to render Markdown, instead of the default Markdown renderer used by the Grid.
1✔
454
     * You'll want to use this if you need to process your Markdown for security purposes, or if you want to use a
1✔
455
     * renderer with different Markdown features.
1✔
456
     * @group Advanced
1✔
457
     */
1✔
458
    readonly markdownDivCreateNode?: (content: string) => DocumentFragment;
1✔
459

1✔
460
    /**
1✔
461
     * Allows overriding the theme of any row
1✔
462
     * @param row represents the row index of the row, increasing by 1 for every represented row. Collapsed rows are not included.
1✔
463
     * @param groupRow represents the row index of the group row. Only distinct when row grouping enabled.
1✔
464
     * @param contentRow represents the index of the row excluding group headers. Only distinct when row grouping enabled.
1✔
465
     * @returns
1✔
466
     */
1✔
467
    readonly getRowThemeOverride?: (row: number, groupRow: number, contentRow: number) => Partial<Theme> | undefined;
1✔
468

1✔
469
    /** Callback for providing a custom editor for a cell.
1✔
470
     * @group Editing
1✔
471
     */
1✔
472
    readonly provideEditor?: ProvideEditorCallback<GridCell>;
1✔
473
    /**
1✔
474
     * Allows coercion of pasted values.
1✔
475
     * @group Editing
1✔
476
     * @param val The pasted value
1✔
477
     * @param cell The cell being pasted into
1✔
478
     * @returns `undefined` to accept default behavior or a `GridCell` which should be used to represent the pasted value.
1✔
479
     */
1✔
480
    readonly coercePasteValue?: (val: string, cell: GridCell) => GridCell | undefined;
1✔
481

1✔
482
    /**
1✔
483
     * Emitted when the grid selection is cleared.
1✔
484
     * @group Selection
1✔
485
     */
1✔
486
    readonly onSelectionCleared?: () => void;
1✔
487

1✔
488
    /**
1✔
489
     * The current selection of the data grid. Contains all selected cells, ranges, rows, and columns.
1✔
490
     * Used in conjunction with {@link onGridSelectionChange}
1✔
491
     * method to implement a controlled selection.
1✔
492
     * @group Selection
1✔
493
     */
1✔
494
    readonly gridSelection?: GridSelection;
1✔
495
    /**
1✔
496
     * Emitted whenever the grid selection changes. Specifying
1✔
497
     * this function will make the grid’s selection controlled, so
1✔
498
     * so you will need to specify {@link gridSelection} as well. See
1✔
499
     * the "Controlled Selection" example for details.
1✔
500
     *
1✔
501
     * @param newSelection The new gridSelection as created by user input.
1✔
502
     * @group Selection
1✔
503
     */
1✔
504
    readonly onGridSelectionChange?: (newSelection: GridSelection) => void;
1✔
505
    /**
1✔
506
     * Emitted whenever the visible cells change, usually due to scrolling.
1✔
507
     * @group Events
1✔
508
     * @param range An inclusive range of all visible cells. May include cells obscured by UI elements such
1✔
509
     * as headers.
1✔
510
     * @param tx The x transform of the cell region.
1✔
511
     * @param ty The y transform of the cell region.
1✔
512
     * @param extras Contains information about the selected cell and
1✔
513
     * any visible freeze columns.
1✔
514
     */
1✔
515
    readonly onVisibleRegionChanged?: (
1✔
516
        range: Rectangle,
1✔
517
        tx: number,
1✔
518
        ty: number,
1✔
519
        extras: {
1✔
520
            /** The selected item if visible */
1✔
521
            selected?: Item;
1✔
522
            /** A selection of visible freeze columns
1✔
523
             * @deprecated
1✔
524
             */
1✔
525
            freezeRegion?: Rectangle;
1✔
526

1✔
527
            /**
1✔
528
             * All visible freeze regions
1✔
529
             */
1✔
530
            freezeRegions?: readonly Rectangle[];
1✔
531
        }
1✔
532
    ) => void;
1✔
533

1✔
534
    /**
1✔
535
     * The primary callback for getting cell data into the data grid.
1✔
536
     * @group Data
1✔
537
     * @param cell The location of the cell being requested.
1✔
538
     * @returns A valid GridCell to be rendered by the Grid.
1✔
539
     */
1✔
540
    readonly getCellContent: (cell: Item) => GridCell;
1✔
541
    /**
1✔
542
     * Determines if row selection requires a modifier key to enable multi-selection or not. In auto mode it adapts to
1✔
543
     * touch or mouse environments automatically, in multi-mode it always acts as if the multi key (Ctrl) is pressed.
1✔
544
     * @group Editing
1✔
545
     * @defaultValue `auto`
1✔
546
     */
1✔
547
    readonly rowSelectionMode?: "auto" | "multi";
1✔
548

1✔
549
    /**
1✔
550
     * Add table headers to copied data.
1✔
551
     * @group Editing
1✔
552
     * @defaultValue `false`
1✔
553
     */
1✔
554
    readonly copyHeaders?: boolean;
1✔
555

1✔
556
    /**
1✔
557
     * Determins which keybindings are enabled.
1✔
558
     * @group Editing
1✔
559
     */
1✔
560
    readonly keybindings?: Partial<Keybinds>;
1✔
561

1✔
562
    /**
1✔
563
     * Determines if the data editor should immediately begin editing when the user types on a selected cell
1✔
564
     * @group Editing
1✔
565
     */
1✔
566
    readonly editOnType?: boolean;
1✔
567

1✔
568
    /**
1✔
569
     * Used to fetch large amounts of cells at once. Used for copy/paste, if unset copy will not work.
1✔
570
     *
1✔
571
     * `getCellsForSelection` is called when the user copies a selection to the clipboard or the data editor needs to
1✔
572
     * inspect data which may be outside the curently visible range. It must return a two-dimensional array (an array of
1✔
573
     * rows, where each row is an array of cells) of the cells in the selection's rectangle. Note that the rectangle can
1✔
574
     * include cells that are not currently visible.
1✔
575
     *
1✔
576
     * If `true` is passed instead of a callback, the data grid will internally use the `getCellContent` callback to
1✔
577
     * provide a basic implementation of `getCellsForSelection`. This can make it easier to light up more data grid
1✔
578
     * functionality, but may have negative side effects if your data source is not able to handle being queried for
1✔
579
     * data outside the normal window.
1✔
580
     *
1✔
581
     * If `getCellsForSelection` returns a thunk, the data may be loaded asynchronously, however the data grid may be
1✔
582
     * unable to properly react to column spans when performing range selections. Copying large amounts of data out of
1✔
583
     * the grid will depend on the performance of the thunk as well.
1✔
584
     * @group Data
1✔
585
     * @param {Rectangle} selection The range of requested cells
1✔
586
     * @param {AbortSignal} abortSignal A signal indicating the requested cells are no longer needed
1✔
587
     * @returns A row-major collection of cells or an async thunk which returns a row-major collection.
1✔
588
     */
1✔
589
    readonly getCellsForSelection?: DataGridSearchProps["getCellsForSelection"] | true;
1✔
590

1✔
591
    /** The number of columns which should remain in place when scrolling horizontally. The row marker column, if
1✔
592
     * enabled is always frozen and is not included in this count.
1✔
593
     * @defaultValue 0
1✔
594
     * @group Style
1✔
595
     */
1✔
596
    readonly freezeColumns?: DataGridSearchProps["freezeColumns"];
1✔
597

1✔
598
    /**
1✔
599
     * Controls the drawing of the left hand vertical border of a column. If set to a boolean value it controls all
1✔
600
     * borders.
1✔
601
     * @defaultValue `true`
1✔
602
     * @group Style
1✔
603
     */
1✔
604
    readonly verticalBorder?: DataGridSearchProps["verticalBorder"] | boolean;
1✔
605

1✔
606
    /**
1✔
607
     * Controls the grouping of rows to be drawn in the grid.
1✔
608
     */
1✔
609
    readonly rowGrouping?: RowGroupingOptions;
1✔
610

1✔
611
    /**
1✔
612
     * Called when data is pasted into the grid. If left undefined, the `DataEditor` will operate in a
1✔
613
     * fallback mode and attempt to paste the text buffer into the current cell assuming the current cell is not
1✔
614
     * readonly and can accept the data type. If `onPaste` is set to false or the function returns false, the grid will
1✔
615
     * simply ignore paste. If `onPaste` evaluates to true the grid will attempt to split the data by tabs and newlines
1✔
616
     * and paste into available cells.
1✔
617
     *
1✔
618
     * The grid will not attempt to add additional rows if more data is pasted then can fit. In that case it is
1✔
619
     * advisable to simply return false from onPaste and handle the paste manually.
1✔
620
     * @group Editing
1✔
621
     */
1✔
622
    readonly onPaste?: ((target: Item, values: readonly (readonly string[])[]) => boolean) | boolean;
1✔
623

1✔
624
    /**
1✔
625
     * The theme used by the data grid to get all color and font information
1✔
626
     * @group Style
1✔
627
     */
1✔
628
    readonly theme?: Partial<Theme>;
1✔
629

1✔
630
    readonly renderers?: readonly InternalCellRenderer<InnerGridCell>[];
1✔
631

1✔
632
    /**
1✔
633
     * An array of custom renderers which can be used to extend the data grid.
1✔
634
     * @group Advanced
1✔
635
     */
1✔
636
    readonly customRenderers?: readonly CustomRenderer<any>[];
1✔
637

1✔
638
    /**
1✔
639
     * Scales most elements in the theme to match rem scaling automatically
1✔
640
     * @defaultValue false
1✔
641
     */
1✔
642
    readonly scaleToRem?: boolean;
1✔
643

1✔
644
    /**
1✔
645
     * Custom predicate function to decide whether the click event occurred outside the grid
1✔
646
     * Especially used when custom editor is opened with the portal and is outside the grid, but there is no possibility
1✔
647
     * to add a class "click-outside-ignore"
1✔
648
     * If this function is supplied and returns false, the click event is ignored
1✔
649
     */
1✔
650
    readonly isOutsideClick?: (e: MouseEvent | TouchEvent) => boolean;
1✔
651

1✔
652
    /**
1✔
653
     * Controls which directions fill is allowed in.
1✔
654
     */
1✔
655
    readonly allowedFillDirections?: FillHandleDirection;
1✔
656

1✔
657
    /**
1✔
658
     * Determines when a cell is considered activated and will emit the `onCellActivated` event. Generally an activated
1✔
659
     * cell will open to edit mode.
1✔
660
     */
1✔
661
    readonly cellActivationBehavior?: CellActiviationBehavior;
1✔
662

1✔
663
    /**
1✔
664
     * Controls if focus will trap inside the data grid when doing tab and caret navigation.
1✔
665
     */
1✔
666
    readonly trapFocus?: boolean;
1✔
667

1✔
668
    /**
1✔
669
     * Allows overriding the default amount of bloom (the size growth of the overlay editor)
1✔
670
     */
1✔
671
    readonly editorBloom?: readonly [number, number];
1✔
672

1✔
673
    /**
1✔
674
     * If set to true, the data grid will attempt to scroll to keep the selction in view
1✔
675
     */
1✔
676
    readonly scrollToActiveCell?: boolean;
1✔
677

1✔
678
    readonly drawFocusRing?: boolean | "no-editor";
1✔
679
}
1✔
680

1✔
681
type ScrollToFn = (
1✔
682
    col: number | { amount: number; unit: "cell" | "px" },
1✔
683
    row: number | { amount: number; unit: "cell" | "px" },
1✔
684
    dir?: "horizontal" | "vertical" | "both",
1✔
685
    paddingX?: number,
1✔
686
    paddingY?: number,
1✔
687
    options?: {
1✔
688
        hAlign?: "start" | "center" | "end";
1✔
689
        vAlign?: "start" | "center" | "end";
1✔
690
        behavior?: ScrollBehavior;
1✔
691
    }
1✔
692
) => void;
1✔
693

1✔
694
/** @category DataEditor */
1✔
695
export interface DataEditorRef {
1✔
696
    /**
1✔
697
     * Programatically appends a row.
1✔
698
     * @param col The column index to focus in the new row.
1✔
699
     * @returns A promise which waits for the append to complete.
1✔
700
     */
1✔
701
    appendRow: (col: number, openOverlay?: boolean, behavior?: ScrollBehavior) => Promise<void>;
1✔
702
    /**
1✔
703
     * Triggers cells to redraw.
1✔
704
     */
1✔
705
    updateCells: DataGridRef["damage"];
1✔
706
    /**
1✔
707
     * Gets the screen space bounds of the requested item.
1✔
708
     */
1✔
709
    getBounds: DataGridRef["getBounds"];
1✔
710
    /**
1✔
711
     * Triggers the data grid to focus itself or the correct accessibility element.
1✔
712
     */
1✔
713
    focus: DataGridRef["focus"];
1✔
714
    /**
1✔
715
     * Generic API for emitting events as if they had been triggered via user interaction.
1✔
716
     */
1✔
717
    emit: (eventName: EmitEvents) => Promise<void>;
1✔
718
    /**
1✔
719
     * Scrolls to the desired cell or location in the grid.
1✔
720
     */
1✔
721
    scrollTo: ScrollToFn;
1✔
722
    /**
1✔
723
     * Causes the columns in the selection to have their natural size recomputed and re-emitted as a resize event.
1✔
724
     */
1✔
725
    remeasureColumns: (cols: CompactSelection) => void;
1✔
726
    /**
1✔
727
     * Gets the mouse args from pointer event position.
1✔
728
     */
1✔
729
    getMouseArgsForPosition: (
1✔
730
        posX: number,
1✔
731
        posY: number,
1✔
732
        ev?: MouseEvent | TouchEvent
1✔
733
    ) => GridMouseEventArgs | undefined;
1✔
734
}
1✔
735

1✔
736
const loadingCell: GridCell = {
1✔
737
    kind: GridCellKind.Loading,
1✔
738
    allowOverlay: false,
1✔
739
};
1✔
740

1✔
741
const emptyGridSelection: GridSelection = {
1✔
742
    columns: CompactSelection.empty(),
1✔
743
    rows: CompactSelection.empty(),
1✔
744
    current: undefined,
1✔
745
};
1✔
746

1✔
747
const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorProps> = (p, forwardedRef) => {
1✔
748
    const [gridSelectionInner, setGridSelectionInner] = React.useState<GridSelection>(emptyGridSelection);
726✔
749

726✔
750
    const [overlay, setOverlay] = React.useState<{
726✔
751
        target: Rectangle;
726✔
752
        content: GridCell;
726✔
753
        theme: FullTheme;
726✔
754
        initialValue: string | undefined;
726✔
755
        cell: Item;
726✔
756
        highlight: boolean;
726✔
757
        forceEditMode: boolean;
726✔
758
    }>();
726✔
759
    const searchInputRef = React.useRef<HTMLInputElement | null>(null);
726✔
760
    const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
726✔
761
    const [mouseState, setMouseState] = React.useState<MouseState>();
726✔
762
    const lastSent = React.useRef<[number, number]>();
726✔
763

726✔
764
    const safeWindow = typeof window === "undefined" ? null : window;
726!
765

726✔
766
    const {
726✔
767
        imageEditorOverride,
726✔
768
        getRowThemeOverride: getRowThemeOverrideIn,
726✔
769
        markdownDivCreateNode,
726✔
770
        width,
726✔
771
        height,
726✔
772
        columns: columnsIn,
726✔
773
        rows: rowsIn,
726✔
774
        getCellContent,
726✔
775
        onCellClicked,
726✔
776
        onCellActivated,
726✔
777
        onFillPattern,
726✔
778
        onFinishedEditing,
726✔
779
        coercePasteValue,
726✔
780
        drawHeader: drawHeaderIn,
726✔
781
        drawCell: drawCellIn,
726✔
782
        editorBloom,
726✔
783
        onHeaderClicked,
726✔
784
        onColumnProposeMove,
726✔
785
        rangeSelectionColumnSpanning = true,
726✔
786
        spanRangeBehavior = "default",
726✔
787
        onGroupHeaderClicked,
726✔
788
        onCellContextMenu,
726✔
789
        className,
726✔
790
        onHeaderContextMenu,
726✔
791
        getCellsForSelection: getCellsForSelectionIn,
726✔
792
        onGroupHeaderContextMenu,
726✔
793
        onGroupHeaderRenamed,
726✔
794
        onCellEdited,
726✔
795
        onCellsEdited,
726✔
796
        onSearchResultsChanged: onSearchResultsChangedIn,
726✔
797
        searchResults,
726✔
798
        onSearchValueChange,
726✔
799
        searchValue,
726✔
800
        onKeyDown: onKeyDownIn,
726✔
801
        onKeyUp: onKeyUpIn,
726✔
802
        keybindings: keybindingsIn,
726✔
803
        editOnType = true,
726✔
804
        onRowAppended,
726✔
805
        onColumnMoved,
726✔
806
        validateCell: validateCellIn,
726✔
807
        highlightRegions: highlightRegionsIn,
726✔
808
        rangeSelect = "rect",
726✔
809
        columnSelect = "multi",
726✔
810
        rowSelect = "multi",
726✔
811
        rangeSelectionBlending = "exclusive",
726✔
812
        columnSelectionBlending = "exclusive",
726✔
813
        rowSelectionBlending = "exclusive",
726✔
814
        onDelete: onDeleteIn,
726✔
815
        onDragStart,
726✔
816
        onMouseMove,
726✔
817
        onPaste,
726✔
818
        copyHeaders = false,
726✔
819
        freezeColumns = 0,
726✔
820
        cellActivationBehavior = "second-click",
726✔
821
        rowSelectionMode = "auto",
726✔
822
        onHeaderMenuClick,
726✔
823
        onHeaderIndicatorClick,
726✔
824
        getGroupDetails,
726✔
825
        rowGrouping,
726✔
826
        onSearchClose: onSearchCloseIn,
726✔
827
        onItemHovered,
726✔
828
        onSelectionCleared,
726✔
829
        showSearch: showSearchIn,
726✔
830
        onVisibleRegionChanged,
726✔
831
        gridSelection: gridSelectionOuter,
726✔
832
        onGridSelectionChange,
726✔
833
        minColumnWidth: minColumnWidthIn = 50,
726✔
834
        maxColumnWidth: maxColumnWidthIn = 500,
726✔
835
        maxColumnAutoWidth: maxColumnAutoWidthIn,
726✔
836
        provideEditor,
726✔
837
        trailingRowOptions,
726✔
838
        freezeTrailingRows = 0,
726✔
839
        allowedFillDirections = "orthogonal",
726✔
840
        scrollOffsetX,
726✔
841
        scrollOffsetY,
726✔
842
        verticalBorder,
726✔
843
        onDragOverCell,
726✔
844
        onDrop,
726✔
845
        onColumnResize: onColumnResizeIn,
726✔
846
        onColumnResizeEnd: onColumnResizeEndIn,
726✔
847
        onColumnResizeStart: onColumnResizeStartIn,
726✔
848
        customRenderers: additionalRenderers,
726✔
849
        fillHandle,
726✔
850
        experimental,
726✔
851
        fixedShadowX,
726✔
852
        fixedShadowY,
726✔
853
        headerIcons,
726✔
854
        imageWindowLoader,
726✔
855
        initialSize,
726✔
856
        isDraggable,
726✔
857
        onDragLeave,
726✔
858
        onRowMoved,
726✔
859
        overscrollX: overscrollXIn,
726✔
860
        overscrollY: overscrollYIn,
726✔
861
        preventDiagonalScrolling,
726✔
862
        rightElement,
726✔
863
        rightElementProps,
726✔
864
        trapFocus = false,
726✔
865
        smoothScrollX,
726✔
866
        smoothScrollY,
726✔
867
        scaleToRem = false,
726✔
868
        rowHeight: rowHeightIn = 34,
726✔
869
        headerHeight: headerHeightIn = 36,
726✔
870
        groupHeaderHeight: groupHeaderHeightIn = headerHeightIn,
726✔
871
        theme: themeIn,
726✔
872
        isOutsideClick,
726✔
873
        renderers,
726✔
874
        resizeIndicator,
726✔
875
        scrollToActiveCell = true,
726✔
876
        drawFocusRing: drawFocusRingIn = true,
726✔
877
    } = p;
726✔
878

726✔
879
    const drawFocusRing = drawFocusRingIn === "no-editor" ? overlay === undefined : drawFocusRingIn;
726!
880

726✔
881
    const rowMarkersObj = typeof p.rowMarkers === "string" ? undefined : p.rowMarkers;
726✔
882

726✔
883
    const rowMarkers = rowMarkersObj?.kind ?? (p.rowMarkers as RowMarkerOptions["kind"]) ?? "none";
726!
884
    const rowMarkerWidthRaw = rowMarkersObj?.width ?? p.rowMarkerWidth;
726!
885
    const rowMarkerStartIndex = rowMarkersObj?.startIndex ?? p.rowMarkerStartIndex ?? 1;
726!
886
    const rowMarkerTheme = rowMarkersObj?.theme ?? p.rowMarkerTheme;
726!
887
    const headerRowMarkerTheme = rowMarkersObj?.headerTheme;
726!
888
    const headerRowMarkerAlwaysVisible = rowMarkersObj?.headerAlwaysVisible;
726!
889
    const headerRowMarkerDisabled = rowSelect !== "multi";
726✔
890
    const rowMarkerCheckboxStyle = rowMarkersObj?.checkboxStyle ?? "square";
726!
891

726✔
892
    const minColumnWidth = Math.max(minColumnWidthIn, 20);
726✔
893
    const maxColumnWidth = Math.max(maxColumnWidthIn, minColumnWidth);
726✔
894
    const maxColumnAutoWidth = Math.max(maxColumnAutoWidthIn ?? maxColumnWidth, minColumnWidth);
726✔
895

726✔
896
    const docStyle = React.useMemo(() => {
726✔
897
        if (typeof window === "undefined") return { fontSize: "16px" };
147!
898
        return window.getComputedStyle(document.documentElement);
147✔
899
    }, []);
726✔
900

726✔
901
    const {
726✔
902
        rows,
726✔
903
        rowNumberMapper,
726✔
904
        rowHeight: rowHeightPostGrouping,
726✔
905
        getRowThemeOverride,
726✔
906
    } = useRowGroupingInner(rowGrouping, rowsIn, rowHeightIn, getRowThemeOverrideIn);
726✔
907

726✔
908
    const remSize = React.useMemo(() => Number.parseFloat(docStyle.fontSize), [docStyle]);
726✔
909
    const { rowHeight, headerHeight, groupHeaderHeight, theme, overscrollX, overscrollY } = useRemAdjuster({
726✔
910
        groupHeaderHeight: groupHeaderHeightIn,
726✔
911
        headerHeight: headerHeightIn,
726✔
912
        overscrollX: overscrollXIn,
726✔
913
        overscrollY: overscrollYIn,
726✔
914
        remSize,
726✔
915
        rowHeight: rowHeightPostGrouping,
726✔
916
        scaleToRem,
726✔
917
        theme: themeIn,
726✔
918
    });
726✔
919

726✔
920
    const keybindings = useKeybindingsWithDefaults(keybindingsIn);
726✔
921

726✔
922
    const rowMarkerWidth = rowMarkerWidthRaw ?? (rowsIn > 10_000 ? 48 : rowsIn > 1000 ? 44 : rowsIn > 100 ? 36 : 32);
726!
923
    const hasRowMarkers = rowMarkers !== "none";
726✔
924
    const rowMarkerOffset = hasRowMarkers ? 1 : 0;
726✔
925
    const showTrailingBlankRow = onRowAppended !== undefined;
726✔
926
    const lastRowSticky = trailingRowOptions?.sticky === true;
726✔
927

726✔
928
    const [showSearchInner, setShowSearchInner] = React.useState(false);
726✔
929
    const showSearch = showSearchIn ?? showSearchInner;
726✔
930

726✔
931
    const onSearchClose = React.useCallback(() => {
726✔
932
        if (onSearchCloseIn !== undefined) {
2✔
933
            onSearchCloseIn();
2✔
934
        } else {
2!
935
            setShowSearchInner(false);
×
936
        }
×
937
    }, [onSearchCloseIn]);
726✔
938

726✔
939
    const gridSelectionOuterMangled: GridSelection | undefined = React.useMemo((): GridSelection | undefined => {
726✔
940
        return gridSelectionOuter === undefined ? undefined : shiftSelection(gridSelectionOuter, rowMarkerOffset);
285✔
941
    }, [gridSelectionOuter, rowMarkerOffset]);
726✔
942
    const gridSelection = gridSelectionOuterMangled ?? gridSelectionInner;
726✔
943

726✔
944
    const abortControllerRef = React.useRef() as React.MutableRefObject<AbortController>;
726✔
945
    if (abortControllerRef.current === undefined) abortControllerRef.current = new AbortController();
726✔
946

726✔
947
    React.useEffect(() => () => abortControllerRef?.current.abort(), []);
726✔
948

726✔
949
    const [getCellsForSelection, getCellsForSeletionDirect] = useCellsForSelection(
726✔
950
        getCellsForSelectionIn,
726✔
951
        getCellContent,
726✔
952
        rowMarkerOffset,
726✔
953
        abortControllerRef.current,
726✔
954
        rows
726✔
955
    );
726✔
956

726✔
957
    const validateCell = React.useCallback<NonNullable<typeof validateCellIn>>(
726✔
958
        (cell, newValue, prevValue) => {
726✔
959
            if (validateCellIn === undefined) return true;
17✔
960
            const item: Item = [cell[0] - rowMarkerOffset, cell[1]];
1✔
961
            return validateCellIn?.(item, newValue, prevValue);
1✔
962
        },
17✔
963
        [rowMarkerOffset, validateCellIn]
726✔
964
    );
726✔
965

726✔
966
    const expectedExternalGridSelection = React.useRef<GridSelection | undefined>(gridSelectionOuter);
726✔
967
    const setGridSelection = React.useCallback(
726✔
968
        (newVal: GridSelection, expand: boolean): void => {
726✔
969
            if (expand) {
182✔
970
                newVal = expandSelection(
141✔
971
                    newVal,
141✔
972
                    getCellsForSelection,
141✔
973
                    rowMarkerOffset,
141✔
974
                    spanRangeBehavior,
141✔
975
                    abortControllerRef.current
141✔
976
                );
141✔
977
            }
141✔
978
            if (onGridSelectionChange !== undefined) {
182✔
979
                expectedExternalGridSelection.current = shiftSelection(newVal, -rowMarkerOffset);
142✔
980
                onGridSelectionChange(expectedExternalGridSelection.current);
142✔
981
            } else {
182✔
982
                setGridSelectionInner(newVal);
40✔
983
            }
40✔
984
        },
182✔
985
        [onGridSelectionChange, getCellsForSelection, rowMarkerOffset, spanRangeBehavior]
726✔
986
    );
726✔
987

726✔
988
    const onColumnResize = whenDefined(
726✔
989
        onColumnResizeIn,
726✔
990
        React.useCallback<NonNullable<typeof onColumnResizeIn>>(
726✔
991
            (_, w, ind, wg) => {
726✔
992
                onColumnResizeIn?.(columnsIn[ind - rowMarkerOffset], w, ind - rowMarkerOffset, wg);
11✔
993
            },
11✔
994
            [onColumnResizeIn, rowMarkerOffset, columnsIn]
726✔
995
        )
726✔
996
    );
726✔
997

726✔
998
    const onColumnResizeEnd = whenDefined(
726✔
999
        onColumnResizeEndIn,
726✔
1000
        React.useCallback<NonNullable<typeof onColumnResizeEndIn>>(
726✔
1001
            (_, w, ind, wg) => {
726✔
1002
                onColumnResizeEndIn?.(columnsIn[ind - rowMarkerOffset], w, ind - rowMarkerOffset, wg);
2✔
1003
            },
2✔
1004
            [onColumnResizeEndIn, rowMarkerOffset, columnsIn]
726✔
1005
        )
726✔
1006
    );
726✔
1007

726✔
1008
    const onColumnResizeStart = whenDefined(
726✔
1009
        onColumnResizeStartIn,
726✔
1010
        React.useCallback<NonNullable<typeof onColumnResizeStartIn>>(
726✔
1011
            (_, w, ind, wg) => {
726✔
1012
                onColumnResizeStartIn?.(columnsIn[ind - rowMarkerOffset], w, ind - rowMarkerOffset, wg);
×
1013
            },
×
1014
            [onColumnResizeStartIn, rowMarkerOffset, columnsIn]
726✔
1015
        )
726✔
1016
    );
726✔
1017

726✔
1018
    const drawHeader = whenDefined(
726✔
1019
        drawHeaderIn,
726✔
1020
        React.useCallback<NonNullable<typeof drawHeaderIn>>(
726✔
1021
            (args, draw) => {
726✔
1022
                return drawHeaderIn?.({ ...args, columnIndex: args.columnIndex - rowMarkerOffset }, draw) ?? false;
×
1023
            },
×
1024
            [drawHeaderIn, rowMarkerOffset]
726✔
1025
        )
726✔
1026
    );
726✔
1027

726✔
1028
    const drawCell = whenDefined(
726✔
1029
        drawCellIn,
726✔
1030
        React.useCallback<NonNullable<typeof drawCellIn>>(
726✔
1031
            (args, draw) => {
726✔
1032
                return drawCellIn?.({ ...args, col: args.col - rowMarkerOffset }, draw) ?? false;
×
1033
            },
×
1034
            [drawCellIn, rowMarkerOffset]
726✔
1035
        )
726✔
1036
    );
726✔
1037

726✔
1038
    const onDelete = React.useCallback<NonNullable<DataEditorProps["onDelete"]>>(
726✔
1039
        sel => {
726✔
1040
            if (onDeleteIn !== undefined) {
9✔
1041
                const result = onDeleteIn(shiftSelection(sel, -rowMarkerOffset));
5✔
1042
                if (typeof result === "boolean") {
5!
1043
                    return result;
×
1044
                }
×
1045
                return shiftSelection(result, rowMarkerOffset);
5✔
1046
            }
5✔
1047
            return true;
4✔
1048
        },
9✔
1049
        [onDeleteIn, rowMarkerOffset]
726✔
1050
    );
726✔
1051

726✔
1052
    const [setCurrent, setSelectedRows, setSelectedColumns] = useSelectionBehavior(
726✔
1053
        gridSelection,
726✔
1054
        setGridSelection,
726✔
1055
        rangeSelectionBlending,
726✔
1056
        columnSelectionBlending,
726✔
1057
        rowSelectionBlending,
726✔
1058
        rangeSelect,
726✔
1059
        rangeSelectionColumnSpanning
726✔
1060
    );
726✔
1061

726✔
1062
    const mergedTheme = React.useMemo(() => {
726✔
1063
        return mergeAndRealizeTheme(getDataEditorTheme(), theme);
147✔
1064
    }, [theme]);
726✔
1065

726✔
1066
    const [clientSize, setClientSize] = React.useState<readonly [number, number, number]>([0, 0, 0]);
726✔
1067

726✔
1068
    const rendererMap = React.useMemo(() => {
726✔
1069
        if (renderers === undefined) return {};
147!
1070
        const result: Partial<Record<InnerGridCellKind | GridCellKind, InternalCellRenderer<InnerGridCell>>> = {};
147✔
1071
        for (const r of renderers) {
147✔
1072
            result[r.kind] = r;
1,911✔
1073
        }
1,911✔
1074
        return result;
147✔
1075
    }, [renderers]);
726✔
1076

726✔
1077
    const getCellRenderer: <T extends InnerGridCell>(cell: T) => CellRenderer<T> | undefined = React.useCallback(
726✔
1078
        <T extends InnerGridCell>(cell: T) => {
726✔
1079
            if (cell.kind !== GridCellKind.Custom) {
147,070✔
1080
                return rendererMap[cell.kind] as unknown as CellRenderer<T>;
143,340✔
1081
            }
143,340✔
1082
            return additionalRenderers?.find(x => x.isMatch(cell)) as CellRenderer<T>;
147,070✔
1083
        },
147,070✔
1084
        [additionalRenderers, rendererMap]
726✔
1085
    );
726✔
1086

726✔
1087
    // eslint-disable-next-line prefer-const
726✔
1088
    let { sizedColumns: columns, nonGrowWidth } = useColumnSizer(
726✔
1089
        columnsIn,
726✔
1090
        rows,
726✔
1091
        getCellsForSeletionDirect,
726✔
1092
        clientSize[0] - (rowMarkerOffset === 0 ? 0 : rowMarkerWidth) - clientSize[2],
726✔
1093
        minColumnWidth,
726✔
1094
        maxColumnAutoWidth,
726✔
1095
        mergedTheme,
726✔
1096
        getCellRenderer,
726✔
1097
        abortControllerRef.current
726✔
1098
    );
726✔
1099
    if (rowMarkers !== "none") nonGrowWidth += rowMarkerWidth;
726✔
1100

726✔
1101
    const enableGroups = React.useMemo(() => {
726✔
1102
        return columns.some(c => c.group !== undefined);
147✔
1103
    }, [columns]);
726✔
1104

726✔
1105
    const totalHeaderHeight = enableGroups ? headerHeight + groupHeaderHeight : headerHeight;
726✔
1106

726✔
1107
    const numSelectedRows = gridSelection.rows.length;
726✔
1108
    const rowMarkerChecked =
726✔
1109
        rowMarkers === "none" ? undefined : numSelectedRows === 0 ? false : numSelectedRows === rows ? true : undefined;
726✔
1110

726✔
1111
    const mangledCols = React.useMemo(() => {
726✔
1112
        if (rowMarkers === "none") return columns;
161✔
1113
        return [
46✔
1114
            {
46✔
1115
                title: "",
46✔
1116
                width: rowMarkerWidth,
46✔
1117
                icon: undefined,
46✔
1118
                hasMenu: false,
46✔
1119
                style: "normal" as const,
46✔
1120
                themeOverride: rowMarkerTheme,
46✔
1121
                rowMarker: rowMarkerCheckboxStyle,
46✔
1122
                rowMarkerChecked,
46✔
1123
                headerRowMarkerTheme,
46✔
1124
                headerRowMarkerAlwaysVisible,
46✔
1125
                headerRowMarkerDisabled,
46✔
1126
            },
46✔
1127
            ...columns,
46✔
1128
        ];
46✔
1129
    }, [
726✔
1130
        rowMarkers,
726✔
1131
        columns,
726✔
1132
        rowMarkerWidth,
726✔
1133
        rowMarkerTheme,
726✔
1134
        rowMarkerCheckboxStyle,
726✔
1135
        rowMarkerChecked,
726✔
1136
        headerRowMarkerTheme,
726✔
1137
        headerRowMarkerAlwaysVisible,
726✔
1138
        headerRowMarkerDisabled,
726✔
1139
    ]);
726✔
1140

726✔
1141
    const visibleRegionRef = React.useRef<VisibleRegion>({
726✔
1142
        height: 1,
726✔
1143
        width: 1,
726✔
1144
        x: 0,
726✔
1145
        y: 0,
726✔
1146
    });
726✔
1147

726✔
1148
    const hasJustScrolled = React.useRef(false);
726✔
1149

726✔
1150
    const { setVisibleRegion, visibleRegion, scrollRef } = useInitialScrollOffset(
726✔
1151
        scrollOffsetX,
726✔
1152
        scrollOffsetY,
726✔
1153
        rowHeight,
726✔
1154
        visibleRegionRef,
726✔
1155
        () => (hasJustScrolled.current = true)
726✔
1156
    );
726✔
1157

726✔
1158
    visibleRegionRef.current = visibleRegion;
726✔
1159

726✔
1160
    const cellXOffset = visibleRegion.x + rowMarkerOffset;
726✔
1161
    const cellYOffset = visibleRegion.y;
726✔
1162

726✔
1163
    const gridRef = React.useRef<DataGridRef | null>(null);
726✔
1164

726✔
1165
    const focus = React.useCallback((immediate?: boolean) => {
726✔
1166
        if (immediate === true) {
134✔
1167
            gridRef.current?.focus();
12✔
1168
        } else {
134✔
1169
            window.requestAnimationFrame(() => {
122✔
1170
                gridRef.current?.focus();
121✔
1171
            });
122✔
1172
        }
122✔
1173
    }, []);
726✔
1174

726✔
1175
    const mangledRows = showTrailingBlankRow ? rows + 1 : rows;
726!
1176

726✔
1177
    const mangledOnCellsEdited = React.useCallback<NonNullable<typeof onCellsEdited>>(
726✔
1178
        (items: readonly EditListItem[]) => {
726✔
1179
            const mangledItems =
29✔
1180
                rowMarkerOffset === 0
29✔
1181
                    ? items
24✔
1182
                    : items.map(x => ({
5✔
1183
                          ...x,
29✔
1184
                          location: [x.location[0] - rowMarkerOffset, x.location[1]] as const,
29✔
1185
                      }));
5✔
1186
            const r = onCellsEdited?.(mangledItems);
29✔
1187

29✔
1188
            if (r !== true) {
29✔
1189
                for (const i of mangledItems) onCellEdited?.(i.location, i.value);
28✔
1190
            }
28✔
1191

29✔
1192
            return r;
29✔
1193
        },
29✔
1194
        [onCellEdited, onCellsEdited, rowMarkerOffset]
726✔
1195
    );
726✔
1196

726✔
1197
    const [fillHighlightRegion, setFillHighlightRegion] = React.useState<Rectangle | undefined>();
726✔
1198

726✔
1199
    // this will generally be undefined triggering the memo less often
726✔
1200
    const highlightRange =
726✔
1201
        gridSelection.current !== undefined &&
726✔
1202
        gridSelection.current.range.width * gridSelection.current.range.height > 1
360✔
1203
            ? gridSelection.current.range
44✔
1204
            : undefined;
682✔
1205

726✔
1206
    const highlightFocus = drawFocusRing ? gridSelection.current?.cell : undefined;
726!
1207
    const highlightFocusCol = highlightFocus?.[0];
726✔
1208
    const highlightFocusRow = highlightFocus?.[1];
726✔
1209

726✔
1210
    const highlightRegions = React.useMemo(() => {
726✔
1211
        if (
297✔
1212
            (highlightRegionsIn === undefined || highlightRegionsIn.length === 0) &&
297✔
1213
            (highlightRange ?? highlightFocusCol ?? highlightFocusRow ?? fillHighlightRegion) === undefined
296✔
1214
        )
297✔
1215
            return undefined;
297✔
1216

155✔
1217
        const regions: Highlight[] = [];
155✔
1218

155✔
1219
        if (highlightRegionsIn !== undefined) {
290✔
1220
            for (const r of highlightRegionsIn) {
1✔
1221
                const maxWidth = mangledCols.length - r.range.x - rowMarkerOffset;
1✔
1222
                if (maxWidth > 0) {
1✔
1223
                    regions.push({
1✔
1224
                        color: r.color,
1✔
1225
                        range: {
1✔
1226
                            ...r.range,
1✔
1227
                            x: r.range.x + rowMarkerOffset,
1✔
1228
                            width: Math.min(maxWidth, r.range.width),
1✔
1229
                        },
1✔
1230
                        style: r.style,
1✔
1231
                    });
1✔
1232
                }
1✔
1233
            }
1✔
1234
        }
1✔
1235

155✔
1236
        if (fillHighlightRegion !== undefined) {
290✔
1237
            regions.push({
6✔
1238
                color: withAlpha(mergedTheme.accentColor, 0),
6✔
1239
                range: fillHighlightRegion,
6✔
1240
                style: "dashed",
6✔
1241
            });
6✔
1242
        }
6✔
1243

155✔
1244
        if (highlightRange !== undefined) {
290✔
1245
            regions.push({
30✔
1246
                color: withAlpha(mergedTheme.accentColor, 0.5),
30✔
1247
                range: highlightRange,
30✔
1248
                style: "solid-outline",
30✔
1249
            });
30✔
1250
        }
30✔
1251

155✔
1252
        if (highlightFocusCol !== undefined && highlightFocusRow !== undefined) {
297✔
1253
            regions.push({
154✔
1254
                color: mergedTheme.accentColor,
154✔
1255
                range: {
154✔
1256
                    x: highlightFocusCol,
154✔
1257
                    y: highlightFocusRow,
154✔
1258
                    width: 1,
154✔
1259
                    height: 1,
154✔
1260
                },
154✔
1261
                style: "solid-outline",
154✔
1262
            });
154✔
1263
        }
154✔
1264

155✔
1265
        return regions.length > 0 ? regions : undefined;
297!
1266
    }, [
726✔
1267
        fillHighlightRegion,
726✔
1268
        highlightRange,
726✔
1269
        highlightFocusCol,
726✔
1270
        highlightFocusRow,
726✔
1271
        highlightRegionsIn,
726✔
1272
        mangledCols.length,
726✔
1273
        mergedTheme.accentColor,
726✔
1274
        rowMarkerOffset,
726✔
1275
    ]);
726✔
1276

726✔
1277
    const mangledColsRef = React.useRef(mangledCols);
726✔
1278
    mangledColsRef.current = mangledCols;
726✔
1279
    const getMangledCellContent = React.useCallback(
726✔
1280
        ([col, row]: Item, forceStrict: boolean = false): InnerGridCell => {
726✔
1281
            const isTrailing = showTrailingBlankRow && row === mangledRows - 1;
148,192✔
1282
            const isRowMarkerCol = col === 0 && hasRowMarkers;
148,192✔
1283
            if (isRowMarkerCol) {
148,192✔
1284
                if (isTrailing) {
2,153✔
1285
                    return loadingCell;
66✔
1286
                }
66✔
1287
                const mappedRow = rowNumberMapper(row);
2,087✔
1288
                if (mappedRow === undefined) return loadingCell;
2,153!
1289
                return {
2,087✔
1290
                    kind: InnerGridCellKind.Marker,
2,087✔
1291
                    allowOverlay: false,
2,087✔
1292
                    checkboxStyle: rowMarkerCheckboxStyle,
2,087✔
1293
                    checked: gridSelection?.rows.hasIndex(row) === true,
2,153✔
1294
                    markerKind: rowMarkers === "clickable-number" ? "number" : rowMarkers,
2,153!
1295
                    row: rowMarkerStartIndex + mappedRow,
2,153✔
1296
                    drawHandle: onRowMoved !== undefined,
2,153✔
1297
                    cursor: rowMarkers === "clickable-number" ? "pointer" : undefined,
2,153!
1298
                };
2,153✔
1299
            } else if (isTrailing) {
148,192✔
1300
                //If the grid is empty, we will return text
3,999✔
1301
                const isFirst = col === rowMarkerOffset;
3,999✔
1302

3,999✔
1303
                const maybeFirstColumnHint = isFirst ? trailingRowOptions?.hint ?? "" : "";
3,999✔
1304
                const c = mangledColsRef.current[col];
3,999✔
1305

3,999✔
1306
                if (c?.trailingRowOptions?.disabled === true) {
3,999!
1307
                    return loadingCell;
×
1308
                } else {
3,999✔
1309
                    const hint = c?.trailingRowOptions?.hint ?? maybeFirstColumnHint;
3,999!
1310
                    const icon = c?.trailingRowOptions?.addIcon ?? trailingRowOptions?.addIcon;
3,999!
1311
                    return {
3,999✔
1312
                        kind: InnerGridCellKind.NewRow,
3,999✔
1313
                        hint,
3,999✔
1314
                        allowOverlay: false,
3,999✔
1315
                        icon,
3,999✔
1316
                    };
3,999✔
1317
                }
3,999✔
1318
            } else {
146,039✔
1319
                const outerCol = col - rowMarkerOffset;
142,040✔
1320
                if (forceStrict || experimental?.strict === true) {
142,040✔
1321
                    const vr = visibleRegionRef.current;
25,865✔
1322
                    const isOutsideMainArea =
25,865✔
1323
                        vr.x > outerCol ||
25,865✔
1324
                        outerCol > vr.x + vr.width ||
25,865✔
1325
                        vr.y > row ||
25,865✔
1326
                        row > vr.y + vr.height ||
25,865✔
1327
                        row >= rowsRef.current;
25,865✔
1328
                    const isSelected = outerCol === vr.extras?.selected?.[0] && row === vr.extras?.selected[1];
25,865!
1329
                    let isInFreezeArea = false;
25,865✔
1330
                    if (vr.extras?.freezeRegions !== undefined) {
25,865✔
1331
                        for (const fr of vr.extras.freezeRegions) {
25,865!
1332
                            if (pointInRect(fr, outerCol, row)) {
×
1333
                                isInFreezeArea = true;
×
1334
                                break;
×
1335
                            }
×
1336
                        }
×
1337
                    }
25,865✔
1338

25,865✔
1339
                    if (isOutsideMainArea && !isSelected && !isInFreezeArea) {
25,865!
1340
                        return loadingCell;
×
1341
                    }
×
1342
                }
25,865✔
1343
                let result = getCellContent([outerCol, row]);
142,040✔
1344
                if (rowMarkerOffset !== 0 && result.span !== undefined) {
142,040!
1345
                    result = {
×
1346
                        ...result,
×
1347
                        span: [result.span[0] + rowMarkerOffset, result.span[1] + rowMarkerOffset],
×
1348
                    };
×
1349
                }
×
1350
                return result;
142,040✔
1351
            }
142,040✔
1352
        },
148,192✔
1353
        [
726✔
1354
            showTrailingBlankRow,
726✔
1355
            mangledRows,
726✔
1356
            hasRowMarkers,
726✔
1357
            rowNumberMapper,
726✔
1358
            rowMarkerCheckboxStyle,
726✔
1359
            gridSelection?.rows,
726✔
1360
            rowMarkers,
726✔
1361
            rowMarkerStartIndex,
726✔
1362
            onRowMoved,
726✔
1363
            rowMarkerOffset,
726✔
1364
            trailingRowOptions?.hint,
726✔
1365
            trailingRowOptions?.addIcon,
726✔
1366
            experimental?.strict,
726✔
1367
            getCellContent,
726✔
1368
        ]
726✔
1369
    );
726✔
1370

726✔
1371
    const mangledGetGroupDetails = React.useCallback<NonNullable<DataEditorProps["getGroupDetails"]>>(
726✔
1372
        group => {
726✔
1373
            let result = getGroupDetails?.(group) ?? { name: group };
8,469✔
1374
            if (onGroupHeaderRenamed !== undefined && group !== "") {
8,469✔
1375
                result = {
88✔
1376
                    icon: result.icon,
88✔
1377
                    name: result.name,
88✔
1378
                    overrideTheme: result.overrideTheme,
88✔
1379
                    actions: [
88✔
1380
                        ...(result.actions ?? []),
88✔
1381
                        {
88✔
1382
                            title: "Rename",
88✔
1383
                            icon: "renameIcon",
88✔
1384
                            onClick: e =>
88✔
1385
                                setRenameGroup({
2✔
1386
                                    group: result.name,
2✔
1387
                                    bounds: e.bounds,
2✔
1388
                                }),
2✔
1389
                        },
88✔
1390
                    ],
88✔
1391
                };
88✔
1392
            }
88✔
1393
            return result;
8,469✔
1394
        },
8,469✔
1395
        [getGroupDetails, onGroupHeaderRenamed]
726✔
1396
    );
726✔
1397

726✔
1398
    const setOverlaySimple = React.useCallback(
726✔
1399
        (val: Omit<NonNullable<typeof overlay>, "theme">) => {
726✔
1400
            const [col, row] = val.cell;
24✔
1401
            const column = mangledCols[col];
24✔
1402
            const groupTheme =
24✔
1403
                column?.group !== undefined ? mangledGetGroupDetails(column.group)?.overrideTheme : undefined;
24!
1404
            const colTheme = column?.themeOverride;
24✔
1405
            const rowTheme = getRowThemeOverride?.(row);
24!
1406

24✔
1407
            setOverlay({
24✔
1408
                ...val,
24✔
1409
                theme: mergeAndRealizeTheme(mergedTheme, groupTheme, colTheme, rowTheme, val.content.themeOverride),
24✔
1410
            });
24✔
1411
        },
24✔
1412
        [getRowThemeOverride, mangledCols, mangledGetGroupDetails, mergedTheme]
726✔
1413
    );
726✔
1414

726✔
1415
    const reselect = React.useCallback(
726✔
1416
        (bounds: Rectangle, fromKeyboard: boolean, initialValue?: string) => {
726✔
1417
            if (gridSelection.current === undefined) return;
25!
1418

25✔
1419
            const [col, row] = gridSelection.current.cell;
25✔
1420
            const c = getMangledCellContent([col, row]);
25✔
1421
            if (c.kind !== GridCellKind.Boolean && c.allowOverlay) {
25✔
1422
                let content = c;
23✔
1423
                if (initialValue !== undefined) {
23✔
1424
                    switch (content.kind) {
12✔
1425
                        case GridCellKind.Number: {
12!
1426
                            const d = maybe(() => (initialValue === "-" ? -0 : Number.parseFloat(initialValue)), 0);
×
1427
                            content = {
×
1428
                                ...content,
×
1429
                                data: Number.isNaN(d) ? 0 : d,
×
1430
                            };
×
1431
                            break;
×
1432
                        }
×
1433
                        case GridCellKind.Text:
12✔
1434
                        case GridCellKind.Markdown:
12✔
1435
                        case GridCellKind.Uri:
12✔
1436
                            content = {
12✔
1437
                                ...content,
12✔
1438
                                data: initialValue,
12✔
1439
                            };
12✔
1440
                            break;
12✔
1441
                    }
12✔
1442
                }
12✔
1443

23✔
1444
                setOverlaySimple({
23✔
1445
                    target: bounds,
23✔
1446
                    content,
23✔
1447
                    initialValue,
23✔
1448
                    cell: [col, row],
23✔
1449
                    highlight: initialValue === undefined,
23✔
1450
                    forceEditMode: initialValue !== undefined,
23✔
1451
                });
23✔
1452
            } else if (c.kind === GridCellKind.Boolean && fromKeyboard && c.readonly !== true) {
25✔
1453
                mangledOnCellsEdited([
1✔
1454
                    {
1✔
1455
                        location: gridSelection.current.cell,
1✔
1456
                        value: {
1✔
1457
                            ...c,
1✔
1458
                            data: toggleBoolean(c.data),
1✔
1459
                        },
1✔
1460
                    },
1✔
1461
                ]);
1✔
1462
                gridRef.current?.damage([{ cell: gridSelection.current.cell }]);
1✔
1463
            }
1✔
1464
        },
25✔
1465
        [getMangledCellContent, gridSelection, mangledOnCellsEdited, setOverlaySimple]
726✔
1466
    );
726✔
1467

726✔
1468
    const focusOnRowFromTrailingBlankRow = React.useCallback(
726✔
1469
        (col: number, row: number) => {
726✔
1470
            const bounds = gridRef.current?.getBounds(col, row);
1✔
1471
            if (bounds === undefined || scrollRef.current === null) {
1!
1472
                return;
×
1473
            }
×
1474

1✔
1475
            const content = getMangledCellContent([col, row]);
1✔
1476
            if (!content.allowOverlay) {
1!
1477
                return;
×
1478
            }
×
1479

1✔
1480
            setOverlaySimple({
1✔
1481
                target: bounds,
1✔
1482
                content,
1✔
1483
                initialValue: undefined,
1✔
1484
                highlight: true,
1✔
1485
                cell: [col, row],
1✔
1486
                forceEditMode: true,
1✔
1487
            });
1✔
1488
        },
1✔
1489
        [getMangledCellContent, scrollRef, setOverlaySimple]
726✔
1490
    );
726✔
1491

726✔
1492
    const scrollTo = React.useCallback<ScrollToFn>(
726✔
1493
        (col, row, dir = "both", paddingX = 0, paddingY = 0, options = undefined): void => {
726✔
1494
            if (scrollRef.current !== null) {
51✔
1495
                const grid = gridRef.current;
51✔
1496
                const canvas = canvasRef.current;
51✔
1497

51✔
1498
                const trueCol = typeof col !== "number" ? (col.unit === "cell" ? col.amount : undefined) : col;
51!
1499
                const trueRow = typeof row !== "number" ? (row.unit === "cell" ? row.amount : undefined) : row;
51!
1500
                const desiredX = typeof col !== "number" && col.unit === "px" ? col.amount : undefined;
51!
1501
                const desiredY = typeof row !== "number" && row.unit === "px" ? row.amount : undefined;
51✔
1502
                if (grid !== null && canvas !== null) {
51✔
1503
                    let targetRect: Rectangle = {
51✔
1504
                        x: 0,
51✔
1505
                        y: 0,
51✔
1506
                        width: 0,
51✔
1507
                        height: 0,
51✔
1508
                    };
51✔
1509

51✔
1510
                    let scrollX = 0;
51✔
1511
                    let scrollY = 0;
51✔
1512

51✔
1513
                    if (trueCol !== undefined || trueRow !== undefined) {
51!
1514
                        targetRect = grid.getBounds((trueCol ?? 0) + rowMarkerOffset, trueRow ?? 0) ?? targetRect;
51!
1515
                        if (targetRect.width === 0 || targetRect.height === 0) return;
51!
1516
                    }
51✔
1517

51✔
1518
                    const scrollBounds = canvas.getBoundingClientRect();
51✔
1519
                    const scale = scrollBounds.width / canvas.offsetWidth;
51✔
1520

51✔
1521
                    if (desiredX !== undefined) {
51!
1522
                        targetRect = {
×
1523
                            ...targetRect,
×
1524
                            x: desiredX - scrollBounds.left - scrollRef.current.scrollLeft,
×
1525
                            width: 1,
×
1526
                        };
×
1527
                    }
×
1528
                    if (desiredY !== undefined) {
51✔
1529
                        targetRect = {
4✔
1530
                            ...targetRect,
4✔
1531
                            y: desiredY + scrollBounds.top - scrollRef.current.scrollTop,
4✔
1532
                            height: 1,
4✔
1533
                        };
4✔
1534
                    }
4✔
1535

51✔
1536
                    if (targetRect !== undefined) {
51✔
1537
                        const bounds = {
51✔
1538
                            x: targetRect.x - paddingX,
51✔
1539
                            y: targetRect.y - paddingY,
51✔
1540
                            width: targetRect.width + 2 * paddingX,
51✔
1541
                            height: targetRect.height + 2 * paddingY,
51✔
1542
                        };
51✔
1543

51✔
1544
                        let frozenWidth = 0;
51✔
1545
                        for (let i = 0; i < freezeColumns; i++) {
51!
1546
                            frozenWidth += columns[i].width;
×
1547
                        }
×
1548
                        let trailingRowHeight = 0;
51✔
1549
                        const freezeTrailingRowsEffective = freezeTrailingRows + (lastRowSticky ? 1 : 0);
51!
1550
                        if (freezeTrailingRowsEffective > 0) {
51✔
1551
                            trailingRowHeight = getFreezeTrailingHeight(
51✔
1552
                                mangledRows,
51✔
1553
                                freezeTrailingRowsEffective,
51✔
1554
                                rowHeight
51✔
1555
                            );
51✔
1556
                        }
51✔
1557

51✔
1558
                        // scrollBounds is already scaled
51✔
1559
                        let sLeft = frozenWidth * scale + scrollBounds.left + rowMarkerOffset * rowMarkerWidth * scale;
51✔
1560
                        let sRight = scrollBounds.right;
51✔
1561
                        let sTop = scrollBounds.top + totalHeaderHeight * scale;
51✔
1562
                        let sBottom = scrollBounds.bottom - trailingRowHeight * scale;
51✔
1563

51✔
1564
                        const minx = targetRect.width + paddingX * 2;
51✔
1565
                        switch (options?.hAlign) {
51✔
1566
                            case "start":
51!
1567
                                sRight = sLeft + minx;
×
1568
                                break;
×
1569
                            case "end":
51!
1570
                                sLeft = sRight - minx;
×
1571
                                break;
×
1572
                            case "center":
51!
1573
                                sLeft = Math.floor((sLeft + sRight) / 2) - minx / 2;
×
1574
                                sRight = sLeft + minx;
×
1575
                                break;
×
1576
                        }
51✔
1577

51✔
1578
                        const miny = targetRect.height + paddingY * 2;
51✔
1579
                        switch (options?.vAlign) {
51✔
1580
                            case "start":
51✔
1581
                                sBottom = sTop + miny;
1✔
1582
                                break;
1✔
1583
                            case "end":
51✔
1584
                                sTop = sBottom - miny;
1✔
1585
                                break;
1✔
1586
                            case "center":
51✔
1587
                                sTop = Math.floor((sTop + sBottom) / 2) - miny / 2;
1✔
1588
                                sBottom = sTop + miny;
1✔
1589
                                break;
1✔
1590
                        }
51✔
1591

51✔
1592
                        if (sLeft > bounds.x) {
51!
1593
                            scrollX = bounds.x - sLeft;
×
1594
                        } else if (sRight < bounds.x + bounds.width) {
51✔
1595
                            scrollX = bounds.x + bounds.width - sRight;
6✔
1596
                        }
6✔
1597

51✔
1598
                        if (sTop > bounds.y) {
51!
1599
                            scrollY = bounds.y - sTop;
×
1600
                        } else if (sBottom < bounds.y + bounds.height) {
51✔
1601
                            scrollY = bounds.y + bounds.height - sBottom;
15✔
1602
                        }
15✔
1603

51✔
1604
                        if (dir === "vertical" || (typeof col === "number" && col < freezeColumns)) {
51✔
1605
                            scrollX = 0;
4✔
1606
                        } else if (
4✔
1607
                            dir === "horizontal" ||
47✔
1608
                            (typeof row === "number" && row >= mangledRows - freezeTrailingRowsEffective)
39✔
1609
                        ) {
47✔
1610
                            scrollY = 0;
10✔
1611
                        }
10✔
1612

51✔
1613
                        if (scrollX !== 0 || scrollY !== 0) {
51✔
1614
                            // Remove scaling as scrollTo method is unaffected by transform scale.
18✔
1615
                            if (scale !== 1) {
18!
1616
                                scrollX /= scale;
×
1617
                                scrollY /= scale;
×
1618
                            }
×
1619
                            scrollRef.current.scrollTo({
18✔
1620
                                left: scrollX + scrollRef.current.scrollLeft,
18✔
1621
                                top: scrollY + scrollRef.current.scrollTop,
18✔
1622
                                behavior: options?.behavior ?? "auto",
18✔
1623
                            });
18✔
1624
                        }
18✔
1625
                    }
51✔
1626
                }
51✔
1627
            }
51✔
1628
        },
51✔
1629
        [
726✔
1630
            rowMarkerOffset,
726✔
1631
            freezeTrailingRows,
726✔
1632
            rowMarkerWidth,
726✔
1633
            scrollRef,
726✔
1634
            totalHeaderHeight,
726✔
1635
            freezeColumns,
726✔
1636
            columns,
726✔
1637
            mangledRows,
726✔
1638
            lastRowSticky,
726✔
1639
            rowHeight,
726✔
1640
        ]
726✔
1641
    );
726✔
1642

726✔
1643
    const focusCallback = React.useRef(focusOnRowFromTrailingBlankRow);
726✔
1644
    const getCellContentRef = React.useRef(getCellContent);
726✔
1645
    const rowsRef = React.useRef(rows);
726✔
1646
    focusCallback.current = focusOnRowFromTrailingBlankRow;
726✔
1647
    getCellContentRef.current = getCellContent;
726✔
1648
    rowsRef.current = rows;
726✔
1649
    const appendRow = React.useCallback(
726✔
1650
        async (col: number, openOverlay: boolean = true, behavior?: ScrollBehavior): Promise<void> => {
726✔
1651
            const c = mangledCols[col];
1✔
1652
            if (c?.trailingRowOptions?.disabled === true) {
1!
1653
                return;
×
1654
            }
×
1655
            const appendResult = onRowAppended?.();
1✔
1656

1✔
1657
            let r: "top" | "bottom" | number | undefined = undefined;
1✔
1658
            let bottom = true;
1✔
1659
            if (appendResult !== undefined) {
1!
1660
                r = await appendResult;
×
1661
                if (r === "top") bottom = false;
×
1662
                if (typeof r === "number") bottom = false;
×
1663
            }
×
1664

1✔
1665
            let backoff = 0;
1✔
1666
            const doFocus = () => {
1✔
1667
                if (rowsRef.current <= rows) {
2✔
1668
                    if (backoff < 500) {
1✔
1669
                        window.setTimeout(doFocus, backoff);
1✔
1670
                    }
1✔
1671
                    backoff = 50 + backoff * 2;
1✔
1672
                    return;
1✔
1673
                }
1✔
1674

1✔
1675
                const row = typeof r === "number" ? r : bottom ? rows : 0;
2!
1676
                scrollToRef.current(col - rowMarkerOffset, row, "both", 0, 0, behavior ? { behavior } : undefined);
2!
1677
                setCurrent(
2✔
1678
                    {
2✔
1679
                        cell: [col, row],
2✔
1680
                        range: {
2✔
1681
                            x: col,
2✔
1682
                            y: row,
2✔
1683
                            width: 1,
2✔
1684
                            height: 1,
2✔
1685
                        },
2✔
1686
                    },
2✔
1687
                    false,
2✔
1688
                    false,
2✔
1689
                    "edit"
2✔
1690
                );
2✔
1691

2✔
1692
                const cell = getCellContentRef.current([col - rowMarkerOffset, row]);
2✔
1693
                if (cell.allowOverlay && isReadWriteCell(cell) && cell.readonly !== true && openOverlay) {
2✔
1694
                    // wait for scroll to have a chance to process
1✔
1695
                    window.setTimeout(() => {
1✔
1696
                        focusCallback.current(col, row);
1✔
1697
                    }, 0);
1✔
1698
                }
1✔
1699
            };
2✔
1700
            // Queue up to allow the consumer to react to the event and let us check if they did
1✔
1701
            doFocus();
1✔
1702
        },
1✔
1703
        [mangledCols, onRowAppended, rowMarkerOffset, rows, setCurrent]
726✔
1704
    );
726✔
1705

726✔
1706
    const getCustomNewRowTargetColumn = React.useCallback(
726✔
1707
        (col: number): number | undefined => {
726✔
1708
            const customTargetColumn =
1✔
1709
                columns[col]?.trailingRowOptions?.targetColumn ?? trailingRowOptions?.targetColumn;
1!
1710

1✔
1711
            if (typeof customTargetColumn === "number") {
1!
1712
                const customTargetOffset = hasRowMarkers ? 1 : 0;
×
1713
                return customTargetColumn + customTargetOffset;
×
1714
            }
×
1715

1✔
1716
            if (typeof customTargetColumn === "object") {
1!
1717
                const maybeIndex = columnsIn.indexOf(customTargetColumn);
×
1718
                if (maybeIndex >= 0) {
×
1719
                    const customTargetOffset = hasRowMarkers ? 1 : 0;
×
1720
                    return maybeIndex + customTargetOffset;
×
1721
                }
×
1722
            }
×
1723

1✔
1724
            return undefined;
1✔
1725
        },
1✔
1726
        [columns, columnsIn, hasRowMarkers, trailingRowOptions?.targetColumn]
726✔
1727
    );
726✔
1728

726✔
1729
    const lastSelectedRowRef = React.useRef<number>();
726✔
1730
    const lastSelectedColRef = React.useRef<number>();
726✔
1731

726✔
1732
    const themeForCell = React.useCallback(
726✔
1733
        (cell: InnerGridCell, pos: Item): FullTheme => {
726✔
1734
            const [col, row] = pos;
28✔
1735
            return mergeAndRealizeTheme(
28✔
1736
                mergedTheme,
28✔
1737
                mangledCols[col]?.themeOverride,
28✔
1738
                getRowThemeOverride?.(row),
28!
1739
                cell.themeOverride
28✔
1740
            );
28✔
1741
        },
28✔
1742
        [getRowThemeOverride, mangledCols, mergedTheme]
726✔
1743
    );
726✔
1744

726✔
1745
    const { mapper } = useRowGrouping(rowGrouping, rowsIn);
726✔
1746

726✔
1747
    const rowGroupingNavBehavior = rowGrouping?.navigationBehavior;
726!
1748

726✔
1749
    const handleSelect = React.useCallback(
726✔
1750
        (args: GridMouseEventArgs) => {
726✔
1751
            const isMultiKey = browserIsOSX.value ? args.metaKey : args.ctrlKey;
126!
1752
            const isMultiRow = isMultiKey && rowSelect === "multi";
126✔
1753
            const isMultiCol = isMultiKey && columnSelect === "multi";
126✔
1754
            const [col, row] = args.location;
126✔
1755
            const selectedColumns = gridSelection.columns;
126✔
1756
            const selectedRows = gridSelection.rows;
126✔
1757
            const [cellCol, cellRow] = gridSelection.current?.cell ?? [];
126✔
1758
            // eslint-disable-next-line unicorn/prefer-switch
126✔
1759
            if (args.kind === "cell") {
126✔
1760
                lastSelectedColRef.current = undefined;
110✔
1761

110✔
1762
                lastMouseSelectLocation.current = [col, row];
110✔
1763

110✔
1764
                if (col === 0 && hasRowMarkers) {
110✔
1765
                    if (
15✔
1766
                        (showTrailingBlankRow === true && row === rows) ||
15✔
1767
                        rowMarkers === "number" ||
15✔
1768
                        rowSelect === "none"
14✔
1769
                    )
15✔
1770
                        return;
15✔
1771

14✔
1772
                    const markerCell = getMangledCellContent(args.location);
14✔
1773
                    if (markerCell.kind !== InnerGridCellKind.Marker) {
15!
1774
                        return;
×
1775
                    }
✔
1776

14✔
1777
                    if (onRowMoved !== undefined) {
15!
1778
                        const renderer = getCellRenderer(markerCell);
×
1779
                        assert(renderer?.kind === InnerGridCellKind.Marker);
×
1780
                        const postClick = renderer?.onClick?.({
×
1781
                            ...args,
×
1782
                            cell: markerCell,
×
1783
                            posX: args.localEventX,
×
1784
                            posY: args.localEventY,
×
1785
                            bounds: args.bounds,
×
1786
                            theme: themeForCell(markerCell, args.location),
×
1787
                            preventDefault: () => undefined,
×
1788
                        }) as MarkerCell | undefined;
×
1789
                        if (postClick === undefined || postClick.checked === markerCell.checked) return;
×
1790
                    }
✔
1791

14✔
1792
                    setOverlay(undefined);
14✔
1793
                    focus();
14✔
1794
                    const isSelected = selectedRows.hasIndex(row);
14✔
1795

14✔
1796
                    const lastHighlighted = lastSelectedRowRef.current;
14✔
1797
                    if (
14✔
1798
                        rowSelect === "multi" &&
14✔
1799
                        (args.shiftKey || args.isLongTouch === true) &&
8✔
1800
                        lastHighlighted !== undefined &&
1✔
1801
                        selectedRows.hasIndex(lastHighlighted)
1✔
1802
                    ) {
15✔
1803
                        const newSlice: Slice = [Math.min(lastHighlighted, row), Math.max(lastHighlighted, row) + 1];
1✔
1804

1✔
1805
                        if (isMultiRow || rowSelectionMode === "multi") {
1!
1806
                            setSelectedRows(undefined, newSlice, true);
×
1807
                        } else {
1✔
1808
                            setSelectedRows(CompactSelection.fromSingleSelection(newSlice), undefined, isMultiRow);
1✔
1809
                        }
1✔
1810
                    } else if (rowSelect === "multi" && (isMultiRow || args.isTouch || rowSelectionMode === "multi")) {
15✔
1811
                        if (isSelected) {
3✔
1812
                            setSelectedRows(selectedRows.remove(row), undefined, true);
1✔
1813
                        } else {
2✔
1814
                            setSelectedRows(undefined, row, true);
2✔
1815
                            lastSelectedRowRef.current = row;
2✔
1816
                        }
2✔
1817
                    } else if (isSelected && selectedRows.length === 1) {
13✔
1818
                        setSelectedRows(CompactSelection.empty(), undefined, isMultiKey);
1✔
1819
                    } else {
10✔
1820
                        setSelectedRows(CompactSelection.fromSingleSelection(row), undefined, isMultiKey);
9✔
1821
                        lastSelectedRowRef.current = row;
9✔
1822
                    }
9✔
1823
                } else if (col >= rowMarkerOffset && showTrailingBlankRow && row === rows) {
110✔
1824
                    const customTargetColumn = getCustomNewRowTargetColumn(col);
1✔
1825
                    void appendRow(customTargetColumn ?? col);
1✔
1826
                } else {
95✔
1827
                    if (cellCol !== col || cellRow !== row) {
94✔
1828
                        const cell = getMangledCellContent(args.location);
88✔
1829
                        const renderer = getCellRenderer(cell);
88✔
1830

88✔
1831
                        if (renderer?.onSelect !== undefined) {
88✔
1832
                            let prevented = false;
7✔
1833
                            renderer.onSelect({
7✔
1834
                                ...args,
7✔
1835
                                cell,
7✔
1836
                                posX: args.localEventX,
7✔
1837
                                posY: args.localEventY,
7✔
1838
                                bounds: args.bounds,
7✔
1839
                                preventDefault: () => (prevented = true),
7✔
1840
                                theme: themeForCell(cell, args.location),
7✔
1841
                            });
7✔
1842
                            if (prevented) {
7✔
1843
                                return;
4✔
1844
                            }
4✔
1845
                        }
7✔
1846

84✔
1847
                        if (rowGroupingNavBehavior === "block" && mapper(row).isGroupHeader) {
88!
1848
                            return;
×
1849
                        }
✔
1850

84✔
1851
                        const isLastStickyRow = lastRowSticky && row === rows;
84✔
1852

88✔
1853
                        const startedFromLastSticky =
88✔
1854
                            lastRowSticky && gridSelection !== undefined && gridSelection.current?.cell[1] === rows;
88✔
1855

88✔
1856
                        if (
88✔
1857
                            (args.shiftKey || args.isLongTouch === true) &&
88✔
1858
                            cellCol !== undefined &&
6✔
1859
                            cellRow !== undefined &&
6✔
1860
                            gridSelection.current !== undefined &&
6✔
1861
                            !startedFromLastSticky
6✔
1862
                        ) {
88✔
1863
                            if (isLastStickyRow) {
6!
1864
                                // If we're making a selection and shift click in to the last sticky row,
×
1865
                                // just drop the event. Don't kill the selection.
×
1866
                                return;
×
1867
                            }
×
1868

6✔
1869
                            const left = Math.min(col, cellCol);
6✔
1870
                            const right = Math.max(col, cellCol);
6✔
1871
                            const top = Math.min(row, cellRow);
6✔
1872
                            const bottom = Math.max(row, cellRow);
6✔
1873
                            setCurrent(
6✔
1874
                                {
6✔
1875
                                    ...gridSelection.current,
6✔
1876
                                    range: {
6✔
1877
                                        x: left,
6✔
1878
                                        y: top,
6✔
1879
                                        width: right - left + 1,
6✔
1880
                                        height: bottom - top + 1,
6✔
1881
                                    },
6✔
1882
                                },
6✔
1883
                                true,
6✔
1884
                                isMultiKey,
6✔
1885
                                "click"
6✔
1886
                            );
6✔
1887
                            lastSelectedRowRef.current = undefined;
6✔
1888
                            focus();
6✔
1889
                        } else {
88✔
1890
                            setCurrent(
78✔
1891
                                {
78✔
1892
                                    cell: [col, row],
78✔
1893
                                    range: { x: col, y: row, width: 1, height: 1 },
78✔
1894
                                },
78✔
1895
                                true,
78✔
1896
                                isMultiKey,
78✔
1897
                                "click"
78✔
1898
                            );
78✔
1899
                            lastSelectedRowRef.current = undefined;
78✔
1900
                            setOverlay(undefined);
78✔
1901
                            focus();
78✔
1902
                        }
78✔
1903
                    }
88✔
1904
                }
94✔
1905
            } else if (args.kind === "header") {
126✔
1906
                lastMouseSelectLocation.current = [col, row];
12✔
1907
                setOverlay(undefined);
12✔
1908
                if (hasRowMarkers && col === 0) {
12✔
1909
                    lastSelectedRowRef.current = undefined;
4✔
1910
                    lastSelectedColRef.current = undefined;
4✔
1911
                    if (rowSelect === "multi") {
4✔
1912
                        if (selectedRows.length !== rows) {
4✔
1913
                            setSelectedRows(CompactSelection.fromSingleSelection([0, rows]), undefined, isMultiKey);
3✔
1914
                        } else {
4✔
1915
                            setSelectedRows(CompactSelection.empty(), undefined, isMultiKey);
1✔
1916
                        }
1✔
1917
                        focus();
4✔
1918
                    }
4✔
1919
                } else {
12✔
1920
                    const lastCol = lastSelectedColRef.current;
8✔
1921
                    if (
8✔
1922
                        columnSelect === "multi" &&
8✔
1923
                        (args.shiftKey || args.isLongTouch === true) &&
7!
1924
                        lastCol !== undefined &&
×
1925
                        selectedColumns.hasIndex(lastCol)
×
1926
                    ) {
8!
1927
                        const newSlice: Slice = [Math.min(lastCol, col), Math.max(lastCol, col) + 1];
×
1928

×
1929
                        if (isMultiCol) {
×
1930
                            setSelectedColumns(undefined, newSlice, isMultiKey);
×
1931
                        } else {
×
1932
                            setSelectedColumns(CompactSelection.fromSingleSelection(newSlice), undefined, isMultiKey);
×
1933
                        }
×
1934
                    } else if (isMultiCol) {
8✔
1935
                        if (selectedColumns.hasIndex(col)) {
1!
1936
                            setSelectedColumns(selectedColumns.remove(col), undefined, isMultiKey);
×
1937
                        } else {
1✔
1938
                            setSelectedColumns(undefined, col, isMultiKey);
1✔
1939
                        }
1✔
1940
                        lastSelectedColRef.current = col;
1✔
1941
                    } else if (columnSelect !== "none") {
8✔
1942
                        if (selectedColumns.hasIndex(col)) {
7!
NEW
1943
                            setSelectedColumns(selectedColumns.remove(col), undefined, isMultiKey);
×
1944
                        } else {
7✔
1945
                            setSelectedColumns(CompactSelection.fromSingleSelection(col), undefined, isMultiKey);
7✔
1946
                        }
7✔
1947
                        lastSelectedColRef.current = col;
7✔
1948
                    }
7✔
1949
                    lastSelectedRowRef.current = undefined;
8✔
1950
                    focus();
8✔
1951
                }
8✔
1952
            } else if (args.kind === groupHeaderKind) {
16✔
1953
                lastMouseSelectLocation.current = [col, row];
3✔
1954
            } else if (args.kind === outOfBoundsKind && !args.isMaybeScrollbar) {
4✔
1955
                setGridSelection(emptyGridSelection, false);
1✔
1956
                setOverlay(undefined);
1✔
1957
                focus();
1✔
1958
                onSelectionCleared?.();
1!
1959
                lastSelectedRowRef.current = undefined;
1✔
1960
                lastSelectedColRef.current = undefined;
1✔
1961
            }
1✔
1962
        },
126✔
1963
        [
726✔
1964
            rowSelect,
726✔
1965
            columnSelect,
726✔
1966
            gridSelection,
726✔
1967
            hasRowMarkers,
726✔
1968
            rowMarkerOffset,
726✔
1969
            showTrailingBlankRow,
726✔
1970
            rows,
726✔
1971
            rowMarkers,
726✔
1972
            getMangledCellContent,
726✔
1973
            onRowMoved,
726✔
1974
            focus,
726✔
1975
            rowSelectionMode,
726✔
1976
            getCellRenderer,
726✔
1977
            themeForCell,
726✔
1978
            setSelectedRows,
726✔
1979
            getCustomNewRowTargetColumn,
726✔
1980
            appendRow,
726✔
1981
            rowGroupingNavBehavior,
726✔
1982
            mapper,
726✔
1983
            lastRowSticky,
726✔
1984
            setCurrent,
726✔
1985
            setSelectedColumns,
726✔
1986
            setGridSelection,
726✔
1987
            onSelectionCleared,
726✔
1988
        ]
726✔
1989
    );
726✔
1990
    const isActivelyDraggingHeader = React.useRef(false);
726✔
1991
    const lastMouseSelectLocation = React.useRef<readonly [number, number]>();
726✔
1992
    const touchDownArgs = React.useRef(visibleRegion);
726✔
1993
    const mouseDownData = React.useRef<{
726✔
1994
        time: number;
726✔
1995
        button: number;
726✔
1996
        location: Item;
726✔
1997
    }>();
726✔
1998
    const onMouseDown = React.useCallback(
726✔
1999
        (args: GridMouseEventArgs) => {
726✔
2000
            isPrevented.current = false;
144✔
2001
            touchDownArgs.current = visibleRegionRef.current;
144✔
2002
            if (args.button !== 0 && args.button !== 1) {
144✔
2003
                mouseDownData.current = undefined;
1✔
2004
                return;
1✔
2005
            }
1✔
2006

143✔
2007
            const time = performance.now();
143✔
2008
            mouseDownData.current = {
143✔
2009
                button: args.button,
143✔
2010
                time,
143✔
2011
                location: args.location,
143✔
2012
            };
143✔
2013

143✔
2014
            if (args?.kind === "header") {
144✔
2015
                isActivelyDraggingHeader.current = true;
18✔
2016
            }
18✔
2017

143✔
2018
            const fh = args.kind === "cell" && args.isFillHandle;
144✔
2019

144✔
2020
            if (!fh && args.kind !== "cell" && args.isEdge) return;
144✔
2021

136✔
2022
            setMouseState({
136✔
2023
                previousSelection: gridSelection,
136✔
2024
                fillHandle: fh,
136✔
2025
            });
136✔
2026
            lastMouseSelectLocation.current = undefined;
136✔
2027

136✔
2028
            if (!args.isTouch && args.button === 0 && !fh) {
144✔
2029
                handleSelect(args);
123✔
2030
            } else if (!args.isTouch && args.button === 1) {
137✔
2031
                lastMouseSelectLocation.current = args.location;
4✔
2032
            }
4✔
2033
        },
144✔
2034
        [gridSelection, handleSelect]
726✔
2035
    );
726✔
2036

726✔
2037
    const [renameGroup, setRenameGroup] = React.useState<{
726✔
2038
        group: string;
726✔
2039
        bounds: Rectangle;
726✔
2040
    }>();
726✔
2041

726✔
2042
    const handleGroupHeaderSelection = React.useCallback(
726✔
2043
        (args: GridMouseEventArgs) => {
726✔
2044
            if (args.kind !== groupHeaderKind || columnSelect !== "multi") {
3!
UNCOV
2045
                return;
×
2046
            }
×
2047
            const isMultiKey = browserIsOSX.value ? args.metaKey : args.ctrlKey;
3!
2048
            const [col] = args.location;
3✔
2049
            const selectedColumns = gridSelection.columns;
3✔
2050

3✔
2051
            if (col < rowMarkerOffset) return;
3!
2052

3✔
2053
            const needle = mangledCols[col];
3✔
2054
            let start = col;
3✔
2055
            let end = col;
3✔
2056
            for (let i = col - 1; i >= rowMarkerOffset; i--) {
3✔
2057
                if (!isGroupEqual(needle.group, mangledCols[i].group)) break;
3!
2058
                start--;
3✔
2059
            }
3✔
2060

3✔
2061
            for (let i = col + 1; i < mangledCols.length; i++) {
3✔
2062
                if (!isGroupEqual(needle.group, mangledCols[i].group)) break;
27!
2063
                end++;
27✔
2064
            }
27✔
2065

3✔
2066
            focus();
3✔
2067

3✔
2068
            if (isMultiKey) {
3✔
2069
                if (selectedColumns.hasAll([start, end + 1])) {
2✔
2070
                    let newVal = selectedColumns;
1✔
2071
                    for (let index = start; index <= end; index++) {
1✔
2072
                        newVal = newVal.remove(index);
11✔
2073
                    }
11✔
2074
                    setSelectedColumns(newVal, undefined, isMultiKey);
1✔
2075
                } else {
1✔
2076
                    setSelectedColumns(undefined, [start, end + 1], isMultiKey);
1✔
2077
                }
1✔
2078
            } else {
3✔
2079
                setSelectedColumns(CompactSelection.fromSingleSelection([start, end + 1]), undefined, isMultiKey);
1✔
2080
            }
1✔
2081
        },
3✔
2082
        [columnSelect, focus, gridSelection.columns, mangledCols, rowMarkerOffset, setSelectedColumns]
726✔
2083
    );
726✔
2084

726✔
2085
    const isPrevented = React.useRef(false);
726✔
2086

726✔
2087
    const normalSizeColumn = React.useCallback(
726✔
2088
        async (col: number): Promise<void> => {
726✔
2089
            if (getCellsForSelection !== undefined && onColumnResize !== undefined) {
2✔
2090
                const start = visibleRegionRef.current.y;
2✔
2091
                const end = visibleRegionRef.current.height;
2✔
2092
                let cells = getCellsForSelection(
2✔
2093
                    {
2✔
2094
                        x: col,
2✔
2095
                        y: start,
2✔
2096
                        width: 1,
2✔
2097
                        height: Math.min(end, rows - start),
2✔
2098
                    },
2✔
2099
                    abortControllerRef.current.signal
2✔
2100
                );
2✔
2101
                if (typeof cells !== "object") {
2!
UNCOV
2102
                    cells = await cells();
×
2103
                }
×
2104
                const inputCol = columns[col - rowMarkerOffset];
2✔
2105
                const offscreen = document.createElement("canvas");
2✔
2106
                const ctx = offscreen.getContext("2d", { alpha: false });
2✔
2107
                if (ctx !== null) {
2✔
2108
                    ctx.font = mergedTheme.baseFontFull;
2✔
2109
                    const newCol = measureColumn(
2✔
2110
                        ctx,
2✔
2111
                        mergedTheme,
2✔
2112
                        inputCol,
2✔
2113
                        0,
2✔
2114
                        cells,
2✔
2115
                        minColumnWidth,
2✔
2116
                        maxColumnWidth,
2✔
2117
                        false,
2✔
2118
                        getCellRenderer
2✔
2119
                    );
2✔
2120
                    onColumnResize?.(inputCol, newCol.width, col, newCol.width);
2✔
2121
                }
2✔
2122
            }
2✔
2123
        },
2✔
2124
        [
726✔
2125
            columns,
726✔
2126
            getCellsForSelection,
726✔
2127
            maxColumnWidth,
726✔
2128
            mergedTheme,
726✔
2129
            minColumnWidth,
726✔
2130
            onColumnResize,
726✔
2131
            rowMarkerOffset,
726✔
2132
            rows,
726✔
2133
            getCellRenderer,
726✔
2134
        ]
726✔
2135
    );
726✔
2136

726✔
2137
    const [scrollDir, setScrollDir] = React.useState<GridMouseEventArgs["scrollEdge"]>();
726✔
2138

726✔
2139
    const fillPattern = React.useCallback(
726✔
2140
        async (previousSelection: GridSelection, currentSelection: GridSelection) => {
726✔
2141
            const patternRange = previousSelection.current?.range;
7✔
2142

7✔
2143
            if (
7✔
2144
                patternRange === undefined ||
7✔
2145
                getCellsForSelection === undefined ||
7✔
2146
                currentSelection.current === undefined
7✔
2147
            ) {
7!
UNCOV
2148
                return;
×
2149
            }
×
2150
            const currentRange = currentSelection.current.range;
7✔
2151

7✔
2152
            if (onFillPattern !== undefined) {
7✔
2153
                let canceled = false;
1✔
2154
                onFillPattern({
1✔
2155
                    fillDestination: { ...currentRange, x: currentRange.x - rowMarkerOffset },
1✔
2156
                    patternSource: { ...patternRange, x: patternRange.x - rowMarkerOffset },
1✔
2157
                    preventDefault: () => (canceled = true),
1✔
2158
                });
1✔
2159
                if (canceled) return;
1!
2160
            }
1✔
2161

7✔
2162
            let cells = getCellsForSelection(patternRange, abortControllerRef.current.signal);
7✔
2163
            if (typeof cells !== "object") cells = await cells();
7!
2164

7✔
2165
            const pattern = cells;
7✔
2166

7✔
2167
            // loop through all cells in currentSelection.current.range
7✔
2168
            const editItemList: EditListItem[] = [];
7✔
2169
            for (let x = 0; x < currentRange.width; x++) {
7✔
2170
                for (let y = 0; y < currentRange.height; y++) {
9✔
2171
                    const cell: Item = [currentRange.x + x, currentRange.y + y];
41✔
2172
                    if (itemIsInRect(cell, patternRange)) continue;
41✔
2173
                    const patternCell = pattern[y % patternRange.height][x % patternRange.width];
29✔
2174
                    if (isInnerOnlyCell(patternCell) || !isReadWriteCell(patternCell)) continue;
41!
2175
                    editItemList.push({
29✔
2176
                        location: cell,
29✔
2177
                        value: { ...patternCell },
29✔
2178
                    });
29✔
2179
                }
29✔
2180
            }
9✔
2181
            mangledOnCellsEdited(editItemList);
7✔
2182

7✔
2183
            gridRef.current?.damage(
7✔
2184
                editItemList.map(c => ({
7✔
2185
                    cell: c.location,
29✔
2186
                }))
7✔
2187
            );
7✔
2188
        },
7✔
2189
        [getCellsForSelection, mangledOnCellsEdited, onFillPattern, rowMarkerOffset]
726✔
2190
    );
726✔
2191

726✔
2192
    const fillRight = React.useCallback(() => {
726✔
2193
        if (gridSelection.current === undefined || gridSelection.current.range.width <= 1) return;
1!
2194

1✔
2195
        const firstColSelection = {
1✔
2196
            ...gridSelection,
1✔
2197
            current: {
1✔
2198
                ...gridSelection.current,
1✔
2199
                range: {
1✔
2200
                    ...gridSelection.current.range,
1✔
2201
                    width: 1,
1✔
2202
                },
1✔
2203
            },
1✔
2204
        };
1✔
2205

1✔
2206
        void fillPattern(firstColSelection, gridSelection);
1✔
2207
    }, [fillPattern, gridSelection]);
726✔
2208

726✔
2209
    const fillDown = React.useCallback(() => {
726✔
2210
        if (gridSelection.current === undefined || gridSelection.current.range.height <= 1) return;
1!
2211

1✔
2212
        const firstRowSelection = {
1✔
2213
            ...gridSelection,
1✔
2214
            current: {
1✔
2215
                ...gridSelection.current,
1✔
2216
                range: {
1✔
2217
                    ...gridSelection.current.range,
1✔
2218
                    height: 1,
1✔
2219
                },
1✔
2220
            },
1✔
2221
        };
1✔
2222

1✔
2223
        void fillPattern(firstRowSelection, gridSelection);
1✔
2224
    }, [fillPattern, gridSelection]);
726✔
2225

726✔
2226
    const onMouseUp = React.useCallback(
726✔
2227
        (args: GridMouseEventArgs, isOutside: boolean) => {
726✔
2228
            const mouse = mouseState;
143✔
2229
            setMouseState(undefined);
143✔
2230
            setFillHighlightRegion(undefined);
143✔
2231
            setScrollDir(undefined);
143✔
2232
            isActivelyDraggingHeader.current = false;
143✔
2233

143✔
2234
            if (isOutside) return;
143✔
2235

142✔
2236
            if (
142✔
2237
                mouse?.fillHandle === true &&
143✔
2238
                gridSelection.current !== undefined &&
5✔
2239
                mouse.previousSelection?.current !== undefined
5✔
2240
            ) {
143✔
2241
                if (fillHighlightRegion === undefined) return;
5!
2242
                const newRange = {
5✔
2243
                    ...gridSelection,
5✔
2244
                    current: {
5✔
2245
                        ...gridSelection.current,
5✔
2246
                        range: combineRects(mouse.previousSelection.current.range, fillHighlightRegion),
5✔
2247
                    },
5✔
2248
                };
5✔
2249
                void fillPattern(mouse.previousSelection, newRange);
5✔
2250
                setGridSelection(newRange, true);
5✔
2251
                return;
5✔
2252
            }
5✔
2253

137✔
2254
            const [col, row] = args.location;
137✔
2255
            const [lastMouseDownCol, lastMouseDownRow] = lastMouseSelectLocation.current ?? [];
143✔
2256

143✔
2257
            const preventDefault = () => {
143✔
UNCOV
2258
                isPrevented.current = true;
×
2259
            };
×
2260

143✔
2261
            const handleMaybeClick = (a: GridMouseCellEventArgs): boolean => {
143✔
2262
                const isValidClick = a.isTouch || (lastMouseDownCol === col && lastMouseDownRow === row);
112✔
2263
                if (isValidClick) {
112✔
2264
                    onCellClicked?.([col - rowMarkerOffset, row], {
102✔
2265
                        ...a,
4✔
2266
                        preventDefault,
4✔
2267
                    });
4✔
2268
                }
102✔
2269
                if (a.button === 1) return !isPrevented.current;
112✔
2270
                if (!isPrevented.current) {
109✔
2271
                    const c = getMangledCellContent(args.location);
109✔
2272
                    const r = getCellRenderer(c);
109✔
2273
                    if (r !== undefined && r.onClick !== undefined && isValidClick) {
109✔
2274
                        const newVal = r.onClick({
21✔
2275
                            ...a,
21✔
2276
                            cell: c,
21✔
2277
                            posX: a.localEventX,
21✔
2278
                            posY: a.localEventY,
21✔
2279
                            bounds: a.bounds,
21✔
2280
                            theme: themeForCell(c, args.location),
21✔
2281
                            preventDefault,
21✔
2282
                        });
21✔
2283
                        if (newVal !== undefined && !isInnerOnlyCell(newVal) && isEditableGridCell(newVal)) {
21✔
2284
                            mangledOnCellsEdited([{ location: a.location, value: newVal }]);
4✔
2285
                            gridRef.current?.damage([
4✔
2286
                                {
4✔
2287
                                    cell: a.location,
4✔
2288
                                },
4✔
2289
                            ]);
4✔
2290
                        }
4✔
2291
                    }
21✔
2292
                    if (isPrevented.current || gridSelection.current === undefined) return false;
109✔
2293

94✔
2294
                    let shouldActivate = false;
94✔
2295
                    switch (c.activationBehaviorOverride ?? cellActivationBehavior) {
109✔
2296
                        case "double-click":
109✔
2297
                        case "second-click": {
109✔
2298
                            if (mouse?.previousSelection?.current?.cell === undefined) break;
93✔
2299
                            const [selectedCol, selectedRow] = gridSelection.current.cell;
21✔
2300
                            const [prevCol, prevRow] = mouse.previousSelection.current.cell;
21✔
2301
                            const isClickOnSelected =
21✔
2302
                                col === selectedCol && col === prevCol && row === selectedRow && row === prevRow;
93✔
2303
                            shouldActivate =
93✔
2304
                                isClickOnSelected &&
93✔
2305
                                (a.isDoubleClick === true || cellActivationBehavior === "second-click");
6✔
2306
                            break;
93✔
2307
                        }
93✔
2308
                        case "single-click": {
109✔
2309
                            shouldActivate = true;
1✔
2310
                            break;
1✔
2311
                        }
1✔
2312
                    }
109✔
2313
                    if (shouldActivate) {
109✔
2314
                        onCellActivated?.([col - rowMarkerOffset, row]);
6✔
2315
                        reselect(a.bounds, false);
6✔
2316
                        return true;
6✔
2317
                    }
6✔
2318
                }
109✔
2319
                return false;
88✔
2320
            };
112✔
2321

143✔
2322
            const clickLocation = args.location[0] - rowMarkerOffset;
143✔
2323
            if (args.isTouch) {
143✔
2324
                const vr = visibleRegionRef.current;
4✔
2325
                const touchVr = touchDownArgs.current;
4✔
2326
                if (vr.x !== touchVr.x || vr.y !== touchVr.y) {
4!
UNCOV
2327
                    // we scrolled, abort
×
2328
                    return;
×
2329
                }
×
2330
                // take care of context menus first if long pressed item is already selected
4✔
2331
                if (args.isLongTouch === true) {
4!
UNCOV
2332
                    if (args.kind === "cell" && itemsAreEqual(gridSelection.current?.cell, args.location)) {
×
2333
                        onCellContextMenu?.([clickLocation, args.location[1]], {
×
2334
                            ...args,
×
2335
                            preventDefault,
×
2336
                        });
×
2337
                        return;
×
2338
                    } else if (args.kind === "header" && gridSelection.columns.hasIndex(col)) {
×
2339
                        onHeaderContextMenu?.(clickLocation, { ...args, preventDefault });
×
2340
                        return;
×
2341
                    } else if (args.kind === groupHeaderKind) {
×
2342
                        if (clickLocation < 0) {
×
2343
                            return;
×
2344
                        }
×
2345

×
2346
                        onGroupHeaderContextMenu?.(clickLocation, { ...args, preventDefault });
×
2347
                        return;
×
2348
                    }
×
2349
                }
×
2350
                if (args.kind === "cell") {
4✔
2351
                    // click that cell
2✔
2352
                    if (!handleMaybeClick(args)) {
2✔
2353
                        handleSelect(args);
2✔
2354
                    }
2✔
2355
                } else if (args.kind === groupHeaderKind) {
2✔
2356
                    onGroupHeaderClicked?.(clickLocation, { ...args, preventDefault });
1✔
2357
                } else {
1✔
2358
                    if (args.kind === headerKind) {
1✔
2359
                        onHeaderClicked?.(clickLocation, {
1✔
2360
                            ...args,
1✔
2361
                            preventDefault,
1✔
2362
                        });
1✔
2363
                    }
1✔
2364
                    handleSelect(args);
1✔
2365
                }
1✔
2366
                return;
4✔
2367
            }
4✔
2368

133✔
2369
            if (args.kind === "header") {
143✔
2370
                if (clickLocation < 0) {
16✔
2371
                    return;
3✔
2372
                }
3✔
2373

13✔
2374
                if (args.isEdge) {
16✔
2375
                    if (args.isDoubleClick === true) {
2✔
2376
                        void normalSizeColumn(col);
1✔
2377
                    }
1✔
2378
                } else if (args.button === 0 && col === lastMouseDownCol && row === lastMouseDownRow) {
16✔
2379
                    onHeaderClicked?.(clickLocation, { ...args, preventDefault });
5✔
2380
                }
5✔
2381
            }
16✔
2382

130✔
2383
            if (args.kind === groupHeaderKind) {
143✔
2384
                if (clickLocation < 0) {
3!
UNCOV
2385
                    return;
×
2386
                }
×
2387

3✔
2388
                if (args.button === 0 && col === lastMouseDownCol && row === lastMouseDownRow) {
3✔
2389
                    onGroupHeaderClicked?.(clickLocation, { ...args, preventDefault });
3!
2390
                    if (!isPrevented.current) {
3✔
2391
                        handleGroupHeaderSelection(args);
3✔
2392
                    }
3✔
2393
                }
3✔
2394
            }
3✔
2395

130✔
2396
            if (args.kind === "cell" && (args.button === 0 || args.button === 1)) {
143✔
2397
                handleMaybeClick(args);
110✔
2398
            }
110✔
2399

130✔
2400
            lastMouseSelectLocation.current = undefined;
130✔
2401
        },
143✔
2402
        [
726✔
2403
            mouseState,
726✔
2404
            gridSelection,
726✔
2405
            rowMarkerOffset,
726✔
2406
            fillHighlightRegion,
726✔
2407
            fillPattern,
726✔
2408
            setGridSelection,
726✔
2409
            onCellClicked,
726✔
2410
            getMangledCellContent,
726✔
2411
            getCellRenderer,
726✔
2412
            cellActivationBehavior,
726✔
2413
            themeForCell,
726✔
2414
            mangledOnCellsEdited,
726✔
2415
            onCellActivated,
726✔
2416
            reselect,
726✔
2417
            onCellContextMenu,
726✔
2418
            onHeaderContextMenu,
726✔
2419
            onGroupHeaderContextMenu,
726✔
2420
            handleSelect,
726✔
2421
            onGroupHeaderClicked,
726✔
2422
            onHeaderClicked,
726✔
2423
            normalSizeColumn,
726✔
2424
            handleGroupHeaderSelection,
726✔
2425
        ]
726✔
2426
    );
726✔
2427

726✔
2428
    const onMouseMoveImpl = React.useCallback(
726✔
2429
        (args: GridMouseEventArgs) => {
726✔
2430
            const a: GridMouseEventArgs = {
39✔
2431
                ...args,
39✔
2432
                location: [args.location[0] - rowMarkerOffset, args.location[1]] as any,
39✔
2433
            };
39✔
2434
            onMouseMove?.(a);
39✔
2435

39✔
2436
            if (mouseState !== undefined && args.buttons === 0) {
39✔
2437
                setMouseState(undefined);
6✔
2438
                setFillHighlightRegion(undefined);
6✔
2439
                setScrollDir(undefined);
6✔
2440
                isActivelyDraggingHeader.current = false;
6✔
2441
            }
6✔
2442

39✔
2443
            setScrollDir(cv => {
39✔
2444
                if (isActivelyDraggingHeader.current) return [args.scrollEdge[0], 0];
39✔
2445
                if (args.scrollEdge[0] === cv?.[0] && args.scrollEdge[1] === cv[1]) return cv;
39✔
2446
                return mouseState === undefined || (mouseDownData.current?.location[0] ?? 0) < rowMarkerOffset
39!
2447
                    ? undefined
13✔
2448
                    : args.scrollEdge;
16✔
2449
            });
39✔
2450
        },
39✔
2451
        [mouseState, onMouseMove, rowMarkerOffset]
726✔
2452
    );
726✔
2453

726✔
2454
    const onHeaderMenuClickInner = React.useCallback(
726✔
2455
        (col: number, screenPosition: Rectangle) => {
726✔
2456
            onHeaderMenuClick?.(col - rowMarkerOffset, screenPosition);
1✔
2457
        },
1✔
2458
        [onHeaderMenuClick, rowMarkerOffset]
726✔
2459
    );
726✔
2460

726✔
2461
    const onHeaderIndicatorClickInner = React.useCallback(
726✔
2462
        (col: number, screenPosition: Rectangle) => {
726✔
UNCOV
2463
            onHeaderIndicatorClick?.(col - rowMarkerOffset, screenPosition);
×
2464
        },
×
2465
        [onHeaderIndicatorClick, rowMarkerOffset]
726✔
2466
    );
726✔
2467

726✔
2468
    const currentCell = gridSelection?.current?.cell;
726✔
2469
    const onVisibleRegionChangedImpl = React.useCallback(
726✔
2470
        (
726✔
2471
            region: Rectangle,
151✔
2472
            clientWidth: number,
151✔
2473
            clientHeight: number,
151✔
2474
            rightElWidth: number,
151✔
2475
            tx: number,
151✔
2476
            ty: number
151✔
2477
        ) => {
151✔
2478
            hasJustScrolled.current = false;
151✔
2479
            let selected = currentCell;
151✔
2480
            if (selected !== undefined) {
151✔
2481
                selected = [selected[0] - rowMarkerOffset, selected[1]];
10✔
2482
            }
10✔
2483

151✔
2484
            const freezeRegion =
151✔
2485
                freezeColumns === 0
151✔
2486
                    ? undefined
150✔
2487
                    : {
1✔
2488
                          x: 0,
1✔
2489
                          y: region.y,
1✔
2490
                          width: freezeColumns,
1✔
2491
                          height: region.height,
1✔
2492
                      };
1✔
2493

151✔
2494
            const freezeRegions: Rectangle[] = [];
151✔
2495
            if (freezeRegion !== undefined) freezeRegions.push(freezeRegion);
151✔
2496
            if (freezeTrailingRows > 0) {
151✔
2497
                freezeRegions.push({
1✔
2498
                    x: region.x - rowMarkerOffset,
1✔
2499
                    y: rows - freezeTrailingRows,
1✔
2500
                    width: region.width,
1✔
2501
                    height: freezeTrailingRows,
1✔
2502
                });
1✔
2503

1✔
2504
                if (freezeColumns > 0) {
1✔
2505
                    freezeRegions.push({
1✔
2506
                        x: 0,
1✔
2507
                        y: rows - freezeTrailingRows,
1✔
2508
                        width: freezeColumns,
1✔
2509
                        height: freezeTrailingRows,
1✔
2510
                    });
1✔
2511
                }
1✔
2512
            }
1✔
2513

151✔
2514
            const newRegion = {
151✔
2515
                x: region.x - rowMarkerOffset,
151✔
2516
                y: region.y,
151✔
2517
                width: region.width,
151✔
2518
                height: showTrailingBlankRow && region.y + region.height >= rows ? region.height - 1 : region.height,
151✔
2519
                tx,
151✔
2520
                ty,
151✔
2521
                extras: {
151✔
2522
                    selected,
151✔
2523
                    freezeRegion,
151✔
2524
                    freezeRegions,
151✔
2525
                },
151✔
2526
            };
151✔
2527
            visibleRegionRef.current = newRegion;
151✔
2528
            setVisibleRegion(newRegion);
151✔
2529
            setClientSize([clientWidth, clientHeight, rightElWidth]);
151✔
2530
            onVisibleRegionChanged?.(newRegion, newRegion.tx, newRegion.ty, newRegion.extras);
151✔
2531
        },
151✔
2532
        [
726✔
2533
            currentCell,
726✔
2534
            rowMarkerOffset,
726✔
2535
            showTrailingBlankRow,
726✔
2536
            rows,
726✔
2537
            freezeColumns,
726✔
2538
            freezeTrailingRows,
726✔
2539
            setVisibleRegion,
726✔
2540
            onVisibleRegionChanged,
726✔
2541
        ]
726✔
2542
    );
726✔
2543

726✔
2544
    const onColumnProposeMoveImpl = whenDefined(
726✔
2545
        onColumnProposeMove,
726✔
2546
        React.useCallback(
726✔
2547
            (startIndex: number, endIndex: number) => {
726✔
UNCOV
2548
                return onColumnProposeMove?.(startIndex - rowMarkerOffset, endIndex - rowMarkerOffset) !== false;
×
2549
            },
×
2550
            [onColumnProposeMove, rowMarkerOffset]
726✔
2551
        )
726✔
2552
    );
726✔
2553

726✔
2554
    const onColumnMovedImpl = whenDefined(
726✔
2555
        onColumnMoved,
726✔
2556
        React.useCallback(
726✔
2557
            (startIndex: number, endIndex: number) => {
726✔
2558
                onColumnMoved?.(startIndex - rowMarkerOffset, endIndex - rowMarkerOffset);
1✔
2559
                if (columnSelect !== "none") {
1✔
2560
                    setSelectedColumns(CompactSelection.fromSingleSelection(endIndex), undefined, true);
1✔
2561
                }
1✔
2562
            },
1✔
2563
            [columnSelect, onColumnMoved, rowMarkerOffset, setSelectedColumns]
726✔
2564
        )
726✔
2565
    );
726✔
2566

726✔
2567
    const isActivelyDragging = React.useRef(false);
726✔
2568
    const onDragStartImpl = React.useCallback(
726✔
2569
        (args: GridDragEventArgs) => {
726✔
2570
            if (args.location[0] === 0 && rowMarkerOffset > 0) {
1!
UNCOV
2571
                args.preventDefault();
×
2572
                return;
×
2573
            }
×
2574
            onDragStart?.({
1✔
2575
                ...args,
1✔
2576
                location: [args.location[0] - rowMarkerOffset, args.location[1]] as any,
1✔
2577
            });
1✔
2578

1✔
2579
            if (!args.defaultPrevented()) {
1✔
2580
                isActivelyDragging.current = true;
1✔
2581
            }
1✔
2582
            setMouseState(undefined);
1✔
2583
        },
1✔
2584
        [onDragStart, rowMarkerOffset]
726✔
2585
    );
726✔
2586

726✔
2587
    const onDragEnd = React.useCallback(() => {
726✔
UNCOV
2588
        isActivelyDragging.current = false;
×
2589
    }, []);
726✔
2590

726✔
2591
    const rowGroupingSelectionBehavior = rowGrouping?.selectionBehavior;
726!
2592

726✔
2593
    const getSelectionRowLimits = React.useCallback(
726✔
2594
        (selectedRow: number): readonly [number, number] | undefined => {
726✔
2595
            if (rowGroupingSelectionBehavior !== "block-spanning") return undefined;
16!
UNCOV
2596

×
2597
            const { isGroupHeader, path, groupRows } = mapper(selectedRow);
×
2598

×
2599
            if (isGroupHeader) {
×
2600
                return [selectedRow, selectedRow];
×
2601
            }
×
2602

×
2603
            const groupRowIndex = path[path.length - 1];
×
2604
            const lowerBounds = selectedRow - groupRowIndex;
×
2605
            const upperBounds = selectedRow + groupRows - groupRowIndex - 1;
×
2606

×
2607
            return [lowerBounds, upperBounds];
×
2608
        },
16✔
2609
        [mapper, rowGroupingSelectionBehavior]
726✔
2610
    );
726✔
2611

726✔
2612
    const hoveredRef = React.useRef<GridMouseEventArgs>();
726✔
2613
    const onItemHoveredImpl = React.useCallback(
726✔
2614
        (args: GridMouseEventArgs) => {
726✔
2615
            // make sure we still have a button down
29✔
2616
            if (mouseEventArgsAreEqual(args, hoveredRef.current)) return;
29!
2617
            hoveredRef.current = args;
29✔
2618
            if (mouseDownData?.current?.button !== undefined && mouseDownData.current.button >= 1) return;
29✔
2619
            if (
28✔
2620
                args.buttons !== 0 &&
28✔
2621
                mouseState !== undefined &&
14✔
2622
                mouseDownData.current?.location[0] === 0 &&
14✔
2623
                args.location[0] === 0 &&
1✔
2624
                rowMarkerOffset === 1 &&
1✔
2625
                rowSelect === "multi" &&
1✔
2626
                mouseState.previousSelection &&
1✔
2627
                !mouseState.previousSelection.rows.hasIndex(mouseDownData.current.location[1]) &&
1✔
2628
                gridSelection.rows.hasIndex(mouseDownData.current.location[1])
1✔
2629
            ) {
29✔
2630
                const start = Math.min(mouseDownData.current.location[1], args.location[1]);
1✔
2631
                const end = Math.max(mouseDownData.current.location[1], args.location[1]) + 1;
1✔
2632
                setSelectedRows(CompactSelection.fromSingleSelection([start, end]), undefined, false);
1✔
2633
            }
1✔
2634
            if (
28✔
2635
                args.buttons !== 0 &&
28✔
2636
                mouseState !== undefined &&
14✔
2637
                gridSelection.current !== undefined &&
14✔
2638
                !isActivelyDragging.current &&
12✔
2639
                !isActivelyDraggingHeader.current &&
12✔
2640
                (rangeSelect === "rect" || rangeSelect === "multi-rect")
12!
2641
            ) {
29✔
2642
                const [selectedCol, selectedRow] = gridSelection.current.cell;
12✔
2643
                // eslint-disable-next-line prefer-const
12✔
2644
                let [col, row] = args.location;
12✔
2645

12✔
2646
                if (row < 0) {
12✔
2647
                    row = visibleRegionRef.current.y;
1✔
2648
                }
1✔
2649

12✔
2650
                if (mouseState.fillHandle === true && mouseState.previousSelection?.current !== undefined) {
12✔
2651
                    const prevRange = mouseState.previousSelection.current.range;
6✔
2652
                    row = Math.min(row, showTrailingBlankRow ? rows - 1 : rows);
6!
2653
                    const rect = getClosestRect(prevRange, col, row, allowedFillDirections);
6✔
2654
                    setFillHighlightRegion(rect);
6✔
2655
                } else {
6✔
2656
                    const startedFromLastStickyRow = showTrailingBlankRow && selectedRow === rows;
6✔
2657
                    if (startedFromLastStickyRow) return;
6!
2658

6✔
2659
                    const landedOnLastStickyRow = showTrailingBlankRow && row === rows;
6✔
2660
                    if (landedOnLastStickyRow) {
6!
UNCOV
2661
                        if (args.kind === outOfBoundsKind) row--;
×
2662
                        else return;
×
2663
                    }
×
2664

6✔
2665
                    col = Math.max(col, rowMarkerOffset);
6✔
2666
                    const clampLimits = getSelectionRowLimits(selectedRow);
6✔
2667
                    row = clampLimits === undefined ? row : clamp(row, clampLimits[0], clampLimits[1]);
6!
2668

6✔
2669
                    // FIXME: Restrict row based on rowGrouping.selectionBehavior here
6✔
2670

6✔
2671
                    const deltaX = col - selectedCol;
6✔
2672
                    const deltaY = row - selectedRow;
6✔
2673

6✔
2674
                    const newRange: Rectangle = {
6✔
2675
                        x: deltaX >= 0 ? selectedCol : col,
6!
2676
                        y: deltaY >= 0 ? selectedRow : row,
6✔
2677
                        width: Math.abs(deltaX) + 1,
6✔
2678
                        height: Math.abs(deltaY) + 1,
6✔
2679
                    };
6✔
2680

6✔
2681
                    setCurrent(
6✔
2682
                        {
6✔
2683
                            ...gridSelection.current,
6✔
2684
                            range: newRange,
6✔
2685
                        },
6✔
2686
                        true,
6✔
2687
                        false,
6✔
2688
                        "drag"
6✔
2689
                    );
6✔
2690
                }
6✔
2691
            }
12✔
2692

28✔
2693
            onItemHovered?.({ ...args, location: [args.location[0] - rowMarkerOffset, args.location[1]] as any });
29✔
2694
        },
29✔
2695
        [
726✔
2696
            mouseState,
726✔
2697
            rowMarkerOffset,
726✔
2698
            rowSelect,
726✔
2699
            gridSelection,
726✔
2700
            rangeSelect,
726✔
2701
            onItemHovered,
726✔
2702
            setSelectedRows,
726✔
2703
            showTrailingBlankRow,
726✔
2704
            rows,
726✔
2705
            allowedFillDirections,
726✔
2706
            getSelectionRowLimits,
726✔
2707
            setCurrent,
726✔
2708
        ]
726✔
2709
    );
726✔
2710

726✔
2711
    const adjustSelectionOnScroll = React.useCallback(() => {
726✔
UNCOV
2712
        const args = hoveredRef.current;
×
2713
        if (args === undefined) return;
×
2714
        const [xDir, yDir] = args.scrollEdge;
×
2715
        let [col, row] = args.location;
×
2716
        const visible = visibleRegionRef.current;
×
2717
        if (xDir === -1) {
×
2718
            col = visible.extras?.freezeRegion?.x ?? visible.x;
×
2719
        } else if (xDir === 1) {
×
2720
            col = visible.x + visible.width;
×
2721
        }
×
2722
        if (yDir === -1) {
×
2723
            row = Math.max(0, visible.y);
×
2724
        } else if (yDir === 1) {
×
2725
            row = Math.min(rows - 1, visible.y + visible.height);
×
2726
        }
×
2727
        col = clamp(col, 0, mangledCols.length - 1);
×
2728
        row = clamp(row, 0, rows - 1);
×
2729
        onItemHoveredImpl({
×
2730
            ...args,
×
2731
            location: [col, row] as any,
×
2732
        });
×
2733
    }, [mangledCols.length, onItemHoveredImpl, rows]);
726✔
2734

726✔
2735
    useAutoscroll(scrollDir, scrollRef, adjustSelectionOnScroll);
726✔
2736

726✔
2737
    // 1 === move one
726✔
2738
    // 2 === move to end
726✔
2739
    const adjustSelection = React.useCallback(
726✔
2740
        (direction: [0 | 1 | -1 | 2 | -2, 0 | 1 | -1 | 2 | -2]) => {
726✔
2741
            if (gridSelection.current === undefined) return;
10!
2742

10✔
2743
            const [x, y] = direction;
10✔
2744
            const [col, row] = gridSelection.current.cell;
10✔
2745
            const old = gridSelection.current.range;
10✔
2746
            let left = old.x;
10✔
2747
            let right = old.x + old.width;
10✔
2748
            let top = old.y;
10✔
2749
            let bottom = old.y + old.height;
10✔
2750

10✔
2751
            const [minRow, maxRowRaw] = getSelectionRowLimits(row) ?? [0, rows - 1];
10✔
2752
            const maxRow = maxRowRaw + 1; // we need an inclusive value
10✔
2753

10✔
2754
            // take care of vertical first in case new spans come in
10✔
2755
            if (y !== 0) {
10✔
2756
                switch (y) {
4✔
2757
                    case 2: {
4✔
2758
                        // go to end
2✔
2759
                        bottom = maxRow;
2✔
2760
                        top = row;
2✔
2761
                        scrollTo(0, bottom, "vertical");
2✔
2762

2✔
2763
                        break;
2✔
2764
                    }
2✔
2765
                    case -2: {
4✔
2766
                        // go to start
1✔
2767
                        top = minRow;
1✔
2768
                        bottom = row + 1;
1✔
2769
                        scrollTo(0, top, "vertical");
1✔
2770

1✔
2771
                        break;
1✔
2772
                    }
1✔
2773
                    case 1: {
4✔
2774
                        // motion down
1✔
2775
                        if (top < row) {
1!
UNCOV
2776
                            top++;
×
2777
                            scrollTo(0, top, "vertical");
×
2778
                        } else {
1✔
2779
                            bottom = Math.min(maxRow, bottom + 1);
1✔
2780
                            scrollTo(0, bottom, "vertical");
1✔
2781
                        }
1✔
2782

1✔
2783
                        break;
1✔
2784
                    }
1✔
2785
                    case -1: {
4!
UNCOV
2786
                        // motion up
×
2787
                        if (bottom > row + 1) {
×
2788
                            bottom--;
×
2789
                            scrollTo(0, bottom, "vertical");
×
2790
                        } else {
×
2791
                            top = Math.max(minRow, top - 1);
×
2792
                            scrollTo(0, top, "vertical");
×
2793
                        }
×
2794

×
2795
                        break;
×
2796
                    }
×
2797
                    default: {
4!
UNCOV
2798
                        assertNever(y);
×
2799
                    }
×
2800
                }
4✔
2801
            }
4✔
2802

10✔
2803
            if (x !== 0) {
10✔
2804
                if (x === 2) {
8✔
2805
                    right = mangledCols.length;
2✔
2806
                    left = col;
2✔
2807
                    scrollTo(right - 1 - rowMarkerOffset, 0, "horizontal");
2✔
2808
                } else if (x === -2) {
8✔
2809
                    left = rowMarkerOffset;
1✔
2810
                    right = col + 1;
1✔
2811
                    scrollTo(left - rowMarkerOffset, 0, "horizontal");
1✔
2812
                } else {
6✔
2813
                    let disallowed: number[] = [];
5✔
2814
                    if (getCellsForSelection !== undefined) {
5✔
2815
                        const cells = getCellsForSelection(
5✔
2816
                            {
5✔
2817
                                x: left,
5✔
2818
                                y: top,
5✔
2819
                                width: right - left - rowMarkerOffset,
5✔
2820
                                height: bottom - top,
5✔
2821
                            },
5✔
2822
                            abortControllerRef.current.signal
5✔
2823
                        );
5✔
2824

5✔
2825
                        if (typeof cells === "object") {
5✔
2826
                            disallowed = getSpanStops(cells);
5✔
2827
                        }
5✔
2828
                    }
5✔
2829
                    if (x === 1) {
5✔
2830
                        // motion right
4✔
2831
                        let done = false;
4✔
2832
                        if (left < col) {
4!
UNCOV
2833
                            if (disallowed.length > 0) {
×
2834
                                const target = range(left + 1, col + 1).find(
×
2835
                                    n => !disallowed.includes(n - rowMarkerOffset)
×
2836
                                );
×
2837
                                if (target !== undefined) {
×
2838
                                    left = target;
×
2839
                                    done = true;
×
2840
                                }
×
2841
                            } else {
×
2842
                                left++;
×
2843
                                done = true;
×
2844
                            }
×
2845
                            if (done) scrollTo(left, 0, "horizontal");
×
2846
                        }
×
2847
                        if (!done) {
4✔
2848
                            right = Math.min(mangledCols.length, right + 1);
4✔
2849
                            scrollTo(right - 1 - rowMarkerOffset, 0, "horizontal");
4✔
2850
                        }
4✔
2851
                    } else if (x === -1) {
5✔
2852
                        // motion left
1✔
2853
                        let done = false;
1✔
2854
                        if (right > col + 1) {
1!
UNCOV
2855
                            if (disallowed.length > 0) {
×
2856
                                const target = range(right - 1, col, -1).find(
×
2857
                                    n => !disallowed.includes(n - rowMarkerOffset)
×
2858
                                );
×
2859
                                if (target !== undefined) {
×
2860
                                    right = target;
×
2861
                                    done = true;
×
2862
                                }
×
2863
                            } else {
×
2864
                                right--;
×
2865
                                done = true;
×
2866
                            }
×
2867
                            if (done) scrollTo(right - rowMarkerOffset, 0, "horizontal");
×
2868
                        }
×
2869
                        if (!done) {
1✔
2870
                            left = Math.max(rowMarkerOffset, left - 1);
1✔
2871
                            scrollTo(left - rowMarkerOffset, 0, "horizontal");
1✔
2872
                        }
1✔
2873
                    } else {
1!
UNCOV
2874
                        assertNever(x);
×
2875
                    }
×
2876
                }
5✔
2877
            }
8✔
2878

10✔
2879
            setCurrent(
10✔
2880
                {
10✔
2881
                    cell: gridSelection.current.cell,
10✔
2882
                    range: {
10✔
2883
                        x: left,
10✔
2884
                        y: top,
10✔
2885
                        width: right - left,
10✔
2886
                        height: bottom - top,
10✔
2887
                    },
10✔
2888
                },
10✔
2889
                true,
10✔
2890
                false,
10✔
2891
                "keyboard-select"
10✔
2892
            );
10✔
2893
        },
10✔
2894
        [
726✔
2895
            getCellsForSelection,
726✔
2896
            getSelectionRowLimits,
726✔
2897
            gridSelection,
726✔
2898
            mangledCols.length,
726✔
2899
            rowMarkerOffset,
726✔
2900
            rows,
726✔
2901
            scrollTo,
726✔
2902
            setCurrent,
726✔
2903
        ]
726✔
2904
    );
726✔
2905

726✔
2906
    const scrollToActiveCellRef = React.useRef(scrollToActiveCell);
726✔
2907
    scrollToActiveCellRef.current = scrollToActiveCell;
726✔
2908

726✔
2909
    const updateSelectedCell = React.useCallback(
726✔
2910
        (col: number, row: number, fromEditingTrailingRow: boolean, freeMove: boolean): boolean => {
726✔
2911
            const rowMax = mangledRows - (fromEditingTrailingRow ? 0 : 1);
67!
2912
            col = clamp(col, rowMarkerOffset, columns.length - 1 + rowMarkerOffset);
67✔
2913
            row = clamp(row, 0, rowMax);
67✔
2914

67✔
2915
            const curCol = currentCell?.[0];
67✔
2916
            const curRow = currentCell?.[1];
67✔
2917

67✔
2918
            if (col === curCol && row === curRow) return false;
67✔
2919
            if (freeMove && gridSelection.current !== undefined) {
67✔
2920
                const newStack = [...gridSelection.current.rangeStack];
1✔
2921
                if (gridSelection.current.range.width > 1 || gridSelection.current.range.height > 1) {
1!
2922
                    newStack.push(gridSelection.current.range);
1✔
2923
                }
1✔
2924
                setGridSelection(
1✔
2925
                    {
1✔
2926
                        ...gridSelection,
1✔
2927
                        current: {
1✔
2928
                            cell: [col, row],
1✔
2929
                            range: { x: col, y: row, width: 1, height: 1 },
1✔
2930
                            rangeStack: newStack,
1✔
2931
                        },
1✔
2932
                    },
1✔
2933
                    true
1✔
2934
                );
1✔
2935
            } else {
67✔
2936
                setCurrent(
29✔
2937
                    {
29✔
2938
                        cell: [col, row],
29✔
2939
                        range: { x: col, y: row, width: 1, height: 1 },
29✔
2940
                    },
29✔
2941
                    true,
29✔
2942
                    false,
29✔
2943
                    "keyboard-nav"
29✔
2944
                );
29✔
2945
            }
29✔
2946

30✔
2947
            if (lastSent.current !== undefined && lastSent.current[0] === col && lastSent.current[1] === row) {
67✔
2948
                lastSent.current = undefined;
2✔
2949
            }
2✔
2950

30✔
2951
            if (scrollToActiveCellRef.current) {
30✔
2952
                scrollTo(col - rowMarkerOffset, row);
30✔
2953
            }
30✔
2954

30✔
2955
            return true;
30✔
2956
        },
67✔
2957
        [
726✔
2958
            mangledRows,
726✔
2959
            rowMarkerOffset,
726✔
2960
            columns.length,
726✔
2961
            currentCell,
726✔
2962
            gridSelection,
726✔
2963
            scrollTo,
726✔
2964
            setGridSelection,
726✔
2965
            setCurrent,
726✔
2966
        ]
726✔
2967
    );
726✔
2968

726✔
2969
    const onFinishEditing = React.useCallback(
726✔
2970
        (newValue: GridCell | undefined, movement: readonly [-1 | 0 | 1, -1 | 0 | 1]) => {
726✔
2971
            if (overlay?.cell !== undefined && newValue !== undefined && isEditableGridCell(newValue)) {
12✔
2972
                mangledOnCellsEdited([{ location: overlay.cell, value: newValue }]);
4✔
2973
                window.requestAnimationFrame(() => {
4✔
2974
                    gridRef.current?.damage([
4✔
2975
                        {
4✔
2976
                            cell: overlay.cell,
4✔
2977
                        },
4✔
2978
                    ]);
4✔
2979
                });
4✔
2980
            }
4✔
2981
            focus(true);
12✔
2982
            setOverlay(undefined);
12✔
2983

12✔
2984
            const [movX, movY] = movement;
12✔
2985
            if (gridSelection.current !== undefined && (movX !== 0 || movY !== 0)) {
12✔
2986
                const isEditingTrailingRow =
3✔
2987
                    gridSelection.current.cell[1] === mangledRows - 1 && newValue !== undefined;
3!
2988
                updateSelectedCell(
3✔
2989
                    clamp(gridSelection.current.cell[0] + movX, 0, mangledCols.length - 1),
3✔
2990
                    clamp(gridSelection.current.cell[1] + movY, 0, mangledRows - 1),
3✔
2991
                    isEditingTrailingRow,
3✔
2992
                    false
3✔
2993
                );
3✔
2994
            }
3✔
2995
            onFinishedEditing?.(newValue, movement);
12✔
2996
        },
12✔
2997
        [
726✔
2998
            overlay?.cell,
726✔
2999
            focus,
726✔
3000
            gridSelection,
726✔
3001
            onFinishedEditing,
726✔
3002
            mangledOnCellsEdited,
726✔
3003
            mangledRows,
726✔
3004
            updateSelectedCell,
726✔
3005
            mangledCols.length,
726✔
3006
        ]
726✔
3007
    );
726✔
3008

726✔
3009
    const overlayID = React.useMemo(() => {
726✔
3010
        return `gdg-overlay-${idCounter++}`;
147✔
3011
    }, []);
726✔
3012

726✔
3013
    const deleteRange = React.useCallback(
726✔
3014
        (r: Rectangle) => {
726✔
3015
            focus();
8✔
3016
            const editList: EditListItem[] = [];
8✔
3017
            for (let x = r.x; x < r.x + r.width; x++) {
8✔
3018
                for (let y = r.y; y < r.y + r.height; y++) {
23✔
3019
                    const cellValue = getCellContent([x - rowMarkerOffset, y]);
1,066✔
3020
                    if (!cellValue.allowOverlay && cellValue.kind !== GridCellKind.Boolean) continue;
1,066✔
3021
                    let newVal: InnerGridCell | undefined = undefined;
1,042✔
3022
                    if (cellValue.kind === GridCellKind.Custom) {
1,066✔
3023
                        const toDelete = getCellRenderer(cellValue);
1✔
3024
                        const editor = toDelete?.provideEditor?.(cellValue);
1!
3025
                        if (toDelete?.onDelete !== undefined) {
1✔
3026
                            newVal = toDelete.onDelete(cellValue);
1✔
3027
                        } else if (isObjectEditorCallbackResult(editor)) {
1!
UNCOV
3028
                            newVal = editor?.deletedValue?.(cellValue);
×
3029
                        }
×
3030
                    } else if (
1✔
3031
                        (isEditableGridCell(cellValue) && cellValue.allowOverlay) ||
1,041✔
3032
                        cellValue.kind === GridCellKind.Boolean
1✔
3033
                    ) {
1,041✔
3034
                        const toDelete = getCellRenderer(cellValue);
1,041✔
3035
                        newVal = toDelete?.onDelete?.(cellValue);
1,041✔
3036
                    }
1,041✔
3037
                    if (newVal !== undefined && !isInnerOnlyCell(newVal) && isEditableGridCell(newVal)) {
1,066✔
3038
                        editList.push({ location: [x, y], value: newVal });
1,041✔
3039
                    }
1,041✔
3040
                }
1,066✔
3041
            }
23✔
3042
            mangledOnCellsEdited(editList);
8✔
3043
            gridRef.current?.damage(editList.map(x => ({ cell: x.location })));
8✔
3044
        },
8✔
3045
        [focus, getCellContent, getCellRenderer, mangledOnCellsEdited, rowMarkerOffset]
726✔
3046
    );
726✔
3047

726✔
3048
    const overlayOpen = overlay !== undefined;
726✔
3049

726✔
3050
    const handleFixedKeybindings = React.useCallback(
726✔
3051
        (event: GridKeyEventArgs): boolean => {
726✔
3052
            const cancel = () => {
73✔
3053
                event.stopPropagation();
50✔
3054
                event.preventDefault();
50✔
3055
            };
50✔
3056

73✔
3057
            const details = {
73✔
3058
                didMatch: false,
73✔
3059
            };
73✔
3060

73✔
3061
            const { bounds } = event;
73✔
3062
            const selectedColumns = gridSelection.columns;
73✔
3063
            const selectedRows = gridSelection.rows;
73✔
3064

73✔
3065
            const keys = keybindings;
73✔
3066

73✔
3067
            if (!overlayOpen && isHotkey(keys.clear, event, details)) {
73✔
3068
                setGridSelection(emptyGridSelection, false);
2✔
3069
                onSelectionCleared?.();
2!
3070
            } else if (!overlayOpen && isHotkey(keys.selectAll, event, details)) {
73✔
3071
                setGridSelection(
1✔
3072
                    {
1✔
3073
                        columns: CompactSelection.empty(),
1✔
3074
                        rows: CompactSelection.empty(),
1✔
3075
                        current: {
1✔
3076
                            cell: gridSelection.current?.cell ?? [rowMarkerOffset, 0],
1!
3077
                            range: {
1✔
3078
                                x: rowMarkerOffset,
1✔
3079
                                y: 0,
1✔
3080
                                width: columnsIn.length,
1✔
3081
                                height: rows,
1✔
3082
                            },
1✔
3083
                            rangeStack: [],
1✔
3084
                        },
1✔
3085
                    },
1✔
3086
                    false
1✔
3087
                );
1✔
3088
            } else if (isHotkey(keys.search, event, details)) {
71!
UNCOV
3089
                searchInputRef?.current?.focus({ preventScroll: true });
×
3090
                setShowSearchInner(true);
×
3091
            } else if (isHotkey(keys.delete, event, details)) {
70✔
3092
                const callbackResult = onDelete?.(gridSelection) ?? true;
8✔
3093
                if (callbackResult !== false) {
8✔
3094
                    const toDelete = callbackResult === true ? gridSelection : callbackResult;
8✔
3095

8✔
3096
                    // delete order:
8✔
3097
                    // 1) primary range
8✔
3098
                    // 2) secondary ranges
8✔
3099
                    // 3) columns
8✔
3100
                    // 4) rows
8✔
3101

8✔
3102
                    if (toDelete.current !== undefined) {
8✔
3103
                        deleteRange(toDelete.current.range);
5✔
3104
                        for (const r of toDelete.current.rangeStack) {
5!
UNCOV
3105
                            deleteRange(r);
×
3106
                        }
×
3107
                    }
5✔
3108

8✔
3109
                    for (const r of toDelete.rows) {
8✔
3110
                        deleteRange({
1✔
3111
                            x: rowMarkerOffset,
1✔
3112
                            y: r,
1✔
3113
                            width: columnsIn.length,
1✔
3114
                            height: 1,
1✔
3115
                        });
1✔
3116
                    }
1✔
3117

8✔
3118
                    for (const col of toDelete.columns) {
8✔
3119
                        deleteRange({
1✔
3120
                            x: col,
1✔
3121
                            y: 0,
1✔
3122
                            width: 1,
1✔
3123
                            height: rows,
1✔
3124
                        });
1✔
3125
                    }
1✔
3126
                }
8✔
3127
            }
8✔
3128

73✔
3129
            if (details.didMatch) {
73✔
3130
                cancel();
11✔
3131
                return true;
11✔
3132
            }
11✔
3133

62✔
3134
            if (gridSelection.current === undefined) return false;
73✔
3135
            let [col, row] = gridSelection.current.cell;
59✔
3136
            const [, startRow] = gridSelection.current.cell;
59✔
3137
            let freeMove = false;
59✔
3138
            let cancelOnlyOnMove = false;
59✔
3139

59✔
3140
            if (isHotkey(keys.scrollToSelectedCell, event, details)) {
73!
UNCOV
3141
                scrollToRef.current(col - rowMarkerOffset, row);
×
3142
            } else if (columnSelect !== "none" && isHotkey(keys.selectColumn, event, details)) {
73✔
3143
                if (selectedColumns.hasIndex(col)) {
2!
UNCOV
3144
                    setSelectedColumns(selectedColumns.remove(col), undefined, true);
×
3145
                } else {
2✔
3146
                    if (columnSelect === "single") {
2!
UNCOV
3147
                        setSelectedColumns(CompactSelection.fromSingleSelection(col), undefined, true);
×
3148
                    } else {
2✔
3149
                        setSelectedColumns(undefined, col, true);
2✔
3150
                    }
2✔
3151
                }
2✔
3152
            } else if (rowSelect !== "none" && isHotkey(keys.selectRow, event, details)) {
59✔
3153
                if (selectedRows.hasIndex(row)) {
2!
UNCOV
3154
                    setSelectedRows(selectedRows.remove(row), undefined, true);
×
3155
                } else {
2✔
3156
                    if (rowSelect === "single") {
2!
UNCOV
3157
                        setSelectedRows(CompactSelection.fromSingleSelection(row), undefined, true);
×
3158
                    } else {
2✔
3159
                        setSelectedRows(undefined, row, true);
2✔
3160
                    }
2✔
3161
                }
2✔
3162
            } else if (!overlayOpen && bounds !== undefined && isHotkey(keys.activateCell, event, details)) {
57✔
3163
                if (row === rows && showTrailingBlankRow) {
7!
UNCOV
3164
                    window.setTimeout(() => {
×
3165
                        const customTargetColumn = getCustomNewRowTargetColumn(col);
×
3166
                        void appendRow(customTargetColumn ?? col);
×
3167
                    }, 0);
×
3168
                } else {
7✔
3169
                    onCellActivated?.([col - rowMarkerOffset, row]);
7✔
3170
                    reselect(bounds, true);
7✔
3171
                }
7✔
3172
            } else if (gridSelection.current.range.height > 1 && isHotkey(keys.downFill, event, details)) {
55✔
3173
                fillDown();
1✔
3174
            } else if (gridSelection.current.range.width > 1 && isHotkey(keys.rightFill, event, details)) {
48✔
3175
                fillRight();
1✔
3176
            } else if (isHotkey(keys.goToNextPage, event, details)) {
47✔
3177
                row += Math.max(1, visibleRegionRef.current.height - 4); // partial cell accounting
1✔
3178
            } else if (isHotkey(keys.goToPreviousPage, event, details)) {
46✔
3179
                row -= Math.max(1, visibleRegionRef.current.height - 4); // partial cell accounting
1✔
3180
            } else if (isHotkey(keys.goToFirstCell, event, details)) {
45✔
3181
                setOverlay(undefined);
1✔
3182
                row = 0;
1✔
3183
                col = 0;
1✔
3184
            } else if (isHotkey(keys.goToLastCell, event, details)) {
44✔
3185
                setOverlay(undefined);
1✔
3186
                row = Number.MAX_SAFE_INTEGER;
1✔
3187
                col = Number.MAX_SAFE_INTEGER;
1✔
3188
            } else if (isHotkey(keys.selectToFirstCell, event, details)) {
43✔
3189
                setOverlay(undefined);
1✔
3190
                adjustSelection([-2, -2]);
1✔
3191
            } else if (isHotkey(keys.selectToLastCell, event, details)) {
42✔
3192
                setOverlay(undefined);
1✔
3193
                adjustSelection([2, 2]);
1✔
3194
            } else if (!overlayOpen) {
41✔
3195
                if (isHotkey(keys.goDownCell, event, details)) {
36✔
3196
                    row += 1;
5✔
3197
                } else if (isHotkey(keys.goUpCell, event, details)) {
36✔
3198
                    row -= 1;
1✔
3199
                } else if (isHotkey(keys.goRightCell, event, details)) {
31✔
3200
                    col += 1;
2✔
3201
                } else if (isHotkey(keys.goLeftCell, event, details)) {
30✔
3202
                    col -= 1;
2✔
3203
                } else if (isHotkey(keys.goDownCellRetainSelection, event, details)) {
28!
UNCOV
3204
                    row += 1;
×
3205
                    freeMove = true;
×
3206
                } else if (isHotkey(keys.goUpCellRetainSelection, event, details)) {
26!
UNCOV
3207
                    row -= 1;
×
3208
                    freeMove = true;
×
3209
                } else if (isHotkey(keys.goRightCellRetainSelection, event, details)) {
26!
UNCOV
3210
                    col += 1;
×
3211
                    freeMove = true;
×
3212
                } else if (isHotkey(keys.goLeftCellRetainSelection, event, details)) {
26✔
3213
                    col -= 1;
1✔
3214
                    freeMove = true;
1✔
3215
                } else if (isHotkey(keys.goToLastRow, event, details)) {
26✔
3216
                    row = rows - 1;
1✔
3217
                } else if (isHotkey(keys.goToFirstRow, event, details)) {
25✔
3218
                    row = Number.MIN_SAFE_INTEGER;
1✔
3219
                } else if (isHotkey(keys.goToLastColumn, event, details)) {
24✔
3220
                    col = Number.MAX_SAFE_INTEGER;
2✔
3221
                } else if (isHotkey(keys.goToFirstColumn, event, details)) {
23✔
3222
                    col = Number.MIN_SAFE_INTEGER;
1✔
3223
                } else if (rangeSelect === "rect" || rangeSelect === "multi-rect") {
21!
3224
                    if (isHotkey(keys.selectGrowDown, event, details)) {
20✔
3225
                        adjustSelection([0, 1]);
1✔
3226
                    } else if (isHotkey(keys.selectGrowUp, event, details)) {
20!
UNCOV
3227
                        adjustSelection([0, -1]);
×
3228
                    } else if (isHotkey(keys.selectGrowRight, event, details)) {
19✔
3229
                        adjustSelection([1, 0]);
4✔
3230
                    } else if (isHotkey(keys.selectGrowLeft, event, details)) {
19✔
3231
                        adjustSelection([-1, 0]);
1✔
3232
                    } else if (isHotkey(keys.selectToLastRow, event, details)) {
15✔
3233
                        adjustSelection([0, 2]);
1✔
3234
                    } else if (isHotkey(keys.selectToFirstRow, event, details)) {
14!
UNCOV
3235
                        adjustSelection([0, -2]);
×
3236
                    } else if (isHotkey(keys.selectToLastColumn, event, details)) {
13✔
3237
                        adjustSelection([2, 0]);
1✔
3238
                    } else if (isHotkey(keys.selectToFirstColumn, event, details)) {
13!
UNCOV
3239
                        adjustSelection([-2, 0]);
×
3240
                    }
×
3241
                }
20✔
3242
                cancelOnlyOnMove = details.didMatch;
36✔
3243
            } else {
40✔
3244
                if (isHotkey(keys.closeOverlay, event, details)) {
4✔
3245
                    setOverlay(undefined);
2✔
3246
                }
2✔
3247

4✔
3248
                if (isHotkey(keys.acceptOverlayDown, event, details)) {
4✔
3249
                    setOverlay(undefined);
2✔
3250
                    row++;
2✔
3251
                }
2✔
3252

4✔
3253
                if (isHotkey(keys.acceptOverlayUp, event, details)) {
4!
UNCOV
3254
                    setOverlay(undefined);
×
3255
                    row--;
×
3256
                }
×
3257

4✔
3258
                if (isHotkey(keys.acceptOverlayLeft, event, details)) {
4!
UNCOV
3259
                    setOverlay(undefined);
×
3260
                    col--;
×
3261
                }
×
3262

4✔
3263
                if (isHotkey(keys.acceptOverlayRight, event, details)) {
4!
UNCOV
3264
                    setOverlay(undefined);
×
3265
                    col++;
×
3266
                }
×
3267
            }
4✔
3268
            // #endregion
59✔
3269

59✔
3270
            const mustRestrictRow = rowGroupingNavBehavior !== undefined && rowGroupingNavBehavior !== "normal";
73!
3271

73✔
3272
            if (mustRestrictRow && row !== startRow) {
73!
UNCOV
3273
                const skipUp =
×
3274
                    rowGroupingNavBehavior === "skip-up" ||
×
3275
                    rowGroupingNavBehavior === "skip" ||
×
3276
                    rowGroupingNavBehavior === "block";
×
3277
                const skipDown =
×
3278
                    rowGroupingNavBehavior === "skip-down" ||
×
3279
                    rowGroupingNavBehavior === "skip" ||
×
3280
                    rowGroupingNavBehavior === "block";
×
3281
                const didMoveUp = row < startRow;
×
3282
                if (didMoveUp && skipUp) {
×
3283
                    while (row >= 0 && mapper(row).isGroupHeader) {
×
3284
                        row--;
×
3285
                    }
×
3286

×
3287
                    if (row < 0) {
×
3288
                        row = startRow;
×
3289
                    }
×
3290
                } else if (!didMoveUp && skipDown) {
×
3291
                    while (row < rows && mapper(row).isGroupHeader) {
×
3292
                        row++;
×
3293
                    }
×
3294

×
3295
                    if (row >= rows) {
×
3296
                        row = startRow;
×
3297
                    }
×
3298
                }
×
3299
            }
✔
3300

59✔
3301
            const moved = updateSelectedCell(col, row, false, freeMove);
59✔
3302

59✔
3303
            const didMatch = details.didMatch;
59✔
3304

59✔
3305
            if (didMatch && (moved || !cancelOnlyOnMove || trapFocus)) {
73✔
3306
                cancel();
39✔
3307
            }
39✔
3308

59✔
3309
            return didMatch;
59✔
3310
        },
73✔
3311
        [
726✔
3312
            rowGroupingNavBehavior,
726✔
3313
            overlayOpen,
726✔
3314
            gridSelection,
726✔
3315
            keybindings,
726✔
3316
            columnSelect,
726✔
3317
            rowSelect,
726✔
3318
            rangeSelect,
726✔
3319
            rowMarkerOffset,
726✔
3320
            mapper,
726✔
3321
            rows,
726✔
3322
            updateSelectedCell,
726✔
3323
            setGridSelection,
726✔
3324
            onSelectionCleared,
726✔
3325
            columnsIn.length,
726✔
3326
            onDelete,
726✔
3327
            trapFocus,
726✔
3328
            deleteRange,
726✔
3329
            setSelectedColumns,
726✔
3330
            setSelectedRows,
726✔
3331
            showTrailingBlankRow,
726✔
3332
            getCustomNewRowTargetColumn,
726✔
3333
            appendRow,
726✔
3334
            onCellActivated,
726✔
3335
            reselect,
726✔
3336
            fillDown,
726✔
3337
            fillRight,
726✔
3338
            adjustSelection,
726✔
3339
        ]
726✔
3340
    );
726✔
3341

726✔
3342
    const onKeyDown = React.useCallback(
726✔
3343
        (event: GridKeyEventArgs) => {
726✔
3344
            let cancelled = false;
73✔
3345
            if (onKeyDownIn !== undefined) {
73✔
3346
                onKeyDownIn({
1✔
3347
                    ...event,
1✔
3348
                    ...(event.location && {
1✔
3349
                        location: [event.location[0] - rowMarkerOffset, event.location[1]] as any,
1✔
3350
                    }),
1✔
3351
                    cancel: () => {
1✔
UNCOV
3352
                        cancelled = true;
×
3353
                    },
×
3354
                });
1✔
3355
            }
1✔
3356

73✔
3357
            if (cancelled) return;
73!
3358

73✔
3359
            if (handleFixedKeybindings(event)) return;
73✔
3360

15✔
3361
            if (gridSelection.current === undefined) return;
15✔
3362
            const [col, row] = gridSelection.current.cell;
12✔
3363
            const vr = visibleRegionRef.current;
12✔
3364

12✔
3365
            if (
12✔
3366
                editOnType &&
12✔
3367
                !event.metaKey &&
12✔
3368
                !event.ctrlKey &&
12✔
3369
                gridSelection.current !== undefined &&
12✔
3370
                event.key.length === 1 &&
12✔
3371
                /[\p{L}\p{M}\p{N}\p{S}\p{P}]/u.test(event.key) &&
12✔
3372
                event.bounds !== undefined &&
12✔
3373
                isReadWriteCell(getCellContent([col - rowMarkerOffset, Math.max(0, Math.min(row, rows - 1))]))
12✔
3374
            ) {
73✔
3375
                if (
12✔
3376
                    (!showTrailingBlankRow || row !== rows) &&
12✔
3377
                    (vr.y > row || row > vr.y + vr.height || vr.x > col || col > vr.x + vr.width)
12✔
3378
                ) {
12!
UNCOV
3379
                    return;
×
3380
                }
×
3381
                reselect(event.bounds, true, event.key);
12✔
3382
                event.stopPropagation();
12✔
3383
                event.preventDefault();
12✔
3384
            }
12✔
3385
        },
73✔
3386
        [
726✔
3387
            editOnType,
726✔
3388
            onKeyDownIn,
726✔
3389
            handleFixedKeybindings,
726✔
3390
            gridSelection,
726✔
3391
            getCellContent,
726✔
3392
            rowMarkerOffset,
726✔
3393
            rows,
726✔
3394
            showTrailingBlankRow,
726✔
3395
            reselect,
726✔
3396
        ]
726✔
3397
    );
726✔
3398

726✔
3399
    const onContextMenu = React.useCallback(
726✔
3400
        (args: GridMouseEventArgs, preventDefault: () => void) => {
726✔
3401
            const adjustedCol = args.location[0] - rowMarkerOffset;
7✔
3402
            if (args.kind === "header") {
7!
UNCOV
3403
                onHeaderContextMenu?.(adjustedCol, { ...args, preventDefault });
×
3404
            }
×
3405

7✔
3406
            if (args.kind === groupHeaderKind) {
7!
UNCOV
3407
                if (adjustedCol < 0) {
×
3408
                    return;
×
3409
                }
×
3410
                onGroupHeaderContextMenu?.(adjustedCol, { ...args, preventDefault });
×
3411
            }
×
3412

7✔
3413
            if (args.kind === "cell") {
7✔
3414
                const [col, row] = args.location;
7✔
3415
                onCellContextMenu?.([adjustedCol, row], {
7✔
3416
                    ...args,
7✔
3417
                    preventDefault,
7✔
3418
                });
7✔
3419

7✔
3420
                if (!gridSelectionHasItem(gridSelection, args.location)) {
7✔
3421
                    updateSelectedCell(col, row, false, false);
3✔
3422
                }
3✔
3423
            }
7✔
3424
        },
7✔
3425
        [
726✔
3426
            gridSelection,
726✔
3427
            onCellContextMenu,
726✔
3428
            onGroupHeaderContextMenu,
726✔
3429
            onHeaderContextMenu,
726✔
3430
            rowMarkerOffset,
726✔
3431
            updateSelectedCell,
726✔
3432
        ]
726✔
3433
    );
726✔
3434

726✔
3435
    const onPasteInternal = React.useCallback(
726✔
3436
        async (e?: ClipboardEvent) => {
726✔
3437
            if (!keybindings.paste) return;
6!
3438
            function pasteToCell(
6✔
3439
                inner: InnerGridCell,
51✔
3440
                target: Item,
51✔
3441
                rawValue: string | boolean | string[] | number | boolean | BooleanEmpty | BooleanIndeterminate,
51✔
3442
                formatted?: string | string[]
51✔
3443
            ): EditListItem | undefined {
51✔
3444
                const stringifiedRawValue =
51✔
3445
                    typeof rawValue === "object" ? rawValue?.join("\n") ?? "" : rawValue?.toString() ?? "";
51!
3446

51✔
3447
                if (!isInnerOnlyCell(inner) && isReadWriteCell(inner) && inner.readonly !== true) {
51✔
3448
                    const coerced = coercePasteValue?.(stringifiedRawValue, inner);
51!
3449
                    if (coerced !== undefined && isEditableGridCell(coerced)) {
51!
UNCOV
3450
                        if (process.env.NODE_ENV !== "production" && coerced.kind !== inner.kind) {
×
3451
                            // eslint-disable-next-line no-console
×
3452
                            console.warn("Coercion should not change cell kind.");
×
3453
                        }
×
3454
                        return {
×
3455
                            location: target,
×
3456
                            value: coerced,
×
3457
                        };
×
3458
                    }
×
3459
                    const r = getCellRenderer(inner);
51✔
3460
                    if (r === undefined) return undefined;
51!
3461
                    if (r.kind === GridCellKind.Custom) {
51✔
3462
                        assert(inner.kind === GridCellKind.Custom);
1✔
3463
                        const newVal = (r as unknown as CustomRenderer<CustomCell<any>>).onPaste?.(
1✔
3464
                            stringifiedRawValue,
1✔
3465
                            inner.data
1✔
3466
                        );
1✔
3467
                        if (newVal === undefined) return undefined;
1!
UNCOV
3468
                        return {
×
3469
                            location: target,
×
3470
                            value: {
×
3471
                                ...inner,
×
3472
                                data: newVal,
×
3473
                            },
×
3474
                        };
×
3475
                    } else {
51✔
3476
                        const newVal = r.onPaste?.(stringifiedRawValue, inner, {
50✔
3477
                            formatted,
50✔
3478
                            formattedString: typeof formatted === "string" ? formatted : formatted?.join("\n"),
50!
3479
                            rawValue,
50✔
3480
                        });
50✔
3481
                        if (newVal === undefined) return undefined;
50✔
3482
                        assert(newVal.kind === inner.kind);
36✔
3483
                        return {
36✔
3484
                            location: target,
36✔
3485
                            value: newVal,
36✔
3486
                        };
36✔
3487
                    }
36✔
3488
                }
51!
UNCOV
3489
                return undefined;
×
3490
            }
51✔
3491

6✔
3492
            const selectedColumns = gridSelection.columns;
6✔
3493
            const selectedRows = gridSelection.rows;
6✔
3494
            const focused =
6✔
3495
                scrollRef.current?.contains(document.activeElement) === true ||
6✔
3496
                canvasRef.current?.contains(document.activeElement) === true;
6✔
3497

6✔
3498
            let target: Item | undefined;
6✔
3499

6✔
3500
            if (gridSelection.current !== undefined) {
6✔
3501
                target = [gridSelection.current.range.x, gridSelection.current.range.y];
5✔
3502
            } else if (selectedColumns.length === 1) {
6!
UNCOV
3503
                target = [selectedColumns.first() ?? 0, 0];
×
3504
            } else if (selectedRows.length === 1) {
1!
UNCOV
3505
                target = [rowMarkerOffset, selectedRows.first() ?? 0];
×
3506
            }
×
3507

6✔
3508
            if (focused && target !== undefined) {
6✔
3509
                let data: CopyBuffer | undefined;
5✔
3510
                let text: string | undefined;
5✔
3511

5✔
3512
                const textPlain = "text/plain";
5✔
3513
                const textHtml = "text/html";
5✔
3514

5✔
3515
                if (navigator.clipboard.read !== undefined) {
5!
UNCOV
3516
                    const clipboardContent = await navigator.clipboard.read();
×
3517

×
3518
                    for (const item of clipboardContent) {
×
3519
                        if (item.types.includes(textHtml)) {
×
3520
                            const htmlBlob = await item.getType(textHtml);
×
3521
                            const html = await htmlBlob.text();
×
3522
                            const decoded = decodeHTML(html);
×
3523
                            if (decoded !== undefined) {
×
3524
                                data = decoded;
×
3525
                                break;
×
3526
                            }
×
3527
                        }
×
3528
                        if (item.types.includes(textPlain)) {
×
3529
                            // eslint-disable-next-line unicorn/no-await-expression-member
×
3530
                            text = await (await item.getType(textPlain)).text();
×
3531
                        }
×
3532
                    }
×
3533
                } else if (navigator.clipboard.readText !== undefined) {
5✔
3534
                    text = await navigator.clipboard.readText();
5✔
3535
                } else if (e !== undefined && e?.clipboardData !== null) {
5!
UNCOV
3536
                    if (e.clipboardData.types.includes(textHtml)) {
×
3537
                        const html = e.clipboardData.getData(textHtml);
×
3538
                        data = decodeHTML(html);
×
3539
                    }
×
3540
                    if (data === undefined && e.clipboardData.types.includes(textPlain)) {
×
3541
                        text = e.clipboardData.getData(textPlain);
×
3542
                    }
×
3543
                } else {
×
3544
                    return; // I didn't want to read that paste value anyway
×
3545
                }
×
3546

5✔
3547
                const [targetCol, targetRow] = target;
5✔
3548

5✔
3549
                const editList: EditListItem[] = [];
5✔
3550
                do {
5✔
3551
                    if (onPaste === undefined) {
5✔
3552
                        const cellData = getMangledCellContent(target);
2✔
3553
                        const rawValue = text ?? data?.map(r => r.map(cb => cb.rawValue).join("\t")).join("\t") ?? "";
2!
3554
                        const newVal = pasteToCell(cellData, target, rawValue, undefined);
2✔
3555
                        if (newVal !== undefined) {
2✔
3556
                            editList.push(newVal);
1✔
3557
                        }
1✔
3558
                        break;
2✔
3559
                    }
2✔
3560

3✔
3561
                    if (data === undefined) {
3✔
3562
                        if (text === undefined) return;
3!
3563
                        data = unquote(text);
3✔
3564
                    }
3✔
3565

3✔
3566
                    if (
3✔
3567
                        onPaste === false ||
3✔
3568
                        (typeof onPaste === "function" &&
3✔
3569
                            onPaste?.(
2✔
3570
                                [target[0] - rowMarkerOffset, target[1]],
2✔
3571
                                data.map(r => r.map(cb => cb.rawValue?.toString() ?? ""))
2!
3572
                            ) !== true)
2✔
3573
                    ) {
5!
UNCOV
3574
                        return;
×
3575
                    }
✔
3576

3✔
3577
                    for (const [row, dataRow] of data.entries()) {
5✔
3578
                        if (row + targetRow >= rows) break;
21!
3579
                        for (const [col, dataItem] of dataRow.entries()) {
21✔
3580
                            const index = [col + targetCol, row + targetRow] as const;
63✔
3581
                            const [writeCol, writeRow] = index;
63✔
3582
                            if (writeCol >= mangledCols.length) continue;
63✔
3583
                            if (writeRow >= mangledRows) continue;
49!
3584
                            const cellData = getMangledCellContent(index);
49✔
3585
                            const newVal = pasteToCell(cellData, index, dataItem.rawValue, dataItem.formatted);
49✔
3586
                            if (newVal !== undefined) {
63✔
3587
                                editList.push(newVal);
35✔
3588
                            }
35✔
3589
                        }
63✔
3590
                    }
21✔
3591
                    // eslint-disable-next-line no-constant-condition
3✔
3592
                } while (false);
5✔
3593

5✔
3594
                mangledOnCellsEdited(editList);
5✔
3595

5✔
3596
                gridRef.current?.damage(
5✔
3597
                    editList.map(c => ({
5✔
3598
                        cell: c.location,
36✔
3599
                    }))
5✔
3600
                );
5✔
3601
            }
5✔
3602
        },
6✔
3603
        [
726✔
3604
            coercePasteValue,
726✔
3605
            getCellRenderer,
726✔
3606
            getMangledCellContent,
726✔
3607
            gridSelection,
726✔
3608
            keybindings.paste,
726✔
3609
            scrollRef,
726✔
3610
            mangledCols.length,
726✔
3611
            mangledOnCellsEdited,
726✔
3612
            mangledRows,
726✔
3613
            onPaste,
726✔
3614
            rowMarkerOffset,
726✔
3615
            rows,
726✔
3616
        ]
726✔
3617
    );
726✔
3618

726✔
3619
    useEventListener("paste", onPasteInternal, safeWindow, false, true);
726✔
3620

726✔
3621
    // While this function is async, we deeply prefer not to await if we don't have to. This will lead to unpacking
726✔
3622
    // promises in rather awkward ways when possible to avoid awaiting. We have to use fallback copy mechanisms when
726✔
3623
    // an await has happened.
726✔
3624
    const onCopy = React.useCallback(
726✔
3625
        async (e?: ClipboardEvent, ignoreFocus?: boolean) => {
726✔
3626
            if (!keybindings.copy) return;
6!
3627
            const focused =
6✔
3628
                ignoreFocus === true ||
6✔
3629
                scrollRef.current?.contains(document.activeElement) === true ||
5✔
3630
                canvasRef.current?.contains(document.activeElement) === true;
5✔
3631

6✔
3632
            const selectedColumns = gridSelection.columns;
6✔
3633
            const selectedRows = gridSelection.rows;
6✔
3634

6✔
3635
            const copyToClipboardWithHeaders = (
6✔
3636
                cells: readonly (readonly GridCell[])[],
5✔
3637
                columnIndexes: readonly number[]
5✔
3638
            ) => {
5✔
3639
                if (!copyHeaders) {
5✔
3640
                    copyToClipboard(cells, columnIndexes, e);
5✔
3641
                } else {
5!
UNCOV
3642
                    const headers = columnIndexes.map(index => ({
×
3643
                        kind: GridCellKind.Text,
×
3644
                        data: columnsIn[index].title,
×
3645
                        displayData: columnsIn[index].title,
×
3646
                        allowOverlay: false,
×
3647
                    })) as GridCell[];
×
3648
                    copyToClipboard([headers, ...cells], columnIndexes, e);
×
3649
                }
×
3650
            };
5✔
3651

6✔
3652
            if (focused && getCellsForSelection !== undefined) {
6✔
3653
                if (gridSelection.current !== undefined) {
6✔
3654
                    let thunk = getCellsForSelection(gridSelection.current.range, abortControllerRef.current.signal);
3✔
3655
                    if (typeof thunk !== "object") {
3!
UNCOV
3656
                        thunk = await thunk();
×
3657
                    }
×
3658
                    copyToClipboardWithHeaders(
3✔
3659
                        thunk,
3✔
3660
                        range(
3✔
3661
                            gridSelection.current.range.x - rowMarkerOffset,
3✔
3662
                            gridSelection.current.range.x + gridSelection.current.range.width - rowMarkerOffset
3✔
3663
                        )
3✔
3664
                    );
3✔
3665
                } else if (selectedRows !== undefined && selectedRows.length > 0) {
3✔
3666
                    const toCopy = [...selectedRows];
1✔
3667
                    const cells = toCopy.map(rowIndex => {
1✔
3668
                        const thunk = getCellsForSelection(
1✔
3669
                            {
1✔
3670
                                x: rowMarkerOffset,
1✔
3671
                                y: rowIndex,
1✔
3672
                                width: columnsIn.length,
1✔
3673
                                height: 1,
1✔
3674
                            },
1✔
3675
                            abortControllerRef.current.signal
1✔
3676
                        );
1✔
3677
                        if (typeof thunk === "object") {
1✔
3678
                            return thunk[0];
1✔
3679
                        }
1!
UNCOV
3680
                        return thunk().then(v => v[0]);
×
3681
                    });
1✔
3682
                    if (cells.some(x => x instanceof Promise)) {
1!
UNCOV
3683
                        const settled = await Promise.all(cells);
×
3684
                        copyToClipboardWithHeaders(settled, range(columnsIn.length));
×
3685
                    } else {
1✔
3686
                        copyToClipboardWithHeaders(cells as (readonly GridCell[])[], range(columnsIn.length));
1✔
3687
                    }
1✔
3688
                } else if (selectedColumns.length > 0) {
3✔
3689
                    const results: (readonly (readonly GridCell[])[])[] = [];
1✔
3690
                    const cols: number[] = [];
1✔
3691
                    for (const col of selectedColumns) {
1✔
3692
                        let thunk = getCellsForSelection(
3✔
3693
                            {
3✔
3694
                                x: col,
3✔
3695
                                y: 0,
3✔
3696
                                width: 1,
3✔
3697
                                height: rows,
3✔
3698
                            },
3✔
3699
                            abortControllerRef.current.signal
3✔
3700
                        );
3✔
3701
                        if (typeof thunk !== "object") {
3!
UNCOV
3702
                            thunk = await thunk();
×
3703
                        }
×
3704
                        results.push(thunk);
3✔
3705
                        cols.push(col - rowMarkerOffset);
3✔
3706
                    }
3✔
3707
                    if (results.length === 1) {
1!
UNCOV
3708
                        copyToClipboardWithHeaders(results[0], cols);
×
3709
                    } else {
1✔
3710
                        // FIXME: this is dumb
1✔
3711
                        const toCopy = results.reduce((pv, cv) => pv.map((row, index) => [...row, ...cv[index]]));
1✔
3712
                        copyToClipboardWithHeaders(toCopy, cols);
1✔
3713
                    }
1✔
3714
                }
1✔
3715
            }
6✔
3716
        },
6✔
3717
        [
726✔
3718
            columnsIn,
726✔
3719
            getCellsForSelection,
726✔
3720
            gridSelection,
726✔
3721
            keybindings.copy,
726✔
3722
            rowMarkerOffset,
726✔
3723
            scrollRef,
726✔
3724
            rows,
726✔
3725
            copyHeaders,
726✔
3726
        ]
726✔
3727
    );
726✔
3728

726✔
3729
    useEventListener("copy", onCopy, safeWindow, false, false);
726✔
3730

726✔
3731
    const onCut = React.useCallback(
726✔
3732
        async (e?: ClipboardEvent) => {
726✔
3733
            if (!keybindings.cut) return;
1!
3734
            const focused =
1✔
3735
                scrollRef.current?.contains(document.activeElement) === true ||
1✔
3736
                canvasRef.current?.contains(document.activeElement) === true;
1✔
3737

1✔
3738
            if (!focused) return;
1!
3739
            await onCopy(e);
1✔
3740
            if (gridSelection.current !== undefined) {
1✔
3741
                let effectiveSelection: GridSelection = {
1✔
3742
                    current: {
1✔
3743
                        cell: gridSelection.current.cell,
1✔
3744
                        range: gridSelection.current.range,
1✔
3745
                        rangeStack: [],
1✔
3746
                    },
1✔
3747
                    rows: CompactSelection.empty(),
1✔
3748
                    columns: CompactSelection.empty(),
1✔
3749
                };
1✔
3750
                const onDeleteResult = onDelete?.(effectiveSelection);
1✔
3751
                if (onDeleteResult === false) return;
1!
3752
                effectiveSelection = onDeleteResult === true ? effectiveSelection : onDeleteResult;
1!
3753
                if (effectiveSelection.current === undefined) return;
1!
3754
                deleteRange(effectiveSelection.current.range);
1✔
3755
            }
1✔
3756
        },
1✔
3757
        [deleteRange, gridSelection, keybindings.cut, onCopy, scrollRef, onDelete]
726✔
3758
    );
726✔
3759

726✔
3760
    useEventListener("cut", onCut, safeWindow, false, false);
726✔
3761

726✔
3762
    const onSearchResultsChanged = React.useCallback(
726✔
3763
        (results: readonly Item[], navIndex: number) => {
726✔
3764
            if (onSearchResultsChangedIn !== undefined) {
7!
UNCOV
3765
                if (rowMarkerOffset !== 0) {
×
3766
                    results = results.map(item => [item[0] - rowMarkerOffset, item[1]]);
×
3767
                }
×
3768
                onSearchResultsChangedIn(results, navIndex);
×
3769
                return;
×
3770
            }
×
3771
            if (results.length === 0 || navIndex === -1) return;
7✔
3772

2✔
3773
            const [col, row] = results[navIndex];
2✔
3774
            if (lastSent.current !== undefined && lastSent.current[0] === col && lastSent.current[1] === row) {
7!
UNCOV
3775
                return;
×
3776
            }
✔
3777
            lastSent.current = [col, row];
2✔
3778
            updateSelectedCell(col, row, false, false);
2✔
3779
        },
7✔
3780
        [onSearchResultsChangedIn, rowMarkerOffset, updateSelectedCell]
726✔
3781
    );
726✔
3782

726✔
3783
    // this effects purpose in life is to scroll the newly selected cell into view when and ONLY when that cell
726✔
3784
    // is from an external gridSelection change. Also note we want the unmangled out selection because scrollTo
726✔
3785
    // expects unmangled indexes
726✔
3786
    const [outCol, outRow] = gridSelectionOuter?.current?.cell ?? [];
726✔
3787
    const scrollToRef = React.useRef(scrollTo);
726✔
3788
    scrollToRef.current = scrollTo;
726✔
3789
    React.useLayoutEffect(() => {
726✔
3790
        if (
231✔
3791
            scrollToActiveCellRef.current &&
231✔
3792
            !hasJustScrolled.current &&
231✔
3793
            outCol !== undefined &&
231✔
3794
            outRow !== undefined &&
88✔
3795
            (outCol !== expectedExternalGridSelection.current?.current?.cell[0] ||
88✔
3796
                outRow !== expectedExternalGridSelection.current?.current?.cell[1])
88✔
3797
        ) {
231!
UNCOV
3798
            scrollToRef.current(outCol, outRow);
×
3799
        }
×
3800
        hasJustScrolled.current = false; //only allow skipping a single scroll
231✔
3801
    }, [outCol, outRow]);
726✔
3802

726✔
3803
    const selectionOutOfBounds =
726✔
3804
        gridSelection.current !== undefined &&
726✔
3805
        (gridSelection.current.cell[0] >= mangledCols.length || gridSelection.current.cell[1] >= mangledRows);
360✔
3806
    React.useLayoutEffect(() => {
726✔
3807
        if (selectionOutOfBounds) {
153✔
3808
            setGridSelection(emptyGridSelection, false);
1✔
3809
        }
1✔
3810
    }, [selectionOutOfBounds, setGridSelection]);
726✔
3811

726✔
3812
    const disabledRows = React.useMemo(() => {
726✔
3813
        if (showTrailingBlankRow === true && trailingRowOptions?.tint === true) {
149✔
3814
            return CompactSelection.fromSingleSelection(mangledRows - 1);
146✔
3815
        }
146✔
3816
        return CompactSelection.empty();
3✔
3817
    }, [mangledRows, showTrailingBlankRow, trailingRowOptions?.tint]);
726✔
3818

726✔
3819
    const mangledVerticalBorder = React.useCallback(
726✔
3820
        (col: number) => {
726✔
3821
            return typeof verticalBorder === "boolean"
7,901!
UNCOV
3822
                ? verticalBorder
×
3823
                : verticalBorder?.(col - rowMarkerOffset) ?? true;
7,901!
3824
        },
7,901✔
3825
        [rowMarkerOffset, verticalBorder]
726✔
3826
    );
726✔
3827

726✔
3828
    const renameGroupNode = React.useMemo(() => {
726✔
3829
        if (renameGroup === undefined || canvasRef.current === null) return null;
150✔
3830
        const { bounds, group } = renameGroup;
2✔
3831
        const canvasBounds = canvasRef.current.getBoundingClientRect();
2✔
3832
        return (
2✔
3833
            <GroupRename
2✔
3834
                bounds={bounds}
2✔
3835
                group={group}
2✔
3836
                canvasBounds={canvasBounds}
2✔
3837
                onClose={() => setRenameGroup(undefined)}
2✔
3838
                onFinish={newVal => {
2✔
3839
                    setRenameGroup(undefined);
1✔
3840
                    onGroupHeaderRenamed?.(group, newVal);
1✔
3841
                }}
1✔
3842
            />
2✔
3843
        );
150✔
3844
    }, [onGroupHeaderRenamed, renameGroup]);
726✔
3845

726✔
3846
    const mangledFreezeColumns = Math.min(mangledCols.length, freezeColumns + (hasRowMarkers ? 1 : 0));
726✔
3847

726✔
3848
    React.useImperativeHandle(
726✔
3849
        forwardedRef,
726✔
3850
        () => ({
726✔
3851
            appendRow: (col: number, openOverlay?: boolean) => appendRow(col + rowMarkerOffset, openOverlay),
24✔
3852
            updateCells: damageList => {
24✔
3853
                if (rowMarkerOffset !== 0) {
2✔
3854
                    damageList = damageList.map(x => ({ cell: [x.cell[0] + rowMarkerOffset, x.cell[1]] }));
1✔
3855
                }
1✔
3856
                return gridRef.current?.damage(damageList);
2✔
3857
            },
2✔
3858
            getBounds: (col, row) => {
24✔
3859
                if (canvasRef?.current === null || scrollRef?.current === null) {
2!
UNCOV
3860
                    return undefined;
×
3861
                }
×
3862

2✔
3863
                if (col === undefined && row === undefined) {
2✔
3864
                    // Return the bounds of the entire scroll area:
1✔
3865
                    const rect = canvasRef.current.getBoundingClientRect();
1✔
3866
                    const scale = rect.width / scrollRef.current.clientWidth;
1✔
3867
                    return {
1✔
3868
                        x: rect.x - scrollRef.current.scrollLeft * scale,
1✔
3869
                        y: rect.y - scrollRef.current.scrollTop * scale,
1✔
3870
                        width: scrollRef.current.scrollWidth * scale,
1✔
3871
                        height: scrollRef.current.scrollHeight * scale,
1✔
3872
                    };
1✔
3873
                }
1✔
3874
                return gridRef.current?.getBounds((col ?? 0) + rowMarkerOffset, row);
2!
3875
            },
2✔
3876
            focus: () => gridRef.current?.focus(),
24✔
3877
            emit: async e => {
24✔
3878
                switch (e) {
5✔
3879
                    case "delete":
5✔
3880
                        onKeyDown({
1✔
3881
                            bounds: undefined,
1✔
3882
                            cancel: () => undefined,
1✔
3883
                            stopPropagation: () => undefined,
1✔
3884
                            preventDefault: () => undefined,
1✔
3885
                            ctrlKey: false,
1✔
3886
                            key: "Delete",
1✔
3887
                            keyCode: 46,
1✔
3888
                            metaKey: false,
1✔
3889
                            shiftKey: false,
1✔
3890
                            altKey: false,
1✔
3891
                            rawEvent: undefined,
1✔
3892
                            location: undefined,
1✔
3893
                        });
1✔
3894
                        break;
1✔
3895
                    case "fill-right":
5✔
3896
                        onKeyDown({
1✔
3897
                            bounds: undefined,
1✔
3898
                            cancel: () => undefined,
1✔
3899
                            stopPropagation: () => undefined,
1✔
3900
                            preventDefault: () => undefined,
1✔
3901
                            ctrlKey: true,
1✔
3902
                            key: "r",
1✔
3903
                            keyCode: 82,
1✔
3904
                            metaKey: false,
1✔
3905
                            shiftKey: false,
1✔
3906
                            altKey: false,
1✔
3907
                            rawEvent: undefined,
1✔
3908
                            location: undefined,
1✔
3909
                        });
1✔
3910
                        break;
1✔
3911
                    case "fill-down":
5✔
3912
                        onKeyDown({
1✔
3913
                            bounds: undefined,
1✔
3914
                            cancel: () => undefined,
1✔
3915
                            stopPropagation: () => undefined,
1✔
3916
                            preventDefault: () => undefined,
1✔
3917
                            ctrlKey: true,
1✔
3918
                            key: "d",
1✔
3919
                            keyCode: 68,
1✔
3920
                            metaKey: false,
1✔
3921
                            shiftKey: false,
1✔
3922
                            altKey: false,
1✔
3923
                            rawEvent: undefined,
1✔
3924
                            location: undefined,
1✔
3925
                        });
1✔
3926
                        break;
1✔
3927
                    case "copy":
5✔
3928
                        await onCopy(undefined, true);
1✔
3929
                        break;
1✔
3930
                    case "paste":
5✔
3931
                        await onPasteInternal();
1✔
3932
                        break;
1✔
3933
                }
5✔
3934
            },
5✔
3935
            scrollTo,
24✔
3936
            remeasureColumns: cols => {
24✔
3937
                for (const col of cols) {
1✔
3938
                    void normalSizeColumn(col + rowMarkerOffset);
1✔
3939
                }
1✔
3940
            },
1✔
3941
            getMouseArgsForPosition: (
24✔
NEW
3942
                posX: number,
×
NEW
3943
                posY: number,
×
NEW
3944
                ev?: MouseEvent | TouchEvent
×
NEW
3945
            ): GridMouseEventArgs | undefined => {
×
3946
                if (gridRef?.current === null) {
×
3947
                    return undefined;
×
3948
                }
×
3949

×
3950
                const args = gridRef.current.getMouseArgsForPosition(posX, posY, ev);
×
3951
                if (args === undefined) {
×
3952
                    return undefined;
×
3953
                }
×
NEW
3954

×
3955
                return {
×
3956
                    ...args,
×
3957
                    location: [args.location[0] - rowMarkerOffset, args.location[1]] as any,
×
3958
                };
×
NEW
3959
            },
×
3960
        }),
24✔
3961
        [appendRow, normalSizeColumn, scrollRef, onCopy, onKeyDown, onPasteInternal, rowMarkerOffset, scrollTo]
726✔
3962
    );
726✔
3963

726✔
3964
    const [selCol, selRow] = currentCell ?? [];
726✔
3965
    const onCellFocused = React.useCallback(
726✔
3966
        (cell: Item) => {
726✔
3967
            const [col, row] = cell;
29✔
3968

29✔
3969
            if (row === -1) {
29!
UNCOV
3970
                if (columnSelect !== "none") {
×
3971
                    setSelectedColumns(CompactSelection.fromSingleSelection(col), undefined, false);
×
3972
                    focus();
×
3973
                }
×
3974
                return;
×
3975
            }
×
3976

29✔
3977
            if (selCol === col && selRow === row) return;
29✔
3978
            setCurrent(
1✔
3979
                {
1✔
3980
                    cell,
1✔
3981
                    range: { x: col, y: row, width: 1, height: 1 },
1✔
3982
                },
1✔
3983
                true,
1✔
3984
                false,
1✔
3985
                "keyboard-nav"
1✔
3986
            );
1✔
3987
            scrollTo(col, row);
1✔
3988
        },
29✔
3989
        [columnSelect, focus, scrollTo, selCol, selRow, setCurrent, setSelectedColumns]
726✔
3990
    );
726✔
3991

726✔
3992
    const [isFocused, setIsFocused] = React.useState(false);
726✔
3993
    const setIsFocusedDebounced = React.useRef(
726✔
3994
        debounce((val: boolean) => {
726✔
3995
            setIsFocused(val);
56✔
3996
        }, 5)
726✔
3997
    );
726✔
3998

726✔
3999
    const onCanvasFocused = React.useCallback(() => {
726✔
4000
        setIsFocusedDebounced.current(true);
74✔
4001

74✔
4002
        // check for mouse state, don't do anything if the user is clicked to focus.
74✔
4003
        if (
74✔
4004
            gridSelection.current === undefined &&
74✔
4005
            gridSelection.columns.length === 0 &&
6✔
4006
            gridSelection.rows.length === 0 &&
5✔
4007
            mouseState === undefined
5✔
4008
        ) {
74✔
4009
            setCurrent(
5✔
4010
                {
5✔
4011
                    cell: [rowMarkerOffset, cellYOffset],
5✔
4012
                    range: {
5✔
4013
                        x: rowMarkerOffset,
5✔
4014
                        y: cellYOffset,
5✔
4015
                        width: 1,
5✔
4016
                        height: 1,
5✔
4017
                    },
5✔
4018
                },
5✔
4019
                true,
5✔
4020
                false,
5✔
4021
                "keyboard-select"
5✔
4022
            );
5✔
4023
        }
5✔
4024
    }, [cellYOffset, gridSelection, mouseState, rowMarkerOffset, setCurrent]);
726✔
4025

726✔
4026
    const onFocusOut = React.useCallback(() => {
726✔
4027
        setIsFocusedDebounced.current(false);
38✔
4028
    }, []);
726✔
4029

726✔
4030
    const [idealWidth, idealHeight] = React.useMemo(() => {
726✔
4031
        let h: number;
162✔
4032
        const scrollbarWidth = experimental?.scrollbarWidthOverride ?? getScrollBarWidth();
162✔
4033
        const rowsCountWithTrailingRow = rows + (showTrailingBlankRow ? 1 : 0);
162!
4034
        if (typeof rowHeight === "number") {
162✔
4035
            h = totalHeaderHeight + rowsCountWithTrailingRow * rowHeight;
161✔
4036
        } else {
162✔
4037
            let avg = 0;
1✔
4038
            const toAverage = Math.min(rowsCountWithTrailingRow, 10);
1✔
4039
            for (let i = 0; i < toAverage; i++) {
1✔
4040
                avg += rowHeight(i);
10✔
4041
            }
10✔
4042
            avg = Math.floor(avg / toAverage);
1✔
4043

1✔
4044
            h = totalHeaderHeight + rowsCountWithTrailingRow * avg;
1✔
4045
        }
1✔
4046
        h += scrollbarWidth;
162✔
4047

162✔
4048
        const w = mangledCols.reduce((acc, x) => x.width + acc, 0) + scrollbarWidth;
162✔
4049

162✔
4050
        // We need to set a reasonable cap here as some browsers will just ignore huge values
162✔
4051
        // rather than treat them as huge values.
162✔
4052
        return [`${Math.min(100_000, w)}px`, `${Math.min(100_000, h)}px`];
162✔
4053
    }, [mangledCols, experimental?.scrollbarWidthOverride, rowHeight, rows, showTrailingBlankRow, totalHeaderHeight]);
726✔
4054

726✔
4055
    const cssStyle = React.useMemo(() => {
726✔
4056
        return makeCSSStyle(mergedTheme);
147✔
4057
    }, [mergedTheme]);
726✔
4058

726✔
4059
    return (
726✔
4060
        <ThemeContext.Provider value={mergedTheme}>
726✔
4061
            <DataEditorContainer
726✔
4062
                style={cssStyle}
726✔
4063
                className={className}
726✔
4064
                inWidth={width ?? idealWidth}
726✔
4065
                inHeight={height ?? idealHeight}>
726✔
4066
                <DataGridSearch
726✔
4067
                    fillHandle={fillHandle}
726✔
4068
                    drawFocusRing={drawFocusRing}
726✔
4069
                    experimental={experimental}
726✔
4070
                    fixedShadowX={fixedShadowX}
726✔
4071
                    fixedShadowY={fixedShadowY}
726✔
4072
                    getRowThemeOverride={getRowThemeOverride}
726✔
4073
                    headerIcons={headerIcons}
726✔
4074
                    imageWindowLoader={imageWindowLoader}
726✔
4075
                    initialSize={initialSize}
726✔
4076
                    isDraggable={isDraggable}
726✔
4077
                    onDragLeave={onDragLeave}
726✔
4078
                    onRowMoved={onRowMoved}
726✔
4079
                    overscrollX={overscrollX}
726✔
4080
                    overscrollY={overscrollY}
726✔
4081
                    preventDiagonalScrolling={preventDiagonalScrolling}
726✔
4082
                    rightElement={rightElement}
726✔
4083
                    rightElementProps={rightElementProps}
726✔
4084
                    smoothScrollX={smoothScrollX}
726✔
4085
                    smoothScrollY={smoothScrollY}
726✔
4086
                    className={className}
726✔
4087
                    enableGroups={enableGroups}
726✔
4088
                    onCanvasFocused={onCanvasFocused}
726✔
4089
                    onCanvasBlur={onFocusOut}
726✔
4090
                    canvasRef={canvasRef}
726✔
4091
                    onContextMenu={onContextMenu}
726✔
4092
                    theme={mergedTheme}
726✔
4093
                    cellXOffset={cellXOffset}
726✔
4094
                    cellYOffset={cellYOffset}
726✔
4095
                    accessibilityHeight={visibleRegion.height}
726✔
4096
                    onDragEnd={onDragEnd}
726✔
4097
                    columns={mangledCols}
726✔
4098
                    nonGrowWidth={nonGrowWidth}
726✔
4099
                    drawHeader={drawHeader}
726✔
4100
                    onColumnProposeMove={onColumnProposeMoveImpl}
726✔
4101
                    drawCell={drawCell}
726✔
4102
                    disabledRows={disabledRows}
726✔
4103
                    freezeColumns={mangledFreezeColumns}
726✔
4104
                    lockColumns={rowMarkerOffset}
726✔
4105
                    firstColAccessible={rowMarkerOffset === 0}
726✔
4106
                    getCellContent={getMangledCellContent}
726✔
4107
                    minColumnWidth={minColumnWidth}
726✔
4108
                    maxColumnWidth={maxColumnWidth}
726✔
4109
                    searchInputRef={searchInputRef}
726✔
4110
                    showSearch={showSearch}
726✔
4111
                    onSearchClose={onSearchClose}
726✔
4112
                    highlightRegions={highlightRegions}
726✔
4113
                    getCellsForSelection={getCellsForSelection}
726✔
4114
                    getGroupDetails={mangledGetGroupDetails}
726✔
4115
                    headerHeight={headerHeight}
726✔
4116
                    isFocused={isFocused}
726✔
4117
                    groupHeaderHeight={enableGroups ? groupHeaderHeight : 0}
726✔
4118
                    freezeTrailingRows={
726✔
4119
                        freezeTrailingRows + (showTrailingBlankRow && trailingRowOptions?.sticky === true ? 1 : 0)
726!
4120
                    }
726✔
4121
                    hasAppendRow={showTrailingBlankRow}
726✔
4122
                    onColumnResize={onColumnResize}
726✔
4123
                    onColumnResizeEnd={onColumnResizeEnd}
726✔
4124
                    onColumnResizeStart={onColumnResizeStart}
726✔
4125
                    onCellFocused={onCellFocused}
726✔
4126
                    onColumnMoved={onColumnMovedImpl}
726✔
4127
                    onDragStart={onDragStartImpl}
726✔
4128
                    onHeaderMenuClick={onHeaderMenuClickInner}
726✔
4129
                    onHeaderIndicatorClick={onHeaderIndicatorClickInner}
726✔
4130
                    onItemHovered={onItemHoveredImpl}
726✔
4131
                    isFilling={mouseState?.fillHandle === true}
726✔
4132
                    onMouseMove={onMouseMoveImpl}
726✔
4133
                    onKeyDown={onKeyDown}
726✔
4134
                    onKeyUp={onKeyUpIn}
726✔
4135
                    onMouseDown={onMouseDown}
726✔
4136
                    onMouseUp={onMouseUp}
726✔
4137
                    onDragOverCell={onDragOverCell}
726✔
4138
                    onDrop={onDrop}
726✔
4139
                    onSearchResultsChanged={onSearchResultsChanged}
726✔
4140
                    onVisibleRegionChanged={onVisibleRegionChangedImpl}
726✔
4141
                    clientSize={clientSize}
726✔
4142
                    rowHeight={rowHeight}
726✔
4143
                    searchResults={searchResults}
726✔
4144
                    searchValue={searchValue}
726✔
4145
                    onSearchValueChange={onSearchValueChange}
726✔
4146
                    rows={mangledRows}
726✔
4147
                    scrollRef={scrollRef}
726✔
4148
                    selection={gridSelection}
726✔
4149
                    translateX={visibleRegion.tx}
726✔
4150
                    translateY={visibleRegion.ty}
726✔
4151
                    verticalBorder={mangledVerticalBorder}
726✔
4152
                    gridRef={gridRef}
726✔
4153
                    getCellRenderer={getCellRenderer}
726✔
4154
                    resizeIndicator={resizeIndicator}
726✔
4155
                />
726✔
4156
                {renameGroupNode}
726✔
4157
                {overlay !== undefined && (
726✔
4158
                    <React.Suspense fallback={null}>
35✔
4159
                        <DataGridOverlayEditor
35✔
4160
                            {...overlay}
35✔
4161
                            validateCell={validateCell}
35✔
4162
                            bloom={editorBloom}
35✔
4163
                            id={overlayID}
35✔
4164
                            getCellRenderer={getCellRenderer}
35✔
4165
                            className={experimental?.isSubGrid === true ? "click-outside-ignore" : undefined}
35!
4166
                            provideEditor={provideEditor}
35✔
4167
                            imageEditorOverride={imageEditorOverride}
35✔
4168
                            onFinishEditing={onFinishEditing}
35✔
4169
                            markdownDivCreateNode={markdownDivCreateNode}
35✔
4170
                            isOutsideClick={isOutsideClick}
35✔
4171
                            customEventTarget={experimental?.eventTarget}
35✔
4172
                        />
35✔
4173
                    </React.Suspense>
35✔
4174
                )}
726✔
4175
            </DataEditorContainer>
726✔
4176
        </ThemeContext.Provider>
726✔
4177
    );
726✔
4178
};
726✔
4179

1✔
4180
/**
1✔
4181
 * The primary component of Glide Data Grid.
1✔
4182
 * @category DataEditor
1✔
4183
 * @param {DataEditorProps} props
1✔
4184
 */
1✔
4185
export const DataEditor = React.forwardRef(DataEditorImpl);
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc