@@ -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 ) ;
@@ -144,21 +153,44 @@ export class SelectionView {
144153 } ;
145154
146155 /**
147- * Adjusts the selection start point when the scroll host scrolls during a drag,
156+ * Adjusts the selection start point when any scroll host scrolls during a drag,
148157 * keeping the anchor pinned to the content rather than the viewport.
158+ *
159+ * @param event The scroll event from one of the registered scroll hosts.
149160 */
150- private handleScroll = ( ) : void => {
151- if ( ! this . scrollHost || ! this . rectElement ) {
161+ private handleScroll = ( event : Event ) : void => {
162+ if ( ! this . rectElement ) {
152163 return ;
153164 }
154165
155- const deltaX = this . scrollHost . scrollLeft - this . startScrollLeft ;
156- const deltaY = this . scrollHost . scrollTop - this . startScrollTop ;
166+ const target = event . target ;
167+ let deltaX = 0 ;
168+ let deltaY = 0 ;
169+ let handled = false ;
170+
171+ const hHost = this . horizontalScrollHost ;
172+ if ( hHost && target === hHost ) {
173+ deltaX = hHost . scrollLeft - this . startScrollLeft ;
174+ deltaY = hHost . scrollTop - this . startScrollTop ;
175+ this . startScrollLeft = hHost . scrollLeft ;
176+ this . startScrollTop = hHost . scrollTop ;
177+ handled = true ;
178+ }
179+
180+ const vHost = this . verticalScrollHost ;
181+ if ( vHost && target === vHost ) {
182+ const verticalDelta = vHost . scrollTop - this . startVerticalScrollTop ;
183+ deltaY += verticalDelta ;
184+ this . startVerticalScrollTop = vHost . scrollTop ;
185+ handled = true ;
186+ }
187+
188+ if ( ! handled ) {
189+ return ;
190+ }
157191
158192 this . startX -= deltaX ;
159193 this . startY -= deltaY ;
160- this . startScrollLeft = this . scrollHost . scrollLeft ;
161- this . startScrollTop = this . scrollHost . scrollTop ;
162194
163195 this . updateRectElement ( this . lastPointerX , this . lastPointerY ) ;
164196
@@ -199,12 +231,17 @@ export class SelectionView {
199231 this . stopAutoScroll ( ) ;
200232 this . removeRectElement ( ) ;
201233
202- if ( this . scrollHost ) {
203- this . scrollHost . removeEventListener ( "scroll" , this . handleScroll ) ;
234+ if ( this . horizontalScrollHost ) {
235+ this . horizontalScrollHost . removeEventListener ( "scroll" , this . handleScroll ) ;
204236 this . startScrollLeft = 0 ;
205237 this . startScrollTop = 0 ;
206238 }
207239
240+ if ( this . verticalScrollHost && this . verticalScrollHost !== this . horizontalScrollHost ) {
241+ this . verticalScrollHost . removeEventListener ( "scroll" , this . handleScroll ) ;
242+ this . startVerticalScrollTop = 0 ;
243+ }
244+
208245 if ( this . captureElement ) {
209246 this . captureElement . removeEventListener ( "pointermove" , this . handlePointerMove ) ;
210247 this . captureElement . removeEventListener ( "pointerup" , this . handlePointerUp ) ;
@@ -221,38 +258,56 @@ export class SelectionView {
221258 * @param clientY The current pointer Y position in viewport coordinates.
222259 */
223260 private updateAutoScroll ( clientX : number , clientY : number ) : void {
224- if ( ! this . scrollHost ) {
261+ if ( ! this . horizontalScrollHost && ! this . verticalScrollHost ) {
225262 return ;
226263 }
227264
228- const hostRect = this . scrollHost . getBoundingClientRect ( ) ;
229265 const edgeThreshold = 10 ;
230266 let scrollDX = 0 ;
231267 let scrollDY = 0 ;
232268
233- const distLeft = clientX - hostRect . left ;
234- const distRight = hostRect . right - clientX ;
235- const distTop = clientY - hostRect . top ;
236- const distBottom = hostRect . bottom - clientY ;
269+ // Horizontal auto-scroll on the horizontal host.
270+ if ( this . horizontalScrollHost ) {
271+ const hostRect = this . horizontalScrollHost . getBoundingClientRect ( ) ;
272+ const distLeft = clientX - hostRect . left ;
273+ const distRight = hostRect . right - clientX ;
237274
238- if ( distLeft < edgeThreshold ) {
239- scrollDX = - Math . max ( 1 , Math . ceil ( ( edgeThreshold - distLeft ) / 2 ) ) ;
240- } else if ( distRight < edgeThreshold ) {
241- scrollDX = Math . max ( 1 , Math . ceil ( ( edgeThreshold - distRight ) / 2 ) ) ;
275+ if ( distLeft < edgeThreshold ) {
276+ scrollDX = - Math . max ( 1 , Math . ceil ( ( edgeThreshold - distLeft ) / 2 ) ) ;
277+ } else if ( distRight < edgeThreshold ) {
278+ scrollDX = Math . max ( 1 , Math . ceil ( ( edgeThreshold - distRight ) / 2 ) ) ;
279+ }
242280 }
243281
244- if ( distTop < edgeThreshold ) {
245- scrollDY = - Math . max ( 1 , Math . ceil ( ( edgeThreshold - distTop ) / 2 ) ) ;
246- } else if ( distBottom < edgeThreshold ) {
247- scrollDY = Math . max ( 1 , Math . ceil ( ( edgeThreshold - distBottom ) / 2 ) ) ;
282+ // Vertical auto-scroll on the vertical host.
283+ const verticalHost = this . verticalScrollHost ?? this . horizontalScrollHost ;
284+ if ( verticalHost ) {
285+ const hostRect = verticalHost . getBoundingClientRect ( ) ;
286+ const distTop = clientY - hostRect . top ;
287+ const distBottom = hostRect . bottom - clientY ;
288+
289+ if ( distTop < edgeThreshold ) {
290+ scrollDY = - Math . max ( 1 , Math . ceil ( ( edgeThreshold - distTop ) / 2 ) ) ;
291+ } else if ( distBottom < edgeThreshold ) {
292+ scrollDY = Math . max ( 1 , Math . ceil ( ( edgeThreshold - distBottom ) / 2 ) ) ;
293+ }
248294 }
249295
250296 this . autoScrollDX = scrollDX ;
251297 this . autoScrollDY = scrollDY ;
252298
253299 if ( scrollDX !== 0 || scrollDY !== 0 ) {
254300 this . autoScrollTimer ??= setInterval ( ( ) => {
255- this . scrollHost ! . scrollBy ( this . autoScrollDX , this . autoScrollDY ) ;
301+ if ( this . autoScrollDX !== 0 && this . horizontalScrollHost ) {
302+ this . horizontalScrollHost . scrollBy ( this . autoScrollDX , 0 ) ;
303+ }
304+
305+ if ( this . autoScrollDY !== 0 ) {
306+ const vh = this . verticalScrollHost ?? this . horizontalScrollHost ;
307+ if ( vh ) {
308+ vh . scrollBy ( 0 , this . autoScrollDY ) ;
309+ }
310+ }
256311 } , 16 ) ;
257312 } else {
258313 this . stopAutoScroll ( ) ;
0 commit comments