@@ -26,14 +26,16 @@ const formElementNames = new Set(["BUTTON", "INPUT", "SELECT", "TEXTAREA"]);
2626export class SelectionView {
2727 private rectElement ?: HTMLDivElement ;
2828 private captureElement ?: HTMLElement ;
29- private scrollHost ?: HTMLElement ;
29+ private horizontalScrollHost ?: HTMLElement ;
30+ private verticalScrollHost ?: HTMLElement ;
3031
3132 private dragPending = false ;
3233 private isDragging = false ;
3334 private startX = 0 ;
3435 private startY = 0 ;
3536 private startScrollLeft = 0 ;
3637 private startScrollTop = 0 ;
38+ private startVerticalScrollTop = 0 ;
3739 private lastPointerX = 0 ;
3840 private lastPointerY = 0 ;
3941 private autoScrollTimer ?: ReturnType < typeof setInterval > ;
@@ -63,13 +65,15 @@ export class SelectionView {
6365 }
6466
6567 /**
66- * Sets the scroll host element whose scroll position is tracked during drag selection
68+ * Sets the scroll host elements whose scroll positions are tracked during drag selection
6769 * so the selection rectangle stays anchored to the content. Also enables edge auto-scroll.
6870 *
69- * @param host The scrollable container element (typically `#trackViewerHost`).
71+ * @param horizontal The horizontally-scrollable container (typically `#trackViewerHost`).
72+ * @param vertical The vertically-scrollable container (typically the arrangement viewer parent).
7073 */
71- public setScrollHost ( host : HTMLElement ) : void {
72- this . scrollHost = host ;
74+ public setScrollHosts ( horizontal : HTMLElement , vertical : HTMLElement ) : void {
75+ this . horizontalScrollHost = horizontal ;
76+ this . verticalScrollHost = vertical ;
7377 }
7478
7579 private handlePointerDown = ( event : PointerEvent ) : void => {
@@ -90,10 +94,15 @@ export class SelectionView {
9094 this . lastPointerX = event . clientX ;
9195 this . lastPointerY = event . clientY ;
9296
93- if ( this . scrollHost ) {
94- this . startScrollLeft = this . scrollHost . scrollLeft ;
95- this . startScrollTop = this . scrollHost . scrollTop ;
96- this . scrollHost . addEventListener ( "scroll" , this . handleScroll , { passive : true } ) ;
97+ if ( this . horizontalScrollHost ) {
98+ this . startScrollLeft = this . horizontalScrollHost . scrollLeft ;
99+ this . startScrollTop = this . horizontalScrollHost . scrollTop ;
100+ this . horizontalScrollHost . addEventListener ( "scroll" , this . handleScroll , { passive : true } ) ;
101+ }
102+
103+ if ( this . verticalScrollHost && this . verticalScrollHost !== this . horizontalScrollHost ) {
104+ this . startVerticalScrollTop = this . verticalScrollHost . scrollTop ;
105+ this . verticalScrollHost . addEventListener ( "scroll" , this . handleScroll , { passive : true } ) ;
97106 }
98107
99108 this . createRectElement ( event . clientX , event . clientY ) ;
@@ -148,21 +157,44 @@ export class SelectionView {
148157 } ;
149158
150159 /**
151- * Adjusts the selection start point when the scroll host scrolls during a drag,
160+ * Adjusts the selection start point when any scroll host scrolls during a drag,
152161 * keeping the anchor pinned to the content rather than the viewport.
162+ *
163+ * @param event The scroll event from one of the registered scroll hosts.
153164 */
154- private handleScroll = ( ) : void => {
155- if ( ! this . scrollHost || ! this . rectElement ) {
165+ private handleScroll = ( event : Event ) : void => {
166+ if ( ! this . rectElement ) {
156167 return ;
157168 }
158169
159- const deltaX = this . scrollHost . scrollLeft - this . startScrollLeft ;
160- const deltaY = this . scrollHost . scrollTop - this . startScrollTop ;
170+ const target = event . target ;
171+ let deltaX = 0 ;
172+ let deltaY = 0 ;
173+ let handled = false ;
174+
175+ const hHost = this . horizontalScrollHost ;
176+ if ( hHost && target === hHost ) {
177+ deltaX = hHost . scrollLeft - this . startScrollLeft ;
178+ deltaY = hHost . scrollTop - this . startScrollTop ;
179+ this . startScrollLeft = hHost . scrollLeft ;
180+ this . startScrollTop = hHost . scrollTop ;
181+ handled = true ;
182+ }
183+
184+ const vHost = this . verticalScrollHost ;
185+ if ( vHost && target === vHost ) {
186+ const verticalDelta = vHost . scrollTop - this . startVerticalScrollTop ;
187+ deltaY += verticalDelta ;
188+ this . startVerticalScrollTop = vHost . scrollTop ;
189+ handled = true ;
190+ }
191+
192+ if ( ! handled ) {
193+ return ;
194+ }
161195
162196 this . startX -= deltaX ;
163197 this . startY -= deltaY ;
164- this . startScrollLeft = this . scrollHost . scrollLeft ;
165- this . startScrollTop = this . scrollHost . scrollTop ;
166198
167199 this . updateRectElement ( this . lastPointerX , this . lastPointerY ) ;
168200
@@ -203,12 +235,17 @@ export class SelectionView {
203235 this . stopAutoScroll ( ) ;
204236 this . removeRectElement ( ) ;
205237
206- if ( this . scrollHost ) {
207- this . scrollHost . removeEventListener ( "scroll" , this . handleScroll ) ;
238+ if ( this . horizontalScrollHost ) {
239+ this . horizontalScrollHost . removeEventListener ( "scroll" , this . handleScroll ) ;
208240 this . startScrollLeft = 0 ;
209241 this . startScrollTop = 0 ;
210242 }
211243
244+ if ( this . verticalScrollHost && this . verticalScrollHost !== this . horizontalScrollHost ) {
245+ this . verticalScrollHost . removeEventListener ( "scroll" , this . handleScroll ) ;
246+ this . startVerticalScrollTop = 0 ;
247+ }
248+
212249 if ( this . captureElement ) {
213250 this . captureElement . removeEventListener ( "pointermove" , this . handlePointerMove ) ;
214251 this . captureElement . removeEventListener ( "pointerup" , this . handlePointerUp ) ;
@@ -225,38 +262,56 @@ export class SelectionView {
225262 * @param clientY The current pointer Y position in viewport coordinates.
226263 */
227264 private updateAutoScroll ( clientX : number , clientY : number ) : void {
228- if ( ! this . scrollHost ) {
265+ if ( ! this . horizontalScrollHost && ! this . verticalScrollHost ) {
229266 return ;
230267 }
231268
232- const hostRect = this . scrollHost . getBoundingClientRect ( ) ;
233269 const edgeThreshold = 10 ;
234270 let scrollDX = 0 ;
235271 let scrollDY = 0 ;
236272
237- const distLeft = clientX - hostRect . left ;
238- const distRight = hostRect . right - clientX ;
239- const distTop = clientY - hostRect . top ;
240- const distBottom = hostRect . bottom - clientY ;
273+ // Horizontal auto-scroll on the horizontal host.
274+ if ( this . horizontalScrollHost ) {
275+ const hostRect = this . horizontalScrollHost . getBoundingClientRect ( ) ;
276+ const distLeft = clientX - hostRect . left ;
277+ const distRight = hostRect . right - clientX ;
241278
242- if ( distLeft < edgeThreshold ) {
243- scrollDX = - Math . max ( 1 , Math . ceil ( ( edgeThreshold - distLeft ) / 2 ) ) ;
244- } else if ( distRight < edgeThreshold ) {
245- scrollDX = Math . max ( 1 , Math . ceil ( ( edgeThreshold - distRight ) / 2 ) ) ;
279+ if ( distLeft < edgeThreshold ) {
280+ scrollDX = - Math . max ( 1 , Math . ceil ( ( edgeThreshold - distLeft ) / 2 ) ) ;
281+ } else if ( distRight < edgeThreshold ) {
282+ scrollDX = Math . max ( 1 , Math . ceil ( ( edgeThreshold - distRight ) / 2 ) ) ;
283+ }
246284 }
247285
248- if ( distTop < edgeThreshold ) {
249- scrollDY = - Math . max ( 1 , Math . ceil ( ( edgeThreshold - distTop ) / 2 ) ) ;
250- } else if ( distBottom < edgeThreshold ) {
251- scrollDY = Math . max ( 1 , Math . ceil ( ( edgeThreshold - distBottom ) / 2 ) ) ;
286+ // Vertical auto-scroll on the vertical host.
287+ const verticalHost = this . verticalScrollHost ?? this . horizontalScrollHost ;
288+ if ( verticalHost ) {
289+ const hostRect = verticalHost . getBoundingClientRect ( ) ;
290+ const distTop = clientY - hostRect . top ;
291+ const distBottom = hostRect . bottom - clientY ;
292+
293+ if ( distTop < edgeThreshold ) {
294+ scrollDY = - Math . max ( 1 , Math . ceil ( ( edgeThreshold - distTop ) / 2 ) ) ;
295+ } else if ( distBottom < edgeThreshold ) {
296+ scrollDY = Math . max ( 1 , Math . ceil ( ( edgeThreshold - distBottom ) / 2 ) ) ;
297+ }
252298 }
253299
254300 this . autoScrollDX = scrollDX ;
255301 this . autoScrollDY = scrollDY ;
256302
257303 if ( scrollDX !== 0 || scrollDY !== 0 ) {
258304 this . autoScrollTimer ??= setInterval ( ( ) => {
259- this . scrollHost ! . scrollBy ( this . autoScrollDX , this . autoScrollDY ) ;
305+ if ( this . autoScrollDX !== 0 && this . horizontalScrollHost ) {
306+ this . horizontalScrollHost . scrollBy ( this . autoScrollDX , 0 ) ;
307+ }
308+
309+ if ( this . autoScrollDY !== 0 ) {
310+ const vh = this . verticalScrollHost ?? this . horizontalScrollHost ;
311+ if ( vh ) {
312+ vh . scrollBy ( 0 , this . autoScrollDY ) ;
313+ }
314+ }
260315 } , 16 ) ;
261316 } else {
262317 this . stopAutoScroll ( ) ;
0 commit comments