@@ -202,7 +202,7 @@ export function Table({
202202 const lastCheckboxRowRef = useRef<number | null>(null)
203203 const isColumnSelectionRef = useRef(false)
204204 const [showDeleteTableConfirm, setShowDeleteTableConfirm] = useState(false)
205- const [deletingColumn, setDeletingColumn ] = useState<string | null>(null)
205+ const [deletingColumns, setDeletingColumns ] = useState<string[] | null>(null)
206206 const [isImportCsvOpen, setIsImportCsvOpen] = useState(false)
207207
208208 const [columnWidths, setColumnWidths] = useState<Record<string, number>>({})
@@ -716,15 +716,16 @@ export function Table({
716716
717717 const handleSelectAllRows = useCallback(() => {
718718 const rws = rowsRef.current
719- if (rws.length === 0) return
719+ const currentCols = columnsRef.current
720+ if (rws.length === 0 || currentCols.length === 0) return
720721 setEditingCell(null)
721- setSelectionAnchor(null )
722- setSelectionFocus(null )
723- const all = new Set<number>()
724- for (const row of rws) {
725- all.add(row.position)
726- }
727- setCheckedRows(all )
722+ setCheckedRows((prev) => (prev.size === 0 ? prev : EMPTY_CHECKED_ROWS) )
723+ setSelectionAnchor({ rowIndex: 0, colIndex: 0 } )
724+ setSelectionFocus({
725+ rowIndex: maxPositionRef.current,
726+ colIndex: currentCols.length - 1,
727+ })
728+ setIsColumnSelection(false )
728729 scrollRef.current?.focus({ preventScroll: true })
729730 }, [])
730731
@@ -1372,6 +1373,7 @@ export function Table({
13721373 const cols = columnsRef.current
13731374 const pMap = positionMapRef.current
13741375 const undoCells: Array<{ rowId: string; data: Record<string, unknown> }> = []
1376+ const batchUpdates: Array<{ rowId: string; data: Record<string, unknown> }> = []
13751377
13761378 if (checked.size > 0) {
13771379 e.preventDefault()
@@ -1393,7 +1395,7 @@ export function Table({
13931395 updates[col.name] = null
13941396 }
13951397 undoCells.push({ rowId: row.id, data: previousData })
1396- mutateRef.current ({ rowId: row.id, data: updates })
1398+ batchUpdates.push ({ rowId: row.id, data: updates })
13971399 }
13981400 e.clipboardData?.setData('text/plain', lines.join('\n'))
13991401 } else {
@@ -1426,11 +1428,14 @@ export function Table({
14261428 }
14271429 lines.push(cells.join('\t'))
14281430 undoCells.push({ rowId: row.id, data: previousData })
1429- mutateRef.current ({ rowId: row.id, data: updates })
1431+ batchUpdates.push ({ rowId: row.id, data: updates })
14301432 }
14311433 e.clipboardData?.setData('text/plain', lines.join('\n'))
14321434 }
14331435
1436+ if (batchUpdates.length > 0) {
1437+ batchUpdateRef.current({ updates: batchUpdates })
1438+ }
14341439 if (undoCells.length > 0) {
14351440 pushUndoRef.current({ type: 'clear-cells', cells: undoCells })
14361441 }
@@ -1732,48 +1737,76 @@ export function Table({
17321737 )
17331738
17341739 const handleDeleteColumn = useCallback((columnName: string) => {
1735- setDeletingColumn(columnName)
1740+ const cols = columnsRef.current
1741+ if (isColumnSelectionRef.current && selectionAnchorRef.current) {
1742+ const sel = computeNormalizedSelection(selectionAnchorRef.current, selectionFocusRef.current)
1743+ if (sel && sel.startCol !== sel.endCol) {
1744+ const names: string[] = []
1745+ for (let c = sel.startCol; c <= sel.endCol; c++) {
1746+ if (c < cols.length) names.push(cols[c].name)
1747+ }
1748+ if (names.length > 0) {
1749+ setDeletingColumns(names)
1750+ return
1751+ }
1752+ }
1753+ }
1754+ setDeletingColumns([columnName])
17361755 }, [])
17371756
17381757 const handleDeleteColumnConfirm = useCallback(() => {
1739- if (!deletingColumn) return
1740- const columnToDelete = deletingColumn
1741- const orderAtDelete = columnOrderRef.current
1742- const cols = schemaColumnsRef.current
1743- const colDef = cols.find((c) => c.name === columnToDelete)
1744- const colPosition = colDef ? cols.indexOf(colDef) : cols.length
1745- const currentRows = rowsRef.current
1746- const cellData = currentRows
1747- .filter((r) => r.data[columnToDelete] != null)
1748- .map((r) => ({ rowId: r.id, value: r.data[columnToDelete] }))
1749- const previousWidth = columnWidthsRef.current[columnToDelete] ?? null
1750-
1751- setDeletingColumn(null)
1752- deleteColumnMutation.mutate(columnToDelete, {
1753- onSuccess: () => {
1754- pushUndoRef.current({
1755- type: 'delete-column',
1756- columnName: columnToDelete,
1757- columnType: colDef?.type ?? 'string',
1758- columnPosition: colPosition >= 0 ? colPosition : cols.length,
1759- columnUnique: colDef?.unique ?? false,
1760- columnRequired: colDef?.required ?? false,
1761- cellData,
1762- previousOrder: orderAtDelete ? [...orderAtDelete] : null,
1763- previousWidth,
1764- })
1765-
1766- if (orderAtDelete) {
1767- const newOrder = orderAtDelete.filter((n) => n !== columnToDelete)
1768- setColumnOrder(newOrder)
1769- updateMetadataRef.current({
1770- columnWidths: columnWidthsRef.current,
1771- columnOrder: newOrder,
1758+ if (!deletingColumns || deletingColumns.length === 0) return
1759+ const columnsToDelete = [...deletingColumns]
1760+ setDeletingColumns(null)
1761+
1762+ let currentOrder = columnOrderRef.current ? [...columnOrderRef.current] : null
1763+
1764+ const deleteNext = (index: number) => {
1765+ if (index >= columnsToDelete.length) return
1766+ const columnToDelete = columnsToDelete[index]
1767+ const cols = schemaColumnsRef.current
1768+ const colDef = cols.find((c) => c.name === columnToDelete)
1769+ const colPosition = colDef ? cols.indexOf(colDef) : cols.length
1770+ const currentRows = rowsRef.current
1771+ const cellData = currentRows
1772+ .filter((r) => r.data[columnToDelete] != null)
1773+ .map((r) => ({ rowId: r.id, value: r.data[columnToDelete] }))
1774+ const previousWidth = columnWidthsRef.current[columnToDelete] ?? null
1775+ const orderSnapshot = currentOrder ? [...currentOrder] : null
1776+
1777+ deleteColumnMutation.mutate(columnToDelete, {
1778+ onSuccess: () => {
1779+ pushUndoRef.current({
1780+ type: 'delete-column',
1781+ columnName: columnToDelete,
1782+ columnType: colDef?.type ?? 'string',
1783+ columnPosition: colPosition >= 0 ? colPosition : cols.length,
1784+ columnUnique: colDef?.unique ?? false,
1785+ columnRequired: colDef?.required ?? false,
1786+ cellData,
1787+ previousOrder: orderSnapshot,
1788+ previousWidth,
17721789 })
1773- }
1774- },
1775- })
1776- }, [deletingColumn])
1790+
1791+ if (currentOrder) {
1792+ currentOrder = currentOrder.filter((n) => n !== columnToDelete)
1793+ setColumnOrder(currentOrder)
1794+ updateMetadataRef.current({
1795+ columnWidths: columnWidthsRef.current,
1796+ columnOrder: currentOrder,
1797+ })
1798+ }
1799+
1800+ deleteNext(index + 1)
1801+ },
1802+ })
1803+ }
1804+
1805+ setSelectionAnchor(null)
1806+ setSelectionFocus(null)
1807+ setIsColumnSelection(false)
1808+ deleteNext(0)
1809+ }, [deletingColumns])
17771810
17781811 const handleSortChange = useCallback((column: string, direction: SortDirection) => {
17791812 setQueryOptions((prev) => ({ ...prev, sort: { [column]: direction } }))
@@ -2241,25 +2274,45 @@ export function Table({
22412274 )}
22422275
22432276 <Modal
2244- open={deletingColumn !== null}
2277+ open={deletingColumns !== null}
22452278 onOpenChange={(open) => {
2246- if (!open) setDeletingColumn (null)
2279+ if (!open) setDeletingColumns (null)
22472280 }}
22482281 >
22492282 <ModalContent size='sm'>
2250- <ModalHeader>Delete Column</ModalHeader>
2283+ <ModalHeader>
2284+ {deletingColumns && deletingColumns.length > 1
2285+ ? `Delete ${deletingColumns.length} Columns`
2286+ : 'Delete Column'}
2287+ </ModalHeader>
22512288 <ModalBody>
22522289 <p className='text-[var(--text-secondary)]'>
2253- Are you sure you want to delete{' '}
2254- <span className='font-medium text-[var(--text-primary)]'>{deletingColumn}</span>?{' '}
2290+ {deletingColumns && deletingColumns.length > 1 ? (
2291+ <>
2292+ Are you sure you want to delete{' '}
2293+ <span className='font-medium text-[var(--text-primary)]'>
2294+ {deletingColumns.length} columns
2295+ </span>
2296+ ?{' '}
2297+ </>
2298+ ) : (
2299+ <>
2300+ Are you sure you want to delete{' '}
2301+ <span className='font-medium text-[var(--text-primary)]'>
2302+ {deletingColumns?.[0]}
2303+ </span>
2304+ ?{' '}
2305+ </>
2306+ )}
22552307 <span className='text-[var(--text-error)]'>
2256- This will remove all data in this column.
2308+ This will remove all data in{' '}
2309+ {deletingColumns && deletingColumns.length > 1 ? 'these columns' : 'this column'}.
22572310 </span>{' '}
22582311 You can undo this action.
22592312 </p>
22602313 </ModalBody>
22612314 <ModalFooter>
2262- <Button variant='default' onClick={() => setDeletingColumn (null)}>
2315+ <Button variant='default' onClick={() => setDeletingColumns (null)}>
22632316 Cancel
22642317 </Button>
22652318 <Button variant='destructive' onClick={handleDeleteColumnConfirm}>
@@ -3190,7 +3243,7 @@ const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
31903243 </button>
31913244 <button
31923245 type='button'
3193- className='flex h-full shrink-0 cursor-pointer items-center pr-2 pl-0.5 text-[var(--text-muted)] opacity-0 transition-opacity hover:text-[var(--text-primary)] group-hover:opacity-100'
3246+ className='flex h-full shrink-0 cursor-pointer items-center pr-2.5 pl-0.5 text-[var(--text-muted)] opacity-0 transition-opacity hover:text-[var(--text-primary)] group-hover:opacity-100'
31943247 onClick={handleChevronClick}
31953248 draggable={false}
31963249 aria-label='Column options'
0 commit comments