Skip to content

Instantly share code, notes, and snippets.

@scbj
Created June 27, 2025 14:38
Show Gist options
  • Select an option

  • Save scbj/2e5f35874a04b00cc855c43734bcdcb3 to your computer and use it in GitHub Desktop.

Select an option

Save scbj/2e5f35874a04b00cc855c43734bcdcb3 to your computer and use it in GitHub Desktop.
import { computed, Ref, ref, watch } from 'vue'
import { ColumnProps } from '@/components/jk/table-2/types'
const minColumnWidth = 80
const defaultColumnWidth = '1fr'
/** Checks if the object is an instance of HTMLElement */
function isHtmlElement(obj: object): obj is HTMLElement {
return obj instanceof HTMLElement
}
/** Generate grid column width based on column properties */
function generateColumnTrackValue(column: ColumnProps) {
return `minmax(${minColumnWidth}px,${column.width ?? defaultColumnWidth})`
}
/** This composable function provides functionality for resizing table columns. */
export function useResizableTableColumns(columns: Ref<ColumnProps[]>) {
const responsiveColumnsTemplate = computed(() =>
columns.value.map(generateColumnTrackValue).join(' ')
)
const absoluteColumnWidths = ref<Nullable<number[]>>(null)
const absoluteColumnsTemplate = computed(
() => absoluteColumnWidths.value?.map((width) => `${width}px`).join(' ')
)
const columnsTemplate = computed(
() => absoluteColumnsTemplate.value ?? responsiveColumnsTemplate.value
)
const columnBeingResizedLabel = ref<Nullable<ColumnProps['label']>>(null)
/**
* Handle mouse down event for the resize handle of the table column.
* @param {MouseEvent} event - The mouse event.
* @param {Column} column - The column that will be resized.
*/
const onResizeHandleMouseDown = (event: MouseEvent, column: ColumnProps) => {
const { target, pageX: initialMousePositionX } = event
if (!target || !isHtmlElement(target)) return
const header = target.closest('th')
const table = target.closest('table')
if (!header || !table) return
const initialHeaderWidth = header.clientWidth
const headers = Array.from(table.querySelectorAll('th'))
const headerWidths = headers.map((th) => th.clientWidth)
const headerIndex = headers.indexOf(header)
const onMouseMove = (event: MouseEvent) => {
const changeInPosition = event.pageX - initialMousePositionX
const newWidth = Math.max(
minColumnWidth,
initialHeaderWidth + changeInPosition
)
headerWidths.splice(headerIndex, 1, newWidth)
absoluteColumnWidths.value = headerWidths
}
const onMouseUp = () => {
columnBeingResizedLabel.value = null
window.removeEventListener('mousemove', onMouseMove)
window.removeEventListener('mouseup', onMouseUp)
}
columnBeingResizedLabel.value = column.label
window.addEventListener('mousemove', onMouseMove)
window.addEventListener('mouseup', onMouseUp)
}
/**
* Watch for changes in the columns. If there are changes and the columns had absolute
* widths, then they must be recalculated to preserve the sizes of the existing columns.
*/
watch(columns, (columns, oldColumns) => {
if (!absoluteColumnWidths.value) return
absoluteColumnWidths.value = columns.map((column) => {
const oldIndex = oldColumns.findIndex(
(oldColumn) => oldColumn.label === column.label
)
return oldIndex !== -1
? absoluteColumnWidths.value?.[oldIndex] ?? minColumnWidth
: minColumnWidth
})
})
return {
columnBeingResizedLabel,
columnsTemplate,
onResizeHandleMouseDown
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment