Skip to content

Commit 48b8416

Browse files
authored
sticky scroll mouse listener improvements (microsoft#199541)
* sticky scroll mouse listener improvements
1 parent 21ec78f commit 48b8416

File tree

1 file changed

+30
-35
lines changed

1 file changed

+30
-35
lines changed

src/vs/base/browser/ui/tree/abstractTree.ts

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IDragAndDropData } from 'vs/base/browser/dnd';
7-
import { $, addDisposableListener, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement } from 'vs/base/browser/dom';
7+
import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement } from 'vs/base/browser/dom';
88
import { DomEmitter } from 'vs/base/browser/event';
99
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
1010
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -26,7 +26,7 @@ import { SetMap } from 'vs/base/common/map';
2626
import { Emitter, Event, EventBufferer, Relay } from 'vs/base/common/event';
2727
import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters';
2828
import { KeyCode } from 'vs/base/common/keyCodes';
29-
import { Disposable, DisposableStore, dispose, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
29+
import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
3030
import { clamp } from 'vs/base/common/numbers';
3131
import { ScrollEvent } from 'vs/base/common/scrollable';
3232
import { ISpliceable } from 'vs/base/common/sequence';
@@ -1457,7 +1457,6 @@ class StickyScrollWidget<T, TFilterData, TRef> implements IDisposable {
14571457

14581458
private readonly _rootDomNode: HTMLElement;
14591459
private _previousState: StickyScrollState<T, TFilterData, TRef> | undefined;
1460-
private _mouseUpTimerDisposable = new MutableDisposable();
14611460

14621461
constructor(
14631462
container: HTMLElement,
@@ -1563,50 +1562,21 @@ class StickyScrollWidget<T, TFilterData, TRef> implements IDisposable {
15631562
const templateData = renderer.renderTemplate(stickyElement);
15641563
renderer.renderElement(nodeCopy, stickyNode.startIndex, templateData, stickyNode.height);
15651564

1566-
const mouseListenerDisposable = this.registerMouseListeners(stickyElement, stickyNode, currentWidgetHeight);
1567-
15681565
// Remove the element from the DOM when state is disposed
15691566
const disposable = toDisposable(() => {
15701567
renderer.disposeElement(nodeCopy, stickyNode.startIndex, templateData, stickyNode.height);
15711568
renderer.disposeTemplate(templateData);
1572-
mouseListenerDisposable.dispose();
15731569
stickyElement.remove();
15741570
});
15751571

15761572
return { element: stickyElement, disposable };
15771573
}
15781574

1579-
private registerMouseListeners(stickyElement: HTMLElement, stickyNode: StickyScrollNode<T, TFilterData>, currentWidgetHeight: number): IDisposable {
1580-
1581-
return addDisposableListener(stickyElement, 'mouseup', (e: MouseEvent) => {
1582-
const isRightClick = e.button === 2;
1583-
if (isRightClick) {
1584-
return;
1585-
}
1586-
1587-
if (isMonacoCustomToggle(e.target as HTMLElement) || isActionItem(e.target as HTMLElement)) {
1588-
return;
1589-
}
1590-
1591-
// Timeout 0 ensures that the tree handles the click event first
1592-
this._mouseUpTimerDisposable.value = disposableTimeout(() => {
1593-
const elementTop = this.view.getElementTop(stickyNode.startIndex);
1594-
// We can't rely on the current sticky node's position
1595-
// because the node might be partially scrolled under the widget
1596-
const previousStickyNodeBottom = currentWidgetHeight;
1597-
this.view.scrollTop = elementTop - previousStickyNodeBottom;
1598-
this.view.setFocus([stickyNode.startIndex]);
1599-
this.view.setSelection([stickyNode.startIndex]);
1600-
}, 0);
1601-
});
1602-
}
1603-
16041575
private setVisible(visible: boolean): void {
16051576
this._rootDomNode.style.display = visible ? 'block' : 'none';
16061577
}
16071578

16081579
dispose(): void {
1609-
this._mouseUpTimerDisposable.dispose();
16101580
this._previousState?.dispose();
16111581
this._rootDomNode.remove();
16121582
}
@@ -1791,7 +1761,11 @@ class Trait<T> {
17911761

17921762
class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<ITreeNode<T, TFilterData>> {
17931763

1794-
constructor(list: TreeNodeList<T, TFilterData, TRef>, private tree: AbstractTree<T, TFilterData, TRef>) {
1764+
constructor(
1765+
list: TreeNodeList<T, TFilterData, TRef>,
1766+
private tree: AbstractTree<T, TFilterData, TRef>,
1767+
private stickyScrollProvider: () => StickyScrollController<T, TFilterData, TRef> | undefined
1768+
) {
17951769
super(list);
17961770
}
17971771

@@ -1840,6 +1814,8 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
18401814
if (!this.tree.expandOnDoubleClick && e.browserEvent.detail === 2) {
18411815
return super.onViewPointer(e);
18421816
}
1817+
} else {
1818+
this.handleStickyScrollMouseEvent(e, node);
18431819
}
18441820

18451821
if (node.collapsible && (!isStickyElement || onTwistie)) {
@@ -1860,6 +1836,24 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
18601836
}
18611837
}
18621838

1839+
private handleStickyScrollMouseEvent(e: IListMouseEvent<ITreeNode<T, TFilterData>>, node: ITreeNode<T, TFilterData>): void {
1840+
if (isMonacoCustomToggle(e.browserEvent.target as HTMLElement) || isActionItem(e.browserEvent.target as HTMLElement)) {
1841+
return;
1842+
}
1843+
1844+
const stickyScrollController = this.stickyScrollProvider();
1845+
if (!stickyScrollController) {
1846+
throw new Error('Sticky scroll controller not found');
1847+
}
1848+
1849+
const nodeIndex = this.list.indexOf(node);
1850+
const elementScrollTop = this.list.getElementTop(nodeIndex);
1851+
const elementTargetViewTop = stickyScrollController.nodePositionTopBelowWidget(node);
1852+
this.tree.scrollTop = elementScrollTop - elementTargetViewTop;
1853+
this.list.setFocus([nodeIndex]);
1854+
this.list.setSelection([nodeIndex]);
1855+
}
1856+
18631857
protected override onDoubleClick(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
18641858
const onTwistie = (e.browserEvent.target as HTMLElement).classList.contains('monaco-tl-twistie');
18651859

@@ -1877,6 +1871,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
18771871

18781872
interface ITreeNodeListOptions<T, TFilterData, TRef> extends IListOptions<ITreeNode<T, TFilterData>> {
18791873
readonly tree: AbstractTree<T, TFilterData, TRef>;
1874+
readonly stickyScrollProvider: () => StickyScrollController<T, TFilterData, TRef> | undefined;
18801875
}
18811876

18821877
/**
@@ -1899,7 +1894,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
18991894
}
19001895

19011896
protected override createMouseController(options: ITreeNodeListOptions<T, TFilterData, TRef>): MouseController<ITreeNode<T, TFilterData>> {
1902-
return new TreeNodeListMouseController(this, options.tree);
1897+
return new TreeNodeListMouseController(this, options.tree, options.stickyScrollProvider);
19031898
}
19041899

19051900
override splice(start: number, deleteCount: number, elements: readonly ITreeNode<T, TFilterData>[] = []): void {
@@ -2058,7 +2053,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
20582053
this.focus = new Trait(() => this.view.getFocusedElements()[0], _options.identityProvider);
20592054
this.selection = new Trait(() => this.view.getSelectedElements()[0], _options.identityProvider);
20602055
this.anchor = new Trait(() => this.view.getAnchorElement(), _options.identityProvider);
2061-
this.view = new TreeNodeList(_user, container, this.treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, _options), tree: this });
2056+
this.view = new TreeNodeList(_user, container, this.treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, _options), tree: this, stickyScrollProvider: () => this.stickyScrollController });
20622057

20632058
this.model = this.createModel(_user, this.view, _options);
20642059
onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;

0 commit comments

Comments
 (0)