@bmsuisse/datagrid 0.29.0 → 0.31.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,375 @@ 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
+ * Renders every editable cell's editor directly, all the time — no click,
868
+ * double-click, F2, or typed character needed to open one first. Selection
869
+ * (drag/shift+click/shift+arrow), copy, paste, and the fill-handle all
870
+ * still work exactly as they do without this flag; only the single-cell
871
+ * "one cell editing at a time" gate is gone, since with this on every
872
+ * editable cell already IS its own editor. A buffered (string/number/
873
+ * currency) editor still only commits on blur/Enter/Tab, same as always —
874
+ * this flag just means it's already open, not that it types straight
875
+ * through to `onCellsChange` on every keystroke. Defaults to false.
876
+ */
877
+ alwaysEdit?: boolean;
878
+ }
879
+
880
+ /** 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. */
881
+ declare function buildIndexMap(ids: string[]): Map<string, number>;
882
+ /** A `CellRange`'s two corners resolved to ordinal row/column indices, with `start <= end` on both axes regardless of which corner `anchor`/`focus` was. */
883
+ interface NormalizedRange {
884
+ rowStart: number;
885
+ rowEnd: number;
886
+ colStart: number;
887
+ colEnd: number;
888
+ }
889
+ /**
890
+ * Resolves a `CellRange` to ordinal indices via `rowIndex`/`columnIndex`
891
+ * (from `buildIndexMap`), or `undefined` if either endpoint's row or column
892
+ * id no longer exists — e.g. the row was removed from `data` since the
893
+ * selection was made.
894
+ */
895
+ declare function normalizeRange(range: CellRange, rowIndex: Map<string, number>, columnIndex: Map<string, number>): NormalizedRange | undefined;
896
+ /** True if `cell` falls inside `range` (inclusive on all sides). */
897
+ declare function isCellInRange(cell: CellAddress, range: CellRange, rowIndex: Map<string, number>, columnIndex: Map<string, number>): boolean;
898
+ /** 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. */
899
+ declare function cellsInRange(range: CellRange, rowIds: string[], columnIds: string[], rowIndex: Map<string, number>, columnIndex: Map<string, number>): CellAddress[];
900
+ /**
901
+ * Extends a fill-handle drag's `base` range (the source selection, already
902
+ * normalized) toward `target`, locked to whichever single axis — row or
903
+ * column — has the larger overshoot past `base`'s own edge on that axis.
904
+ * Matches Excel/Sheets' own autofill behavior: dragging the fill handle
905
+ * never extends diagonally, only straight down/up/left/right from the
906
+ * source range. Returns `base` unchanged if `target` is inside or on its
907
+ * border (no overshoot on either axis).
908
+ */
909
+ declare function extendRangeForFill(base: NormalizedRange, target: {
910
+ row: number;
911
+ col: number;
912
+ }): NormalizedRange;
913
+
914
+ type NavigationDirection = "up" | "down" | "left" | "right";
915
+ interface CellSelectionOptions {
916
+ /** 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. */
917
+ rowIds: string[];
918
+ /** Every editable-surface column id, in on-screen order. */
919
+ columnIds: string[];
920
+ /** 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). */
921
+ onNavigateToRow?: (rowId: string) => void;
922
+ }
923
+ interface CellSelectionController {
924
+ /** The committed selection (mouseup already happened, or a keyboard move), or `undefined` before any cell has ever been selected. */
925
+ selection: CellRange | undefined;
926
+ /** True while a mouse-drag range-select is in progress (between `startSelection` and `endDrag`). */
927
+ isDragging: boolean;
928
+ /** `selection` extended live to the drag's current cell while `isDragging`; otherwise identical to `selection`. This is the range a renderer should actually paint. */
929
+ effectiveRange: CellRange | undefined;
930
+ /** 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. */
931
+ startSelection: (cell: CellAddress, options?: {
932
+ extend?: boolean;
933
+ }) => void;
934
+ /** 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. */
935
+ updateDrag: (cell: CellAddress) => void;
936
+ /** Mouseup: commits the in-progress drag's live focus cell as the real selection. No-op if not currently dragging. */
937
+ endDrag: () => void;
938
+ /** 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. */
939
+ moveSelection: (direction: NavigationDirection, options?: {
940
+ extend?: boolean;
941
+ }) => void;
942
+ /** Directly replaces the selection, e.g. to leave a specific cell selected after a paste/fill commits. */
943
+ setSelection: (range: CellRange) => void;
944
+ clearSelection: () => void;
945
+ /** 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). */
946
+ isFillDragging: boolean;
947
+ /** 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. */
948
+ fillPreviewRange: CellRange | undefined;
949
+ /** Starts a fill-drag from the current `selection` (mousedown on the fill handle). No-op if there's no selection yet. */
950
+ startFillDrag: () => void;
951
+ /** 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. */
952
+ updateFillDrag: (cell: CellAddress) => void;
953
+ /**
954
+ * Commits the fill-drag (mouseup): sets the extended range as the new
955
+ * `selection` and returns both the original source range and the final
956
+ * extended one, so the caller can compute the actual fill values (which
957
+ * source cell each newly-covered cell copies from) — that data-dependent
958
+ * computation doesn't belong in this hook, which only knows row/column
959
+ * ids, not row data. Returns `undefined` if not fill-dragging, there was
960
+ * no base selection, or no target cell was ever reported (a drag that
961
+ * never moved) — in the last case, nothing to commit, current selection
962
+ * unchanged.
963
+ */
964
+ endFillDrag: () => {
965
+ sourceRange: CellRange;
966
+ finalRange: CellRange;
967
+ } | undefined;
968
+ }
969
+ /**
970
+ * Owns the rectangular range-selection cursor for `cellEditing` mode: the
971
+ * committed selection, an in-progress mouse-drag's live extent, and
972
+ * keyboard navigation — all keyed by row-id/column-id (via `rowIds`/
973
+ * `columnIds`'s ordinal position), never by DOM node or virtualized index,
974
+ * so a selection is meaningful even for a row that isn't currently mounted.
975
+ */
976
+ declare function useCellSelection({ rowIds, columnIds, onNavigateToRow }: CellSelectionOptions): CellSelectionController;
977
+
978
+ /**
979
+ * Everything `renderCellModeCell` needs to render one cell under
980
+ * `cellEditing` mode. Bundled into one object handed through a `useRef` for
981
+ * the identical reason `EditingCellContext` is (see that type's own doc):
982
+ * `<DataGrid>`'s `tanstackColumns` memo must not depend on anything that
983
+ * changes per-keystroke, or typing into one cell would remount the whole
984
+ * table body.
985
+ */
986
+ interface CellEditingCellContext<TRow> {
987
+ /** Mirrors `CellEditingOptions.alwaysEdit` — every editable cell renders `AlwaysEditCell` instead of going through `editingCell`/`onBeginEdit` at all when this is set. */
988
+ alwaysEdit: boolean;
989
+ /** Mirrors `CellEditingOptions.disabled`. Click-to-edit mode blocks interaction by simply never opening an editor while this is set (see `onBeginEdit`'s own gate) — `AlwaysEditCell`'s editors are always open regardless, so it reads this directly to disable its rendered form control instead. */
990
+ disabled: boolean;
991
+ /** The one cell currently showing its editor, or `undefined` — at most one at a time, the same convention `EditingCellContext.activeRowId` uses at row granularity. Meaningless (always `undefined`) under `alwaysEdit`, where every editable cell already has its own editor. */
992
+ editingCell: CellAddress | undefined;
993
+ /** 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." */
994
+ initialText: string | undefined;
995
+ /** 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. */
996
+ hasDraft: boolean;
997
+ /** The in-progress typed value for `editingCell`, meaningful only when `hasDraft` is true. */
998
+ draftValue: unknown;
999
+ getRowId: (row: TRow) => string;
1000
+ getError: (rowId: string, columnId: string) => string | undefined;
1001
+ /**
1002
+ * The value to actually display for one cell: `rawValue` (the row's real
1003
+ * data) unless a still-"fresh" local override exists. An override is
1004
+ * fresh only while `rawValue` still equals what it was at the moment of
1005
+ * the edit (`previousValue`) — the instant the row's real data changes to
1006
+ * ANYTHING else (the consumer's `onCellsChange` handler updated its own
1007
+ * `data` to reflect the accepted edit, or a refetch landed with something
1008
+ * different entirely, e.g. server-side normalization), real data is
1009
+ * trusted again instead of the stale local echo. This is what lets a
1010
+ * fire-and-forget, no-rollback model (see `CellEditingOptions.onCellsChange`'s
1011
+ * own doc) still self-correct if the consumer's eventual real data
1012
+ * diverges from what was optimistically shown.
1013
+ */
1014
+ resolveValue: (rowId: string, columnId: string, rawValue: unknown) => unknown;
1015
+ /** Starts editing `cell`. */
1016
+ onBeginEdit: (cell: CellAddress, initialText?: string) => void;
1017
+ /** Reverts without committing — Escape, or a blur while the current draft is invalid. */
1018
+ onCancelEdit: () => void;
1019
+ /** 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. */
1020
+ onChangeDraft: (value: unknown) => void;
1021
+ /** 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`. */
1022
+ onCommitEdit: (row: TRow, previousValue: unknown) => void;
1023
+ /**
1024
+ * Commits an explicit `value` directly, bypassing the draft state
1025
+ * entirely — for an "atomic" editor (enum/boolean/date) whose `onChange`
1026
+ * fires once with the complete new value and wants to commit in that same
1027
+ * call. `onCommitEdit` above can't be reused for this: it reads `hasDraft`/
1028
+ * `draftValue` from React state, which a same-tick `onChangeDraft` call
1029
+ * immediately before it would NOT yet reflect (state updates don't apply
1030
+ * until the next render, even within the same event handler).
1031
+ */
1032
+ onCommitValue: (row: TRow, previousValue: unknown, value: unknown) => void;
1033
+ onSetError: (rowId: string, columnId: string, message: string | undefined) => void;
1034
+ /** Commits one independently-managed cell's change directly — for `AlwaysEditCell`, which owns its own draft state locally rather than through `editingCell`/`onChangeDraft`/`onCommitEdit`'s single shared slot (every editable cell needs its own simultaneous editor under `alwaysEdit`, not just one at a time). */
1035
+ applyChange: (change: CellChange<TRow>) => void;
1036
+ /**
1037
+ * Reads (and clears) whether `onCommitEdit`/`onCommitValue` committed
1038
+ * synchronously during the current call stack. `<DataGrid>`'s own keydown
1039
+ * handler needs this: `renderCellModeCell`'s Enter/Tab commit deliberately
1040
+ * doesn't stop the event from bubbling, so that handler can advance the
1041
+ * selection afterward — but `editingCell` itself is React state, which
1042
+ * doesn't reflect `closeEditor()`'s update until the next render, so
1043
+ * reading it directly during that same bubbled dispatch always still
1044
+ * shows the row that JUST closed. This ref-backed flag is the one piece of
1045
+ * "did a commit just happen in this exact gesture" state that's readable
1046
+ * synchronously, in the same event, immediately after the commit.
1047
+ */
1048
+ consumeJustCommitted: () => boolean;
1049
+ }
1050
+ interface CellEditingState<TRow> {
1051
+ /** The live context, read directly by a render path with no remount-avoidance concerns of its own. */
1052
+ ctx: CellEditingCellContext<TRow> | undefined;
1053
+ /** 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. */
1054
+ ctxRef: {
1055
+ current: CellEditingCellContext<TRow> | undefined;
1056
+ };
1057
+ /** Commits a whole gesture's changes at once (paste, fill-drag) — no `editingCell` involved, unlike `onCommitEdit` above. */
1058
+ applyChanges: (changes: CellChange<TRow>[]) => void;
1059
+ }
1060
+ /**
1061
+ * Owns the "true spreadsheet" per-cell edit state: which single cell (if
1062
+ * any) is showing its editor, that cell's in-progress typed draft, per-cell
1063
+ * validation errors, and the local-override echo every committed change
1064
+ * leaves behind until the consumer's own real data catches up. Every
1065
+ * mutation — a typed commit, a future paste, a future fill-drag — funnels
1066
+ * through `applyChanges`, so a later undo/redo can be built on top of that
1067
+ * one function without a rearchitecture.
1068
+ */
1069
+ declare function useCellEditingState<TRow>(cellEditing: CellEditingOptions<TRow> | undefined, getRowId: (row: TRow) => string): CellEditingState<TRow>;
1070
+
1071
+ /**
1072
+ * Renders one cell under `cellEditing` mode — the immediate-apply
1073
+ * counterpart to `edit/renderEditableCell.tsx`'s accumulate-then-save one.
1074
+ * Reuses the exact same per-type editor widgets via `registry.tsx`'s
1075
+ * `renderDefaultEditWidget` (and the same `column.renderEditCell`/
1076
+ * `column.validateEdit` escape hatches) — only the commit semantics differ.
1077
+ *
1078
+ * Not responsible for range-selection visuals — that's `SelectionOverlay`,
1079
+ * one overlay for the whole grid — this only decides whether THIS cell is
1080
+ * the one currently showing its editor.
1081
+ */
1082
+ declare function renderCellModeCell<TRow>(column: ColumnDef<TRow>, row: TRow, rawValue: unknown, ctx: CellEditingCellContext<TRow> | undefined): ReactNode;
1083
+
1084
+ /**
1085
+ * Renders the current range selection (and, while a fill-handle drag is in
1086
+ * progress, a dashed preview of the range it would extend to) as absolutely-
1087
+ * positioned overlay divs, geometry read live from the DOM rects of the
1088
+ * relevant cells' `data-cell-row`/`data-cell-col` nodes — deliberately not
1089
+ * derived from `columnSizing`/row-height state, since reading the live DOM
1090
+ * always matches exactly what's rendered (including natural, non-resizing
1091
+ * column widths `columnSizing`'s own math doesn't account for).
1092
+ *
1093
+ * Must render as a CHILD of the scrollable container (not a sibling outside
1094
+ * it), with that container given `position: relative` — position/left/top
1095
+ * are computed in the container's own unscrolled content-space (rect minus
1096
+ * the container's rect, plus the container's current scroll offset), so once
1097
+ * set, the overlay scrolls together with the table content via ordinary
1098
+ * browser scrolling with no scroll listener of its own. A virtualized grid's
1099
+ * scroll-driven re-renders (the virtualizer's own React state) naturally
1100
+ * recompute this on every such render; a non-virtualized grid never needs to
1101
+ * (native scroll changes nothing in content-space).
1102
+ *
1103
+ * Deliberately isolated from `visibleColumns`/`tanstackColumns` — it reads
1104
+ * nothing from either, only `range`/`fillPreviewRange` and the DOM — so
1105
+ * painting/updating a selection or a fill-drag (including during a fast
1106
+ * mouse-drag) never touches the column-definition memo that would otherwise
1107
+ * remount the whole table body.
1108
+ */
1109
+ declare function SelectionOverlay({ containerRef, range, fillPreviewRange, onFillHandleMouseDown, }: {
1110
+ containerRef: {
1111
+ current: HTMLDivElement | null;
1112
+ };
1113
+ range: CellRange | undefined;
1114
+ /** The live preview range during a fill-handle drag, or `undefined` when not fill-dragging. */
1115
+ fillPreviewRange?: CellRange | undefined;
1116
+ /** 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). */
1117
+ onFillHandleMouseDown?: (event: MouseEvent<HTMLDivElement>) => void;
1118
+ }): ReactElement | null;
1119
+
1120
+ /**
1121
+ * Coerces one pasted/filled raw TSV cell string into the typed value a
1122
+ * column's editor/validator expects — the paste/fill equivalent of
1123
+ * `edit/registry.tsx`'s per-type dispatch, reusing the exact same `toDate`/
1124
+ * `toNumber` parsing `column/format.ts` uses for display so paste coercion
1125
+ * can't drift from what the grid already shows.
1126
+ *
1127
+ * Returns `undefined` for a value that can't be coerced at all (e.g. "abc"
1128
+ * into a number column) — the caller's job to skip that cell rather than
1129
+ * hand `onCellsChange` a `NaN`/`Invalid Date`. An empty string coerces to
1130
+ * `null` for `number`/`currency` (pasting a blank cell over a numeric one
1131
+ * clears it, matching `NumberEditor`'s own null-for-cleared convention) —
1132
+ * `string` columns instead treat an empty string as itself a valid value.
1133
+ * An Excel/Sheets error string (`#N/A`, `#DIV/0!`, etc.) pasted into any
1134
+ * column but `string` is likewise skipped rather than coerced.
1135
+ */
1136
+ declare function coerceValueForColumn<TRow>(column: ColumnDef<TRow>, raw: string): {
1137
+ value: unknown;
1138
+ } | undefined;
1139
+
1140
+ /**
1141
+ * Serializes the cells inside `range` to Excel-compatible TSV text: tab-
1142
+ * separated columns, newline-separated rows, walking `rowIds`/`columnIds` in
1143
+ * their on-screen order (not `range`'s own anchor/focus order). Each cell's
1144
+ * text comes from `column.formatForClipboard` if the column supplies one,
1145
+ * else the same `defaultFormat` display text the grid itself renders —
1146
+ * deliberately not a second "raw value to string" path.
1147
+ */
1148
+ declare function rangeToTsv<TRow>(range: CellRange, rows: TRow[], rowIds: string[], columns: ColumnDef<TRow>[], columnIds: string[], getRowId: (row: TRow) => string): string;
1149
+ /**
1150
+ * Parses Excel-compatible TSV/tab-delimited clipboard text into a 2D array
1151
+ * of raw string cells. A small character-by-character state machine (not a
1152
+ * naive split-by-newline-then-split-by-tab) so a quoted field's own embedded
1153
+ * newline — a genuine multi-line cell copied from Excel/Sheets — isn't
1154
+ * mistaken for a row boundary, and a doubled `""` inside a quoted field
1155
+ * unescapes to one literal `"`.
1156
+ */
1157
+ declare function parseTsv(text: string): string[][];
1158
+
1159
+ /**
1160
+ * Computes the `CellChange`s a fill-handle drag produces: every cell newly
1161
+ * covered by `finalRange` but not already inside `sourceRange` copies its
1162
+ * value from the corresponding source cell, TILING the source pattern across
1163
+ * the fill direction when the extension is longer than the source itself —
1164
+ * the same repeat-the-pattern behavior Excel's own fill-handle uses for a
1165
+ * multi-cell source (a single-cell source is just the degenerate case: every
1166
+ * new cell copies that one cell). Value-copy only, no numeric/date
1167
+ * auto-increment — MVP scope, matching the akeneo hand-rolled
1168
+ * implementation this feature replaces, which also only ever copies a
1169
+ * single value uniformly.
1170
+ *
1171
+ * Cells already inside `sourceRange` are left out of the result entirely —
1172
+ * they're untouched by a fill, only the newly-covered ones change.
1173
+ * A target cell that's missing, not editable, or whose source cell is
1174
+ * missing is silently skipped.
1175
+ */
1176
+ declare function computeFillChanges<TRow>(sourceRange: CellRange, finalRange: CellRange, rowIds: string[], columnIds: string[], columns: ColumnDef<TRow>[], getRow: (rowId: string) => TRow | undefined): CellChange<TRow>[];
1177
+
787
1178
  /**
788
1179
  * Column visibility keyed by `ColumnDef.id`. A missing key means visible —
789
1180
  * this matches TanStack Table's own `VisibilityState` convention, so a
@@ -1151,6 +1542,16 @@ interface DataGridProps<TRow> {
1151
1542
  * contract — shared with `<TreeDataGrid>`'s own `editing` prop.
1152
1543
  */
1153
1544
  editing?: EditingOptions<TRow>;
1545
+ /**
1546
+ * Enables the "true spreadsheet" cell-editing mode: click/type directly
1547
+ * into any cell, drag or shift+arrow to range-select, paste, drag a
1548
+ * fill-handle — every change applies immediately, with no row-level
1549
+ * Save/Discard gate. This is a different, mutually exclusive state machine
1550
+ * from `editing` above, not a superset of it — set at most one of the two
1551
+ * on a given `<DataGrid>`; `editing` is ignored once `cellEditing` is set.
1552
+ * See `CellEditingOptions` for the full contract.
1553
+ */
1554
+ cellEditing?: CellEditingOptions<TRow>;
1154
1555
  }
1155
1556
 
1156
1557
  /**
@@ -1161,7 +1562,7 @@ interface DataGridProps<TRow> {
1161
1562
  * (`"server"`). Column filter UI defaults to the type-appropriate widget
1162
1563
  * from `src/filter` unless a column supplies its own `cell` renderer.
1163
1564
  */
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;
1565
+ 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
1566
 
1166
1567
  interface GridStateController {
1167
1568
  state: GridState;
@@ -1581,4 +1982,4 @@ interface UseTreeStateOptions<TRow> extends TreeAccessors<TRow> {
1581
1982
  */
1582
1983
  declare function useTreeState<TRow>({ data, getRowId, getChildren, hasChildren, onLoadChildren, expanded: controlledExpanded, onExpandedChange, childrenMap: controlledChildrenMap, onChildrenMapChange, }: UseTreeStateOptions<TRow>): TreeStateController<TRow>;
1583
1984
 
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 };
1985
+ 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 };