Skip to content

Commit 754b39d

Browse files
waleedlatif1claude
andcommitted
fix(tables): multi-column delete, select-all cell model, cut flash, chevron alignment
- Multi-select delete: detect column selection range and delete all selected columns sequentially with individual undo entries - Select all (header checkbox): use cell selection model instead of checkbox model for consistent highlighting - Cut flash: batch cell clears into single mutation to prevent stale data flashing from multiple onSettled invalidations - Chevron alignment: adjust right padding from pr-2 to pr-2.5 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 81f6791 commit 754b39d

File tree

1 file changed

+110
-57
lines changed
  • apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table

1 file changed

+110
-57
lines changed

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx

Lines changed: 110 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)