import { computed, toValue, type ComputedRef, type Ref } from 'vue' export type SortedItemsSource = | T | Ref | ComputedRef | (() => T) export type SortOrder = 'asc' | 'desc' export interface SortDescriptor { key: TKey direction?: SortOrder | null } export interface CompareSortValuesOptions { caseSensitive?: boolean } export interface SortComparatorContext { key: TKey direction: SortOrder a: TItem b: TItem } export type SortComparator = ( aValue: unknown, bValue: unknown, context: SortComparatorContext, ) => number export interface UseSortedItemsOptions { items: SortedItemsSource sort: SortedItemsSource | null | undefined> enabled?: SortedItemsSource caseSensitive?: SortedItemsSource getValue: (item: TItem, key: TKey) => unknown compare?: SortComparator } export interface UseSortedItemsReturn { sort: ComputedRef | null | undefined> sortedItems: ComputedRef } /** Shared sorting for SDK tables and lists with stable empty-value handling. */ export function useSortedItems( options: UseSortedItemsOptions, ): UseSortedItemsReturn { const sort = computed(() => toValue(options.sort)) const enabled = computed(() => toValue(options.enabled) ?? true) const sortedItems = computed(() => { const items = [...toValue(options.items)] const activeSort = sort.value if (!enabled.value || !activeSort?.key || !activeSort.direction) return items const direction = activeSort.direction const caseSensitive = toValue(options.caseSensitive) ?? true return items.sort((a, b) => { const aValue = options.getValue(a, activeSort.key) const bValue = options.getValue(b, activeSort.key) if (options.compare) { return options.compare(aValue, bValue, { key: activeSort.key, direction, a, b, }) } return compareSortValues(aValue, bValue, direction, { caseSensitive }) }) }) return { sort, sortedItems, } } export function compareSortValues( aValue: unknown, bValue: unknown, direction: SortOrder = 'asc', options: CompareSortValuesOptions = {}, ): number { const aEmpty = aValue === null || aValue === undefined const bEmpty = bValue === null || bValue === undefined if (aEmpty && bEmpty) return 0 if (aEmpty) return 1 if (bEmpty) return -1 const aComparable = toComparableSortValue(aValue, options) const bComparable = toComparableSortValue(bValue, options) let comparison = 0 if (typeof aComparable === 'number' && typeof bComparable === 'number') { comparison = aComparable - bComparable } else { comparison = String(aComparable).localeCompare(String(bComparable)) } return direction === 'asc' ? comparison : -comparison } function toComparableSortValue(value: unknown, options: CompareSortValuesOptions): unknown { if (value instanceof Date) return value.getTime() if (typeof value === 'string' && options.caseSensitive === false) { return value.toLowerCase() } return value }