@@ -32,6 +32,14 @@ export class SelectionView {
3232 private startX = 0 ;
3333 private startY = 0 ;
3434
35+ /**
36+ * Ratio of viewport pixels to CSS pixels inside #trackViewerContainer.
37+ * Viewport-pixel deltas from getBoundingClientRect() are divided by this
38+ * factor to obtain CSS-pixel values for overlay positioning. CSS-pixel
39+ * constants (offsetY, heightOffset, etc.) are never divided.
40+ */
41+ private zoomFactor = 1 ;
42+
3543 public constructor ( private manager : SelectionManager , private eventContainer : HTMLElement ) {
3644 requisitions . register ( "selectionChanged" , this . handleSelectionChanged ) ;
3745 eventContainer . addEventListener ( "pointerdown" , this . handlePointerDown ) ;
@@ -224,6 +232,13 @@ export class SelectionView {
224232
225233 const containerRect = overlayContainer . getBoundingClientRect ( ) ;
226234
235+ // Measure the actual zoom factor from the DOM: offsetWidth gives CSS pixels,
236+ // getBoundingClientRect().width gives viewport pixels (post-zoom). The ratio is
237+ // the effective zoom factor, which is more reliable than parsing style.zoom.
238+ const cssW = contentHost . offsetWidth ;
239+ const viewportW = contentHost . getBoundingClientRect ( ) . width ;
240+ this . zoomFactor = cssW > 0 ? viewportW / cssW : 1 ;
241+
227242 // Separate entries by granularity.
228243 const trackEntries : ISelectionEntry [ ] = [ ] ;
229244 const measureEntries : ISelectionEntry [ ] = [ ] ;
@@ -399,21 +414,25 @@ export class SelectionView {
399414 const firstContent = runs [ 0 ] . querySelector < HTMLElement > ( contentSelector ) ;
400415 if ( firstContent ) {
401416 const firstRect = firstContent . getBoundingClientRect ( ) ;
402- minLeft = Math . max ( minLeft , firstRect . left - 10 ) ;
417+ minLeft = Math . max ( minLeft , firstRect . left - ( 10 * this . zoomFactor ) ) ;
403418 }
404419
405420 // Narrow right edge to the last run's inner content.
406421 const lastContent = runs [ runs . length - 1 ] . querySelector < HTMLElement > ( contentSelector ) ;
407422 if ( lastContent ) {
408423 const lastRect = lastContent . getBoundingClientRect ( ) ;
409- maxRight = lastRect . right + 2 ;
424+ maxRight = lastRect . right + ( 2 * this . zoomFactor ) ;
410425 }
411426
427+ // Convert viewport-pixel deltas to CSS pixels. offsetY/heightOffset
428+ // are CSS pixels and must not be divided.
429+ const z = this . zoomFactor ;
430+
412431 this . createOverlay ( overlayContainer , {
413- x : minLeft - containerRect . left ,
414- y : minTop - containerRect . top + offsetY ,
415- width : maxRight - minLeft ,
416- height : maxBottom - minTop + heightOffset ,
432+ x : ( minLeft - containerRect . left ) / z ,
433+ y : ( ( minTop - containerRect . top ) / z ) + offsetY ,
434+ width : ( maxRight - minLeft ) / z ,
435+ height : ( ( maxBottom - minTop ) / z ) + heightOffset ,
417436 } ) ;
418437 }
419438
@@ -740,7 +759,8 @@ export class SelectionView {
740759
741760 const rect = this . computeMergedRect ( contentHost , selectors . join ( "," ) , containerRect ) ;
742761 if ( rect ) {
743- rect . y -= 10 ;
762+ rect . x += 4 ;
763+ rect . width -= 8 ;
744764 this . createOverlay ( overlayContainer , rect ) ;
745765 }
746766 }
@@ -778,13 +798,15 @@ export class SelectionView {
778798 }
779799 }
780800
781- const padding = 2 ; // small visual breathing room on each side
801+ // Convert viewport-pixel deltas to CSS pixels. offsetY/heightOffset are
802+ // already CSS pixels and must not be divided.
803+ const z = this . zoomFactor ;
782804
783805 this . createOverlay ( overlayContainer , {
784- x : minLeft - containerRect . left - padding ,
785- y : minTop - containerRect . top + offsetY ,
786- width : ( maxRight - minLeft ) + ( 2 * padding ) ,
787- height : maxBottom - minTop + heightOffset ,
806+ x : ( minLeft - containerRect . left ) / z ,
807+ y : ( ( minTop - containerRect . top ) / z ) + offsetY ,
808+ width : ( maxRight - minLeft ) / z ,
809+ height : ( ( maxBottom - minTop ) / z ) + heightOffset ,
788810 } ) ;
789811 }
790812
@@ -805,11 +827,13 @@ export class SelectionView {
805827 const marginBottom = parseFloat ( style . marginBottom ) || 0 ;
806828 const marginLeft = parseFloat ( style . marginLeft ) || 0 ;
807829
830+ // All values in viewport pixels for consistent min/max computation.
831+ // Callers convert to CSS pixels before passing to createOverlay.
808832 return {
809- x : elRect . left - marginLeft - containerRect . left ,
810- y : elRect . top - marginTop - containerRect . top ,
811- width : elRect . width + marginLeft + marginRight ,
812- height : elRect . height + marginTop + marginBottom ,
833+ x : elRect . left - ( marginLeft * this . zoomFactor ) - containerRect . left ,
834+ y : elRect . top - ( marginTop * this . zoomFactor ) - containerRect . top ,
835+ width : elRect . width + ( ( marginLeft + marginRight ) * this . zoomFactor ) ,
836+ height : elRect . height + ( ( marginTop + marginBottom ) * this . zoomFactor ) ,
813837 } ;
814838 }
815839
@@ -864,13 +888,13 @@ export class SelectionView {
864888 }
865889 }
866890
867- const padding = 2 ;
891+ const z = this . zoomFactor ;
868892
869893 return {
870- x : minLeft - containerRect . left - padding ,
871- y : minTop - containerRect . top ,
872- width : ( maxRight - minLeft ) + ( 2 * padding ) ,
873- height : maxBottom - minTop ,
894+ x : ( minLeft - containerRect . left ) / z ,
895+ y : ( minTop - containerRect . top ) / z ,
896+ width : ( maxRight - minLeft ) / z ,
897+ height : ( maxBottom - minTop ) / z ,
874898 } ;
875899 }
876900
0 commit comments