- 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>
206 lines
5.9 KiB
TypeScript
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 });
|
|
} |