Skip to content

Commit 31fa3ca

Browse files
committed
Support separate horizontal and vertical scroll hosts for draw-selection anchoring and auto-scroll
- Split single scrollHost into horizontalScrollHost and verticalScrollHost so the selection rectangle stays anchored to content during both horizontal and vertical scrolling - Auto-scroll scrolls the horizontal host for X and the vertical host for Y independently - Remove unused PermMatrix component and its tests. Signed-off-by: Mike Lischke <mike@lischke-online.de>
1 parent d7858d2 commit 31fa3ca

5 files changed

Lines changed: 98 additions & 196 deletions

File tree

src/components/ui/Arrangement/ArrangementViewer.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,11 @@ export class ArrangementViewer extends UIComponent<IArrangementViewerProps, IArr
9494
const { autoFollowIsOn, viewerZoom } = this.state;
9595

9696
services.selectionManager.setEventContainer(this.arrangementViewerRef.current!);
97-
services.selectionManager.setScrollHost(this.viewerRef.current!);
97+
98+
const verticalHost = this.arrangementViewerRef.current?.parentElement;
99+
if (verticalHost) {
100+
services.selectionManager.setScrollHosts(this.viewerRef.current!, verticalHost);
101+
}
98102

99103
requisitions.register("settingsChanged", this.handleSettingsChanged);
100104
requisitions.register("trackViewModeToggled", this.handleTrackViewModeToggled);

src/ui/PermMatrix.tsx

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/ui/SelectionManager.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,14 @@ export class SelectionManager {
120120
}
121121

122122
/**
123-
* Sets the scroll host element for the selection view.
123+
* Sets the scroll host elements for the selection view.
124124
* Must be called after {@link setEventContainer}.
125125
*
126-
* @param host The scrollable container element (typically `#trackViewerHost`).
126+
* @param horizontal The horizontally-scrollable container (typically `#trackViewerHost`).
127+
* @param vertical The vertically-scrollable container.
127128
*/
128-
public setScrollHost(host: HTMLElement): void {
129-
this.view?.setScrollHost(host);
129+
public setScrollHosts(horizontal: HTMLElement, vertical: HTMLElement): void {
130+
this.view?.setScrollHosts(horizontal, vertical);
130131
}
131132

132133
public registerHitTester(tester: ISelectionHitTester): void {

src/ui/SelectionView.ts

Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@ const formElementNames = new Set(["BUTTON", "INPUT", "SELECT", "TEXTAREA"]);
2626
export 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();

tests/ui/PermMatrix.spec.tsx

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)