@@ -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