import { computed, ref, watch, type ComputedRef, type Ref } from 'vue' import { useResizeObserver } from '@vueuse/core' export interface WidthColumn { visible?: boolean showInModal?: boolean width?: string | number minWidth?: string | number } export interface DynamicWidthContext { dynamicWidth: number | null remainingWidth: number totalTableWidth: number } export interface UseTableWidthOptions { columns: Ref | ComputedRef isDynamic?: (col: T) => boolean widthFor?: (col: T, ctx: DynamicWidthContext) => number | null minWidthFilter?: (col: T) => boolean } type TableRefLike = HTMLElement | { $el?: HTMLElement | null } | null | undefined function getNumericColumnWidth(width: WidthColumn['width']): number { if (typeof width === 'number') return width if (typeof width === 'string') { const parsed = parseFloat(width) return Number.isFinite(parsed) ? parsed : 0 } return 0 } export function useTableWidth( tableRef: Ref, opts: UseTableWidthOptions, ) { const tableWidth = ref(0) const isDynamic = opts.isDynamic ?? ((col: T) => col.showInModal === true) const minWidthFilter = opts.minWidthFilter ?? (() => true) const containerEl = computed(() => { const value = tableRef.value if (!value) return null if (value instanceof HTMLElement) return value.parentElement const el = (value as { $el?: HTMLElement | null }).$el return el?.parentElement ?? null }) const measureContainerWidth = (el: HTMLElement | null) => { if (el) { tableWidth.value = el.clientWidth } } watch(containerEl, measureContainerWidth, { flush: 'post', immediate: true }) useResizeObserver(containerEl, (entries) => { for (const entry of entries) { tableWidth.value = entry.contentRect.width } }) const dynamicWidthColumns: ComputedRef = computed(() => { const visibleColumns = opts.columns.value.filter((col) => col.visible ?? true) const modalColumns = visibleColumns.filter((col) => isDynamic(col)) const totalTableWidth = tableWidth.value || 0 const totalFixedWidth = visibleColumns .filter((col) => !isDynamic(col)) .reduce((sum, col) => sum + getNumericColumnWidth(col.width), 0) const remainingWidth = totalTableWidth - totalFixedWidth const dynamicWidth = remainingWidth > 0 && modalColumns.length > 0 ? remainingWidth / modalColumns.length : null const ctx: DynamicWidthContext = { dynamicWidth, remainingWidth, totalTableWidth } return visibleColumns.map((col) => { if (opts.widthFor) { const override = opts.widthFor(col, ctx) if (override !== null && override !== undefined) { return { ...col, width: override } } } if (isDynamic(col) && dynamicWidth !== null) { return { ...col, width: Math.floor(dynamicWidth) } } return col }) }) const minTableWidth: ComputedRef = computed(() => { return opts.columns.value.reduce((sum, col) => { if (!minWidthFilter(col)) return sum const value = typeof col.minWidth === 'number' ? col.minWidth : parseFloat(String(col.minWidth)) || 0 return sum + value }, 0) }) return { tableWidth, dynamicWidthColumns, minTableWidth } }