From 39e585c440338318b256a5e80b08ea329c717668 Mon Sep 17 00:00:00 2001 From: Isaac Hunja Date: Wed, 4 Mar 2026 06:55:32 +0300 Subject: [PATCH 1/2] fix(table-core): consistent alphanumeric sorting for mixed letter/digit strings The previous regex-based chunk splitting approach produced inconsistent sort order for mixed alphanumeric strings. When comparing 'apple1' vs 'appleA', the regex would split 'apple1' into ['apple', '1'] but leave 'appleA' as one chunk ['appleA'], causing misaligned comparisons where letters sorted before digits at the start of a string but after digits in the middle. This replaces the chunk-based approach with a character-by-character comparison that: - Compares non-digit characters lexicographically - Extracts and compares full digit sequences as numbers - Consistently sorts letters before digits at any position The exported `reSplitAlphaNumeric` regex is preserved (deprecated) for backwards compatibility. Closes #6174 --- packages/table-core/src/sortingFns.ts | 78 +++++++++++++++------------ 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/packages/table-core/src/sortingFns.ts b/packages/table-core/src/sortingFns.ts index b3c6e45c5b..999822c1e1 100644 --- a/packages/table-core/src/sortingFns.ts +++ b/packages/table-core/src/sortingFns.ts @@ -1,5 +1,8 @@ import { SortingFn } from './features/RowSorting' +/** + * @deprecated No longer used internally. Kept for backwards compatibility. + */ export const reSplitAlphaNumeric = /([0-9]+)/gm const alphanumeric: SortingFn = (rowA, rowB, columnId) => { @@ -67,51 +70,58 @@ function toString(a: any) { return '' } +function isDigitChar(ch: string): boolean { + return ch >= '0' && ch <= '9' +} + // Mixed sorting is slow, but very inclusive of many edge cases. // It handles numbers, mixed alphanumeric combinations, and even // null, undefined, and Infinity +// +// Uses a character-by-character approach to ensure consistent ordering +// between letters and digits regardless of their position in the string. +// Letters always sort before digits (e.g. "appleA" < "apple1"). function compareAlphanumeric(aStr: string, bStr: string) { - // Split on number groups, but keep the delimiter - // Then remove falsey split values - const a = aStr.split(reSplitAlphaNumeric).filter(Boolean) - const b = bStr.split(reSplitAlphaNumeric).filter(Boolean) - - // While - while (a.length && b.length) { - const aa = a.shift()! - const bb = b.shift()! - - const an = parseInt(aa, 10) - const bn = parseInt(bb, 10) - - const combo = [an, bn].sort() - - // Both are string - if (isNaN(combo[0]!)) { - if (aa > bb) { + let ai = 0 + let bi = 0 + + while (ai < aStr.length && bi < bStr.length) { + const aIsDigit = isDigitChar(aStr[ai]!) + const bIsDigit = isDigitChar(bStr[bi]!) + + if (aIsDigit && bIsDigit) { + // Both are digits - extract full numeric sequences and compare as numbers + let aNumStr = '' + let bNumStr = '' + while (ai < aStr.length && isDigitChar(aStr[ai]!)) { + aNumStr += aStr[ai] + ai++ + } + while (bi < bStr.length && isDigitChar(bStr[bi]!)) { + bNumStr += bStr[bi] + bi++ + } + const diff = parseInt(aNumStr, 10) - parseInt(bNumStr, 10) + if (diff !== 0) { + return diff + } + } else if (aIsDigit !== bIsDigit) { + // One is a digit, one is a letter - letters sort before digits + return aIsDigit ? 1 : -1 + } else { + // Both are non-digit characters - compare lexicographically + if (aStr[ai]! > bStr[bi]!) { return 1 } - if (bb > aa) { + if (aStr[ai]! < bStr[bi]!) { return -1 } - continue - } - - // One is a string, one is a number - if (isNaN(combo[1]!)) { - return isNaN(an) ? -1 : 1 - } - - // Both are numbers - if (an > bn) { - return 1 - } - if (bn > an) { - return -1 + ai++ + bi++ } } - return a.length - b.length + return aStr.length - bStr.length } // Exports From a3dce0fd2ffcc3cbf5212798b54fc8093c94e16e Mon Sep 17 00:00:00 2001 From: Isaac Hunja Date: Wed, 4 Mar 2026 07:11:02 +0300 Subject: [PATCH 2/2] chore: add changeset --- .changeset/fix-alphanumeric-sorting.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-alphanumeric-sorting.md diff --git a/.changeset/fix-alphanumeric-sorting.md b/.changeset/fix-alphanumeric-sorting.md new file mode 100644 index 0000000000..7d8cb1e787 --- /dev/null +++ b/.changeset/fix-alphanumeric-sorting.md @@ -0,0 +1,5 @@ +--- +'@tanstack/table-core': patch +--- + +Fix inconsistent alphanumeric sorting where letters and digits were ordered differently depending on position in the string