import { computed, ref, toValue, watch, type ComputedRef, type Ref } from 'vue' export type ExpansionSetSource = | T | Ref | ComputedRef | (() => T) export interface UseExpansionSetOptions { defaultIds?: ExpansionSetSource allIds?: ExpansionSetSource expandAll?: ExpansionSetSource } export interface UseExpansionSetReturn { expandedIds: Ref> expandedList: ComputedRef isExpanded: (id: string) => boolean expand: (id: string) => void collapse: (id: string) => void toggle: (id: string) => boolean expandMany: (ids: readonly string[]) => void setExpanded: (ids: readonly string[]) => void collapseAll: () => void reset: () => void } /** Shared expansion state for trees, grouped selectors, and disclosure lists. */ export function useExpansionSet(options: UseExpansionSetOptions = {}): UseExpansionSetReturn { const expandedIds = ref(new Set(normalizeIds(toValue(options.defaultIds)))) const expandedList = computed(() => [...expandedIds.value]) if (options.expandAll !== undefined) { watch( () => toValue(options.expandAll), (shouldExpandAll) => { if (shouldExpandAll === undefined || shouldExpandAll === null) return if (shouldExpandAll) { setExpanded(normalizeIds(toValue(options.allIds))) } else { reset() } }, { immediate: true }, ) } if (options.allIds !== undefined) { watch( () => normalizeIds(toValue(options.allIds)), (ids) => { if (toValue(options.expandAll)) { setExpanded(ids) } }, ) } function isExpanded(id: string): boolean { return expandedIds.value.has(id) } function expand(id: string) { if (expandedIds.value.has(id)) return expandedIds.value = new Set([...expandedIds.value, id]) } function collapse(id: string) { if (!expandedIds.value.has(id)) return const next = new Set(expandedIds.value) next.delete(id) expandedIds.value = next } function toggle(id: string): boolean { if (expandedIds.value.has(id)) { collapse(id) return false } expand(id) return true } function expandMany(ids: readonly string[]) { expandedIds.value = new Set([...expandedIds.value, ...normalizeIds(ids)]) } function setExpanded(ids: readonly string[]) { expandedIds.value = new Set(normalizeIds(ids)) } function collapseAll() { expandedIds.value = new Set() } function reset() { setExpanded(normalizeIds(toValue(options.defaultIds))) } return { expandedIds, expandedList, isExpanded, expand, collapse, toggle, expandMany, setExpanded, collapseAll, reset, } } function normalizeIds(ids: readonly string[] | null | undefined): string[] { if (!ids) return [] return [...new Set(ids.filter(Boolean))] }