@@ -26,6 +26,7 @@ import { unique } from "radash";
2626import { memo , useCallback , useMemo , useRef , useState } from "react" ;
2727import { Roarr as log } from "roarr" ;
2828import { useShallow } from "zustand/shallow" ;
29+ import { useResizeObserver } from "usehooks-ts" ;
2930
3031import { TRACK_TABLE_HEADER_HEIGHT , TRACK_TABLE_ROW_HEIGHT } from "../../common/constants" ;
3132import { ClientErrors } from "../../common/errorMessages" ;
@@ -222,6 +223,12 @@ const TrackTable = memo(({ playlistId }: TrackTableProps) => {
222223 const numTracksInPlaylist = allTrackDefNodes . nodes . length ;
223224 const containerElement = useRef < HTMLDivElement | null > ( null ) ;
224225 const gridRef = useRef < DataEditorRef | null > ( null ) ;
226+ const tableContainerRef = useRef < HTMLDivElement > ( null ) ;
227+ const { width : containerWidth = 0 } = useResizeObserver ( {
228+ // @ts -expect-error - incompatible with stricter React 19 types
229+ ref : tableContainerRef ,
230+ box : "border-box" ,
231+ } ) ;
225232
226233 // filter trackDefNodes based on filterQuery value
227234 const [ filterQuery , setFilterQuery ] = useState < string > ( "" ) ;
@@ -404,21 +411,37 @@ const TrackTable = memo(({ playlistId }: TrackTableProps) => {
404411 const getRowThemeOverride = useCallback (
405412 ( row : number ) => {
406413 const track = sortedTrackDefs [ row ] ;
414+ const isSelected = selection . rows . hasIndex ( row ) ;
415+ const isPrevSelected = row > 0 && selection . rows . hasIndex ( row - 1 ) ;
416+ const themeOverride : Partial < GridTheme > = { } ;
417+
418+ // Highlight first row of selection range OR row immediately after selection range
419+ const isSelectionEdge = ( isSelected && ! isPrevSelected ) || ( ! isSelected && isPrevSelected ) ;
420+ if ( isSelectionEdge ) {
421+ themeOverride . horizontalBorderColor = theme . textDark ;
422+ }
423+
424+ // Apply active track background
407425 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
408426 if ( track && activeTrackId === track . id ) {
409- return {
410- bgCell : theme . bgCellMedium ,
411- } ;
427+ themeOverride . bgCell = theme . bgCellMedium ;
412428 }
413429 // Apply hover styling
414- if ( row === hoveredRow ) {
415- return {
416- bgCell : theme . bgHeaderHovered ,
417- } ;
430+ else if ( row === hoveredRow ) {
431+ themeOverride . bgCell = theme . bgHeaderHovered ;
418432 }
419- return undefined ;
433+
434+ return Object . keys ( themeOverride ) . length > 0 ? themeOverride : undefined ;
420435 } ,
421- [ sortedTrackDefs , activeTrackId , theme . bgCellMedium , theme . bgHeaderHovered , hoveredRow ] ,
436+ [
437+ sortedTrackDefs ,
438+ activeTrackId ,
439+ theme . bgCellMedium ,
440+ theme . bgHeaderHovered ,
441+ theme . textDark ,
442+ hoveredRow ,
443+ selection ,
444+ ] ,
422445 ) ;
423446
424447 // Handle item hover to track which row is being hovered
@@ -441,6 +464,7 @@ const TrackTable = memo(({ playlistId }: TrackTableProps) => {
441464
442465 const table = (
443466 < div
467+ ref = { tableContainerRef }
444468 className = { classNames ( styles . trackTable , { [ styles . contextMenuIsOpen ] : isContextMenuOpen } ) }
445469 onClick = { ( e ) => {
446470 // Clear selection when clicking directly on the container div (empty space)
@@ -452,6 +476,7 @@ const TrackTable = memo(({ playlistId }: TrackTableProps) => {
452476 >
453477 < DataEditor
454478 ref = { gridRef }
479+ width = { containerWidth }
455480 columns = { columns }
456481 rows = { sortedTrackDefs . length }
457482 getCellContent = { getCellContent }
0 commit comments