diff --git a/examples/react/excel-sorting/index.html b/examples/react/excel-sorting/index.html
new file mode 100644
index 0000000000..2e45988a52
--- /dev/null
+++ b/examples/react/excel-sorting/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Excel-like Sorting with sortEmpty
+
+
+
+
+
+
diff --git a/examples/react/excel-sorting/package.json b/examples/react/excel-sorting/package.json
new file mode 100644
index 0000000000..c997435788
--- /dev/null
+++ b/examples/react/excel-sorting/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "tanstack-table-example-excel-sorting",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "serve": "vite preview",
+ "start": "vite"
+ },
+ "dependencies": {
+ "@faker-js/faker": "^8.4.1",
+ "@tanstack/react-table": "^8.21.3",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@rollup/plugin-replace": "^5.0.7",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "typescript": "5.4.5",
+ "vite": "^5.3.2"
+ }
+}
diff --git a/examples/react/excel-sorting/src/index.css b/examples/react/excel-sorting/src/index.css
new file mode 100644
index 0000000000..27f9c18a84
--- /dev/null
+++ b/examples/react/excel-sorting/src/index.css
@@ -0,0 +1,184 @@
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ margin: 0;
+ padding: 20px;
+ background-color: #f5f5f5;
+}
+
+.app {
+ max-width: 1200px;
+ margin: 0 auto;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+}
+
+.header {
+ padding: 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+}
+
+.header h1 {
+ margin: 0 0 10px 0;
+ font-size: 24px;
+}
+
+.header p {
+ margin: 0 0 15px 0;
+ opacity: 0.9;
+}
+
+.header code {
+ background: rgba(255, 255, 255, 0.2);
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+}
+
+.features {
+ margin-top: 15px;
+}
+
+.features h3 {
+ margin: 0 0 10px 0;
+ font-size: 16px;
+}
+
+.features ul {
+ margin: 0;
+ padding-left: 20px;
+}
+
+.features li {
+ margin-bottom: 5px;
+}
+
+.table-container {
+ padding: 20px;
+ overflow-x: auto;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 1px solid #e0e0e0;
+ border-radius: 6px;
+ overflow: hidden;
+}
+
+th {
+ background: #f8f9fa;
+ border-bottom: 2px solid #e0e0e0;
+ border-right: 1px solid #e0e0e0;
+ padding: 12px 8px;
+ text-align: left;
+ font-weight: 600;
+ font-size: 14px;
+ color: #495057;
+}
+
+th:last-child {
+ border-right: none;
+}
+
+th.sortable {
+ cursor: pointer;
+ user-select: none;
+ transition: background-color 0.2s;
+}
+
+th.sortable:hover {
+ background: #e9ecef;
+}
+
+.header-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.sort-indicator {
+ margin-left: 4px;
+ font-size: 12px;
+}
+
+td {
+ padding: 10px 8px;
+ border-bottom: 1px solid #f0f0f0;
+ border-right: 1px solid #f0f0f0;
+ font-size: 14px;
+}
+
+td:last-child {
+ border-right: none;
+}
+
+tr:hover {
+ background-color: #f8f9fa;
+}
+
+tr:last-child td {
+ border-bottom: none;
+}
+
+.info {
+ padding: 20px;
+ background: #f8f9fa;
+ border-top: 1px solid #e0e0e0;
+}
+
+.info h3 {
+ margin: 0 0 10px 0;
+ font-size: 16px;
+ color: #495057;
+}
+
+.info pre {
+ background: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ padding: 10px;
+ font-size: 12px;
+ overflow-x: auto;
+ margin: 0 0 20px 0;
+}
+
+.comparison {
+ margin-top: 20px;
+}
+
+.comparison-table {
+ font-size: 13px;
+ margin-top: 10px;
+}
+
+.comparison-table th {
+ background: #6c757d;
+ color: white;
+ font-size: 12px;
+ padding: 8px;
+}
+
+.comparison-table td {
+ padding: 8px;
+ text-align: center;
+}
+
+.comparison-table td:first-child {
+ text-align: left;
+ font-weight: 500;
+}
+
+code {
+ background: #f1f3f4;
+ padding: 2px 4px;
+ border-radius: 3px;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 13px;
+}
diff --git a/examples/react/excel-sorting/src/main.tsx b/examples/react/excel-sorting/src/main.tsx
new file mode 100644
index 0000000000..6534acae84
--- /dev/null
+++ b/examples/react/excel-sorting/src/main.tsx
@@ -0,0 +1,293 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import {
+ createColumnHelper,
+ flexRender,
+ getCoreRowModel,
+ getSortedRowModel,
+ useReactTable,
+} from '@tanstack/react-table'
+import './index.css'
+
+type Product = {
+ id: number
+ name: string
+ price: number | null | undefined
+ stock: number | null
+ category: string
+ description: string
+}
+
+const defaultData: Product[] = [
+ {
+ id: 1,
+ name: 'Laptop',
+ price: 999,
+ stock: 10,
+ category: 'Electronics',
+ description: 'High-performance laptop',
+ },
+ {
+ id: 2,
+ name: 'Mouse',
+ price: 25,
+ stock: null,
+ category: 'Electronics',
+ description: '',
+ },
+ {
+ id: 3,
+ name: 'Keyboard',
+ price: null,
+ stock: 5,
+ category: 'Electronics',
+ description: 'Mechanical keyboard',
+ },
+ {
+ id: 4,
+ name: 'Monitor',
+ price: 399,
+ stock: undefined,
+ category: 'Electronics',
+ description: '4K display',
+ },
+ {
+ id: 5,
+ name: 'Desk',
+ price: undefined,
+ stock: 2,
+ category: 'Furniture',
+ description: 'Standing desk',
+ },
+ {
+ id: 6,
+ name: 'Chair',
+ price: 199,
+ stock: 0,
+ category: 'Furniture',
+ description: 'Ergonomic chair',
+ },
+ {
+ id: 7,
+ name: 'Webcam',
+ price: 89,
+ stock: 15,
+ category: 'Electronics',
+ description: '',
+ },
+ {
+ id: 8,
+ name: 'Headphones',
+ price: 150,
+ stock: null,
+ category: 'Electronics',
+ description: 'Noise-cancelling',
+ },
+ {
+ id: 9,
+ name: 'Tablet',
+ price: 299,
+ stock: 8,
+ category: 'Electronics',
+ description: ' ',
+ },
+ {
+ id: 10,
+ name: 'Bookshelf',
+ price: 75,
+ stock: undefined,
+ category: 'Furniture',
+ description: 'Wooden bookshelf',
+ },
+]
+
+const columnHelper = createColumnHelper()
+
+const columns = [
+ columnHelper.accessor('id', {
+ header: 'ID',
+ size: 60,
+ }),
+ columnHelper.accessor('name', {
+ header: 'Product Name',
+ size: 150,
+ }),
+ columnHelper.accessor('price', {
+ header: 'Price ($)',
+ sortEmpty: 'last', // NEW: Empty values go to bottom
+ cell: info => {
+ const value = info.getValue()
+ return value != null ? `$${value}` : '-'
+ },
+ size: 100,
+ }),
+ columnHelper.accessor('stock', {
+ header: 'Stock',
+ sortEmpty: 'last',
+ cell: info => {
+ const value = info.getValue()
+ return value != null ? value.toString() : 'N/A'
+ },
+ size: 80,
+ }),
+ columnHelper.accessor('category', {
+ header: 'Category',
+ size: 120,
+ }),
+ columnHelper.accessor('description', {
+ header: 'Description',
+ sortEmpty: 'last',
+ isEmptyValue: value =>
+ !value || (typeof value === 'string' && value.trim() === ''),
+ cell: info => {
+ const value = info.getValue()
+ return value?.trim() ? value : '(No description)'
+ },
+ size: 200,
+ }),
+]
+
+function App() {
+ const [data] = React.useState(() => [...defaultData])
+ const [sorting, setSorting] = React.useState([])
+
+ const table = useReactTable({
+ data,
+ columns,
+ state: {
+ sorting,
+ },
+ onSortingChange: setSorting,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ })
+
+ return (
+
+
+
Excel-like Sorting with sortEmpty
+
+ This example shows the new sortEmpty
option. Click column
+ headers to sort. null/undefined/empty values always appear at the
+ bottom.
+
+
+
Key Features:
+
+ -
+ Price & Stock: null and undefined values are
+ sorted to the bottom
+
+ -
+ Description: empty strings and strings with only
+ whitespace are sorted to the bottom
+
+ -
+ Excel-like behavior: empty values always appear
+ at the bottom regardless of sort direction
+
+
+
+
+
+
+
+
+ {table.getHeaderGroups().map(headerGroup => (
+
+ {headerGroup.headers.map(header => (
+ {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault()
+ header.column.getToggleSortingHandler()?.(e)
+ }
+ }}
+ tabIndex={header.column.getCanSort() ? 0 : -1}
+ className={header.column.getCanSort() ? 'sortable' : ''}
+ >
+
+ {flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ {{
+ asc: ' 🔼',
+ desc: ' 🔽',
+ }[header.column.getIsSorted() as string] ?? null}
+
+
+ |
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map(row => (
+
+ {row.getVisibleCells().map(cell => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ |
+ ))}
+
+ ))}
+
+
+
+
+
+
Sorting State:
+
{JSON.stringify(sorting, null, 2)}
+
+
+
Comparison of sortUndefined vs sortEmpty:
+
+
+
+ Feature |
+ sortUndefined |
+ sortEmpty (new) |
+
+
+
+
+ Values processed |
+ undefined only |
+ null, undefined, empty strings |
+
+
+ Custom empty values |
+ ❌ |
+ ✅ isEmptyValue function |
+
+
+ Options |
+ false, -1, 1, 'first', 'last' |
+ false, 'first', 'last' |
+
+
+ Status |
+ ⚠️ Deprecated |
+ ✅ Recommended |
+
+
+
+
+
+
+ )
+}
+
+const rootElement = document.getElementById('root')
+if (!rootElement) throw new Error('Failed to find the root element')
+
+ReactDOM.createRoot(rootElement).render(
+
+
+
+)
diff --git a/examples/react/excel-sorting/tsconfig.json b/examples/react/excel-sorting/tsconfig.json
new file mode 100644
index 0000000000..a7fc6fbf23
--- /dev/null
+++ b/examples/react/excel-sorting/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/examples/react/excel-sorting/tsconfig.node.json b/examples/react/excel-sorting/tsconfig.node.json
new file mode 100644
index 0000000000..42872c59f5
--- /dev/null
+++ b/examples/react/excel-sorting/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/examples/react/excel-sorting/vite.config.ts b/examples/react/excel-sorting/vite.config.ts
new file mode 100644
index 0000000000..fcea1bef84
--- /dev/null
+++ b/examples/react/excel-sorting/vite.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import { resolve } from 'path'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: {
+ '@tanstack/react-table': resolve(__dirname, '../../../packages/react-table/src'),
+ '@tanstack/table-core': resolve(__dirname, '../../../packages/table-core/src'),
+ },
+ },
+})
diff --git a/packages/table-core/src/features/RowSorting.ts b/packages/table-core/src/features/RowSorting.ts
index c2e7c32d53..aecf5c709a 100644
--- a/packages/table-core/src/features/RowSorting.ts
+++ b/packages/table-core/src/features/RowSorting.ts
@@ -80,6 +80,7 @@ export interface SortingColumnDef {
*/
sortingFn?: SortingFnOption
/**
+ * @deprecated Use `sortEmpty` for more comprehensive empty value handling
* The priority of undefined values when sorting this column.
* - `false`
* - Undefined values will be considered tied and need to be sorted by the next column filter or original index (whichever applies)
@@ -91,6 +92,26 @@ export interface SortingColumnDef {
* @link [Guide](https://tanstack.com/table/v8/docs/guide/sorting)
*/
sortUndefined?: false | -1 | 1 | 'first' | 'last'
+ /**
+ * Determines how empty values (null, undefined, empty string) are sorted
+ * - `false`
+ * - Empty values are sorted normally
+ * - `'first'`
+ * - Empty values are sorted to the top
+ * - `'last'`
+ * - Empty values are sorted to the bottom
+ * @default false
+ * @link [API Docs](https://tanstack.com/table/v8/docs/api/features/sorting#sortempty)
+ * @link [Guide](https://tanstack.com/table/v8/docs/guide/sorting)
+ */
+ sortEmpty?: false | 'first' | 'last'
+ /**
+ * Custom function to determine if a value should be considered empty
+ * @default (value) => value == null || value === ''
+ * @link [API Docs](https://tanstack.com/table/v8/docs/api/features/sorting#isemptyvalue)
+ * @link [Guide](https://tanstack.com/table/v8/docs/guide/sorting)
+ */
+ isEmptyValue?: (value: unknown) => boolean
}
export interface SortingColumn {
@@ -305,6 +326,14 @@ export const RowSorting: TableFeature = {
column: Column,
table: Table
): void => {
+ if (column.columnDef.sortUndefined && !column.columnDef.sortEmpty) {
+ if (process.env.NODE_ENV !== 'production') {
+ console.warn(
+ `[TanStack Table] sortUndefined is deprecated. Please use sortEmpty instead for more comprehensive empty value handling. Column: ${column.id}`
+ )
+ }
+ }
+
column.getAutoSortingFn = () => {
const firstRows = table.getFilteredRowModel().flatRows.slice(10)
diff --git a/packages/table-core/src/utils/getSortedRowModel.ts b/packages/table-core/src/utils/getSortedRowModel.ts
index eb5e9d5729..f27bfc46dd 100644
--- a/packages/table-core/src/utils/getSortedRowModel.ts
+++ b/packages/table-core/src/utils/getSortedRowModel.ts
@@ -26,6 +26,8 @@ export function getSortedRowModel(): (
string,
{
sortUndefined?: false | -1 | 1 | 'first' | 'last'
+ sortEmpty?: false | 'first' | 'last'
+ isEmptyValue?: (value: unknown) => boolean
invertSorting?: boolean
sortingFn: SortingFn
}
@@ -37,6 +39,8 @@ export function getSortedRowModel(): (
columnInfoById[sortEntry.id] = {
sortUndefined: column.columnDef.sortUndefined,
+ sortEmpty: column.columnDef.sortEmpty,
+ isEmptyValue: column.columnDef.isEmptyValue ?? ((value: unknown) => value == null || value === ''),
invertSorting: column.columnDef.invertSorting,
sortingFn: column.getSortingFn(),
}
@@ -52,12 +56,31 @@ export function getSortedRowModel(): (
const sortEntry = availableSorting[i]!
const columnInfo = columnInfoById[sortEntry.id]!
const sortUndefined = columnInfo.sortUndefined
+ const sortEmpty = columnInfo.sortEmpty
+ const isEmptyValue = columnInfo.isEmptyValue!
const isDesc = sortEntry?.desc ?? false
let sortInt = 0
- // All sorting ints should always return in ascending order
- if (sortUndefined) {
+ if (sortEmpty) {
+ const aValue = rowA.getValue(sortEntry.id)
+ const bValue = rowB.getValue(sortEntry.id)
+
+ const aIsEmpty = isEmptyValue(aValue)
+ const bIsEmpty = isEmptyValue(bValue)
+
+ if (aIsEmpty && bIsEmpty) {
+ continue // Both empty, check next sort column
+ }
+
+ if (aIsEmpty || bIsEmpty) {
+ const emptyPosition = sortEmpty === 'last' ? 1 : -1
+ sortInt = aIsEmpty ? emptyPosition : -emptyPosition
+ return sortInt
+ }
+ }
+ // Backward compatibility: handle sortUndefined if sortEmpty not set
+ else if (sortUndefined) {
const aValue = rowA.getValue(sortEntry.id)
const bValue = rowB.getValue(sortEntry.id)
diff --git a/packages/table-core/tests/sortEmpty.test.ts b/packages/table-core/tests/sortEmpty.test.ts
new file mode 100644
index 0000000000..a319d0ca9b
--- /dev/null
+++ b/packages/table-core/tests/sortEmpty.test.ts
@@ -0,0 +1,290 @@
+import { describe, expect, it } from 'vitest'
+import {
+ createColumnHelper,
+ getCoreRowModel,
+ getSortedRowModel,
+ createTable,
+} from '../src'
+
+type Product = {
+ id: number
+ name: string
+ price: number | null | undefined
+ description: string
+}
+
+const columnHelper = createColumnHelper()
+
+const testData: Product[] = [
+ { id: 1, name: 'Product A', price: 100, description: 'Description A' },
+ { id: 2, name: 'Product B', price: null, description: '' },
+ { id: 3, name: 'Product C', price: 50, description: 'Description C' },
+ { id: 4, name: 'Product D', price: undefined, description: ' ' },
+ { id: 5, name: 'Product E', price: 200, description: 'Description E' },
+]
+
+describe('sortEmpty option', () => {
+ describe('sortEmpty: "last"', () => {
+ const columns = [
+ columnHelper.accessor('id', { header: 'ID' }),
+ columnHelper.accessor('name', { header: 'Name' }),
+ columnHelper.accessor('price', {
+ header: 'Price',
+ sortEmpty: 'last',
+ }),
+ columnHelper.accessor('description', {
+ header: 'Description',
+ sortEmpty: 'last',
+ isEmptyValue: (value) => !value || (typeof value === 'string' && value.trim() === ''),
+ }),
+ ]
+
+ it('should sort null and undefined to bottom when sortEmpty is "last"', () => {
+ const table = createTable({
+ data: testData,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting: [{ id: 'price', desc: false }],
+ },
+ onStateChange: () => {},
+ renderFallbackValue: null,
+ })
+
+ const sortedRows = table.getSortedRowModel().rows
+ const prices = sortedRows.map(row => row.original.price)
+
+ expect(prices).toEqual([50, 100, 200, null, undefined])
+ })
+
+ it('should respect custom isEmptyValue function', () => {
+ const table = createTable({
+ data: testData,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting: [{ id: 'description', desc: false }],
+ },
+ onStateChange: () => {},
+ renderFallbackValue: null,
+ })
+
+ const sortedRows = table.getSortedRowModel().rows
+ const descriptions = sortedRows.map(row => row.original.description)
+
+ expect(descriptions).toEqual([
+ 'Description A',
+ 'Description C',
+ 'Description E',
+ '', // empty string
+ ' ', // whitespace only
+ ])
+ })
+
+ it('should handle descending sort with sortEmpty', () => {
+ const table = createTable({
+ data: testData,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting: [{ id: 'price', desc: true }],
+ },
+ onStateChange: () => {},
+ renderFallbackValue: null,
+ })
+
+ const sortedRows = table.getSortedRowModel().rows
+ const prices = sortedRows.map(row => row.original.price)
+
+ // Empty values still at bottom even in desc sort
+ expect(prices).toEqual([200, 100, 50, null, undefined])
+ })
+ })
+
+ describe('sortEmpty: "first"', () => {
+ const columns = [
+ columnHelper.accessor('id', { header: 'ID' }),
+ columnHelper.accessor('name', { header: 'Name' }),
+ columnHelper.accessor('price', {
+ header: 'Price',
+ sortEmpty: 'first',
+ }),
+ ]
+
+ it('should sort null and undefined to top when sortEmpty is "first"', () => {
+ const table = createTable({
+ data: testData,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting: [{ id: 'price', desc: false }],
+ },
+ onStateChange: () => {},
+ renderFallbackValue: null,
+ })
+
+ const sortedRows = table.getSortedRowModel().rows
+ const prices = sortedRows.map(row => row.original.price)
+
+ expect(prices).toEqual([null, undefined, 50, 100, 200])
+ })
+
+ it('should handle descending sort with sortEmpty: "first"', () => {
+ const table = createTable({
+ data: testData,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting: [{ id: 'price', desc: true }],
+ },
+ onStateChange: () => {},
+ renderFallbackValue: null,
+ })
+
+ const sortedRows = table.getSortedRowModel().rows
+ const prices = sortedRows.map(row => row.original.price)
+
+ // Empty values still at top even in desc sort
+ expect(prices).toEqual([null, undefined, 200, 100, 50])
+ })
+ })
+
+ describe('backward compatibility with sortUndefined', () => {
+ const legacyColumns = [
+ columnHelper.accessor('id', { header: 'ID' }),
+ columnHelper.accessor('price', {
+ header: 'Price',
+ sortUndefined: 'last',
+ }),
+ ]
+
+ it('should maintain backward compatibility with sortUndefined', () => {
+ const table = createTable({
+ data: testData,
+ columns: legacyColumns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting: [{ id: 'price', desc: false }],
+ },
+ onStateChange: () => {},
+ renderFallbackValue: null,
+ })
+
+ const sortedRows = table.getSortedRowModel().rows
+ const prices = sortedRows.map(row => row.original.price)
+
+ // sortUndefined only affects undefined, not null
+ // null should be sorted normally, undefined at the end
+ expect(prices[prices.length - 1]).toBeUndefined()
+ expect(prices.includes(null)).toBe(true)
+ })
+ })
+
+ describe('sortEmpty takes precedence over sortUndefined', () => {
+ const mixedColumns = [
+ columnHelper.accessor('id', { header: 'ID' }),
+ columnHelper.accessor('price', {
+ header: 'Price',
+ sortEmpty: 'first',
+ sortUndefined: 'last', // This should be ignored
+ }),
+ ]
+
+ it('should use sortEmpty when both sortEmpty and sortUndefined are specified', () => {
+ const table = createTable({
+ data: testData,
+ columns: mixedColumns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting: [{ id: 'price', desc: false }],
+ },
+ onStateChange: () => {},
+ renderFallbackValue: null,
+ })
+
+ const sortedRows = table.getSortedRowModel().rows
+ const prices = sortedRows.map(row => row.original.price)
+
+ // sortEmpty: 'first' should take precedence
+ expect(prices).toEqual([null, undefined, 50, 100, 200])
+ })
+ })
+
+ describe('default isEmptyValue behavior', () => {
+ const defaultColumns = [
+ columnHelper.accessor('id', { header: 'ID' }),
+ columnHelper.accessor('price', {
+ header: 'Price',
+ sortEmpty: 'last',
+ }),
+ ]
+
+ it('should use default isEmptyValue function (null, undefined, empty string)', () => {
+ const dataWithEmptyString = [
+ ...testData,
+ { id: 6, name: 'Product F', price: '' as unknown as number, description: 'Description F' },
+ ]
+
+ const table = createTable({
+ data: dataWithEmptyString,
+ columns: defaultColumns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting: [{ id: 'price', desc: false }],
+ },
+ onStateChange: () => {},
+ renderFallbackValue: null,
+ })
+
+ const sortedRows = table.getSortedRowModel().rows
+ const prices = sortedRows.map(row => row.original.price)
+
+ // Default isEmptyValue should treat '', null, undefined as empty
+ expect(prices).toEqual([50, 100, 200, null, undefined, ''])
+ })
+ })
+
+ describe('sortEmpty: false (disabled)', () => {
+ const disabledColumns = [
+ columnHelper.accessor('id', { header: 'ID' }),
+ columnHelper.accessor('price', {
+ header: 'Price',
+ sortEmpty: false,
+ }),
+ ]
+
+ it('should sort normally when sortEmpty is false', () => {
+ const table = createTable({
+ data: testData,
+ columns: disabledColumns,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting: [{ id: 'price', desc: false }],
+ },
+ onStateChange: () => {},
+ renderFallbackValue: null,
+ })
+
+ const sortedRows = table.getSortedRowModel().rows
+ const prices = sortedRows.map(row => row.original.price)
+
+ // Should use normal sorting behavior (null/undefined treated as values)
+ // The exact order depends on the sorting function implementation
+ expect(prices.length).toBe(5)
+ expect(prices.includes(50)).toBe(true)
+ expect(prices.includes(100)).toBe(true)
+ expect(prices.includes(200)).toBe(true)
+ expect(prices.includes(null)).toBe(true)
+ expect(prices.includes(undefined)).toBe(true)
+ })
+ })
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e57f437917..7756073df8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -43,7 +43,7 @@ importers:
version: 11.1.2(size-limit@11.1.2)
'@tanstack/config':
specifier: ^0.6.0
- version: 0.6.0(@types/node@20.11.30)(esbuild@0.20.2)(rollup@4.13.0)(typescript@5.4.3)(vite@5.4.19)
+ version: 0.6.0(@types/node@20.11.30)(esbuild@0.25.4)(rollup@4.13.0)(typescript@5.4.3)(vite@5.4.19)
'@testing-library/jest-dom':
specifier: ^6.4.2
version: 6.4.2(vitest@1.4.0)
@@ -82,7 +82,7 @@ importers:
version: 4.0.0-alpha.8
prettier-plugin-svelte:
specifier: ^3.2.2
- version: 3.2.2(prettier@4.0.0-alpha.8)(svelte@3.59.2)
+ version: 3.2.2(prettier@4.0.0-alpha.8)(svelte@4.2.20)
rimraf:
specifier: ^5.0.5
version: 5.0.5
@@ -94,7 +94,7 @@ importers:
version: 0.3.1
rollup-plugin-svelte:
specifier: ^7.2.0
- version: 7.2.0(rollup@4.13.0)(svelte@3.59.2)
+ version: 7.2.0(rollup@4.13.0)(svelte@4.2.20)
rollup-plugin-visualizer:
specifier: ^5.12.0
version: 5.12.0(rollup@4.13.0)
@@ -1853,6 +1853,40 @@ importers:
specifier: ^5.3.2
version: 5.4.19(@types/node@20.11.30)(less@4.2.0)(sass@1.71.1)(terser@5.29.1)
+ examples/react/excel-sorting:
+ dependencies:
+ '@faker-js/faker':
+ specifier: ^8.4.1
+ version: 8.4.1
+ '@tanstack/react-table':
+ specifier: ^8.21.3
+ version: link:../../../packages/react-table
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ devDependencies:
+ '@rollup/plugin-replace':
+ specifier: ^5.0.7
+ version: 5.0.7(rollup@4.13.0)
+ '@types/react':
+ specifier: ^18.3.3
+ version: 18.3.22
+ '@types/react-dom':
+ specifier: ^18.3.0
+ version: 18.3.7(@types/react@18.3.22)
+ '@vitejs/plugin-react':
+ specifier: ^4.3.1
+ version: 4.5.0(vite@5.4.19)
+ typescript:
+ specifier: 5.4.5
+ version: 5.4.5
+ vite:
+ specifier: ^5.3.2
+ version: 5.4.19(@types/node@20.11.30)(less@4.2.0)(sass@1.71.1)(terser@5.29.1)
+
examples/react/expanding:
dependencies:
'@faker-js/faker':
@@ -2160,7 +2194,7 @@ importers:
version: 11.11.0
'@emotion/babel-plugin-jsx-pragmatic':
specifier: ^0.2.1
- version: 0.2.1(@babel/core@7.24.3)
+ version: 0.2.1(@babel/core@7.27.1)
'@faker-js/faker':
specifier: ^8.4.1
version: 8.4.1
@@ -3728,7 +3762,7 @@ packages:
typescript: 5.4.5
vite: 5.4.19(@types/node@20.11.30)(less@4.2.0)(sass@1.71.1)(terser@5.29.1)
watchpack: 2.4.0
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
webpack-dev-middleware: 6.1.2(webpack@5.94.0)
webpack-dev-server: 4.15.1(webpack@5.94.0)
webpack-merge: 5.10.0
@@ -3764,7 +3798,7 @@ packages:
dependencies:
'@angular-devkit/architect': 0.1703.17
rxjs: 7.8.1
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
webpack-dev-server: 4.15.1(webpack@5.94.0)
transitivePeerDependencies:
- chokidar
@@ -4882,6 +4916,16 @@ packages:
'@babel/helper-plugin-utils': 7.24.0
dev: true
+ /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.27.1):
+ resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/helper-plugin-utils': 7.24.0
+ dev: true
+
/@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.27.1):
resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
engines: {node: '>=6.9.0'}
@@ -6629,13 +6673,13 @@ packages:
tslib: 2.6.2
dev: false
- /@emotion/babel-plugin-jsx-pragmatic@0.2.1(@babel/core@7.24.3):
+ /@emotion/babel-plugin-jsx-pragmatic@0.2.1(@babel/core@7.27.1):
resolution: {integrity: sha512-xy1SlgEJygAAIvIuC2idkGKJYa6v5iwoyILkvNKgk347bV+IImXrUat5Z86EmLGyWhEoTplVT9EHqTnHZG4HFw==}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
- '@babel/core': 7.24.3
- '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.3)
+ '@babel/core': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.27.1)
dev: true
/@emotion/babel-plugin@11.11.0:
@@ -8174,7 +8218,7 @@ packages:
dependencies:
'@angular/compiler-cli': 17.3.12(@angular/compiler@17.3.12)(typescript@5.4.5)
typescript: 5.4.5
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/@nodelib/fs.scandir@2.1.5:
@@ -8819,7 +8863,7 @@ packages:
rollup: 4.13.0
dev: true
- /@rollup/plugin-json@6.1.0(rollup@4.13.0):
+ /@rollup/plugin-json@6.1.0(rollup@4.41.1):
resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
engines: {node: '>=14.0.0'}
peerDependencies:
@@ -8828,8 +8872,8 @@ packages:
rollup:
optional: true
dependencies:
- '@rollup/pluginutils': 5.1.0(rollup@4.13.0)
- rollup: 4.13.0
+ '@rollup/pluginutils': 5.1.0(rollup@4.41.1)
+ rollup: 4.41.1
dev: true
/@rollup/plugin-node-resolve@15.2.3(rollup@4.13.0):
@@ -8850,6 +8894,24 @@ packages:
rollup: 4.13.0
dev: true
+ /@rollup/plugin-node-resolve@15.2.3(rollup@4.41.1):
+ resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^2.78.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ dependencies:
+ '@rollup/pluginutils': 5.1.0(rollup@4.41.1)
+ '@types/resolve': 1.20.2
+ deepmerge: 4.3.1
+ is-builtin-module: 3.2.1
+ is-module: 1.0.0
+ resolve: 1.22.8
+ rollup: 4.41.1
+ dev: true
+
/@rollup/plugin-replace@5.0.5(rollup@4.13.0):
resolution: {integrity: sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==}
engines: {node: '>=14.0.0'}
@@ -8916,6 +8978,21 @@ packages:
rollup: 4.13.0
dev: true
+ /@rollup/pluginutils@5.1.0(rollup@4.41.1):
+ resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ dependencies:
+ '@types/estree': 1.0.7
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+ rollup: 4.41.1
+ dev: true
+
/@rollup/rollup-android-arm-eabi@4.13.0:
resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==}
cpu: [arm]
@@ -9388,7 +9465,7 @@ packages:
tslib: 2.6.2
dev: false
- /@tanstack/config@0.6.0(@types/node@20.11.30)(esbuild@0.20.2)(rollup@4.13.0)(typescript@5.4.3)(vite@5.4.19):
+ /@tanstack/config@0.6.0(@types/node@20.11.30)(esbuild@0.25.4)(rollup@4.13.0)(typescript@5.4.3)(vite@5.4.19):
resolution: {integrity: sha512-ndVPsyXWZFz3RcpRF7q5L4Ol5zY+m1H2lAiufw+J4BrV09042PETU2OZAREYz88ZcLtu6p+LZAHKltmqrL8gDg==}
engines: {node: '>=18'}
hasBin: true
@@ -9398,7 +9475,7 @@ packages:
chalk: 5.3.0
commander: 12.0.0
current-git-branch: 1.1.0
- esbuild-register: 3.5.0(esbuild@0.20.2)
+ esbuild-register: 3.5.0(esbuild@0.25.4)
git-log-parser: 1.2.0
interpret: 3.1.1
jsonfile: 6.1.0
@@ -9792,11 +9869,11 @@ packages:
/@types/babel__core@7.20.5:
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
dependencies:
- '@babel/parser': 7.24.1
- '@babel/types': 7.24.0
+ '@babel/parser': 7.27.2
+ '@babel/types': 7.27.1
'@types/babel__generator': 7.6.8
'@types/babel__template': 7.4.4
- '@types/babel__traverse': 7.20.5
+ '@types/babel__traverse': 7.20.7
dev: true
/@types/babel__generator@7.6.8:
@@ -9812,12 +9889,6 @@ packages:
'@babel/types': 7.24.0
dev: true
- /@types/babel__traverse@7.20.5:
- resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==}
- dependencies:
- '@babel/types': 7.24.0
- dev: true
-
/@types/babel__traverse@7.20.7:
resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
dependencies:
@@ -10137,7 +10208,7 @@ packages:
/@vitest/snapshot@1.4.0:
resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==}
dependencies:
- magic-string: 0.30.8
+ magic-string: 0.30.17
pathe: 1.1.2
pretty-format: 29.7.0
dev: true
@@ -10145,7 +10216,7 @@ packages:
/@vitest/snapshot@1.6.1:
resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==}
dependencies:
- magic-string: 0.30.8
+ magic-string: 0.30.17
pathe: 1.1.2
pretty-format: 29.7.0
dev: true
@@ -10939,7 +11010,7 @@ packages:
'@babel/core': 7.26.10
find-cache-dir: 4.0.0
schema-utils: 4.3.2
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/babel-plugin-add-module-exports@0.2.1:
@@ -11754,7 +11825,7 @@ packages:
normalize-path: 3.0.0
schema-utils: 4.3.2
serialize-javascript: 6.0.2
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/core-js-compat@3.36.1:
@@ -11865,7 +11936,7 @@ packages:
postcss-modules-values: 4.0.0(postcss@8.4.38)
postcss-value-parser: 4.2.0
semver: 7.6.0
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/css-select@5.1.0:
@@ -12426,13 +12497,13 @@ packages:
resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
dev: true
- /esbuild-register@3.5.0(esbuild@0.20.2):
+ /esbuild-register@3.5.0(esbuild@0.25.4):
resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==}
peerDependencies:
esbuild: '>=0.12 <1'
dependencies:
debug: 4.3.4
- esbuild: 0.20.2
+ esbuild: 0.25.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -14486,7 +14557,7 @@ packages:
dependencies:
klona: 2.0.6
less: 4.2.0
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/less@4.2.0:
@@ -14535,7 +14606,7 @@ packages:
webpack-sources:
optional: true
dependencies:
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
webpack-sources: 3.3.0
dev: true
@@ -14978,7 +15049,7 @@ packages:
dependencies:
schema-utils: 4.3.2
tapable: 2.2.2
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/minimalistic-assert@1.0.1:
@@ -15232,8 +15303,8 @@ packages:
optional: true
dependencies:
'@angular/compiler-cli': 17.3.12(@angular/compiler@17.3.12)(typescript@5.4.5)
- '@rollup/plugin-json': 6.1.0(rollup@4.13.0)
- '@rollup/plugin-node-resolve': 15.2.3(rollup@4.13.0)
+ '@rollup/plugin-json': 6.1.0(rollup@4.41.1)
+ '@rollup/plugin-node-resolve': 15.2.3(rollup@4.41.1)
'@rollup/wasm-node': 4.41.1
ajv: 8.17.1
ansi-colors: 4.1.3
@@ -15258,7 +15329,7 @@ packages:
typescript: 5.4.5
optionalDependencies:
esbuild: 0.20.2
- rollup: 4.13.0
+ rollup: 4.41.1
dev: true
/nice-napi@1.0.2:
@@ -16053,7 +16124,7 @@ packages:
jiti: 1.21.0
postcss: 8.4.35
semver: 7.6.0
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
transitivePeerDependencies:
- typescript
dev: true
@@ -16141,14 +16212,14 @@ packages:
picocolors: 1.1.1
source-map-js: 1.2.1
- /prettier-plugin-svelte@3.2.2(prettier@4.0.0-alpha.8)(svelte@3.59.2):
+ /prettier-plugin-svelte@3.2.2(prettier@4.0.0-alpha.8)(svelte@4.2.20):
resolution: {integrity: sha512-ZzzE/wMuf48/1+Lf2Ffko0uDa6pyCfgHV6+uAhtg2U0AAXGrhCSW88vEJNAkAxW5qyrFY1y1zZ4J8TgHrjW++Q==}
peerDependencies:
prettier: ^3.0.0
svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0
dependencies:
prettier: 4.0.0-alpha.8
- svelte: 3.59.2
+ svelte: 4.2.20
dev: true
/prettier@3.5.3:
@@ -16747,7 +16818,7 @@ packages:
rollup: 2.x || 3.x || 4.x
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.13.0)
- magic-string: 0.30.8
+ magic-string: 0.30.17
rollup: 4.13.0
dev: true
@@ -16766,7 +16837,7 @@ packages:
- debug
dev: true
- /rollup-plugin-svelte@7.2.0(rollup@4.13.0)(svelte@3.59.2):
+ /rollup-plugin-svelte@7.2.0(rollup@4.13.0)(svelte@4.2.20):
resolution: {integrity: sha512-Qvo5VNFQZtaI+sHSjcCIFDP+olfKVyslAoJIkL3DxuhUpNY5Ys0+hhxUY3kuEKt9BXFgkFJiiic/XRb07zdSbg==}
engines: {node: '>=10'}
peerDependencies:
@@ -16776,7 +16847,7 @@ packages:
'@rollup/pluginutils': 4.2.1
resolve.exports: 2.0.2
rollup: 4.13.0
- svelte: 3.59.2
+ svelte: 4.2.20
dev: true
/rollup-plugin-visualizer@5.12.0(rollup@4.13.0):
@@ -16935,7 +17006,7 @@ packages:
dependencies:
neo-async: 2.6.2
sass: 1.71.1
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/sass@1.71.1:
@@ -17463,7 +17534,7 @@ packages:
resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==}
hasBin: true
dependencies:
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.0
buffer-crc32: 0.2.13
minimist: 1.2.8
sander: 0.5.1
@@ -17486,7 +17557,7 @@ packages:
dependencies:
iconv-lite: 0.6.3
source-map-js: 1.2.0
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/source-map-support@0.5.21:
@@ -17886,7 +17957,7 @@ packages:
'@babel/core': 7.24.3
'@types/pug': 2.0.10
detect-indent: 6.1.0
- magic-string: 0.30.8
+ magic-string: 0.30.17
sorcery: 0.11.0
strip-indent: 3.0.0
svelte: 4.2.20
@@ -17955,7 +18026,7 @@ packages:
yallist: 4.0.0
dev: true
- /terser-webpack-plugin@5.3.14(esbuild@0.20.2)(webpack@5.94.0):
+ /terser-webpack-plugin@5.3.14(esbuild@0.25.4)(webpack@5.94.0):
resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==}
engines: {node: '>= 10.13.0'}
peerDependencies:
@@ -17972,12 +18043,12 @@ packages:
optional: true
dependencies:
'@jridgewell/trace-mapping': 0.3.25
- esbuild: 0.20.2
+ esbuild: 0.25.4
jest-worker: 27.5.1
schema-utils: 4.3.2
serialize-javascript: 6.0.2
terser: 5.39.2
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/terser@5.29.1:
@@ -19075,7 +19146,7 @@ packages:
mime-types: 2.1.35
range-parser: 1.2.1
schema-utils: 4.3.2
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/webpack-dev-middleware@6.1.2(webpack@5.94.0):
@@ -19092,7 +19163,7 @@ packages:
mime-types: 2.1.35
range-parser: 1.2.1
schema-utils: 4.3.2
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/webpack-dev-server@4.15.1(webpack@5.94.0):
@@ -19136,7 +19207,7 @@ packages:
serve-index: 1.9.1
sockjs: 0.3.24
spdy: 4.0.2
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
webpack-dev-middleware: 5.3.4(webpack@5.94.0)
ws: 8.16.0
transitivePeerDependencies:
@@ -19171,14 +19242,14 @@ packages:
optional: true
dependencies:
typed-assert: 1.0.9
- webpack: 5.94.0(esbuild@0.20.2)
+ webpack: 5.94.0(esbuild@0.25.4)
dev: true
/webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
dev: true
- /webpack@5.94.0(esbuild@0.20.2):
+ /webpack@5.94.0(esbuild@0.25.4):
resolution: {integrity: sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==}
engines: {node: '>=10.13.0'}
hasBin: true
@@ -19208,7 +19279,7 @@ packages:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.2
- terser-webpack-plugin: 5.3.14(esbuild@0.20.2)(webpack@5.94.0)
+ terser-webpack-plugin: 5.3.14(esbuild@0.25.4)(webpack@5.94.0)
watchpack: 2.4.4
webpack-sources: 3.3.0
transitivePeerDependencies: