import { computed, toValue, type ComputedRef, type Ref } from 'vue' export type SelectionLimitSource = | T | Ref | ComputedRef | (() => T) export interface UseSelectionLimitOptions { count: SelectionLimitSource max?: SelectionLimitSource } export interface UseSelectionLimitReturn { count: ComputedRef max: ComputedRef isLimited: ComputedRef isAtLimit: ComputedRef canAddMore: ComputedRef remaining: ComputedRef canAdd: (amount?: number, fromCount?: number) => boolean } /** Shared cap logic for chip, tag, and multi-select controls. */ export function useSelectionLimit(options: UseSelectionLimitOptions): UseSelectionLimitReturn { const count = computed(() => Math.max(0, Math.floor(toValue(options.count) || 0))) const max = computed(() => normalizeMax(toValue(options.max))) const isLimited = computed(() => max.value !== undefined) const remaining = computed(() => { if (max.value === undefined) return undefined return Math.max(0, max.value - count.value) }) const isAtLimit = computed(() => remaining.value === 0) const canAddMore = computed(() => canAdd(1)) function canAdd(amount = 1, fromCount = count.value): boolean { const normalizedAmount = Math.max(0, Math.floor(amount)) const normalizedCount = Math.max(0, Math.floor(fromCount)) if (max.value === undefined) return true return normalizedCount + normalizedAmount <= max.value } return { count, max, isLimited, isAtLimit, canAddMore, remaining, canAdd, } } function normalizeMax(value: number | null | undefined): number | undefined { if (value === null || value === undefined || !Number.isFinite(value)) return undefined return Math.max(0, Math.floor(value)) }