diff --git a/packages/core/src/cells/number-cell.tsx b/packages/core/src/cells/number-cell.tsx index 784c8ecac..5ae9bf4d5 100644 --- a/packages/core/src/cells/number-cell.tsx +++ b/packages/core/src/cells/number-cell.tsx @@ -9,6 +9,28 @@ const NumberOverlayEditor = React.lazy( async () => await import("../internal/data-grid-overlay-editor/private/number-overlay-editor.js") ); +function parseToNumberOrBigInt(str: string): number | bigint | undefined { + const trimmed = str.trim(); + if (trimmed === "") return undefined; + + // Check if it's a plain integer string. + if (/^-?\d+$/.test(trimmed)) { + try { + const big = BigInt(trimmed); + if (big >= BigInt(Number.MIN_SAFE_INTEGER) && big <= BigInt(Number.MAX_SAFE_INTEGER)) { + return Number(big); + } + return big; + } catch { + return undefined; + } + } + + // Otherwise, try to parse as a float. + const num = Number.parseFloat(trimmed); + return Number.isNaN(num) ? undefined : num; +} + export const numberCellRenderer: InternalCellRenderer = { getAccessibilityString: c => c.data?.toString() ?? "", kind: GridCellKind.Number, @@ -37,28 +59,46 @@ export const numberCellRenderer: InternalCellRenderer = { = BigInt(Number.MIN_SAFE_INTEGER) + ? Number(value.data) + : undefined + : value.data + } fixedDecimals={value.fixedDecimals} allowNegative={value.allowNegative} thousandSeparator={value.thousandSeparator} decimalSeparator={value.decimalSeparator} validatedSelection={validatedSelection} - onChange={x => + onChange={x => { + const newNumber = parseToNumberOrBigInt(x.value); onChange({ ...value, - data: Number.isNaN(x.floatValue ?? 0) ? 0 : x.floatValue, - }) - } + data: newNumber, + }); + }} /> ); }, onPaste: (toPaste, cell, details) => { - const newNumber = - typeof details.rawValue === "number" - ? details.rawValue - : Number.parseFloat(typeof details.rawValue === "string" ? details.rawValue : toPaste); - if (Number.isNaN(newNumber) || cell.data === newNumber) return undefined; + let newNumber: number | bigint | undefined; + + if (typeof details.rawValue === "number" || typeof details.rawValue === "bigint") { + newNumber = details.rawValue; + } else { + const strVal = typeof details.rawValue === "string" ? details.rawValue : toPaste; + newNumber = parseToNumberOrBigInt(strVal); + } + + if ( + newNumber === undefined || + (typeof newNumber === "number" && Number.isNaN(newNumber)) || + cell.data === newNumber + ) + return undefined; return { ...cell, data: newNumber, displayData: details.formattedString ?? cell.displayData }; }, }; diff --git a/packages/core/src/data-editor/copy-paste.ts b/packages/core/src/data-editor/copy-paste.ts index 256c497e2..47bb8c4ff 100644 --- a/packages/core/src/data-editor/copy-paste.ts +++ b/packages/core/src/data-editor/copy-paste.ts @@ -16,7 +16,7 @@ type StringArrayCellBuffer = { type BasicCellBuffer = { formatted: string; - rawValue: string | number | boolean | BooleanEmpty | BooleanIndeterminate | undefined; + rawValue: string | number | bigint | boolean | BooleanEmpty | BooleanIndeterminate | undefined; format: "string" | "number" | "boolean" | "url"; doNotEscape?: boolean; }; @@ -40,10 +40,10 @@ function convertCellToBuffer(cell: GridCell): CellBuffer { cell.data === true ? "TRUE" : cell.data === false - ? "FALSE" - : cell.data === BooleanIndeterminate - ? "INDETERMINATE" - : "", + ? "FALSE" + : cell.data === BooleanIndeterminate + ? "INDETERMINATE" + : "", rawValue: cell.data, format: "boolean", }; @@ -85,12 +85,14 @@ function convertCellToBuffer(cell: GridCell): CellBuffer { rawValue: cell.data, format: "string", }; - case GridCellKind.Number: + case GridCellKind.Number: { + const v = cell.data; return { formatted: cell.displayData, - rawValue: cell.data, + rawValue: v, format: "number", }; + } case GridCellKind.Loading: return { formatted: "#LOADING", diff --git a/packages/core/src/internal/data-grid/data-grid-types.ts b/packages/core/src/internal/data-grid/data-grid-types.ts index 665f3a301..b0ba0b3c5 100644 --- a/packages/core/src/internal/data-grid/data-grid-types.ts +++ b/packages/core/src/internal/data-grid/data-grid-types.ts @@ -353,7 +353,7 @@ export interface TextCell extends BaseGridCell { export interface NumberCell extends BaseGridCell { readonly kind: GridCellKind.Number; readonly displayData: string; - readonly data: number | undefined; + readonly data: number | bigint | undefined; readonly readonly?: boolean; readonly fixedDecimals?: number; readonly allowNegative?: boolean; diff --git a/packages/source/src/use-column-sort.ts b/packages/source/src/use-column-sort.ts index 61b25ab83..8164b4b63 100644 --- a/packages/source/src/use-column-sort.ts +++ b/packages/source/src/use-column-sort.ts @@ -26,8 +26,8 @@ function cellToSortData(c: GridCell): string { } } -function tryParse(val: string | number): number | string { - if (typeof val === "number") return val; +function tryParse(val: string | number | bigint): number | bigint | string { + if (typeof val === "number" || typeof val === "bigint") return val; if (val.length > 0) { const x = Number(val); if (!isNaN(x)) { @@ -37,21 +37,34 @@ function tryParse(val: string | number): number | string { return val; } -export function compareSmart(a: string | number, b: string | number): number { - a = tryParse(a); - b = tryParse(b); - if (typeof a === "string" && typeof b === "string") { - return a.localeCompare(b); - } else if (typeof a === "number" && typeof b === "number") { - if (a === b) return 0; - return a > b ? 1 : -1; - } else if (a == b) { - return 0; +export function compareSmart(a: string | number | bigint, b: string | number | bigint): number { + const pa = tryParse(a); + const pb = tryParse(b); + + if (typeof pa === "string" && typeof pb === "string") { + return pa.localeCompare(pb); } - return a > b ? 1 : -1; + + const aIsNumeric = typeof pa === "number" || typeof pa === "bigint"; + const bIsNumeric = typeof pb === "number" || typeof pb === "bigint"; + + if (aIsNumeric && !bIsNumeric) return -1; + if (!aIsNumeric && bIsNumeric) return 1; + if (!aIsNumeric && !bIsNumeric) return (pa as string).localeCompare(pb as string); + + if (pa == pb) return 0; + + // Both are numeric, potentially mixed. Convert to number for comparison. + // This may lose precision on huge BigInts, but it is the only pragmatic + // way to compare floats and bigints, and it avoids crashing. + const numA = Number(pa); + const numB = Number(pb); + + if (numA === numB) return 0; + return numA > numB ? 1 : -1; } -export function compareRaw(a: string | number, b: string | number) { +export function compareRaw(a: string | number | bigint, b: string | number | bigint) { if (a > b) return 1; if (a === b) return 0; return -1; @@ -78,11 +91,14 @@ export function useColumnSort(p: Props): Result { return Array.isArray(sort) ? sort : [sort]; }, [sort]); - const sortCols = React.useMemo(() => - sorts.map(s => { - const c = p.columns.findIndex(col => s.column === col || (col.id !== undefined && s.column.id === col.id)); - return c === -1 ? undefined : c; - }), + const sortCols = React.useMemo( + () => + sorts.map(s => { + const c = p.columns.findIndex( + col => s.column === col || (col.id !== undefined && s.column.id === col.id) + ); + return c === -1 ? undefined : c; + }), [sorts, p.columns] );