@tanstack/table-core
Version:
Headless UI for building powerful tables & datagrids for TS/JS.
279 lines (277 loc) • 9.14 kB
JavaScript
import { constructAggregationFn } from "./rowAggregationFeature.types.js";
//#region src/features/row-aggregation/aggregationFns.ts
function isNumber(value) {
return typeof value === "number";
}
function isValidDate(value) {
return value instanceof Date && !Number.isNaN(value.getTime());
}
function getRangeKind(value) {
if (isNumber(value)) return "number";
if (isValidDate(value)) return "date";
}
function compareRangeValues(left, right) {
return (left instanceof Date ? left.getTime() : left) - (right instanceof Date ? right.getTime() : right);
}
/** Comparable representation of a range value; `Date`s compare by time. */
function toRangeNumber(value) {
return value instanceof Date ? value.getTime() : value;
}
/**
* Sums numeric selected-row values. Non-number values contribute zero. As in
* the previous API, `NaN` is a number and therefore propagates through the sum.
*/
const aggregationFn_sum = constructAggregationFn({
aggregate: (context) => {
const rows = context.rows;
let sum = 0;
for (let i = 0; i < rows.length; i++) {
const value = context.getValue(rows[i]);
sum += typeof value === "number" ? value : 0;
}
return sum;
},
merge: ({ subRowResults }) => {
let sum = 0;
for (let i = 0; i < subRowResults.length; i++) {
const value = subRowResults[i];
if (isNumber(value)) sum += value;
}
return sum;
}
});
/**
* Finds the minimum numeric or Date value from the selected rows. Invalid value
* types are ignored; `NaN` preserves the legacy numeric seeding behavior.
*/
const aggregationFn_min = constructAggregationFn({
aggregate: (context) => {
const rows = context.rows;
let kind;
let result;
let resultNumber = 0;
for (let i = 0; i < rows.length; i++) {
const value = context.getValue(rows[i]);
const valueKind = getRangeKind(value);
if (!valueKind || kind !== void 0 && valueKind !== kind) continue;
const valueNumber = toRangeNumber(value);
if (kind === void 0) {
kind = valueKind;
result = value;
resultNumber = valueNumber;
} else if (valueNumber - resultNumber < 0) {
result = value;
resultNumber = valueNumber;
}
}
return result;
},
merge: ({ subRowResults }) => {
let result;
let kind;
for (let i = 0; i < subRowResults.length; i++) {
const value = subRowResults[i];
const valueKind = getRangeKind(value);
if (!valueKind) continue;
if (value === void 0) continue;
kind ??= valueKind;
if (kind !== valueKind) continue;
if (result === void 0 || compareRangeValues(value, result) < 0) result = value;
}
return result;
}
});
/**
* Finds the maximum numeric or Date value from the selected rows. Invalid value
* types are ignored; `NaN` preserves the legacy numeric seeding behavior.
*/
const aggregationFn_max = constructAggregationFn({
aggregate: (context) => {
const rows = context.rows;
let kind;
let result;
let resultNumber = 0;
for (let i = 0; i < rows.length; i++) {
const value = context.getValue(rows[i]);
const valueKind = getRangeKind(value);
if (!valueKind || kind !== void 0 && valueKind !== kind) continue;
const valueNumber = toRangeNumber(value);
if (kind === void 0) {
kind = valueKind;
result = value;
resultNumber = valueNumber;
} else if (valueNumber - resultNumber > 0) {
result = value;
resultNumber = valueNumber;
}
}
return result;
},
merge: ({ subRowResults }) => {
let result;
let kind;
for (let i = 0; i < subRowResults.length; i++) {
const value = subRowResults[i];
const valueKind = getRangeKind(value);
if (!valueKind) continue;
if (value === void 0) continue;
kind ??= valueKind;
if (kind !== valueKind) continue;
if (result === void 0 || compareRangeValues(value, result) > 0) result = value;
}
return result;
}
});
/**
* Finds the minimum and maximum numeric or Date values from the selected rows.
* Empty inputs return
* `[undefined, undefined]`, preserving the previous built-in result shape.
*/
const aggregationFn_extent = constructAggregationFn({
aggregate: (context) => {
const rows = context.rows;
let kind;
let min;
let max;
let minNumber = 0;
let maxNumber = 0;
for (let i = 0; i < rows.length; i++) {
const value = context.getValue(rows[i]);
const valueKind = getRangeKind(value);
if (!valueKind || kind !== void 0 && valueKind !== kind) continue;
const valueNumber = toRangeNumber(value);
if (kind === void 0) {
kind = valueKind;
min = max = value;
minNumber = maxNumber = valueNumber;
} else {
if (valueNumber - minNumber < 0) {
min = value;
minNumber = valueNumber;
}
if (valueNumber - maxNumber > 0) {
max = value;
maxNumber = valueNumber;
}
}
}
if (kind === void 0) return [void 0, void 0];
return [min, max];
},
merge: ({ subRowResults }) => {
let result = [void 0, void 0];
let kind;
for (let i = 0; i < subRowResults.length; i++) {
const extent = subRowResults[i];
const min = extent[0];
const max = extent[1];
const valueKind = getRangeKind(min);
if (!valueKind || min === void 0 || max === void 0) continue;
kind ??= valueKind;
if (kind !== valueKind) continue;
if (result[0] === void 0) result = [min, max];
else {
if (compareRangeValues(min, result[0]) < 0) result[0] = min;
const currentMax = result[1];
if (currentMax === void 0 || compareRangeValues(max, currentMax) > 0) result[1] = max;
}
}
return result;
}
});
/**
* Averages number and number-like row values. Nullish and non-numeric values
* are ignored; other values retain the legacy unary-plus coercion behavior.
*/
const aggregationFn_mean = constructAggregationFn({ aggregate: (context) => {
const rows = context.rows;
let count = 0;
let sum = 0;
for (let i = 0; i < rows.length; i++) {
const value = context.getValue(rows[i]);
if (value == null) continue;
const numberValue = typeof value === "number" ? value : +value;
if (!Number.isNaN(numberValue)) {
count++;
sum += numberValue;
}
}
return count ? sum / count : void 0;
} });
/**
* Computes the median when every row value is a number. Returns `undefined`
* for empty inputs or when any value is non-numeric.
*/
const aggregationFn_median = constructAggregationFn({ aggregate: (context) => {
const rows = context.rows;
if (!rows.length) return void 0;
const values = new Array(rows.length);
for (let i = 0; i < rows.length; i++) {
const value = context.getValue(rows[i]);
if (typeof value !== "number") return void 0;
values[i] = value;
}
values.sort((a, b) => a - b);
const mid = Math.floor(values.length / 2);
return values.length % 2 ? values[mid] : (values[mid - 1] + values[mid]) / 2;
} });
/** Collects distinct row values using JavaScript `Set` semantics. */
const aggregationFn_unique = constructAggregationFn({ aggregate: (context) => {
const rows = context.rows;
const values = /* @__PURE__ */ new Set();
for (let i = 0; i < rows.length; i++) values.add(context.getValue(rows[i]));
return Array.from(values);
} });
/** Counts distinct row values using JavaScript `Set` semantics. */
const aggregationFn_uniqueCount = constructAggregationFn({ aggregate: (context) => {
const rows = context.rows;
const values = /* @__PURE__ */ new Set();
for (let i = 0; i < rows.length; i++) values.add(context.getValue(rows[i]));
return values.size;
} });
/** Counts rows, independently of the column's values. */
const aggregationFn_count = constructAggregationFn({
aggregate: ({ rows }) => rows.length,
merge: ({ subRowResults }) => {
let count = 0;
for (let i = 0; i < subRowResults.length; i++) {
const value = subRowResults[i];
if (isNumber(value)) count += value;
}
return count;
}
});
/** Returns the first row's value, including a nullish value. */
const aggregationFn_first = constructAggregationFn({
aggregate: (context) => context.rows[0] ? context.getValue(context.rows[0]) : void 0,
merge: ({ subRowResults }) => subRowResults[0]
});
/** Returns the last row's value, including a nullish value. */
const aggregationFn_last = constructAggregationFn({
aggregate: (context) => {
const row = context.rows[context.rows.length - 1];
return row ? context.getValue(row) : void 0;
},
merge: ({ subRowResults }) => subRowResults[subRowResults.length - 1]
});
/**
* Full built-in registry. Register individual definitions for tree-shaking.
*
* @deprecated Import individual `aggregationFn_*` definitions instead for a
* smaller bundle. This registry remains available for compatibility.
*/
const aggregationFns = {
sum: aggregationFn_sum,
min: aggregationFn_min,
max: aggregationFn_max,
extent: aggregationFn_extent,
mean: aggregationFn_mean,
median: aggregationFn_median,
unique: aggregationFn_unique,
uniqueCount: aggregationFn_uniqueCount,
count: aggregationFn_count,
first: aggregationFn_first,
last: aggregationFn_last
};
//#endregion
export { aggregationFn_count, aggregationFn_extent, aggregationFn_first, aggregationFn_last, aggregationFn_max, aggregationFn_mean, aggregationFn_median, aggregationFn_min, aggregationFn_sum, aggregationFn_unique, aggregationFn_uniqueCount, aggregationFns };