From d6b31371da14dfb3f86ca324adc5fb0f4fb28d93 Mon Sep 17 00:00:00 2001 From: aquapi Date: Wed, 24 Jun 2026 21:26:37 +0700 Subject: [PATCH 01/12] optimize some aggregationFns and sortFns - mostly turn forEach to c-style for --- packages/table-core/src/fns/aggregationFns.ts | 75 ++++++++++--------- packages/table-core/src/fns/sortFns.ts | 5 +- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/packages/table-core/src/fns/aggregationFns.ts b/packages/table-core/src/fns/aggregationFns.ts index 457296d35c..0a395a1024 100755 --- a/packages/table-core/src/fns/aggregationFns.ts +++ b/packages/table-core/src/fns/aggregationFns.ts @@ -18,10 +18,12 @@ export function aggregationFn_sum< ) { // It's faster to just add the aggregations together instead of // process leaf nodes individually - return childRows.reduce((sumValue, next) => { - const nextValue = next.getValue(columnId) - return sumValue + (typeof nextValue === 'number' ? nextValue : 0) - }, 0) + let sumValue = 0 + for (let i = 0; i < childRows.length; i++) { + const nextValue = childRows[i]!.getValue(columnId) + if (typeof nextValue === 'number') sumValue += nextValue + } + return sumValue; } /** @@ -38,10 +40,10 @@ export function aggregationFn_min< _leafRows: Array>, childRows: Array>, ) { - let minValue: number | undefined + let minValue: number | undefined; - childRows.forEach((row) => { - const value = row.getValue(columnId) + for (let i = 0; i < childRows.length; i++) { + const value = childRows[i]!.getValue(columnId) if ( value != null && @@ -50,7 +52,7 @@ export function aggregationFn_min< ) { minValue = value } - }) + } return minValue } @@ -71,8 +73,9 @@ export function aggregationFn_max< ) { let maxValue: number | undefined - childRows.forEach((row) => { - const value = row.getValue(columnId) + for (let i = 0; i < childRows.length; i++) { + const value = childRows[i]!.getValue(columnId) + if ( value != null && typeof value === 'number' && @@ -80,7 +83,7 @@ export function aggregationFn_max< ) { maxValue = value } - }) + } return maxValue } @@ -102,8 +105,8 @@ export function aggregationFn_extent< let minValue: number | undefined let maxValue: number | undefined - childRows.forEach((row) => { - const value = row.getValue(columnId) + for (let i = 0; i < childRows.length; i++) { + const value = childRows[i]!.getValue(columnId) if (value != null && typeof value === 'number') { if (minValue === undefined) { minValue = maxValue = value @@ -112,7 +115,7 @@ export function aggregationFn_extent< if (maxValue! < value) maxValue = value } } - }) + } return [minValue, maxValue] } @@ -130,23 +133,23 @@ export function aggregationFn_mean< let count = 0 let sumValue = 0 - leafRows.forEach((row) => { - const value = row.getValue(columnId) - if (value != null && typeof value === 'number') { - ++count - sumValue += value - } else if (value != null) { - const numValue = +value - if (!Number.isNaN(numValue)) { + for (let i = 0; i < leafRows.length; i++) { + let value = leafRows[i]!.getValue(columnId) + if (value != null) { + if (typeof value === 'number') { ++count - sumValue += numValue + sumValue += value + } else { + const numValue = +value + if (!Number.isNaN(numValue)) { + ++count + sumValue += numValue + } } } - }) + } if (count) return sumValue / count - - return } /** @@ -159,24 +162,26 @@ export function aggregationFn_median< TFeatures extends TableFeatures, TData extends RowData, >(columnId: string, leafRows: Array>) { - if (!leafRows.length) { + const len = leafRows.length; + + if (len === 0) { return } + if (len === 1) { + const v = leafRows[0]!.getValue(columnId); + return typeof v === 'number' ? v : undefined; + } - const values: Array = new Array(leafRows.length) - for (let i = 0; i < leafRows.length; i++) { + const values: Array = new Array(len) + for (let i = 0; i < len; i++) { const v = leafRows[i]!.getValue(columnId) if (typeof v !== 'number') return values[i] = v } - if (values.length === 1) { - return values[0] - } - - const mid = Math.floor(values.length / 2) + const mid = len >>> 1; // Divide by 2 and floor values.sort((a, b) => a - b) - return values.length % 2 !== 0 + return (len & 1) === 1 ? values[mid] : (values[mid - 1]! + values[mid]!) / 2 } diff --git a/packages/table-core/src/fns/sortFns.ts b/packages/table-core/src/fns/sortFns.ts index 80cad617dd..b36a811eae 100644 --- a/packages/table-core/src/fns/sortFns.ts +++ b/packages/table-core/src/fns/sortFns.ts @@ -136,10 +136,7 @@ function compareBasic(a: any, b: any) { function toString(a: any) { if (typeof a === 'number') { - if (isNaN(a) || a === Infinity || a === -Infinity) { - return '' - } - return String(a) + return Number.isFinite(a) ? '' + a : ''; } if (typeof a === 'string') { return a From 61f605542799d3726c7184f2cf40bb6f84eff095 Mon Sep 17 00:00:00 2001 From: aquapi Date: Thu, 25 Jun 2026 01:04:50 +0700 Subject: [PATCH 02/12] micro-optimize _createSortedRowModel, tableMemo and memo utils --- .../row-sorting/createSortedRowModel.ts | 172 +++++++++--------- packages/table-core/src/fns/aggregationFns.ts | 2 +- packages/table-core/src/utils.ts | 96 +++++----- 3 files changed, 134 insertions(+), 136 deletions(-) diff --git a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts index 87415c6efd..0620083a8e 100644 --- a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts +++ b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts @@ -1,12 +1,12 @@ import { makeObjectMap, tableMemo } from '../../utils' import { table_autoResetPageIndex } from '../row-pagination/rowPaginationFeature.utils' import { column_getCanSort, column_getSortFn } from './rowSortingFeature.utils' +import type { ColumnSort, SortFn } from './rowSortingFeature.types' import type { Column_Internal } from '../../types/Column' import type { TableFeatures } from '../../types/TableFeatures' import type { RowModel } from '../../core/row-models/coreRowModelsFeature.types' import type { Table, Table_Internal } from '../../types/Table' import type { Row } from '../../types/Row' -import type { SortFn } from './rowSortingFeature.types' import type { RowData } from '../../types/type-utils' /** @@ -50,105 +50,113 @@ function _createSortedRowModel< const sortedFlatRows: Array> = [] - // Filter out sortings that correspond to non existing columns - const availableSorting = sorting.filter((sort) => - column_getCanSort( - table.getColumn(sort.id) as Column_Internal, - ), - ) - const columnInfoById = makeObjectMap<{ sortUndefined?: false | -1 | 1 | 'first' | 'last' invertSorting?: boolean sortFn: SortFn }>() - availableSorting.forEach((sortEntry) => { - const column: Column_Internal | undefined = - table.getColumn(sortEntry.id) - if (!column) return - - columnInfoById[sortEntry.id] = { - sortUndefined: column.columnDef.sortUndefined, - invertSorting: column.columnDef.invertSorting, - sortFn: column_getSortFn(column), + const availableSorting: typeof sorting = []; + for (let i = 0; i < sorting.length; i++) { + const { id } = sorting[i]!; + + const column = table.getColumn(id) as Column_Internal | undefined; + if (column && column_getCanSort(column)) { + availableSorting.push(column as any); + columnInfoById[id] = { + sortUndefined: column.columnDef.sortUndefined, + invertSorting: column.columnDef.invertSorting, + sortFn: column_getSortFn(column), + } } - }) + } - const sortData = (rows: Array>) => { - const sortedData = rows.slice() - - sortedData.sort((rowA, rowB) => { - for (let i = 0; i < availableSorting.length; i++) { - const sortEntry = availableSorting[i]! - const columnInfo = columnInfoById[sortEntry.id]! - const sortUndefined = columnInfo.sortUndefined - const isDesc = sortEntry.desc - - let sortInt = 0 - - // All sorting ints should always return in ascending order - if (sortUndefined) { - const aValue = rowA.getValue(sortEntry.id) - const bValue = rowB.getValue(sortEntry.id) - - const aUndefined = aValue === undefined - const bUndefined = bValue === undefined - - if (aUndefined || bUndefined) { - if (sortUndefined === 'first') return aUndefined ? -1 : 1 - if (sortUndefined === 'last') return aUndefined ? 1 : -1 - sortInt = - aUndefined && bUndefined - ? 0 - : aUndefined - ? sortUndefined - : -sortUndefined - } - } + return { + rows: sortData(preSortedRowModel.rows, sortedFlatRows, availableSorting, columnInfoById), + flatRows: sortedFlatRows, + rowsById: preSortedRowModel.rowsById, + } +} - if (sortInt === 0) { - sortInt = columnInfo.sortFn(rowA, rowB, sortEntry.id) +const sortData = < + TFeatures extends TableFeatures, + TData extends RowData = any, +>( + rows: Array>, + sortedFlatRows: Array>, + availableSorting: Array, + columnInfoById: Record; + }> +) => { + const sortedData = rows.slice() + + sortedData.sort((rowA, rowB) => { + for (let i = 0; i < availableSorting.length; i++) { + const sortEntry = availableSorting[i]! + const columnInfo = columnInfoById[sortEntry.id]! + const sortUndefined = columnInfo.sortUndefined + const isDesc = sortEntry.desc + + let sortInt = 0 + + // All sorting ints should always return in ascending order + if (sortUndefined) { + const aValue = rowA.getValue(sortEntry.id) + const bValue = rowB.getValue(sortEntry.id) + + const aUndefined = aValue === undefined + const bUndefined = bValue === undefined + + if (aUndefined || bUndefined) { + if (sortUndefined === 'first') return aUndefined ? -1 : 1 + if (sortUndefined === 'last') return aUndefined ? 1 : -1 + sortInt = + aUndefined && bUndefined + ? 0 + : aUndefined + ? sortUndefined + : -sortUndefined } + } - // If sorting is non-zero, take care of desc and inversion - if (sortInt !== 0) { - if (isDesc) { - sortInt *= -1 - } + if (sortInt === 0) { + sortInt = columnInfo.sortFn(rowA, rowB, sortEntry.id) + } - if (columnInfo.invertSorting) { - sortInt *= -1 - } + // If sorting is non-zero, take care of desc and inversion + if (sortInt !== 0) { + if (isDesc) { + sortInt *= -1 + } - return sortInt + if (columnInfo.invertSorting) { + sortInt *= -1 } - } - return rowA.index - rowB.index - }) - // If there are sub-rows, sort them. Clone only rows that need mutation - // (i.e. have subRows) so we don't corrupt the source row model. - for (let i = 0; i < sortedData.length; i++) { - const row = sortedData[i]! - if (row.subRows.length) { - // Preserve prototype chain so methods like getValue() remain accessible - const cloned = Object.create(Object.getPrototypeOf(row)) - Object.assign(cloned, row) - cloned.subRows = sortData(row.subRows) - sortedData[i] = cloned - sortedFlatRows.push(cloned) - } else { - sortedFlatRows.push(row) + return sortInt } } + return rowA.index - rowB.index + }) - return sortedData + // If there are sub-rows, sort them. Clone only rows that need mutation + // (i.e. have subRows) so we don't corrupt the source row model. + for (let i = 0; i < sortedData.length; i++) { + const row = sortedData[i]! + if (row.subRows.length) { + // Preserve prototype chain so methods like getValue() remain accessible + const cloned = Object.create(Object.getPrototypeOf(row)) + Object.assign(cloned, row) + cloned.subRows = sortData(row.subRows, sortedFlatRows, availableSorting, columnInfoById) + sortedData[i] = cloned + sortedFlatRows.push(cloned) + } else { + sortedFlatRows.push(row) + } } - return { - rows: sortData(preSortedRowModel.rows), - flatRows: sortedFlatRows, - rowsById: preSortedRowModel.rowsById, - } + return sortedData } diff --git a/packages/table-core/src/fns/aggregationFns.ts b/packages/table-core/src/fns/aggregationFns.ts index 0a395a1024..267e6a3e04 100755 --- a/packages/table-core/src/fns/aggregationFns.ts +++ b/packages/table-core/src/fns/aggregationFns.ts @@ -134,7 +134,7 @@ export function aggregationFn_mean< let sumValue = 0 for (let i = 0; i < leafRows.length; i++) { - let value = leafRows[i]!.getValue(columnId) + const value = leafRows[i]!.getValue(columnId) if (value != null) { if (typeof value === 'number') { ++count diff --git a/packages/table-core/src/utils.ts b/packages/table-core/src/utils.ts index c5d642d2d2..30faa6c50f 100755 --- a/packages/table-core/src/utils.ts +++ b/packages/table-core/src/utils.ts @@ -157,25 +157,29 @@ export const memo = , TDepArgs, TResult>({ const memoizedFn = (depArgs?: TDepArgs): TResult => { onBeforeCompare?.() const newDeps = memoDeps?.(depArgs) - let depsChanged = !newDeps || newDeps.length !== deps?.length - if (!depsChanged && newDeps) { + + if (!newDeps || newDeps.length !== deps?.length) { + onAfterCompare?.(true); + } else { + let depsChanged = false; for (let i = 0; i < newDeps.length; i++) { - if (newDeps[i] !== deps![i]) { + if (newDeps[i] !== deps[i]) { depsChanged = true break } } - } - onAfterCompare?.(depsChanged) - if (!depsChanged) { - return result! + onAfterCompare?.(depsChanged) + if (!depsChanged) + return result! } deps = newDeps onBeforeUpdate?.() - result = fn(...(newDeps ?? ([] as any))) + result = deps + // @ts-expect-error no correct type for this + ? fn(...deps) : fn() onAfterUpdate?.(result) return result @@ -197,13 +201,7 @@ interface TableMemoOptions< table: Table_Internal } -const pad = (str: number | string, num: number) => { - str = String(str) - while (str.length < num) { - str = ' ' + str - } - return str -} +const noopCallback = () => {} /** * Creates a table-aware memoized function. @@ -232,8 +230,6 @@ export function tableMemo< let debugCache: boolean | undefined if (process.env.NODE_ENV === 'development') { - const { debugCache: _debugCache, debugAll } = table.options - debugCache = _debugCache const { parentName } = getFunctionNameInfo(fnName, '.') const debugByParent = @@ -241,17 +237,18 @@ export function tableMemo< table.options[ `debug${(parentName != 'table' ? parentName + 's' : parentName).replace( parentName, - parentName.charAt(0).toUpperCase() + parentName.slice(1), + parentName[0]!.toUpperCase() + parentName.slice(1), )}` ] const debugByFeature = feature ? // @ts-expect-error table.options[ - `debug${feature.charAt(0).toUpperCase() + feature.slice(1)}` + `debug${feature[0]!.toUpperCase() + feature.slice(1)}` ] : false - debug = debugAll || debugByParent || debugByFeature + debug = table.options.debugAll || debugByParent || debugByFeature + debugCache = table.options.debugCache; } function logTime(time: number, depsChanged: boolean) { @@ -264,7 +261,7 @@ export function tableMemo< runCount++ console.groupCollapsed( - `%c⏱ ${pad(`${time.toFixed(1)} ms`, 12)} %c${runType}%c ${fnName}%c ${objectId ? `(${fnName.split('.')[0]}Id: ${objectId})` : ''}`, + `%c⏱ ${`${time.toFixed(1)} ms`.padStart(12)} %c${runType}%c ${fnName}%c ${objectId ? `(${fnName.split('.')[0]}Id: ${objectId})` : ''}`, `font-size: .6rem; font-weight: bold; ${ depsChanged ? `color: hsl( @@ -284,58 +281,51 @@ export function tableMemo< console.groupEnd() } - const onAfterUpdateHandler = () => { - if (!onAfterUpdate) { - return + const onAfterUpdateHandler = onAfterUpdate + ? () => { + const { schedule, untrack } = table._reactivity + schedule(() => untrack(() => onAfterUpdate())) } + : noopCallback - const { schedule, untrack } = table._reactivity - schedule(() => untrack(() => onAfterUpdate())) - } - - const debugOptions = + const allOptions = process.env.NODE_ENV === 'development' ? { - onBeforeCompare: () => { - if (debugCache) { + ...memoOptions, + onBeforeCompare: debugCache + ? () => { beforeCompareTime = performance.now() } - }, - onAfterCompare: (depsChanged: boolean) => { - if (debugCache) { + : noopCallback, + onAfterCompare: debugCache + ? (depsChanged: boolean) => { afterCompareTime = performance.now() - const compareTime = - Math.round((afterCompareTime - beforeCompareTime) * 100) / 100 if (!depsChanged) { + const compareTime = Math.round((afterCompareTime - beforeCompareTime) * 100) / 100 logTime(compareTime, depsChanged) } } - }, - onBeforeUpdate: () => { - if (debug) { + : noopCallback, + onBeforeUpdate: debug + ? () => { startCalcTime = performance.now() } - }, - onAfterUpdate: () => { - if (debug) { + : noopCallback, + onAfterUpdate: debug + ? () => { endCalcTime = performance.now() - const executionTime = - Math.round((endCalcTime - startCalcTime) * 100) / 100 + const executionTime = Math.round((endCalcTime - startCalcTime) * 100) / 100 logTime(executionTime, true) + onAfterUpdateHandler() } - onAfterUpdateHandler() - }, + : onAfterUpdateHandler, } : { - onAfterUpdate: () => { - onAfterUpdateHandler() - }, + ...memoOptions, + onAfterUpdate: onAfterUpdateHandler, } - return memo({ - ...memoOptions, - ...debugOptions, - }) + return memo(allOptions) } export interface API, TDepArgs> { From b810d763695798e06da5aa9755e1a4dc6ce9f336 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:31:12 +0000 Subject: [PATCH 03/12] ci: apply automated fixes --- .../row-sorting/createSortedRowModel.ts | 42 ++++++++----- packages/table-core/src/fns/aggregationFns.ts | 16 +++-- packages/table-core/src/fns/sortFns.ts | 2 +- packages/table-core/src/utils.ts | 59 ++++++++++--------- 4 files changed, 65 insertions(+), 54 deletions(-) diff --git a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts index 0620083a8e..63e65d5e3c 100644 --- a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts +++ b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts @@ -56,13 +56,15 @@ function _createSortedRowModel< sortFn: SortFn }>() - const availableSorting: typeof sorting = []; + const availableSorting: typeof sorting = [] for (let i = 0; i < sorting.length; i++) { - const { id } = sorting[i]!; + const { id } = sorting[i]! - const column = table.getColumn(id) as Column_Internal | undefined; + const column = table.getColumn(id) as + | Column_Internal + | undefined if (column && column_getCanSort(column)) { - availableSorting.push(column as any); + availableSorting.push(column as any) columnInfoById[id] = { sortUndefined: column.columnDef.sortUndefined, invertSorting: column.columnDef.invertSorting, @@ -72,24 +74,29 @@ function _createSortedRowModel< } return { - rows: sortData(preSortedRowModel.rows, sortedFlatRows, availableSorting, columnInfoById), + rows: sortData( + preSortedRowModel.rows, + sortedFlatRows, + availableSorting, + columnInfoById, + ), flatRows: sortedFlatRows, rowsById: preSortedRowModel.rowsById, } } -const sortData = < - TFeatures extends TableFeatures, - TData extends RowData = any, ->( +const sortData = ( rows: Array>, sortedFlatRows: Array>, availableSorting: Array, - columnInfoById: Record; - }> + columnInfoById: Record< + string, + { + sortUndefined?: false | -1 | 1 | 'first' | 'last' + invertSorting?: boolean + sortFn: SortFn + } + >, ) => { const sortedData = rows.slice() @@ -150,7 +157,12 @@ const sortData = < // Preserve prototype chain so methods like getValue() remain accessible const cloned = Object.create(Object.getPrototypeOf(row)) Object.assign(cloned, row) - cloned.subRows = sortData(row.subRows, sortedFlatRows, availableSorting, columnInfoById) + cloned.subRows = sortData( + row.subRows, + sortedFlatRows, + availableSorting, + columnInfoById, + ) sortedData[i] = cloned sortedFlatRows.push(cloned) } else { diff --git a/packages/table-core/src/fns/aggregationFns.ts b/packages/table-core/src/fns/aggregationFns.ts index 267e6a3e04..ae3e3d38e8 100755 --- a/packages/table-core/src/fns/aggregationFns.ts +++ b/packages/table-core/src/fns/aggregationFns.ts @@ -23,7 +23,7 @@ export function aggregationFn_sum< const nextValue = childRows[i]!.getValue(columnId) if (typeof nextValue === 'number') sumValue += nextValue } - return sumValue; + return sumValue } /** @@ -40,7 +40,7 @@ export function aggregationFn_min< _leafRows: Array>, childRows: Array>, ) { - let minValue: number | undefined; + let minValue: number | undefined for (let i = 0; i < childRows.length; i++) { const value = childRows[i]!.getValue(columnId) @@ -162,14 +162,14 @@ export function aggregationFn_median< TFeatures extends TableFeatures, TData extends RowData, >(columnId: string, leafRows: Array>) { - const len = leafRows.length; + const len = leafRows.length if (len === 0) { return } if (len === 1) { - const v = leafRows[0]!.getValue(columnId); - return typeof v === 'number' ? v : undefined; + const v = leafRows[0]!.getValue(columnId) + return typeof v === 'number' ? v : undefined } const values: Array = new Array(len) @@ -179,11 +179,9 @@ export function aggregationFn_median< values[i] = v } - const mid = len >>> 1; // Divide by 2 and floor + const mid = len >>> 1 // Divide by 2 and floor values.sort((a, b) => a - b) - return (len & 1) === 1 - ? values[mid] - : (values[mid - 1]! + values[mid]!) / 2 + return (len & 1) === 1 ? values[mid] : (values[mid - 1]! + values[mid]!) / 2 } /** diff --git a/packages/table-core/src/fns/sortFns.ts b/packages/table-core/src/fns/sortFns.ts index b36a811eae..cd96315cf5 100644 --- a/packages/table-core/src/fns/sortFns.ts +++ b/packages/table-core/src/fns/sortFns.ts @@ -136,7 +136,7 @@ function compareBasic(a: any, b: any) { function toString(a: any) { if (typeof a === 'number') { - return Number.isFinite(a) ? '' + a : ''; + return Number.isFinite(a) ? '' + a : '' } if (typeof a === 'string') { return a diff --git a/packages/table-core/src/utils.ts b/packages/table-core/src/utils.ts index 30faa6c50f..13513fcf98 100755 --- a/packages/table-core/src/utils.ts +++ b/packages/table-core/src/utils.ts @@ -159,9 +159,9 @@ export const memo = , TDepArgs, TResult>({ const newDeps = memoDeps?.(depArgs) if (!newDeps || newDeps.length !== deps?.length) { - onAfterCompare?.(true); + onAfterCompare?.(true) } else { - let depsChanged = false; + let depsChanged = false for (let i = 0; i < newDeps.length; i++) { if (newDeps[i] !== deps[i]) { depsChanged = true @@ -170,16 +170,16 @@ export const memo = , TDepArgs, TResult>({ } onAfterCompare?.(depsChanged) - if (!depsChanged) - return result! + if (!depsChanged) return result! } deps = newDeps onBeforeUpdate?.() result = deps - // @ts-expect-error no correct type for this - ? fn(...deps) : fn() + ? // @ts-expect-error no correct type for this + fn(...deps) + : fn() onAfterUpdate?.(result) return result @@ -242,13 +242,11 @@ export function tableMemo< ] const debugByFeature = feature ? // @ts-expect-error - table.options[ - `debug${feature[0]!.toUpperCase() + feature.slice(1)}` - ] + table.options[`debug${feature[0]!.toUpperCase() + feature.slice(1)}`] : false debug = table.options.debugAll || debugByParent || debugByFeature - debugCache = table.options.debugCache; + debugCache = table.options.debugCache } function logTime(time: number, depsChanged: boolean) { @@ -283,45 +281,48 @@ export function tableMemo< const onAfterUpdateHandler = onAfterUpdate ? () => { - const { schedule, untrack } = table._reactivity - schedule(() => untrack(() => onAfterUpdate())) - } + const { schedule, untrack } = table._reactivity + schedule(() => untrack(() => onAfterUpdate())) + } : noopCallback const allOptions = process.env.NODE_ENV === 'development' ? { - ...memoOptions, + ...memoOptions, onBeforeCompare: debugCache ? () => { - beforeCompareTime = performance.now() - } + beforeCompareTime = performance.now() + } : noopCallback, onAfterCompare: debugCache ? (depsChanged: boolean) => { - afterCompareTime = performance.now() - if (!depsChanged) { - const compareTime = Math.round((afterCompareTime - beforeCompareTime) * 100) / 100 - logTime(compareTime, depsChanged) + afterCompareTime = performance.now() + if (!depsChanged) { + const compareTime = + Math.round((afterCompareTime - beforeCompareTime) * 100) / + 100 + logTime(compareTime, depsChanged) + } } - } : noopCallback, onBeforeUpdate: debug ? () => { - startCalcTime = performance.now() - } + startCalcTime = performance.now() + } : noopCallback, onAfterUpdate: debug ? () => { - endCalcTime = performance.now() - const executionTime = Math.round((endCalcTime - startCalcTime) * 100) / 100 - logTime(executionTime, true) - onAfterUpdateHandler() - } + endCalcTime = performance.now() + const executionTime = + Math.round((endCalcTime - startCalcTime) * 100) / 100 + logTime(executionTime, true) + onAfterUpdateHandler() + } : onAfterUpdateHandler, } : { - ...memoOptions, + ...memoOptions, onAfterUpdate: onAfterUpdateHandler, } From 5982ef3e1ee36f7e2d69ff3f30694a8039b644cf Mon Sep 17 00:00:00 2001 From: aquapi Date: Thu, 25 Jun 2026 23:49:36 +0700 Subject: [PATCH 04/12] optimize column construction --- .../src/core/columns/constructColumn.ts | 2 +- .../core/columns/coreColumnsFeature.utils.ts | 100 ++++++++++-------- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/packages/table-core/src/core/columns/constructColumn.ts b/packages/table-core/src/core/columns/constructColumn.ts index b6ca11debf..06135e0894 100644 --- a/packages/table-core/src/core/columns/constructColumn.ts +++ b/packages/table-core/src/core/columns/constructColumn.ts @@ -112,7 +112,7 @@ export function constructColumn< column.columnDef = resolvedColumnDef as ColumnDef column.columns = [] column.depth = depth - column.id = `${String(id)}` + column.id = '' + id column.parent = parent return column as Column diff --git a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts index 2fae8a1fd7..5c2a0354d9 100644 --- a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts +++ b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts @@ -29,7 +29,9 @@ export function column_getFlatColumns< >( column: Column, ): Array> { - return [column, ...column.columns.flatMap((col) => col.getFlatColumns())] + const arr = column.columns.flatMap((col) => col.getFlatColumns()); + arr.unshift(column); + return arr; } /** @@ -65,6 +67,24 @@ export function column_getLeafColumns< return [column] } +const DefaultColumnDef = { + header: (props) => { + const resolvedColumnDef = props.header.column + .columnDef as ColumnDefResolved<{}, any> + + if (resolvedColumnDef.accessorKey) { + return resolvedColumnDef.accessorKey + } + + if (resolvedColumnDef.accessorFn) { + return resolvedColumnDef.id + } + + return null + }, + cell: (props) => props.renderValue()?.toString?.() ?? null, +} as Partial> + /** * Merges built-in, feature, and user default column definitions. * @@ -83,27 +103,45 @@ export function table_getDefaultColumnDef< >( table: Table_Internal, ): Partial> { - return { - header: (props) => { - const resolvedColumnDef = props.header.column - .columnDef as ColumnDefResolved<{}, TData> + const defaultColumn = Object.create(DefaultColumnDef); + + for (let i = 0, features = Object.values(table._features); i < features.length; i++) { + Object.assign(defaultColumn, features[i].getDefaultColumnDef?.()); + } - if (resolvedColumnDef.accessorKey) { - return resolvedColumnDef.accessorKey - } + return Object.assign(defaultColumn, table.options.defaultColumn); +} + +const table_getAllColumns_recurseColumns = < + TFeatures extends TableFeatures, + TData extends RowData, +>( + table: Table_Internal, + colDefs: ReadonlyArray>, + parent: Column | undefined, + depth: number, +): Array> => { + const result: Array> = new Array(colDefs.length) - if (resolvedColumnDef.accessorFn) { - return resolvedColumnDef.id - } + for (let i = 0; i < colDefs.length; i++) { + const columnDef = colDefs[i]!; - return null - }, - cell: (props) => props.renderValue()?.toString?.() ?? null, - ...Object.values(table._features).reduce((obj, feature) => { - return Object.assign(obj, feature.getDefaultColumnDef?.()) - }, {}), - ...table.options.defaultColumn, - } as Partial> + const column = constructColumn(table, columnDef, depth, parent) + + const groupingColumnDef = columnDef as GroupColumnDef< + TFeatures, + TData, + unknown + > + + column.columns = groupingColumnDef.columns + ? table_getAllColumns_recurseColumns(table, groupingColumnDef.columns, column, depth + 1) + : [] + + result[i] = column + } + + return result } /** @@ -123,29 +161,7 @@ export function table_getAllColumns< >( table: Table_Internal, ): Array> { - const recurseColumns = ( - colDefs: ReadonlyArray>, - parent?: Column, - depth = 0, - ): Array> => { - return colDefs.map((columnDef) => { - const column = constructColumn(table, columnDef, depth, parent) - - const groupingColumnDef = columnDef as GroupColumnDef< - TFeatures, - TData, - unknown - > - - column.columns = groupingColumnDef.columns - ? recurseColumns(groupingColumnDef.columns, column, depth + 1) - : [] - - return column - }) - } - - return recurseColumns(table.options.columns) + return table_getAllColumns_recurseColumns(table, table.options.columns, undefined, 0); } /** From 561ae9f61178b5ce638a65509992fd436bc966b5 Mon Sep 17 00:00:00 2001 From: aquapi Date: Thu, 25 Jun 2026 23:56:32 +0700 Subject: [PATCH 05/12] optimize utils.ts (flattenBy), fix @ts-expect-error location --- packages/table-core/src/utils.ts | 33 ++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/table-core/src/utils.ts b/packages/table-core/src/utils.ts index 13513fcf98..8931519972 100755 --- a/packages/table-core/src/utils.ts +++ b/packages/table-core/src/utils.ts @@ -101,6 +101,22 @@ export function isFunction(d: any): d is T { return d instanceof Function } +const flattenBy_recurse = ( + subArr: Array, + getChildren: (item: TNode) => Array, + flat: Array +) => { + for (let i = 0; i < subArr.length; i++) { + const item = subArr[i]! + + flat.push(item) + const children = getChildren(item) + if (children.length) { + flattenBy_recurse(children, getChildren, flat); + } + } +} + /** * Flattens a tree of nodes by recursively reading child nodes. * @@ -111,19 +127,7 @@ export function flattenBy( getChildren: (item: TNode) => Array, ) { const flat: Array = [] - - const recurse = (subArr: Array) => { - subArr.forEach((item) => { - flat.push(item) - const children = getChildren(item) - if (children.length) { - recurse(children) - } - }) - } - - recurse(arr) - + flattenBy_recurse(arr, getChildren, flat); return flat } @@ -179,7 +183,8 @@ export const memo = , TDepArgs, TResult>({ result = deps ? // @ts-expect-error no correct type for this fn(...deps) - : fn() + : // @ts-expect-error no correct type for this + fn() onAfterUpdate?.(result) return result From d0850524f2d0044a1d92deb15ba4a12ef3307016 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:29:03 +0000 Subject: [PATCH 06/12] ci: apply automated fixes --- .../core/columns/coreColumnsFeature.utils.ts | 38 +++++++++++++------ packages/table-core/src/utils.ts | 6 +-- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts index 5c2a0354d9..32cb99d24d 100644 --- a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts +++ b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts @@ -29,9 +29,9 @@ export function column_getFlatColumns< >( column: Column, ): Array> { - const arr = column.columns.flatMap((col) => col.getFlatColumns()); - arr.unshift(column); - return arr; + const arr = column.columns.flatMap((col) => col.getFlatColumns()) + arr.unshift(column) + return arr } /** @@ -103,13 +103,17 @@ export function table_getDefaultColumnDef< >( table: Table_Internal, ): Partial> { - const defaultColumn = Object.create(DefaultColumnDef); + const defaultColumn = Object.create(DefaultColumnDef) - for (let i = 0, features = Object.values(table._features); i < features.length; i++) { - Object.assign(defaultColumn, features[i].getDefaultColumnDef?.()); + for ( + let i = 0, features = Object.values(table._features); + i < features.length; + i++ + ) { + Object.assign(defaultColumn, features[i].getDefaultColumnDef?.()) } - return Object.assign(defaultColumn, table.options.defaultColumn); + return Object.assign(defaultColumn, table.options.defaultColumn) } const table_getAllColumns_recurseColumns = < @@ -121,10 +125,12 @@ const table_getAllColumns_recurseColumns = < parent: Column | undefined, depth: number, ): Array> => { - const result: Array> = new Array(colDefs.length) + const result: Array> = new Array( + colDefs.length, + ) for (let i = 0; i < colDefs.length; i++) { - const columnDef = colDefs[i]!; + const columnDef = colDefs[i]! const column = constructColumn(table, columnDef, depth, parent) @@ -135,7 +141,12 @@ const table_getAllColumns_recurseColumns = < > column.columns = groupingColumnDef.columns - ? table_getAllColumns_recurseColumns(table, groupingColumnDef.columns, column, depth + 1) + ? table_getAllColumns_recurseColumns( + table, + groupingColumnDef.columns, + column, + depth + 1, + ) : [] result[i] = column @@ -161,7 +172,12 @@ export function table_getAllColumns< >( table: Table_Internal, ): Array> { - return table_getAllColumns_recurseColumns(table, table.options.columns, undefined, 0); + return table_getAllColumns_recurseColumns( + table, + table.options.columns, + undefined, + 0, + ) } /** diff --git a/packages/table-core/src/utils.ts b/packages/table-core/src/utils.ts index 8931519972..63fced3c21 100755 --- a/packages/table-core/src/utils.ts +++ b/packages/table-core/src/utils.ts @@ -104,7 +104,7 @@ export function isFunction(d: any): d is T { const flattenBy_recurse = ( subArr: Array, getChildren: (item: TNode) => Array, - flat: Array + flat: Array, ) => { for (let i = 0; i < subArr.length; i++) { const item = subArr[i]! @@ -112,7 +112,7 @@ const flattenBy_recurse = ( flat.push(item) const children = getChildren(item) if (children.length) { - flattenBy_recurse(children, getChildren, flat); + flattenBy_recurse(children, getChildren, flat) } } } @@ -127,7 +127,7 @@ export function flattenBy( getChildren: (item: TNode) => Array, ) { const flat: Array = [] - flattenBy_recurse(arr, getChildren, flat); + flattenBy_recurse(arr, getChildren, flat) return flat } From cf6b9d7ff32b92725f5f17a36de1fb60db1ad4de Mon Sep 17 00:00:00 2001 From: aquapi Date: Fri, 26 Jun 2026 10:16:45 +0700 Subject: [PATCH 07/12] fix 'DefaultColumnDef is lost by the spread in constructColumn.' --- .../core/columns/coreColumnsFeature.utils.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts index 32cb99d24d..aa15b62f39 100644 --- a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts +++ b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts @@ -67,23 +67,21 @@ export function column_getLeafColumns< return [column] } -const DefaultColumnDef = { - header: (props) => { - const resolvedColumnDef = props.header.column - .columnDef as ColumnDefResolved<{}, any> +const defaultColumnDef_header: Partial>['header'] = (props) => { + const resolvedColumnDef = props.header.column + .columnDef as ColumnDefResolved<{}, any> - if (resolvedColumnDef.accessorKey) { - return resolvedColumnDef.accessorKey - } + if (resolvedColumnDef.accessorKey) { + return resolvedColumnDef.accessorKey + } - if (resolvedColumnDef.accessorFn) { - return resolvedColumnDef.id - } + if (resolvedColumnDef.accessorFn) { + return resolvedColumnDef.id + } - return null - }, - cell: (props) => props.renderValue()?.toString?.() ?? null, -} as Partial> + return null +} +const defaultColumnDef_cell: Partial>['cell'] = (props) => props.renderValue()?.toString?.() ?? null /** * Merges built-in, feature, and user default column definitions. @@ -103,7 +101,10 @@ export function table_getDefaultColumnDef< >( table: Table_Internal, ): Partial> { - const defaultColumn = Object.create(DefaultColumnDef) + const defaultColumn = { + header: defaultColumnDef_header, + cell: defaultColumnDef_cell + }; for ( let i = 0, features = Object.values(table._features); From 595e890730fa064d87586fd2da106e3a24863643 Mon Sep 17 00:00:00 2001 From: aquapi Date: Fri, 26 Jun 2026 10:22:01 +0700 Subject: [PATCH 08/12] fix: preserve original ColumnSort --- .../src/features/row-sorting/createSortedRowModel.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts index 63e65d5e3c..f881e23c59 100644 --- a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts +++ b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts @@ -58,14 +58,14 @@ function _createSortedRowModel< const availableSorting: typeof sorting = [] for (let i = 0; i < sorting.length; i++) { - const { id } = sorting[i]! + const sort = sorting[i]!; - const column = table.getColumn(id) as + const column = table.getColumn(sort.id) as | Column_Internal | undefined if (column && column_getCanSort(column)) { - availableSorting.push(column as any) - columnInfoById[id] = { + availableSorting.push(sort) + columnInfoById[sort.id] = { sortUndefined: column.columnDef.sortUndefined, invertSorting: column.columnDef.invertSorting, sortFn: column_getSortFn(column), From 55ad6b25979e6c02d697d4fec2b3606bb83eb583 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 14:06:47 +0000 Subject: [PATCH 09/12] ci: apply automated fixes --- .../core/columns/coreColumnsFeature.utils.ts | 18 ++++++++++++------ .../row-sorting/createSortedRowModel.ts | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts index aa15b62f39..b356690413 100644 --- a/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts +++ b/packages/table-core/src/core/columns/coreColumnsFeature.utils.ts @@ -67,9 +67,13 @@ export function column_getLeafColumns< return [column] } -const defaultColumnDef_header: Partial>['header'] = (props) => { - const resolvedColumnDef = props.header.column - .columnDef as ColumnDefResolved<{}, any> +const defaultColumnDef_header: Partial< + ColumnDef +>['header'] = (props) => { + const resolvedColumnDef = props.header.column.columnDef as ColumnDefResolved< + {}, + any + > if (resolvedColumnDef.accessorKey) { return resolvedColumnDef.accessorKey @@ -81,7 +85,9 @@ const defaultColumnDef_header: Partial>['header'] = return null } -const defaultColumnDef_cell: Partial>['cell'] = (props) => props.renderValue()?.toString?.() ?? null +const defaultColumnDef_cell: Partial>['cell'] = ( + props, +) => props.renderValue()?.toString?.() ?? null /** * Merges built-in, feature, and user default column definitions. @@ -103,8 +109,8 @@ export function table_getDefaultColumnDef< ): Partial> { const defaultColumn = { header: defaultColumnDef_header, - cell: defaultColumnDef_cell - }; + cell: defaultColumnDef_cell, + } for ( let i = 0, features = Object.values(table._features); diff --git a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts index f881e23c59..dbfb441521 100644 --- a/packages/table-core/src/features/row-sorting/createSortedRowModel.ts +++ b/packages/table-core/src/features/row-sorting/createSortedRowModel.ts @@ -58,7 +58,7 @@ function _createSortedRowModel< const availableSorting: typeof sorting = [] for (let i = 0; i < sorting.length; i++) { - const sort = sorting[i]!; + const sort = sorting[i]! const column = table.getColumn(sort.id) as | Column_Internal From 96350a15f6f534d20e54e51bd75901dd5b918fc7 Mon Sep 17 00:00:00 2001 From: aquapi Date: Fri, 26 Jun 2026 23:25:14 +0700 Subject: [PATCH 10/12] optimize column faceting --- .../columnFacetingFeature.utils.ts | 30 ++++--------------- .../createFacetedUniqueValues.ts | 14 ++++----- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts b/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts index 8c76af0f48..3009cbb502 100644 --- a/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts +++ b/packages/table-core/src/features/column-faceting/columnFacetingFeature.utils.ts @@ -23,10 +23,7 @@ export function column_getFacetedMinMaxValues< column: Column_Internal, table: Table_Internal, ): [number, number] | undefined { - const facetedMinMaxValuesFn = - table.options.features.facetedMinMaxValues?.(table, column.id) ?? - (() => undefined) - return facetedMinMaxValuesFn() + return table.options.features.facetedMinMaxValues?.(table, column.id)?.() } /** @@ -49,10 +46,7 @@ export function column_getFacetedRowModel< column: Column_Internal | undefined, table: Table_Internal, ): RowModel { - const facetedRowModelFn = - table.options.features.facetedRowModel?.(table, column?.id ?? '') ?? - (() => table.getPreFilteredRowModel()) - return facetedRowModelFn() + return table.options.features.facetedRowModel?.(table, column?.id ?? '')?.() ?? table.getPreFilteredRowModel() } /** @@ -74,10 +68,7 @@ export function column_getFacetedUniqueValues< column: Column_Internal, table: Table_Internal, ): Map { - const facetedUniqueValuesFn = - table.options.features.facetedUniqueValues?.(table, column.id) ?? - (() => new Map()) - return facetedUniqueValuesFn() + return table.options.features.facetedUniqueValues?.(table, column.id)?.() ?? new Map() } /** @@ -95,10 +86,7 @@ export function table_getGlobalFacetedMinMaxValues< TFeatures extends TableFeatures, TData extends RowData, >(table: Table_Internal): undefined | [number, number] { - const facetedMinMaxValuesFn = - table.options.features.facetedMinMaxValues?.(table, '__global__') ?? - (() => undefined) - return facetedMinMaxValuesFn() + return table.options.features.facetedMinMaxValues?.(table, '__global__')?.() } /** @@ -117,10 +105,7 @@ export function table_getGlobalFacetedRowModel< TFeatures extends TableFeatures, TData extends RowData, >(table: Table_Internal): RowModel { - const facetedRowModelFn = - table.options.features.facetedRowModel?.(table, '__global__') ?? - (() => table.getPreFilteredRowModel()) - return facetedRowModelFn() + return table.options.features.facetedRowModel?.(table, '__global__')?.() ?? table.getPreFilteredRowModel() } /** @@ -138,8 +123,5 @@ export function table_getGlobalFacetedUniqueValues< TFeatures extends TableFeatures, TData extends RowData, >(table: Table_Internal): Map { - const facetedUniqueValuesFn = - table.options.features.facetedUniqueValues?.(table, '__global__') ?? - (() => new Map()) - return facetedUniqueValuesFn() + return table.options.features.facetedUniqueValues?.(table, '__global__')?.() ?? new Map() } diff --git a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts index 8c6ce5c86e..9c54459885 100644 --- a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts +++ b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts @@ -51,14 +51,12 @@ function _createFacetedUniqueValues< for (let j = 0; j < values.length; j++) { const value = values[j] - if (facetedUniqueValues.has(value)) { - facetedUniqueValues.set( - value, - (facetedUniqueValues.get(value) ?? 0) + 1, - ) - } else { - facetedUniqueValues.set(value, 1) - } + + const facetedUniqueValue = facetedUniqueValues.get(value) + facetedUniqueValues.set( + value, + facetedUniqueValue ? (facetedUniqueValue + 1) : 1, + ) } } From 91901ff76aa436a22cc6d7c2edf963f618561bba Mon Sep 17 00:00:00 2001 From: aquapi Date: Fri, 26 Jun 2026 23:27:45 +0700 Subject: [PATCH 11/12] optimize createFacetedUniqueValues --- .../src/features/column-faceting/createFacetedUniqueValues.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts index 9c54459885..727d0eb461 100644 --- a/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts +++ b/packages/table-core/src/features/column-faceting/createFacetedUniqueValues.ts @@ -52,10 +52,10 @@ function _createFacetedUniqueValues< for (let j = 0; j < values.length; j++) { const value = values[j] - const facetedUniqueValue = facetedUniqueValues.get(value) + const prevValue = facetedUniqueValues.get(value) facetedUniqueValues.set( value, - facetedUniqueValue ? (facetedUniqueValue + 1) : 1, + prevValue ? (prevValue + 1) : 1, ) } } From 30aa208e74e788d966c37d104b9415884e4c882d Mon Sep 17 00:00:00 2001 From: aquapi Date: Fri, 26 Jun 2026 23:28:09 +0700 Subject: [PATCH 12/12] micro-optimize memo --- packages/table-core/src/utils.ts | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/packages/table-core/src/utils.ts b/packages/table-core/src/utils.ts index 63fced3c21..a7b71b4663 100755 --- a/packages/table-core/src/utils.ts +++ b/packages/table-core/src/utils.ts @@ -101,22 +101,6 @@ export function isFunction(d: any): d is T { return d instanceof Function } -const flattenBy_recurse = ( - subArr: Array, - getChildren: (item: TNode) => Array, - flat: Array, -) => { - for (let i = 0; i < subArr.length; i++) { - const item = subArr[i]! - - flat.push(item) - const children = getChildren(item) - if (children.length) { - flattenBy_recurse(children, getChildren, flat) - } - } -} - /** * Flattens a tree of nodes by recursively reading child nodes. * @@ -125,9 +109,16 @@ const flattenBy_recurse = ( export function flattenBy( arr: Array, getChildren: (item: TNode) => Array, + flat: Array = [] ) { - const flat: Array = [] - flattenBy_recurse(arr, getChildren, flat) + for (let i = 0; i < arr.length; i++) { + const item = arr[i]! + + flat.push(item) + const children = getChildren(item) + children.length && flattenBy(children, getChildren, flat) + } + return flat } @@ -180,7 +171,7 @@ export const memo = , TDepArgs, TResult>({ deps = newDeps onBeforeUpdate?.() - result = deps + result = deps?.length ? // @ts-expect-error no correct type for this fn(...deps) : // @ts-expect-error no correct type for this