@@ -1155,8 +1155,8 @@ export class ConciseDiffViewState<K> {
11551155
11561156 private readonly props : ConciseDiffViewStateProps < K > ;
11571157
1158- // Drag selection state
1159- private dragState : { hunk : DiffViewerPatchHunk ; hunkIdx : number ; line : PatchLine ; lineIdx : number ; didMove : boolean } | null = null ;
1158+ private selectionAnchor : { hunkIdx : number ; lineIdx : number } | null = null ;
1159+ private dragSelectionState : { hunk : DiffViewerPatchHunk ; didMove : boolean } | null = null ;
11601160 private suppressNextClick = false ;
11611161
11621162 constructor ( props : ConciseDiffViewStateProps < K > ) {
@@ -1284,7 +1284,8 @@ export class ConciseDiffViewState<K> {
12841284 }
12851285
12861286 private startDrag ( element : HTMLElement , pointerId : number , hunk : DiffViewerPatchHunk , hunkIdx : number , line : PatchLine , lineIdx : number ) {
1287- this . dragState = { hunk, hunkIdx, line, lineIdx, didMove : false } ;
1287+ this . selectionAnchor = { hunkIdx, lineIdx } ;
1288+ this . dragSelectionState = { hunk, didMove : false } ;
12881289
12891290 // Set initial selection
12901291 this . props . selection . current = {
@@ -1303,7 +1304,7 @@ export class ConciseDiffViewState<K> {
13031304 element ,
13041305 "pointermove" ,
13051306 ( e : PointerEvent ) => {
1306- if ( ! this . dragState ) return ;
1307+ if ( ! this . dragSelectionState || ! this . selectionAnchor ) return ;
13071308
13081309 // Get the root element for this diff view
13091310 const rootElement = document . getElementById ( this . props . rootElementId ) ;
@@ -1323,12 +1324,12 @@ export class ConciseDiffViewState<K> {
13231324 const currentLineIdx = Number ( lineElement . dataset . lineIdx ) ;
13241325
13251326 // Only allow dragging within the same hunk
1326- if ( currentHunkIdx !== this . dragState . hunkIdx || ! Number . isFinite ( currentHunkIdx ) || ! Number . isFinite ( currentLineIdx ) ) {
1327+ if ( currentHunkIdx !== this . selectionAnchor . hunkIdx || ! Number . isFinite ( currentHunkIdx ) || ! Number . isFinite ( currentLineIdx ) ) {
13271328 return ;
13281329 }
13291330
1330- if ( this . dragState ) {
1331- this . dragState . didMove = true ;
1331+ if ( this . dragSelectionState ) {
1332+ this . dragSelectionState . didMove = true ;
13321333 }
13331334 this . updateDragSelection ( currentLineIdx ) ;
13341335 } ,
@@ -1340,10 +1341,10 @@ export class ConciseDiffViewState<K> {
13401341 abortController . abort ( ) ;
13411342
13421343 // Suppress the click event only if we actually moved during the drag
1343- if ( this . dragState ?. didMove ) {
1344+ if ( this . dragSelectionState ?. didMove ) {
13441345 this . suppressNextClick = true ;
13451346 }
1346- this . dragState = null ;
1347+ this . dragSelectionState = null ;
13471348 } ;
13481349
13491350 on ( element , "pointerup" , onDragEnd , { signal } ) ;
@@ -1359,17 +1360,18 @@ export class ConciseDiffViewState<K> {
13591360 }
13601361
13611362 private updateDragSelection ( currentLineIdx : number ) {
1362- if ( ! this . dragState ) return ;
1363+ if ( ! this . dragSelectionState || ! this . selectionAnchor ) return ;
13631364
1364- const { hunk, hunkIdx, lineIdx : startIdx } = this . dragState ;
1365+ const { hunk } = this . dragSelectionState ;
1366+ const { hunkIdx, lineIdx : anchorIdx } = this . selectionAnchor ;
13651367 const currentLine = hunk . lines [ currentLineIdx ] ;
13661368
13671369 if ( currentLine . type === PatchLineType . SPACER || currentLine . type === PatchLineType . HEADER ) {
13681370 return ;
13691371 }
13701372
1371- const minIdx = Math . min ( startIdx , currentLineIdx ) ;
1372- const maxIdx = Math . max ( startIdx , currentLineIdx ) ;
1373+ const minIdx = Math . min ( anchorIdx , currentLineIdx ) ;
1374+ const maxIdx = Math . max ( anchorIdx , currentLineIdx ) ;
13731375
13741376 this . props . selection . current = {
13751377 hunk : hunkIdx ,
@@ -1385,52 +1387,36 @@ export class ConciseDiffViewState<K> {
13851387 // New selection (no shift or different hunk)
13861388 if ( ! shift || ! existingSelection || existingSelection . hunk !== hunkIdx ) {
13871389 this . props . selection . current = { hunk : hunkIdx , start : clicked , end : clicked } ;
1390+ this . selectionAnchor = { hunkIdx, lineIdx } ;
13881391 return ;
13891392 }
13901393
13911394 // Shift click on single-line selection: clear selection
13921395 if ( existingSelection . start . idx === existingSelection . end . idx && lineIdx === existingSelection . start . idx ) {
13931396 this . props . selection . current = undefined ;
1397+ this . selectionAnchor = null ;
13941398 return ;
13951399 }
13961400
1397- // Shift click outside selection: expand selection
1398- if ( lineIdx < existingSelection . start . idx ) {
1399- this . props . selection . current = { ... existingSelection , start : clicked } ;
1400- return ;
1401- }
1402- if ( lineIdx > existingSelection . end . idx ) {
1403- this . props . selection . current = { ... existingSelection , end : clicked } ;
1404- return ;
1401+ // Determine anchor point: use existing anchor, or default to start of selection
1402+ let anchorIdx : number ;
1403+ if ( this . selectionAnchor && this . selectionAnchor . hunkIdx === hunkIdx ) {
1404+ anchorIdx = this . selectionAnchor . lineIdx ;
1405+ } else {
1406+ // No anchor or anchor is in different hunk, default to start of selection
1407+ anchorIdx = existingSelection . start . idx ;
1408+ this . selectionAnchor = { hunkIdx , lineIdx : anchorIdx } ;
14051409 }
14061410
1407- // Shift click inside selection: shrink closest side
1408- const distToStart = lineIdx - existingSelection . start . idx ;
1409- const distToEnd = existingSelection . end . idx - lineIdx ;
1411+ // Shift click: create selection from anchor to clicked line
1412+ const minIdx = Math . min ( anchorIdx , lineIdx ) ;
1413+ const maxIdx = Math . max ( anchorIdx , lineIdx ) ;
14101414
1411- if ( distToStart <= distToEnd ) {
1412- // Shrink from start: move start to line after clicked
1413- const newStartIdx = lineIdx + 1 ;
1414- if ( newStartIdx > existingSelection . end . idx ) {
1415- this . props . selection . current = undefined ;
1416- return ;
1417- }
1418- this . props . selection . current = {
1419- ...existingSelection ,
1420- start : this . createLineRef ( hunk . lines [ newStartIdx ] , newStartIdx ) ,
1421- } ;
1422- } else {
1423- // Shrink from end: move end to line before clicked
1424- const newEndIdx = lineIdx - 1 ;
1425- if ( newEndIdx < existingSelection . start . idx ) {
1426- this . props . selection . current = undefined ;
1427- return ;
1428- }
1429- this . props . selection . current = {
1430- ...existingSelection ,
1431- end : this . createLineRef ( hunk . lines [ newEndIdx ] , newEndIdx ) ,
1432- } ;
1433- }
1415+ this . props . selection . current = {
1416+ hunk : hunkIdx ,
1417+ start : this . createLineRef ( hunk . lines [ minIdx ] , minIdx ) ,
1418+ end : this . createLineRef ( hunk . lines [ maxIdx ] , maxIdx ) ,
1419+ } ;
14341420 }
14351421
14361422 isSelected ( hunkIdx : number , lineIdx : number ) : boolean {
0 commit comments