Skip to content

Commit 0e7f032

Browse files
waleedlatif1claude
andcommitted
feat(tables): add column reorder undo/redo, body drop targets, and escape cancel
Column drag-and-drop now supports dropping anywhere in a column (not just headers), pressing Escape to cancel a drag, and full undo/redo integration for column reordering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9512cd4 commit 0e7f032

3 files changed

Lines changed: 69 additions & 3 deletions

File tree

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

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,15 @@ export function Table({
251251
const deleteColumnMutation = useDeleteColumn({ workspaceId, tableId })
252252
const updateMetadataMutation = useUpdateTableMetadata({ workspaceId, tableId })
253253

254-
const { pushUndo, undo, redo } = useTableUndo({ workspaceId, tableId })
254+
const handleColumnOrderChange = useCallback((order: string[]) => {
255+
setColumnOrder(order)
256+
}, [])
257+
258+
const { pushUndo, undo, redo } = useTableUndo({
259+
workspaceId,
260+
tableId,
261+
onColumnOrderChange: handleColumnOrderChange,
262+
})
255263
const undoRef = useRef(undo)
256264
undoRef.current = undo
257265
const redoRef = useRef(redo)
@@ -750,6 +758,11 @@ export function Table({
750758
newOrder.splice(insertIndex, 0, dragged)
751759
const orderChanged = newOrder.some((name, i) => currentOrder[i] !== name)
752760
if (orderChanged) {
761+
pushUndoRef.current({
762+
type: 'reorder-columns',
763+
previousOrder: currentOrder,
764+
newOrder,
765+
})
753766
setColumnOrder(newOrder)
754767
updateMetadataRef.current({
755768
columnWidths: columnWidthsRef.current,
@@ -768,6 +781,37 @@ export function Table({
768781
setDropTargetColumnName(null)
769782
}, [])
770783

784+
const handleScrollDragOver = useCallback((e: React.DragEvent) => {
785+
if (!dragColumnNameRef.current) return
786+
e.preventDefault()
787+
e.dataTransfer.dropEffect = 'move'
788+
789+
const scrollEl = scrollRef.current
790+
if (!scrollEl) return
791+
const scrollRect = scrollEl.getBoundingClientRect()
792+
const cursorX = e.clientX - scrollRect.left + scrollEl.scrollLeft
793+
794+
const cols = columnsRef.current
795+
let left = CHECKBOX_COL_WIDTH
796+
for (const col of cols) {
797+
const w = columnWidthsRef.current[col.name] ?? COL_WIDTH
798+
if (cursorX < left + w) {
799+
const midX = left + w / 2
800+
const side = cursorX < midX ? 'left' : 'right'
801+
if (col.name !== dropTargetColumnNameRef.current || side !== dropSideRef.current) {
802+
setDropTargetColumnName(col.name)
803+
setDropSide(side)
804+
}
805+
return
806+
}
807+
left += w
808+
}
809+
}, [])
810+
811+
const handleScrollDrop = useCallback((e: React.DragEvent) => {
812+
e.preventDefault()
813+
}, [])
814+
771815
useEffect(() => {
772816
if (!tableData?.metadata || metadataSeededRef.current) return
773817
if (!tableData.metadata.columnWidths && !tableData.metadata.columnOrder) return
@@ -878,6 +922,12 @@ export function Table({
878922

879923
if (e.key === 'Escape') {
880924
e.preventDefault()
925+
if (dragColumnNameRef.current) {
926+
setDragColumnName(null)
927+
setDropTargetColumnName(null)
928+
setDropSide('left')
929+
return
930+
}
881931
setSelectionAnchor(null)
882932
setSelectionFocus(null)
883933
setCheckedRows((prev) => (prev.size === 0 ? prev : EMPTY_CHECKED_ROWS))
@@ -1805,6 +1855,8 @@ export function Table({
18051855
resizingColumn && 'select-none'
18061856
)}
18071857
data-table-scroll
1858+
onDragOver={handleScrollDragOver}
1859+
onDrop={handleScrollDrop}
18081860
>
18091861
<div className='relative h-fit' style={{ width: `${tableWidth}px` }}>
18101862
<table

apps/sim/hooks/use-table-undo.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Hook that connects the table undo/redo store to React Query mutations.
33
*/
44

5-
import { useCallback, useEffect } from 'react'
5+
import { useCallback, useEffect, useRef } from 'react'
66
import { createLogger } from '@sim/logger'
77
import {
88
useAddTableColumn,
@@ -14,6 +14,7 @@ import {
1414
useDeleteTableRows,
1515
useRenameTable,
1616
useUpdateColumn,
17+
useUpdateTableMetadata,
1718
useUpdateTableRow,
1819
} from '@/hooks/queries/tables'
1920
import { runWithoutRecording, useTableUndoStore } from '@/stores/table/store'
@@ -33,9 +34,10 @@ export function extractCreatedRowId(response: Record<string, unknown>): string |
3334
interface UseTableUndoProps {
3435
workspaceId: string
3536
tableId: string
37+
onColumnOrderChange?: (order: string[]) => void
3638
}
3739

38-
export function useTableUndo({ workspaceId, tableId }: UseTableUndoProps) {
40+
export function useTableUndo({ workspaceId, tableId, onColumnOrderChange }: UseTableUndoProps) {
3941
const push = useTableUndoStore((s) => s.push)
4042
const popUndo = useTableUndoStore((s) => s.popUndo)
4143
const popRedo = useTableUndoStore((s) => s.popRedo)
@@ -55,6 +57,10 @@ export function useTableUndo({ workspaceId, tableId }: UseTableUndoProps) {
5557
const updateColumnMutation = useUpdateColumn({ workspaceId, tableId })
5658
const deleteColumnMutation = useDeleteColumn({ workspaceId, tableId })
5759
const renameTableMutation = useRenameTable(workspaceId)
60+
const updateMetadataMutation = useUpdateTableMetadata({ workspaceId, tableId })
61+
62+
const onColumnOrderChangeRef = useRef(onColumnOrderChange)
63+
onColumnOrderChangeRef.current = onColumnOrderChange
5864

5965
useEffect(() => {
6066
return () => clear(tableId)
@@ -229,6 +235,13 @@ export function useTableUndo({ workspaceId, tableId }: UseTableUndoProps) {
229235
renameTableMutation.mutate({ tableId: action.tableId, name })
230236
break
231237
}
238+
239+
case 'reorder-columns': {
240+
const order = direction === 'undo' ? action.previousOrder : action.newOrder
241+
onColumnOrderChangeRef.current?.(order)
242+
updateMetadataMutation.mutate({ columnOrder: order })
243+
break
244+
}
232245
}
233246
} catch (err) {
234247
logger.error('Failed to execute undo/redo action', { action, direction, err })

apps/sim/stores/table/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export type TableUndoAction =
4242
newValue: boolean
4343
}
4444
| { type: 'rename-table'; tableId: string; previousName: string; newName: string }
45+
| { type: 'reorder-columns'; previousOrder: string[]; newOrder: string[] }
4546

4647
export interface UndoEntry {
4748
id: string

0 commit comments

Comments
 (0)