import { ComputedRef, Ref, nextTick, ref } from 'vue'; import { QTableColumn } from 'quasar'; /** Row index standing for the header row, so header cells share the body's * `${rowIndex}-${colName}` focus key and arrow navigation can cross between * the header and the first data row. */ export const HEADER_ROW_INDEX = -1; interface Params { filteredRows: ComputedRef[]>; onCommit?: ( row: Record, colName: string ) => void | Promise; /** Whether the header row can take focus (admin + editable table + header shown). */ headerFocusable?: ComputedRef; /** Opens the header editor (rename column dialog) on Enter. */ onHeaderEdit?: (colName: string) => void; } export function useInlineCellEdit({ filteredRows, onCommit, headerFocusable, onHeaderEdit, }: Params) { const showCellEditDialog: Ref = ref(false); const editingCellValue: Ref = ref(''); const editingRow: Ref | null> = ref(null); const editingColName: Ref = ref(''); const focusedCellKey: Ref = ref(null); const focusedRowIndex: Ref = ref(0); const focusedColName: Ref = ref(''); const focusedRow: Ref | null> = ref(null); function focusCell( rowIndex: number, colName: string, row: Record | null ) { focusedCellKey.value = `${rowIndex}-${colName}`; focusedRowIndex.value = rowIndex; focusedColName.value = colName; focusedRow.value = row; } /** Focus a cell identified by key alone — column-group header cells span * several columns and belong to no row, so they have no (row, col) pair. */ function focusCellKey(key: string) { focusedCellKey.value = key; focusedColName.value = ''; focusedRow.value = null; } function openCellEdit( rowIndex: number, row: Record, colName: string ) { focusCell(rowIndex, colName, row); startInlineEdit(row, colName, row[colName]); } function startInlineEdit( row: Record, colName: string, value: unknown ) { editingRow.value = row; editingColName.value = colName; editingCellValue.value = String(value ?? ''); showCellEditDialog.value = true; } function commitInlineEdit() { if (editingRow.value && editingColName.value) { editingRow.value[editingColName.value] = editingCellValue.value; if (onCommit) { void onCommit(editingRow.value, editingColName.value); } } showCellEditDialog.value = false; editingRow.value = null; editingColName.value = ''; } function cancelInlineEdit() { showCellEditDialog.value = false; editingRow.value = null; editingColName.value = ''; } function onCellKeydown( event: KeyboardEvent, rowIndex: number, colName: string, row: Record, cols: QTableColumn[] ) { const editableCols = cols.filter( (c) => c.name !== 'quantity' && c.name !== 'add_to_cart' ); const colIdx = editableCols.findIndex((c) => c.name === colName); if (colIdx === -1) return; const rows = filteredRows.value; const isHeader = rowIndex === HEADER_ROW_INDEX; if (event.key === 'Enter') { event.preventDefault(); if (isHeader) { onHeaderEdit?.(colName); return; } focusCell(rowIndex, colName, row); startInlineEdit(row, colName, row[colName]); return; } let nextRowIndex = rowIndex; let nextColIdx = colIdx; if (event.key === 'ArrowRight') { event.preventDefault(); nextColIdx = colIdx < editableCols.length - 1 ? colIdx + 1 : colIdx; } else if (event.key === 'ArrowLeft') { event.preventDefault(); nextColIdx = colIdx > 0 ? colIdx - 1 : 0; } else if (event.key === 'ArrowDown') { event.preventDefault(); // From the header the next stop is the first data row (index 0). if (isHeader) nextRowIndex = rows.length ? 0 : rowIndex; else nextRowIndex = rowIndex < rows.length - 1 ? rowIndex + 1 : rowIndex; } else if (event.key === 'ArrowUp') { event.preventDefault(); // Above the first data row sits the header, when it can take focus. if (isHeader) nextRowIndex = rowIndex; else if (rowIndex === 0) nextRowIndex = headerFocusable?.value ? HEADER_ROW_INDEX : 0; else nextRowIndex = rowIndex - 1; } else { return; } const nextColName = editableCols[nextColIdx].name; const nextRow = nextRowIndex === HEADER_ROW_INDEX ? null : (rows[nextRowIndex] as Record); focusCell(nextRowIndex, nextColName, nextRow); nextTick(() => { const el = document.querySelector( `[data-cell-key="${nextRowIndex}-${nextColName}"]` ); el?.focus(); }); } return { showCellEditDialog, editingCellValue, focusedCellKey, focusCell, focusCellKey, openCellEdit, startInlineEdit, commitInlineEdit, cancelInlineEdit, onCellKeydown, }; }