@absoluteui/helpers/compare
Comparers, natural ordering, equality, range checks, coalescing, and pattern matching.
import { ... } from '@absoluteui/helpers/compare';How to use
Section titled “How to use”Comparers and predicates for sorting, range checks, fallbacks, and small decision tables. compareValues orders numbers, strings, bigints, and dates consistently and puts null/undefined last in either direction, so grids sort predictably with incomplete data. For sorting arrays in one call, sortBy in helpers/array builds on the same rules.
How do I sort a grid by several columns with empty values last?
Section titled “How do I sort a grid by several columns with empty values last?”Build a comparer with by for each column and chain them with composeComparers (or thenBy). Pass an Intl.Collator for language-correct string order.
import { by, composeComparers } from '@absoluteui/helpers/compare';
const rows = [ { name: 'Ölund', dept: 'IT', hired: new Date('2021-03-01') }, { name: 'Adams', dept: null, hired: new Date('2019-07-15') }, { name: 'Zhou', dept: 'IT', hired: new Date('2023-01-09') }, { name: 'Berg', dept: 'HR', hired: new Date('2020-11-30') },];const collator = new Intl.Collator('sv');rows.sort(composeComparers( by((r) => r.dept, { collator }), by((r) => r.hired, { direction: 'desc' }),));rows.map((r) => r.name); // => ['Berg', 'Zhou', 'Ölund', 'Adams']How do I sort document names the way people expect?
Section titled “How do I sort document names the way people expect?”naturalCompare compares digit runs as numbers, so “Payslip 2” comes before “Payslip 10”.
import { naturalCompare } from '@absoluteui/helpers/compare';
const files = ['Payslip 10.pdf', 'Payslip 2.pdf', 'payslip 1.pdf'];[...files].sort((a, b) => naturalCompare(a, b, { sensitivity: 'base' })); // => ['payslip 1.pdf', 'Payslip 2.pdf', 'Payslip 10.pdf'][...files].sort(); // => ['Payslip 10.pdf', 'Payslip 2.pdf', 'payslip 1.pdf']How do I check that a value falls inside a period or band?
Section titled “How do I check that a value falls inside a period or band?”between works for numbers, strings, and dates, with inclusive or half-open bounds; missing values are never inside.
import { between } from '@absoluteui/helpers/compare';
const payPeriod = [new Date('2026-10-01T00:00:00Z'), new Date('2026-11-01T00:00:00Z')] as const;const inPeriod = (d: Date | null) => between(d, payPeriod[0], payPeriod[1], { inclusive: 'left' });inPeriod(new Date('2026-10-31T23:59:00Z')); // => trueinPeriod(new Date('2026-11-01T00:00:00Z')); // => falseinPeriod(null); // => falsebetween(48_000, 45_000, 60_000); // => trueHow do I pick a display value or map a status without long if chains?
Section titled “How do I pick a display value or map a status without long if chains?”coalesceBlank returns the first value that is not null, undefined, or blank text; match evaluates cases in order and stops at the first that fits.
import { coalesceBlank, match } from '@absoluteui/helpers/compare';
const employee = { preferredName: ' ', firstName: 'Asha', login: 'arao' };coalesceBlank(employee.preferredName, employee.firstName, employee.login); // => 'Asha'
const balanceLabel = (days: number): string => match<number, string>(days) .with((d) => d < 0, () => 'Overdrawn') .with(0, () => 'None left') .with((d) => d <= 3, (d) => `Low (${d})`) .otherwise((d) => `${d} days`);[-1, 0, 2, 14].map(balanceLabel); // => ['Overdrawn', 'None left', 'Low (2)', '14 days']How do I combine filter conditions?
Section titled “How do I combine filter conditions?”allPass, anyPass, and complement build one predicate from several, for example the filters a user switched on.
import { allPass, complement } from '@absoluteui/helpers/compare';
interface Emp { name: string; dept: string; active: boolean }const staff: Emp[] = [ { name: 'Asha', dept: 'HR', active: true }, { name: 'Ben', dept: 'IT', active: true }, { name: 'Chen', dept: 'HR', active: false },];const isHr = (e: Emp) => e.dept === 'HR';const isInactive = (e: Emp) => !e.active;staff.filter(allPass([isHr, complement(isInactive)])).map((e) => e.name); // => ['Asha']API reference
Section titled “API reference”Functions
Section titled “Functions”allPass
Predicate that passes when all predicates pass (short-circuit; empty -> true).
function allPass<T>(predicates: readonly ((value: T) => boolean)[]): (value: T) => boolean;Example:
allPass([(n: number) => n > 0, (n) => n < 10])(5) // => trueanyPass
Predicate that passes when any predicate passes (empty -> false).
function anyPass<T>(predicates: readonly ((value: T) => boolean)[]): (value: T) => boolean;Example:
anyPass([(n: number) => n < 0, (n) => n > 10])(11) // => truebetween
True when value lies between min and max (numbers, bigints, strings, dates).
Null-like values are never between.
function between<T>(value: T, min: T, max: T, options?: BetweenOptions): boolean;Example:
between(5, 1, 5) // => trueExample:
between(5, 1, 5, { inclusive: 'left' }) // => falseby
Builds a comparer from a key selector. Nulls stay where nulls says in either direction.
function by<T>(selector: (item: T) => unknown, options?: ByOptions): Comparer<T>;Example:
const people = [{ name: 'Ada', age: 36 }, { name: 'Ben', age: 41 }, { name: 'Cy', age: 29 }];people.sort(by((p) => p.age, { direction: 'desc' })).map((p) => p.name); // => ['Ben', 'Ada', 'Cy']coalesce
First value that is not null/undefined.
function coalesce<T>(...values: readonly T[]): NonNullable<T> | undefined;Example:
coalesce(undefined, null, 0, 1) // => 0coalesceBlank
First value that is not nullish and not a blank (whitespace-only) string.
function coalesceBlank<T>(...values: readonly T[]): NonNullable<T> | undefined;Example:
coalesceBlank(' ', null, 'x') // => 'x'compareValues
Compares numbers, bigints, strings, dates and booleans ascending; mixed types order
boolean < number/bigint < string < date < other. Null-like values (null, undefined,
NaN, invalid dates) go last by default.
function compareValues(a: unknown, b: unknown, options?: CompareOptions): number;Example:
[3, null, 1].sort(compareValues) // => [1, 3, null]complement
Negates a predicate.
function complement<A extends unknown[]>(predicate: (...args: A) => boolean): (...args: A) => boolean;Example:
[1, 2].filter(complement((n: number) => n % 2 === 0)) // => [1]composeComparers
Combines comparers: the first non-zero result wins.
function composeComparers<T>(...comparers: readonly Comparer<T>[]): Comparer<T>;Example:
const rows = [{ dept: 'IT', salary: 50 }, { dept: 'HR', salary: 40 }, { dept: 'IT', salary: 70 }];rows.sort(composeComparers(by((r) => r.dept), by((r) => r.salary, { direction: 'desc' }))).map((r) => r.salary); // => [40, 70, 50]cond
Returns a function that runs the handler of the first passing predicate, else fallback (or undefined).
function cond<T, R>(pairs: readonly (readonly [(value: T) => boolean, (value: T) => R])[], fallback?: (value: T) => R): (value: T) => R | undefined;Example:
const grade = cond([[(n: number) => n >= 90, () => 'A'], [(n) => n >= 80, () => 'B']], () => 'C');equals
Equality: SameValueZero by default, structural with { deep: true } (see deepEqual).
function equals(a: unknown, b: unknown, options?: { readonly deep?: boolean;}): boolean;Example:
equals({ a: 1 }, { a: 1 }, { deep: true }) // => trueifElse
Returns a function choosing between two branches.
function ifElse<T, A, B>(predicate: (value: T) => boolean, onTrue: (value: T) => A, onFalse: (value: T) => B): (value: T) => A | B;Example:
ifElse((n: number) => n % 2 === 0, () => 'even', () => 'odd')(3) // => 'odd'inRange
Half-open numeric range test start <= value < end.
function inRange(value: number, start: number, end: number): boolean;Example:
inRange(3, 0, 3) // => falseisAscending
True when the array is in ascending order.
function isAscending<T>(items: readonly T[], options?: OrderCheckOptions<T>): boolean;Example:
isAscending([1, 2, 2, 3]) // => trueisDescending
True when the array is in descending order.
function isDescending<T>(items: readonly T[], options?: OrderCheckOptions<T>): boolean;Example:
isDescending([3, 2, 1], { strict: true }) // => trueisEqualBy
Equality on a selected key (SameValueZero, or deep with { deep: true }).
function isEqualBy<T>(selector: (value: T) => unknown, options?: { readonly deep?: boolean;}): (a: T, b: T) => boolean;Example:
isEqualBy((u: { id: number }) => u.id)({ id: 1 }, { id: 1 }) // => truematch
Minimal pattern matching; the first matching case wins and later cases are not evaluated.
function match<T, R = unknown>(value: T): Matcher<T, R>;Example:
const label = (status: number): string => match<number, string>(status).with(200, () => 'ok').with((s) => s >= 500, () => 'server').otherwise(() => 'other');label(200); // => 'ok'label(503); // => 'server'label(404); // => 'other'maxOf
Largest item by comparer (first wins on ties), or undefined when empty.
function maxOf<T>(items: Iterable<T>, comparer?: Comparer<T>): T | undefined;Example:
maxOf(['b', 'a'], naturalCompare) // => 'b'minOf
Smallest item by comparer (first wins on ties), or undefined when empty.
function minOf<T>(items: Iterable<T>, comparer?: Comparer<T>): T | undefined;Example:
minOf([3, 1, 2]) // => 1naturalCompare
Numeric-aware, locale-aware string comparison (Intl.Collator with numeric: true, cached).
function naturalCompare(a: string, b: string, options?: NaturalCompareOptions): number;Example:
['file10', 'file2'].sort(naturalCompare) // => ['file2', 'file10']nonePass
Predicate that passes when no predicate passes.
function nonePass<T>(predicates: readonly ((value: T) => boolean)[]): (value: T) => boolean;Example:
nonePass([(s: string) => s === ''])('x') // => trueonePass
Predicate that passes when exactly one predicate passes (n-ary xor on predicates).
function onePass<T>(predicates: readonly ((value: T) => boolean)[]): (value: T) => boolean;Example:
onePass([(n: number) => n > 0, (n) => n > 5])(3) // => truereverseComparer
Reverses a comparer.
function reverseComparer<T>(comparer: Comparer<T>): Comparer<T>;Example:
[1, 3, 2].sort(reverseComparer(compareValues)) // => [3, 2, 1]thenBy
Adds a key-selector tie-breaker to a comparer (use composeComparers to chain comparers).
function thenBy<T>(primary: Comparer<T>, selector: (item: T) => unknown, options?: ByOptions): Comparer<T>;Example:
const rows = [{ first: 'Ben', last: 'Kay' }, { first: 'Ada', last: 'Lee' }, { first: 'Al', last: 'Kay' }];rows.sort(thenBy(by((r) => r.last), (r) => r.first)).map((r) => r.first); // => ['Al', 'Ben', 'Ada']unless
Returns a function that applies fn unless predicate holds.
function unless<T, R>(predicate: (value: T) => boolean, fn: (value: T) => R): (value: T) => T | R;Example:
unless((s: string) => s.endsWith('/'), (s) => `${s}/`)('a') // => 'a/'when
Returns a function that applies fn when predicate holds, else returns the input.
function when<T, R>(predicate: (value: T) => boolean, fn: (value: T) => R): (value: T) => T | R;Example:
[1, -2].map(when((n: number) => n < 0, (n) => -n)) // => [1, 2]Interfaces
Section titled “Interfaces”BetweenOptions
interface BetweenOptions extends CompareOptions { readonly inclusive?: 'both' | 'none' | 'left' | 'right' | boolean;}ByOptions
interface ByOptions extends CompareOptions { readonly direction?: 'asc' | 'desc';}CompareOptions
interface CompareOptions { readonly nulls?: NullsOrder; readonly collator?: Intl.Collator;}Matcher
Fluent matcher returned by match.
interface Matcher<T, R> { with<U extends T>(predicate: (value: T) => value is U, handler: (value: U) => R): Matcher<T, R>; with(test: ((value: T) => boolean) | T, handler: (value: T) => R): Matcher<T, R>; otherwise(handler: (value: T) => R): R;}NaturalCompareOptions
interface NaturalCompareOptions { readonly locale?: string | readonly string[]; readonly sensitivity?: Intl.CollatorOptions['sensitivity'];}OrderCheckOptions
interface OrderCheckOptions<T> { readonly comparer?: Comparer<T>; readonly strict?: boolean;}Comparer
Standard comparator: negative, zero or positive.
type Comparer<T> = (a: T, b: T) => number;NullsOrder
Where null/undefined (and invalid numbers/dates) sort.
type NullsOrder = 'first' | 'last';