import { useCallback, useMemo } from 'react' import type { TableRow, TableWidgetState } from '../types' import { widgetStoreActions } from '../../stores/widget-store' import { useWidgetSelector } from '../../stores/use-widget-selector' export interface UseSelectionOptions { /** Callback when selection changes */ onSelectionChange?: (selectedIds: (string | number)[]) => void } export interface UseSelectionResult { /** Array of selected row IDs */ selectedIds: (string | number)[] /** Check if a row is selected */ isSelected: (id: string | number) => boolean /** Check if all provided rows are selected */ isAllSelected: (rows: TableRow[]) => boolean /** Check if some but not all rows are selected (for indeterminate state) */ isIndeterminate: (rows: TableRow[]) => boolean /** Toggle selection of a single row */ toggleRow: (id: string | number) => void /** Toggle selection of all provided rows */ toggleAll: (rows: TableRow[]) => void /** Select specific rows */ selectRows: (ids: (string | number)[]) => void /** Deselect specific rows */ deselectRows: (ids: (string | number)[]) => void /** Clear all selections */ clearSelection: () => void } /** * Hook for managing table row selection with persistence across pagination. * Selection state is stored in the widget store for the given widget ID. * * @param widgetId - The widget ID to persist selection state under. * @param options - Optional configuration including an `onSelectionChange` callback. * @returns Selection state and control functions (toggle, select, deselect, clear). * * @example * ```tsx * const { selectedIds, isAllSelected, handleSelectRow, clearSelection } = useSelection(widgetId) * ``` */ export function useSelection( widgetId: string, options?: UseSelectionOptions, ): UseSelectionResult { const { onSelectionChange } = options ?? {} // Get store state const selected = useWidgetSelector( widgetId, (w) => (w as TableWidgetState | undefined)?.selected, ) // Use store values with fallback to empty array const selectedIds = useMemo(() => selected ?? [], [selected]) // Check if a row is selected const isSelected = useCallback( (id: string | number) => selectedIds.includes(id), [selectedIds], ) // Check if all provided rows are selected const isAllSelected = useCallback( (rows: TableRow[]) => { if (rows.length === 0) return false return rows.every((row) => selectedIds.includes(row.id)) }, [selectedIds], ) // Check if some but not all rows are selected const isIndeterminate = useCallback( (rows: TableRow[]) => { if (rows.length === 0) return false const selectedCount = rows.filter((row) => selectedIds.includes(row.id), ).length return selectedCount > 0 && selectedCount < rows.length }, [selectedIds], ) // Toggle selection of a single row const toggleRow = useCallback( (id: string | number) => { const widget = widgetStoreActions.getWidget(widgetId) const currentSelected = widget?.selected ?? [] const newSelected = currentSelected.includes(id) ? currentSelected.filter((s) => s !== id) : [...currentSelected, id] widgetStoreActions.setWidget(widgetId, { selected: newSelected, }) if (onSelectionChange) { onSelectionChange(newSelected) } }, [widgetId, onSelectionChange], ) // Toggle selection of all provided rows const toggleAll = useCallback( (rows: TableRow[]) => { const widget = widgetStoreActions.getWidget(widgetId) const currentSelected = widget?.selected ?? [] const rowIds = rows.map((row) => row.id) const allSelected = rowIds.every((id) => currentSelected.includes(id)) const newSelected = allSelected ? currentSelected.filter((id) => !rowIds.includes(id)) : [...new Set([...currentSelected, ...rowIds])] widgetStoreActions.setWidget(widgetId, { selected: newSelected, }) if (onSelectionChange) { onSelectionChange(newSelected) } }, [widgetId, onSelectionChange], ) // Select specific rows const selectRows = useCallback( (ids: (string | number)[]) => { const widget = widgetStoreActions.getWidget(widgetId) const currentSelected = widget?.selected ?? [] const newSelected = [...new Set([...currentSelected, ...ids])] widgetStoreActions.setWidget(widgetId, { selected: newSelected, }) if (onSelectionChange) { onSelectionChange(newSelected) } }, [widgetId, onSelectionChange], ) // Deselect specific rows const deselectRows = useCallback( (ids: (string | number)[]) => { const widget = widgetStoreActions.getWidget(widgetId) const currentSelected = widget?.selected ?? [] const newSelected = currentSelected.filter((id) => !ids.includes(id)) widgetStoreActions.setWidget(widgetId, { selected: newSelected, }) if (onSelectionChange) { onSelectionChange(newSelected) } }, [widgetId, onSelectionChange], ) // Clear all selections const clearSelection = useCallback(() => { const newSelected: (string | number)[] = [] widgetStoreActions.setWidget(widgetId, { selected: newSelected, }) if (onSelectionChange) { onSelectionChange(newSelected) } }, [widgetId, onSelectionChange]) return { selectedIds, isSelected, isAllSelected, isIndeterminate, toggleRow, toggleAll, selectRows, deselectRows, clearSelection, } }