/** * Copyright Aquera Inc 2025 * * This source code is licensed under the BSD-3-Clause license found in the * LICENSE file in the root directory of this source tree. */ /** * Resizable Helper Utility * Provides common functionality for resizable table columns */ export interface ResizeOptions { minWidth?: number; startX: number; startWidth: number; element: HTMLElement; } export interface ColumnResizeData { columnIndex: number; newWidth: number; } /** * Handles the start of a resize operation * @param e Mouse event * @param options Resize configuration options * @returns Cleanup function to remove event listeners */ export function handleResizeStart(e: MouseEvent, options: ResizeOptions): () => void { e.preventDefault(); e.stopPropagation(); const { minWidth = 50, startX, startWidth, element } = options; const resizer = e.target as HTMLElement; resizer.classList.add('resizing'); const onMouseMove = (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); const delta = e.pageX - startX; const newWidth = Math.max(minWidth, startWidth + delta); element.style.width = newWidth + 'px'; synchronizeColumnWidth(element, newWidth); }; const onMouseUp = () => { resizer.classList.remove('resizing'); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); return () => { document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; } /** * Gets the column index of an element within its table row * @param element The table cell or header element * @param selector The CSS selector for the column elements * @returns Column index (0-based) */ export function getColumnIndex(element: HTMLElement, selector: string): number { const tableRow = element.closest('nile-table-row'); if (tableRow) { const columns = tableRow.querySelectorAll(selector); return Array.from(columns).indexOf(element); } return 0; } /** * Synchronizes the width of all cells in the same column * @param sourceElement The element that was resized * @param newWidth The new width to apply */ export function synchronizeColumnWidth(sourceElement: HTMLElement, newWidth: number): void { const tableBody = sourceElement.closest('nile-table-body'); if (!tableBody) return; const isHeader = sourceElement.tagName.toLowerCase() === 'nile-table-header-item'; const selector = isHeader ? 'nile-table-header-item' : 'nile-table-cell-item'; const targetSelector = isHeader ? 'nile-table-cell-item' : 'nile-table-cell-item'; const columnIndex = getColumnIndex(sourceElement, selector); const rows = tableBody.querySelectorAll('nile-table-row'); rows.forEach(row => { const cells = row.querySelectorAll(targetSelector); if (cells[columnIndex]) { cells[columnIndex].style.width = newWidth + 'px'; } }); } /** * Resets all column widths in a table to their default values * @param tableBody The table body element to reset */ export function resetAllColumnWidths(tableBody: HTMLElement): void { const headerItems = tableBody.querySelectorAll('nile-table-header-item'); const cellItems = tableBody.querySelectorAll('nile-table-cell-item'); headerItems.forEach(header => { header.style.removeProperty('width'); }); cellItems.forEach(cell => { cell.style.removeProperty('width'); }); } /** * Checks if an element has the resizable attribute * @param element The element to check * @returns True if the element is resizable */ export function isResizable(element: HTMLElement): boolean { return element.hasAttribute('resizable'); } /** * Creates a resize handler function for use in component event listeners * @param element The element to make resizable * @param minWidth Minimum width constraint * @returns Event handler function */ export function createResizeHandler(element: HTMLElement, minWidth: number = 50): (e: MouseEvent) => void { return (e: MouseEvent) => { const options: ResizeOptions = { minWidth, startX: e.pageX, startWidth: element.offsetWidth, element }; handleResizeStart(e, options); }; } /** * Detects if any columns in a table are resizable * @param tableBody The table body element * @returns True if any columns have resizable attribute */ export function hasResizableColumns(tableBody: HTMLElement): boolean { const directChildren = Array.from(tableBody.children); const hasResizableHeaders = directChildren.some(child => { if (child.tagName.toLowerCase() === 'nile-table-header-item') { return child.hasAttribute('resizable'); } return false; }); const hasResizableCells = directChildren.some(child => { if (child.tagName.toLowerCase() === 'nile-table-cell-item') { return child.hasAttribute('resizable'); } return false; }); return hasResizableHeaders || hasResizableCells; }