import { computed, toValue, type ComputedRef, type Ref } from 'vue' import { useSelectionLimit, type SelectionLimitSource } from './useSelectionLimit' export type ListSelectionValue = string | number export type ListSelectionSource = | T | Ref | ComputedRef | (() => T) export type WidenListSelectionValue = TValue extends string ? string : TValue extends number ? number : TValue export interface UseListSelectionOptions< TItem, TValue extends ListSelectionValue = ListSelectionValue, > { selected: ListSelectionSource items?: ListSelectionSource getValue?: (item: TItem) => TValue disabled?: ListSelectionSource max?: SelectionLimitSource } export interface UseListSelectionReturn { selectedValues: ComputedRef selectedSet: ComputedRef> itemValues: ComputedRef selectedCount: ComputedRef itemCount: ComputedRef isAllSelected: ComputedRef isLimited: ComputedRef isAtLimit: ComputedRef canAddMore: ComputedRef remaining: ComputedRef isSelected: (value: TValue) => boolean isFullySelected: (values: readonly TValue[]) => boolean isPartiallySelected: (values: readonly TValue[]) => boolean selectValues: (values: readonly TValue[]) => TValue[] toggleValue: (value: TValue) => TValue[] toggleValues: (values: readonly TValue[]) => TValue[] selectAll: () => TValue[] toggleAll: () => TValue[] clear: () => TValue[] } /** Shared list-selection state for checkbox lists, sample selectors, and chip selectors. */ export function useListSelection< TItem = ListSelectionValue, TValue extends ListSelectionValue = TItem extends ListSelectionValue ? TItem : ListSelectionValue, >(options: UseListSelectionOptions): UseListSelectionReturn> { type ResolvedValue = WidenListSelectionValue const disabled = computed(() => toValue(options.disabled) ?? false) const selectedValues = computed(() => [...toValue(options.selected)] as ResolvedValue[]) const selectedSet = computed(() => new Set(selectedValues.value)) const itemValues = computed(() => { const items = toValue(options.items) ?? [] return items.map(item => getItemValue(item, options.getValue) as ResolvedValue) }) const selectedCount = computed(() => selectedValues.value.length) const itemCount = computed(() => itemValues.value.length) const isAllSelected = computed(() => ( itemValues.value.length > 0 && itemValues.value.every(value => selectedSet.value.has(value)) )) const selectionLimit = useSelectionLimit({ count: selectedCount, max: options.max, }) function isSelected(value: ResolvedValue): boolean { return selectedSet.value.has(value) } function isFullySelected(values: readonly ResolvedValue[]): boolean { return values.length > 0 && values.every(value => selectedSet.value.has(value)) } function isPartiallySelected(values: readonly ResolvedValue[]): boolean { if (values.length === 0) return false const selected = values.filter(value => selectedSet.value.has(value)).length return selected > 0 && selected < values.length } function selectValues(values: readonly ResolvedValue[]): ResolvedValue[] { if (disabled.value) return selectedValues.value const missing = uniqueValues(values).filter(value => !selectedSet.value.has(value)) if (!selectionLimit.canAdd(missing.length)) return selectedValues.value return [...selectedValues.value, ...missing] } function toggleValue(value: ResolvedValue): ResolvedValue[] { if (disabled.value) return selectedValues.value if (selectedSet.value.has(value)) { return selectedValues.value.filter(selected => selected !== value) } if (!selectionLimit.canAddMore.value) return selectedValues.value return [...selectedValues.value, value] } function toggleValues(values: readonly ResolvedValue[]): ResolvedValue[] { if (disabled.value) return selectedValues.value const unique = uniqueValues(values) if (isFullySelected(unique)) { const remove = new Set(unique) return selectedValues.value.filter(value => !remove.has(value)) } return selectValues(unique) } function selectAll(): ResolvedValue[] { return selectValues(itemValues.value) } function toggleAll(): ResolvedValue[] { if (isAllSelected.value) return clear() return selectAll() } function clear(): ResolvedValue[] { return disabled.value ? selectedValues.value : [] } return { selectedValues, selectedSet, itemValues, selectedCount, itemCount, isAllSelected, isLimited: selectionLimit.isLimited, isAtLimit: selectionLimit.isAtLimit, canAddMore: selectionLimit.canAddMore, remaining: selectionLimit.remaining, isSelected, isFullySelected, isPartiallySelected, selectValues, toggleValue, toggleValues, selectAll, toggleAll, clear, } } function getItemValue( item: TItem, getValue: ((item: TItem) => TValue) | undefined, ): TValue { return getValue ? getValue(item) : item as unknown as TValue } function uniqueValues(values: readonly TValue[]): TValue[] { return [...new Set(values)] }