Files
ui-essentials/projects/ui-data-utils/src/lib/sorting.ts
skyai_dev 5346d6d0c9 Add comprehensive library expansion with new components and demos
- Add new libraries: ui-accessibility, ui-animations, ui-backgrounds, ui-code-display, ui-data-utils, ui-font-manager, hcl-studio
- Add extensive layout components: gallery-grid, infinite-scroll-container, kanban-board, masonry, split-view, sticky-layout
- Add comprehensive demo components for all new features
- Update project configuration and dependencies
- Expand component exports and routing structure
- Add UI landing pages planning document

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-05 05:37:37 +10:00

206 lines
5.9 KiB
TypeScript

/**
* Sorting utilities for data manipulation
*/
import { SortConfig, MultiSortConfig, ComparatorFn, SortDirection, DataType } from './types';
/**
* Get nested property value from object
* @param obj - Object to get value from
* @param path - Property path (supports dot notation)
*/
function getNestedValue(obj: any, path: string | keyof any): any {
if (typeof path === 'string' && path.includes('.')) {
return path.split('.').reduce((current, key) => current?.[key], obj);
}
return obj?.[path];
}
/**
* Convert value to appropriate type for comparison
* @param value - Value to convert
* @param dataType - Target data type
*/
function convertValue(value: any, dataType: DataType): any {
if (value == null) return value;
switch (dataType) {
case 'string':
return String(value).toLowerCase();
case 'number':
const num = Number(value);
return isNaN(num) ? 0 : num;
case 'date':
if (value instanceof Date) return value;
const date = new Date(value);
return isNaN(date.getTime()) ? new Date(0) : date;
case 'boolean':
return Boolean(value);
default:
return value;
}
}
/**
* Create a comparator function for a specific property and direction
* @param key - Property key to sort by
* @param direction - Sort direction
* @param dataType - Data type for proper comparison
*/
export function createComparator<T = any>(
key: keyof T,
direction: SortDirection = 'asc',
dataType: DataType = 'string'
): ComparatorFn<T> {
return (a: T, b: T): number => {
const valueA = convertValue(getNestedValue(a, key), dataType);
const valueB = convertValue(getNestedValue(b, key), dataType);
// Handle null/undefined values
if (valueA == null && valueB == null) return 0;
if (valueA == null) return direction === 'asc' ? -1 : 1;
if (valueB == null) return direction === 'asc' ? 1 : -1;
let result = 0;
if (dataType === 'string') {
result = valueA.localeCompare(valueB);
} else if (dataType === 'date') {
result = valueA.getTime() - valueB.getTime();
} else {
// For numbers and booleans
if (valueA < valueB) result = -1;
else if (valueA > valueB) result = 1;
else result = 0;
}
return direction === 'desc' ? -result : result;
};
}
/**
* Sort array by single property
* @param data - Array to sort
* @param config - Sort configuration
*/
export function sortBy<T>(data: T[], config: SortConfig<T>): T[] {
if (!Array.isArray(data) || data.length === 0) return data;
const comparator = createComparator(
config.key,
config.direction,
config.dataType || 'string'
);
return [...data].sort(comparator);
}
/**
* Sort array by multiple properties with priority
* @param data - Array to sort
* @param config - Multi-sort configuration
*/
export function sortByMultiple<T>(data: T[], config: MultiSortConfig<T>): T[] {
if (!Array.isArray(data) || data.length === 0) return data;
if (!config.sorts || config.sorts.length === 0) return data;
// Create comparators for each sort configuration
const comparators = config.sorts.map(sortConfig =>
createComparator(
sortConfig.key,
sortConfig.direction,
sortConfig.dataType || 'string'
)
);
return [...data].sort((a: T, b: T): number => {
// Apply each comparator in order until we find a non-zero result
for (const comparator of comparators) {
const result = comparator(a, b);
if (result !== 0) return result;
}
return 0;
});
}
/**
* Create a stable sort function that maintains relative order of equal elements
* @param data - Array to sort
* @param comparator - Comparison function
*/
export function stableSort<T>(data: T[], comparator: ComparatorFn<T>): T[] {
if (!Array.isArray(data) || data.length === 0) return data;
// Add index to maintain stability
const indexed = data.map((item, index) => ({ item, index }));
indexed.sort((a, b) => {
const result = comparator(a.item, b.item);
// If items are equal, maintain original order
return result !== 0 ? result : a.index - b.index;
});
return indexed.map(({ item }) => item);
}
/**
* Check if an array is sorted according to a comparator
* @param data - Array to check
* @param comparator - Comparison function
*/
export function isSorted<T>(data: T[], comparator: ComparatorFn<T>): boolean {
if (!Array.isArray(data) || data.length <= 1) return true;
for (let i = 1; i < data.length; i++) {
if (comparator(data[i - 1], data[i]) > 0) return false;
}
return true;
}
/**
* Reverse sort direction
* @param direction - Current direction
*/
export function reverseDirection(direction: SortDirection): SortDirection {
return direction === 'asc' ? 'desc' : 'asc';
}
/**
* Get sort direction for next click (for UI toggling)
* @param currentDirection - Current sort direction
* @param allowUnsorted - Whether to allow unsorted state
*/
export function getNextSortDirection(
currentDirection: SortDirection | null,
allowUnsorted: boolean = true
): SortDirection | null {
if (currentDirection === null) return 'asc';
if (currentDirection === 'asc') return 'desc';
if (currentDirection === 'desc' && allowUnsorted) return null;
return 'asc'; // Cycle back to asc if unsorted not allowed
}
/**
* Sort array by multiple keys with individual directions
* @param data - Array to sort
* @param keys - Array of keys to sort by
* @param directions - Array of directions (defaults to 'asc' for all)
* @param dataTypes - Array of data types (defaults to 'string' for all)
*/
export function sortByKeys<T>(
data: T[],
keys: (keyof T)[],
directions: SortDirection[] = [],
dataTypes: DataType[] = []
): T[] {
if (!Array.isArray(data) || data.length === 0) return data;
if (!keys || keys.length === 0) return data;
const sorts: SortConfig<T>[] = keys.map((key, index) => ({
key,
direction: directions[index] || 'asc',
dataType: dataTypes[index] || 'string'
}));
return sortByMultiple(data, { sorts });
}