| 1 |
import { computed, ref, watch, type ComputedRef, type Ref } from 'vue' |
| 2 |
|
| 3 |
export interface VisibilityColumn { |
| 4 |
key: string |
| 5 |
title?: string |
| 6 |
visible?: boolean |
| 7 |
showInModal?: boolean |
| 8 |
} |
| 9 |
|
| 10 |
export interface TempVisibilityColumn { |
| 11 |
key: string |
| 12 |
title?: string |
| 13 |
visible: boolean |
| 14 |
showInModal: boolean |
| 15 |
} |
| 16 |
|
| 17 |
export interface UseTableColumnVisibilityOptions<T extends VisibilityColumn> { |
| 18 |
columns: Ref<T[]> | ComputedRef<T[]> |
| 19 |
minVisible?: number |
| 20 |
save?: (columns: TempVisibilityColumn[]) => void |
| 21 |
} |
| 22 |
|
| 23 |
export function useTableColumnVisibility<T extends VisibilityColumn>( |
| 24 |
opts: UseTableColumnVisibilityOptions<T>, |
| 25 |
) { |
| 26 |
const minVisible = opts.minVisible ?? 1 |
| 27 |
const showMenu = ref(false) |
| 28 |
const tempColumns = ref<TempVisibilityColumn[]>([]) |
| 29 |
|
| 30 |
const visibleColumnsCount = computed(() => tempColumns.value.filter((c) => c.visible).length) |
| 31 |
|
| 32 |
const areAllColumnsSelected = computed<boolean>({ |
| 33 |
get() { |
| 34 |
const toggleable = tempColumns.value.filter((c) => c.key !== 'id' && c.showInModal) |
| 35 |
return toggleable.length > 0 && toggleable.every((c) => c.visible) |
| 36 |
}, |
| 37 |
set(value: boolean) { |
| 38 |
tempColumns.value.forEach((c) => { |
| 39 |
if (c.key !== 'id' && c.showInModal) { |
| 40 |
c.visible = value |
| 41 |
} |
| 42 |
}) |
| 43 |
const idColumn = tempColumns.value.find((c) => c.key === 'id') |
| 44 |
if (idColumn) idColumn.visible = true |
| 45 |
}, |
| 46 |
}) |
| 47 |
|
| 48 |
const isCheckboxDisabled = (columnKey: string): boolean => { |
| 49 |
const col = tempColumns.value.find((c) => c.key === columnKey) |
| 50 |
return visibleColumnsCount.value === minVisible && !!col?.visible |
| 51 |
} |
| 52 |
|
| 53 |
const openMenu = () => { |
| 54 |
tempColumns.value = opts.columns.value.map((column) => ({ |
| 55 |
key: column.key, |
| 56 |
title: column.title, |
| 57 |
visible: column.visible ?? true, |
| 58 |
showInModal: column.showInModal ?? true, |
| 59 |
})) |
| 60 |
showMenu.value = true |
| 61 |
} |
| 62 |
|
| 63 |
const applyColumnVisibility = () => { |
| 64 |
tempColumns.value.forEach((tempColumn) => { |
| 65 |
const column = opts.columns.value.find((c) => c.key === tempColumn.key) |
| 66 |
if (column) column.visible = tempColumn.visible |
| 67 |
}) |
| 68 |
opts.save?.(tempColumns.value) |
| 69 |
showMenu.value = false |
| 70 |
} |
| 71 |
|
| 72 |
// Safety net: never allow all columns to be unchecked — force 'id' visible. |
| 73 |
watch( |
| 74 |
() => tempColumns.value.map((column) => column.visible), |
| 75 |
() => { |
| 76 |
const visibleColumns = tempColumns.value.filter((column) => column.visible) |
| 77 |
if (visibleColumns.length === 0) { |
| 78 |
const idColumn = tempColumns.value.find((column) => column.key === 'id') |
| 79 |
if (idColumn) idColumn.visible = true |
| 80 |
} |
| 81 |
}, |
| 82 |
) |
| 83 |
|
| 84 |
return { |
| 85 |
showMenu, |
| 86 |
tempColumns, |
| 87 |
openMenu, |
| 88 |
applyColumnVisibility, |
| 89 |
areAllColumnsSelected, |
| 90 |
isCheckboxDisabled, |
| 91 |
} |
| 92 |
} |
| 93 |
|