import type { ComputedRef, Ref } from 'vue' import type { TableSelectionOptions } from '../../types/TableSchema' import { computed, ref } from 'vue' export interface UseTableSelectionOptions extends TableSelectionOptions { cleanData?: (item: T) => T data: Ref | ComputedRef } export function useTableSelection(options: UseTableSelectionOptions) { const allSelectorEl = ref() const lastSelectedIndex = ref(null) const computedSelectedItems = computed(() => options.selectedItems.value) const isSelectable = computed(() => options.selectable === true && Array.isArray(options.selectedItems.value)) const allItems = computed(() => options.data.value) function updateAllSelectorState() { if (allSelectorEl.value === undefined) { return } // If we don't know the total items, we can't determine if "all" are selected if (!allItems.value) { allSelectorEl.value.indeterminate = options.selectedItems.value.length > 0 return } // Get all possible item IDs const allItemIds = allItems.value.map(item => item.id) // Check if all items are selected const allSelected = allItemIds.length > 0 && options.selectedItems.value.length === allItemIds.length && allItemIds.every(id => options.selectedItems.value.includes(id)) // Set checked state allSelectorEl.value.checked = allSelected // Set indeterminate state (some but not all items selected) allSelectorEl.value.indeterminate = !allSelected && options.selectedItems.value.length > 0 } function toggleSelectItem(item: T, event?: MouseEvent) { const currentIndex = allItems.value.findIndex(i => i.id === item.id) // Handle Shift+click for range selection if (event?.shiftKey && lastSelectedIndex.value !== null && currentIndex !== -1) { const startIndex = Math.min(lastSelectedIndex.value, currentIndex) const endIndex = Math.max(lastSelectedIndex.value, currentIndex) // Get all items in the range const rangeItems = allItems.value.slice(startIndex, endIndex + 1) const rangeItemIds = rangeItems.map(i => i.id) // Check if all items in range are currently selected const allRangeSelected = rangeItemIds.every(id => options.selectedItems.value.includes(id) ) if (allRangeSelected) { // Deselect all items in range options.selectedItems.value = options.selectedItems.value.filter(id => !rangeItemIds.includes(id) ) } else { // Select all items in range (add missing ones) const newSelections = rangeItemIds.filter(id => !options.selectedItems.value.includes(id) ) options.selectedItems.value.push(...newSelections) } return } // Regular single item selection logic if (computedSelectedItems.value.length === 0) { // Clean the item if a cleanData function is provided, otherwise use the default cleaning const cleanItem = options.cleanData ? options.cleanData(item) : { ...item } // If no cleanData function is provided, use the default cleaning if (!options.cleanData) { Object.keys(cleanItem).forEach((key) => { if (key.startsWith('_')) { delete cleanItem[key] } }) } options.onSelect(cleanItem) lastSelectedIndex.value = currentIndex return } const index = computedSelectedItems.value.indexOf(item.id) if (index > -1) { options.selectedItems.value.splice(index, 1) } else { options.selectedItems.value.push(item.id) } // Update last selected index for future range selections lastSelectedIndex.value = currentIndex } // Update toggleSelectAll to pass allItems to updateAllSelectorState function toggleSelectAll(event: Event) { const value = (event.target as HTMLInputElement).checked if (allItems.value && value) { options.selectedItems.value = allItems.value.map(item => item.id) } else { options.selectedItems.value = [] } // Reset last selected index when selecting/deselecting all lastSelectedIndex.value = null } return { computedSelectedItems, isSelectable, allSelectorEl, updateAllSelectorState, toggleSelectItem, toggleSelectAll } }