@bmsuisse/datagrid 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ReactNode, ReactElement, ComponentType, RefObject } from 'react';
1
+ import { ReactNode, ReactElement, MouseEvent, ComponentType, RefObject } from 'react';
2
2
  import { ColumnSizingState, RowData } from '@tanstack/react-table';
3
3
  export { ColumnSizingState } from '@tanstack/react-table';
4
4
 
@@ -209,9 +209,21 @@ interface BaseColumn<TRow> {
209
209
  * this cell, including the default editors' own `onChange`.
210
210
  */
211
211
  validateEdit?: (value: unknown, row: TRow) => string | undefined;
212
+ /**
213
+ * Overrides the plain-text form of this column's value used when copying a
214
+ * `cellEditing`-mode selection to the clipboard. Defaults to the same
215
+ * `defaultFormat` display text every other rendering path already uses —
216
+ * only needed for a column with a custom `cell` whose displayed content
217
+ * isn't a faithful plain-text rendering of the underlying value (e.g. a
218
+ * badge/icon), the same situation `renderFilter`/`renderEditCell` exist to
219
+ * override for their own concerns.
220
+ */
221
+ formatForClipboard?: (value: unknown, row: TRow) => string;
212
222
  }
213
223
  type StringColumn<TRow> = BaseColumn<TRow> & {
214
224
  type: "string";
225
+ /** Opt-in: defaults to false. Selects `MultilineStringEditor` (an auto-growing `<textarea>`) instead of `StringEditor`'s single-line `<Input>` for this column's default edit widget — same variant-flag idiom as `NumberColumn.currency`, not a separate `type`. Has no effect on static (non-editing) display, which stays whatever `column.cell`/`defaultFormat` already renders. */
226
+ multiline?: boolean;
215
227
  };
216
228
  type NumberColumn<TRow> = BaseColumn<TRow> & {
217
229
  type: "number" | "currency";
@@ -658,6 +670,16 @@ declare function DateEditor<TRow>({ column, rowId, value, onChange, error, autoF
658
670
  /** Default inline editor for `type: "enum"` columns: a single-value select over `column.options`. */
659
671
  declare function EnumEditor<TRow>({ column, rowId, value, onChange, error, autoFocus, }: EditWidgetProps<EnumColumn<TRow>>): ReactElement;
660
672
 
673
+ /**
674
+ * Default inline editor for a `type: "string"` column with `multiline: true`:
675
+ * an auto-growing `<textarea>` in place of `StringEditor`'s single-line
676
+ * `<Input>`. Dispatched by `registry.tsx`'s existing `case "string":` branch
677
+ * — a rendering choice, not a new column `type` — so it works identically
678
+ * under both `DataGridProps.editing` (row-batch) and `.cellEditing`
679
+ * (immediate-apply) modes with zero extra wiring in either.
680
+ */
681
+ declare function MultilineStringEditor<TRow>({ column, rowId, value, onChange, error, autoFocus, }: EditWidgetProps<StringColumn<TRow>>): ReactElement;
682
+
661
683
  /**
662
684
  * Default inline editor for `type: "number"` / `type: "currency"` columns: a
663
685
  * native number input. Emits `null` for an emptied field (not `NaN` or `""`)
@@ -784,6 +806,355 @@ interface EditingBarProps<TRow> {
784
806
  */
785
807
  declare function EditingBar<TRow>({ editing, pendingRowCount, editErrors, onSave, onDiscard, testIdPrefix, }: EditingBarProps<TRow>): ReactElement;
786
808
 
809
+ /**
810
+ * Identifies one cell by stable row id + column id — never by DOM node or
811
+ * virtualization index, so a selection/edit survives a virtualized row
812
+ * mounting, unmounting, and remounting as the grid scrolls.
813
+ */
814
+ interface CellAddress {
815
+ rowId: string;
816
+ columnId: string;
817
+ }
818
+ /**
819
+ * A rectangular block of cells, expressed as its two corners: `anchor` is
820
+ * the cell where the selection/drag started, `focus` is the other corner —
821
+ * the one that moves as you shift+arrow or drag the mouse. Either corner can
822
+ * be the top-left or bottom-right one; use `normalizeRange` (in
823
+ * `rangeUtils.ts`) to resolve that.
824
+ */
825
+ interface CellRange {
826
+ anchor: CellAddress;
827
+ focus: CellAddress;
828
+ }
829
+ /**
830
+ * One committed cell edit — from a typed commit, a paste, or a fill-drag.
831
+ * `previousValue` isn't read by anything in this pass; it's carried now so a
832
+ * future undo/redo can be built on top of the same `CellChange` log without
833
+ * a breaking API change later.
834
+ */
835
+ interface CellChange<TRow> {
836
+ rowId: string;
837
+ row: TRow;
838
+ columnId: string;
839
+ previousValue: unknown;
840
+ value: unknown;
841
+ }
842
+ /**
843
+ * Enables the "true spreadsheet" cell-editing mode on `<DataGrid>`: click or
844
+ * type directly into any cell, range-select, paste, fill-handle — every
845
+ * change applies immediately, with no row-level Save/Discard gate. This is a
846
+ * different state machine from `DataGridProps.editing`'s accumulate-then-save
847
+ * workflow, not a superset of it — set at most one of `editing`/`cellEditing`
848
+ * on a given `<DataGrid>`; `editing` is ignored once `cellEditing` is set.
849
+ */
850
+ interface CellEditingOptions<TRow> {
851
+ /**
852
+ * Called once per gesture — one typed-cell commit, one paste, one
853
+ * fill-drag — with every cell that gesture changed. There's no debouncing:
854
+ * each gesture's full change set is computed synchronously before this
855
+ * fires, so a single keystroke commit is one `[change]`, and pasting a
856
+ * 50-cell block is one call with 50 entries.
857
+ *
858
+ * Fire-and-forget: a rejection does not roll back the grid's own local
859
+ * display of the new values — there's no "pending" state to roll back to
860
+ * in an immediate-apply model, unlike `EditingOptions.onSave`. Show your
861
+ * own error UI (e.g. a toast) if a change needs to be surfaced as failed.
862
+ */
863
+ onCellsChange: (changes: CellChange<TRow>[]) => void | Promise<void>;
864
+ /** Disables all cell-editing interaction — e.g. while a previous gesture's `onCellsChange` is still in flight. Defaults to false. */
865
+ disabled?: boolean;
866
+ }
867
+
868
+ /** Maps each id to its position in `ids` — built once per (rowIds|columnIds) identity change, then reused for every lookup during a drag, rather than an O(n) `indexOf` per cell per mousemove. */
869
+ declare function buildIndexMap(ids: string[]): Map<string, number>;
870
+ /** A `CellRange`'s two corners resolved to ordinal row/column indices, with `start <= end` on both axes regardless of which corner `anchor`/`focus` was. */
871
+ interface NormalizedRange {
872
+ rowStart: number;
873
+ rowEnd: number;
874
+ colStart: number;
875
+ colEnd: number;
876
+ }
877
+ /**
878
+ * Resolves a `CellRange` to ordinal indices via `rowIndex`/`columnIndex`
879
+ * (from `buildIndexMap`), or `undefined` if either endpoint's row or column
880
+ * id no longer exists — e.g. the row was removed from `data` since the
881
+ * selection was made.
882
+ */
883
+ declare function normalizeRange(range: CellRange, rowIndex: Map<string, number>, columnIndex: Map<string, number>): NormalizedRange | undefined;
884
+ /** True if `cell` falls inside `range` (inclusive on all sides). */
885
+ declare function isCellInRange(cell: CellAddress, range: CellRange, rowIndex: Map<string, number>, columnIndex: Map<string, number>): boolean;
886
+ /** Every cell address inside `range`, in row-major order (top-to-bottom, left-to-right), skipping the row-major walk entirely once `normalizeRange` can't resolve either endpoint. */
887
+ declare function cellsInRange(range: CellRange, rowIds: string[], columnIds: string[], rowIndex: Map<string, number>, columnIndex: Map<string, number>): CellAddress[];
888
+ /**
889
+ * Extends a fill-handle drag's `base` range (the source selection, already
890
+ * normalized) toward `target`, locked to whichever single axis — row or
891
+ * column — has the larger overshoot past `base`'s own edge on that axis.
892
+ * Matches Excel/Sheets' own autofill behavior: dragging the fill handle
893
+ * never extends diagonally, only straight down/up/left/right from the
894
+ * source range. Returns `base` unchanged if `target` is inside or on its
895
+ * border (no overshoot on either axis).
896
+ */
897
+ declare function extendRangeForFill(base: NormalizedRange, target: {
898
+ row: number;
899
+ col: number;
900
+ }): NormalizedRange;
901
+
902
+ type NavigationDirection = "up" | "down" | "left" | "right";
903
+ interface CellSelectionOptions {
904
+ /** Every row id, in on-screen order — the FULL logical row list, not just currently-mounted virtual items, so navigation and range math work correctly regardless of virtualization. */
905
+ rowIds: string[];
906
+ /** Every editable-surface column id, in on-screen order. */
907
+ columnIds: string[];
908
+ /** Called when navigation lands on a row id that (per the caller) may not currently be mounted — e.g. to `virtualizer.scrollToIndex` it into view. Not called for a move that stays on the same row (left/right). */
909
+ onNavigateToRow?: (rowId: string) => void;
910
+ }
911
+ interface CellSelectionController {
912
+ /** The committed selection (mouseup already happened, or a keyboard move), or `undefined` before any cell has ever been selected. */
913
+ selection: CellRange | undefined;
914
+ /** True while a mouse-drag range-select is in progress (between `startSelection` and `endDrag`). */
915
+ isDragging: boolean;
916
+ /** `selection` extended live to the drag's current cell while `isDragging`; otherwise identical to `selection`. This is the range a renderer should actually paint. */
917
+ effectiveRange: CellRange | undefined;
918
+ /** Mousedown on `cell`: starts a fresh single-cell selection, or (`extend: true`, e.g. shift+mousedown) extends the existing selection's focus corner to `cell` while keeping its anchor. Either way, also opens a drag (see `endDrag`) — a plain click-no-move sequence still round-trips through `startSelection`/`endDrag` and ends up exactly the same as if `updateDrag` were never called. */
919
+ startSelection: (cell: CellAddress, options?: {
920
+ extend?: boolean;
921
+ }) => void;
922
+ /** Mousemove while dragging: updates the live drag-focus cell. Callers should coalesce rapid calls themselves (e.g. one `requestAnimationFrame`-scheduled flush per animation frame) — this hook applies whatever it's given without its own throttling. No-op if not currently dragging. */
923
+ updateDrag: (cell: CellAddress) => void;
924
+ /** Mouseup: commits the in-progress drag's live focus cell as the real selection. No-op if not currently dragging. */
925
+ endDrag: () => void;
926
+ /** Keyboard navigation: moves the focus corner by one row/column in `direction`, clamped to grid bounds. Collapses the selection to the new single cell unless `extend` (shift+arrow), which instead moves only the focus corner, keeping the existing anchor. */
927
+ moveSelection: (direction: NavigationDirection, options?: {
928
+ extend?: boolean;
929
+ }) => void;
930
+ /** Directly replaces the selection, e.g. to leave a specific cell selected after a paste/fill commits. */
931
+ setSelection: (range: CellRange) => void;
932
+ clearSelection: () => void;
933
+ /** True while a fill-handle drag is in progress (between `startFillDrag` and `endFillDrag`). Mutually exclusive with `isDragging` — starting one doesn't check for the other, but a caller should never let both begin at once (the fill handle and the rest of a cell are different mousedown targets). */
934
+ isFillDragging: boolean;
935
+ /** The selection extended toward the fill-drag's current target cell, axis-locked (see `extendRangeForFill`'s own doc) — the range a renderer should paint as the live fill preview. `undefined` when not fill-dragging, or before the first `updateFillDrag` call of a given drag. */
936
+ fillPreviewRange: CellRange | undefined;
937
+ /** Starts a fill-drag from the current `selection` (mousedown on the fill handle). No-op if there's no selection yet. */
938
+ startFillDrag: () => void;
939
+ /** Updates the fill-drag's live target cell (mousemove while fill-dragging). Callers should coalesce rapid calls themselves, same as `updateDrag`. No-op if not currently fill-dragging. */
940
+ updateFillDrag: (cell: CellAddress) => void;
941
+ /**
942
+ * Commits the fill-drag (mouseup): sets the extended range as the new
943
+ * `selection` and returns both the original source range and the final
944
+ * extended one, so the caller can compute the actual fill values (which
945
+ * source cell each newly-covered cell copies from) — that data-dependent
946
+ * computation doesn't belong in this hook, which only knows row/column
947
+ * ids, not row data. Returns `undefined` if not fill-dragging, there was
948
+ * no base selection, or no target cell was ever reported (a drag that
949
+ * never moved) — in the last case, nothing to commit, current selection
950
+ * unchanged.
951
+ */
952
+ endFillDrag: () => {
953
+ sourceRange: CellRange;
954
+ finalRange: CellRange;
955
+ } | undefined;
956
+ }
957
+ /**
958
+ * Owns the rectangular range-selection cursor for `cellEditing` mode: the
959
+ * committed selection, an in-progress mouse-drag's live extent, and
960
+ * keyboard navigation — all keyed by row-id/column-id (via `rowIds`/
961
+ * `columnIds`'s ordinal position), never by DOM node or virtualized index,
962
+ * so a selection is meaningful even for a row that isn't currently mounted.
963
+ */
964
+ declare function useCellSelection({ rowIds, columnIds, onNavigateToRow }: CellSelectionOptions): CellSelectionController;
965
+
966
+ /**
967
+ * Everything `renderCellModeCell` needs to render one cell under
968
+ * `cellEditing` mode. Bundled into one object handed through a `useRef` for
969
+ * the identical reason `EditingCellContext` is (see that type's own doc):
970
+ * `<DataGrid>`'s `tanstackColumns` memo must not depend on anything that
971
+ * changes per-keystroke, or typing into one cell would remount the whole
972
+ * table body.
973
+ */
974
+ interface CellEditingCellContext<TRow> {
975
+ /** The one cell currently showing its editor, or `undefined` — at most one at a time, the same convention `EditingCellContext.activeRowId` uses at row granularity. */
976
+ editingCell: CellAddress | undefined;
977
+ /** Replaces (doesn't append to) the editor's starting value when editing began by typing a printable character directly (Excel's "just start typing" gesture) rather than double-click/Enter/F2, which start from the cell's current value. `undefined` means "start from the current value." */
978
+ initialText: string | undefined;
979
+ /** Whether at least one `onChangeDraft` has fired this edit session — distinguishes "no keystrokes yet, show the starting value" from "the user explicitly typed something," including an empty string. */
980
+ hasDraft: boolean;
981
+ /** The in-progress typed value for `editingCell`, meaningful only when `hasDraft` is true. */
982
+ draftValue: unknown;
983
+ getRowId: (row: TRow) => string;
984
+ getError: (rowId: string, columnId: string) => string | undefined;
985
+ /**
986
+ * The value to actually display for one cell: `rawValue` (the row's real
987
+ * data) unless a still-"fresh" local override exists. An override is
988
+ * fresh only while `rawValue` still equals what it was at the moment of
989
+ * the edit (`previousValue`) — the instant the row's real data changes to
990
+ * ANYTHING else (the consumer's `onCellsChange` handler updated its own
991
+ * `data` to reflect the accepted edit, or a refetch landed with something
992
+ * different entirely, e.g. server-side normalization), real data is
993
+ * trusted again instead of the stale local echo. This is what lets a
994
+ * fire-and-forget, no-rollback model (see `CellEditingOptions.onCellsChange`'s
995
+ * own doc) still self-correct if the consumer's eventual real data
996
+ * diverges from what was optimistically shown.
997
+ */
998
+ resolveValue: (rowId: string, columnId: string, rawValue: unknown) => unknown;
999
+ /** Starts editing `cell`. */
1000
+ onBeginEdit: (cell: CellAddress, initialText?: string) => void;
1001
+ /** Reverts without committing — Escape, or a blur while the current draft is invalid. */
1002
+ onCancelEdit: () => void;
1003
+ /** Records a keystroke/selection's new value for the cell currently being edited — for an "atomic" editor (enum/boolean/date, where a single selection IS the complete gesture) the caller follows this immediately with `onCommitEdit`; for a free-text editor (string/number) this only buffers, and the caller commits separately on blur/Enter/Tab. */
1004
+ onChangeDraft: (value: unknown) => void;
1005
+ /** Commits the current draft (or, if `hasDraft` is false, effectively a no-op close) for the cell currently being edited, as one `CellChange`, and closes its editor. `previousValue` is the row's real value before this edit, for the resulting `CellChange.previousValue`. */
1006
+ onCommitEdit: (row: TRow, previousValue: unknown) => void;
1007
+ /**
1008
+ * Commits an explicit `value` directly, bypassing the draft state
1009
+ * entirely — for an "atomic" editor (enum/boolean/date) whose `onChange`
1010
+ * fires once with the complete new value and wants to commit in that same
1011
+ * call. `onCommitEdit` above can't be reused for this: it reads `hasDraft`/
1012
+ * `draftValue` from React state, which a same-tick `onChangeDraft` call
1013
+ * immediately before it would NOT yet reflect (state updates don't apply
1014
+ * until the next render, even within the same event handler).
1015
+ */
1016
+ onCommitValue: (row: TRow, previousValue: unknown, value: unknown) => void;
1017
+ onSetError: (rowId: string, columnId: string, message: string | undefined) => void;
1018
+ /**
1019
+ * Reads (and clears) whether `onCommitEdit`/`onCommitValue` committed
1020
+ * synchronously during the current call stack. `<DataGrid>`'s own keydown
1021
+ * handler needs this: `renderCellModeCell`'s Enter/Tab commit deliberately
1022
+ * doesn't stop the event from bubbling, so that handler can advance the
1023
+ * selection afterward — but `editingCell` itself is React state, which
1024
+ * doesn't reflect `closeEditor()`'s update until the next render, so
1025
+ * reading it directly during that same bubbled dispatch always still
1026
+ * shows the row that JUST closed. This ref-backed flag is the one piece of
1027
+ * "did a commit just happen in this exact gesture" state that's readable
1028
+ * synchronously, in the same event, immediately after the commit.
1029
+ */
1030
+ consumeJustCommitted: () => boolean;
1031
+ }
1032
+ interface CellEditingState<TRow> {
1033
+ /** The live context, read directly by a render path with no remount-avoidance concerns of its own. */
1034
+ ctx: CellEditingCellContext<TRow> | undefined;
1035
+ /** Same context, mirrored into a ref every render — see `CellEditingCellContext`'s own doc for why `<DataGrid>`'s per-cell closures must read through this instead of closing over `ctx` directly. */
1036
+ ctxRef: {
1037
+ current: CellEditingCellContext<TRow> | undefined;
1038
+ };
1039
+ /** Commits a whole gesture's changes at once (paste, fill-drag) — no `editingCell` involved, unlike `onCommitEdit` above. */
1040
+ applyChanges: (changes: CellChange<TRow>[]) => void;
1041
+ }
1042
+ /**
1043
+ * Owns the "true spreadsheet" per-cell edit state: which single cell (if
1044
+ * any) is showing its editor, that cell's in-progress typed draft, per-cell
1045
+ * validation errors, and the local-override echo every committed change
1046
+ * leaves behind until the consumer's own real data catches up. Every
1047
+ * mutation — a typed commit, a future paste, a future fill-drag — funnels
1048
+ * through `applyChanges`, so a later undo/redo can be built on top of that
1049
+ * one function without a rearchitecture.
1050
+ */
1051
+ declare function useCellEditingState<TRow>(cellEditing: CellEditingOptions<TRow> | undefined, getRowId: (row: TRow) => string): CellEditingState<TRow>;
1052
+
1053
+ /**
1054
+ * Renders one cell under `cellEditing` mode — the immediate-apply
1055
+ * counterpart to `edit/renderEditableCell.tsx`'s accumulate-then-save one.
1056
+ * Reuses the exact same per-type editor widgets via `registry.tsx`'s
1057
+ * `renderDefaultEditWidget` (and the same `column.renderEditCell`/
1058
+ * `column.validateEdit` escape hatches) — only the commit semantics differ.
1059
+ *
1060
+ * Not responsible for range-selection visuals — that's `SelectionOverlay`,
1061
+ * one overlay for the whole grid — this only decides whether THIS cell is
1062
+ * the one currently showing its editor.
1063
+ */
1064
+ declare function renderCellModeCell<TRow>(column: ColumnDef<TRow>, row: TRow, rawValue: unknown, ctx: CellEditingCellContext<TRow> | undefined): ReactNode;
1065
+
1066
+ /**
1067
+ * Renders the current range selection (and, while a fill-handle drag is in
1068
+ * progress, a dashed preview of the range it would extend to) as absolutely-
1069
+ * positioned overlay divs, geometry read live from the DOM rects of the
1070
+ * relevant cells' `data-cell-row`/`data-cell-col` nodes — deliberately not
1071
+ * derived from `columnSizing`/row-height state, since reading the live DOM
1072
+ * always matches exactly what's rendered (including natural, non-resizing
1073
+ * column widths `columnSizing`'s own math doesn't account for).
1074
+ *
1075
+ * Must render as a CHILD of the scrollable container (not a sibling outside
1076
+ * it), with that container given `position: relative` — position/left/top
1077
+ * are computed in the container's own unscrolled content-space (rect minus
1078
+ * the container's rect, plus the container's current scroll offset), so once
1079
+ * set, the overlay scrolls together with the table content via ordinary
1080
+ * browser scrolling with no scroll listener of its own. A virtualized grid's
1081
+ * scroll-driven re-renders (the virtualizer's own React state) naturally
1082
+ * recompute this on every such render; a non-virtualized grid never needs to
1083
+ * (native scroll changes nothing in content-space).
1084
+ *
1085
+ * Deliberately isolated from `visibleColumns`/`tanstackColumns` — it reads
1086
+ * nothing from either, only `range`/`fillPreviewRange` and the DOM — so
1087
+ * painting/updating a selection or a fill-drag (including during a fast
1088
+ * mouse-drag) never touches the column-definition memo that would otherwise
1089
+ * remount the whole table body.
1090
+ */
1091
+ declare function SelectionOverlay({ containerRef, range, fillPreviewRange, onFillHandleMouseDown, }: {
1092
+ containerRef: {
1093
+ current: HTMLDivElement | null;
1094
+ };
1095
+ range: CellRange | undefined;
1096
+ /** The live preview range during a fill-handle drag, or `undefined` when not fill-dragging. */
1097
+ fillPreviewRange?: CellRange | undefined;
1098
+ /** Mousedown on the fill handle — starts a fill-drag. Omit to render no handle at all (e.g. a read-only or non-editable selection). */
1099
+ onFillHandleMouseDown?: (event: MouseEvent<HTMLDivElement>) => void;
1100
+ }): ReactElement | null;
1101
+
1102
+ /**
1103
+ * Coerces one pasted/filled raw TSV cell string into the typed value a
1104
+ * column's editor/validator expects — the paste/fill equivalent of
1105
+ * `edit/registry.tsx`'s per-type dispatch, reusing the exact same `toDate`/
1106
+ * `toNumber` parsing `column/format.ts` uses for display so paste coercion
1107
+ * can't drift from what the grid already shows.
1108
+ *
1109
+ * Returns `undefined` for a value that can't be coerced at all (e.g. "abc"
1110
+ * into a number column) — the caller's job to skip that cell rather than
1111
+ * hand `onCellsChange` a `NaN`/`Invalid Date`. An empty string coerces to
1112
+ * `null` for `number`/`currency` (pasting a blank cell over a numeric one
1113
+ * clears it, matching `NumberEditor`'s own null-for-cleared convention) —
1114
+ * `string` columns instead treat an empty string as itself a valid value.
1115
+ */
1116
+ declare function coerceValueForColumn<TRow>(column: ColumnDef<TRow>, raw: string): {
1117
+ value: unknown;
1118
+ } | undefined;
1119
+
1120
+ /**
1121
+ * Serializes the cells inside `range` to Excel-compatible TSV text: tab-
1122
+ * separated columns, newline-separated rows, walking `rowIds`/`columnIds` in
1123
+ * their on-screen order (not `range`'s own anchor/focus order). Each cell's
1124
+ * text comes from `column.formatForClipboard` if the column supplies one,
1125
+ * else the same `defaultFormat` display text the grid itself renders —
1126
+ * deliberately not a second "raw value to string" path.
1127
+ */
1128
+ declare function rangeToTsv<TRow>(range: CellRange, rows: TRow[], rowIds: string[], columns: ColumnDef<TRow>[], columnIds: string[], getRowId: (row: TRow) => string): string;
1129
+ /**
1130
+ * Parses Excel-compatible TSV/tab-delimited clipboard text into a 2D array
1131
+ * of raw string cells. A small character-by-character state machine (not a
1132
+ * naive split-by-newline-then-split-by-tab) so a quoted field's own embedded
1133
+ * newline — a genuine multi-line cell copied from Excel/Sheets — isn't
1134
+ * mistaken for a row boundary, and a doubled `""` inside a quoted field
1135
+ * unescapes to one literal `"`.
1136
+ */
1137
+ declare function parseTsv(text: string): string[][];
1138
+
1139
+ /**
1140
+ * Computes the `CellChange`s a fill-handle drag produces: every cell newly
1141
+ * covered by `finalRange` but not already inside `sourceRange` copies its
1142
+ * value from the corresponding source cell, TILING the source pattern across
1143
+ * the fill direction when the extension is longer than the source itself —
1144
+ * the same repeat-the-pattern behavior Excel's own fill-handle uses for a
1145
+ * multi-cell source (a single-cell source is just the degenerate case: every
1146
+ * new cell copies that one cell). Value-copy only, no numeric/date
1147
+ * auto-increment — MVP scope, matching the akeneo hand-rolled
1148
+ * implementation this feature replaces, which also only ever copies a
1149
+ * single value uniformly.
1150
+ *
1151
+ * Cells already inside `sourceRange` are left out of the result entirely —
1152
+ * they're untouched by a fill, only the newly-covered ones change.
1153
+ * A target cell that's missing, not editable, or whose source cell is
1154
+ * missing is silently skipped.
1155
+ */
1156
+ declare function computeFillChanges<TRow>(sourceRange: CellRange, finalRange: CellRange, rowIds: string[], columnIds: string[], columns: ColumnDef<TRow>[], getRow: (rowId: string) => TRow | undefined): CellChange<TRow>[];
1157
+
787
1158
  /**
788
1159
  * Column visibility keyed by `ColumnDef.id`. A missing key means visible —
789
1160
  * this matches TanStack Table's own `VisibilityState` convention, so a
@@ -1151,6 +1522,16 @@ interface DataGridProps<TRow> {
1151
1522
  * contract — shared with `<TreeDataGrid>`'s own `editing` prop.
1152
1523
  */
1153
1524
  editing?: EditingOptions<TRow>;
1525
+ /**
1526
+ * Enables the "true spreadsheet" cell-editing mode: click/type directly
1527
+ * into any cell, drag or shift+arrow to range-select, paste, drag a
1528
+ * fill-handle — every change applies immediately, with no row-level
1529
+ * Save/Discard gate. This is a different, mutually exclusive state machine
1530
+ * from `editing` above, not a superset of it — set at most one of the two
1531
+ * on a given `<DataGrid>`; `editing` is ignored once `cellEditing` is set.
1532
+ * See `CellEditingOptions` for the full contract.
1533
+ */
1534
+ cellEditing?: CellEditingOptions<TRow>;
1154
1535
  }
1155
1536
 
1156
1537
  /**
@@ -1161,7 +1542,7 @@ interface DataGridProps<TRow> {
1161
1542
  * (`"server"`). Column filter UI defaults to the type-appropriate widget
1162
1543
  * from `src/filter` unless a column supplies its own `cell` renderer.
1163
1544
  */
1164
- declare function DataGrid<TRow extends RowData>({ columns, dataSource, getRowId, rowActions, headerActions, enableMultiSort, initialState, columnVisibility, renderDetail, enableColumnResizing, columnResizeMode, columnSizing: controlledColumnSizing, onColumnSizingChange, virtualize, showPagination, getRowProps, getRowTestId, gridState, selectedIds: controlledSelectedIds, onSelectedIdsChange, testId, loading: loadingProp, groupBy, renderGroupHeader, defaultGroupsExpanded, expandedGroups: controlledExpandedGroups, onExpandedGroupsChange, zebra, editing, }: DataGridProps<TRow>): ReactElement;
1545
+ declare function DataGrid<TRow extends RowData>({ columns, dataSource, getRowId, rowActions, headerActions, enableMultiSort, initialState, columnVisibility, renderDetail, enableColumnResizing, columnResizeMode, columnSizing: controlledColumnSizing, onColumnSizingChange, virtualize, showPagination, getRowProps, getRowTestId, gridState, selectedIds: controlledSelectedIds, onSelectedIdsChange, testId, loading: loadingProp, groupBy, renderGroupHeader, defaultGroupsExpanded, expandedGroups: controlledExpandedGroups, onExpandedGroupsChange, zebra, editing, cellEditing, }: DataGridProps<TRow>): ReactElement;
1165
1546
 
1166
1547
  interface GridStateController {
1167
1548
  state: GridState;
@@ -1581,4 +1962,4 @@ interface UseTreeStateOptions<TRow> extends TreeAccessors<TRow> {
1581
1962
  */
1582
1963
  declare function useTreeState<TRow>({ data, getRowId, getChildren, hasChildren, onLoadChildren, expanded: controlledExpanded, onExpandedChange, childrenMap: controlledChildrenMap, onChildrenMapChange, }: UseTreeStateOptions<TRow>): TreeStateController<TRow>;
1583
1964
 
1584
- export { ActionsMenu, type BaseColumn, type BooleanColumn, BooleanEditor, BooleanFilter, type ClientProcessingResult, type ColumnAlign, type ColumnDef, type ColumnGroup, ColumnSelector, type ColumnSelectorProps, type ColumnVisibility, type CompositeFilterDescriptor, DataGrid, type DataGridProps, type DataSource, type DateColumn, DateEditor, DateRangeFilter, type DebouncedCallback, EditFieldError, type EditWidgetProps, type EditedRow, EditingBar, type EditingBarProps, type EditingCellContext, type EditingOptions, type EditingState, type EnumColumn, EnumEditor, EnumFilter, type EnumOption, type FilterDescriptor, type FilterOperator, type FilterWidgetProps, type FlatTreeRow, type GridState, type GridStateController, type GroupExpansionController, type MenuItem, type MenuItemContext, type NumberColumn, NumberComparisonFilter, NumberEditor, NumberHistogramFilter, type NumberHistogramFilterProps, NumberRangeFilter, type PendingRowEdit, type ResolvedMenuItem, type SortDescriptor, type StickyGroupHeaderTop, type StringColumn, StringEditor, StringFilter, type TreeAccessors, TreeDataGrid, type TreeDataGridProps, type TreeStateController, UNARY_OPERATORS, type UseTreeStateOptions, alignClassName, canHideColumn, canHideGroup, countVisible, defaultAlign, defaultFormat, editErrorId, evaluateFilter, facetedNumberValues, fieldKey, flattenTree, getColumnValue, groupColumns, isColumnVisible, isCompositeFilterDescriptor, isEditable, isFilterDescriptor, isFilterable, isSortable, processClientData, renderDefaultEditWidget, renderDefaultFilterWidget, renderEditableCell, resolveMenuItems, storageKeyFor, toDate, useDebouncedCallback, useEditingState, useGridState, useGroupExpansion, useStickyGroupHeaderTop, useTreeState, useVisibleColumns };
1965
+ export { ActionsMenu, type BaseColumn, type BooleanColumn, BooleanEditor, BooleanFilter, type CellAddress, type CellChange, type CellEditingCellContext, type CellEditingOptions, type CellEditingState, type CellRange, type CellSelectionController, type CellSelectionOptions, type ClientProcessingResult, type ColumnAlign, type ColumnDef, type ColumnGroup, ColumnSelector, type ColumnSelectorProps, type ColumnVisibility, type CompositeFilterDescriptor, DataGrid, type DataGridProps, type DataSource, type DateColumn, DateEditor, DateRangeFilter, type DebouncedCallback, EditFieldError, type EditWidgetProps, type EditedRow, EditingBar, type EditingBarProps, type EditingCellContext, type EditingOptions, type EditingState, type EnumColumn, EnumEditor, EnumFilter, type EnumOption, type FilterDescriptor, type FilterOperator, type FilterWidgetProps, type FlatTreeRow, type GridState, type GridStateController, type GroupExpansionController, type MenuItem, type MenuItemContext, MultilineStringEditor, type NavigationDirection, type NormalizedRange, type NumberColumn, NumberComparisonFilter, NumberEditor, NumberHistogramFilter, type NumberHistogramFilterProps, NumberRangeFilter, type PendingRowEdit, type ResolvedMenuItem, SelectionOverlay, type SortDescriptor, type StickyGroupHeaderTop, type StringColumn, StringEditor, StringFilter, type TreeAccessors, TreeDataGrid, type TreeDataGridProps, type TreeStateController, UNARY_OPERATORS, type UseTreeStateOptions, alignClassName, buildIndexMap, canHideColumn, canHideGroup, cellsInRange, coerceValueForColumn, computeFillChanges, countVisible, defaultAlign, defaultFormat, editErrorId, evaluateFilter, extendRangeForFill, facetedNumberValues, fieldKey, flattenTree, getColumnValue, groupColumns, isCellInRange, isColumnVisible, isCompositeFilterDescriptor, isEditable, isFilterDescriptor, isFilterable, isSortable, normalizeRange, parseTsv, processClientData, rangeToTsv, renderCellModeCell, renderDefaultEditWidget, renderDefaultFilterWidget, renderEditableCell, resolveMenuItems, storageKeyFor, toDate, useCellEditingState, useCellSelection, useDebouncedCallback, useEditingState, useGridState, useGroupExpansion, useStickyGroupHeaderTop, useTreeState, useVisibleColumns };