Make arrows configurable via API

This commit is contained in:
Daniel Imms
2026-02-02 12:33:33 -08:00
parent fae6f03f90
commit 77695cb405
11 changed files with 65 additions and 18 deletions
+13 -1
View File
@@ -225,7 +225,19 @@
/* Arrows */
.xterm .xterm-scrollable-element > .scrollbar > .scra {
cursor: pointer;
font-size: 11px !important;
background-color: var(--vscode-scrollbarSliderBackground, rgba(100, 100, 100, 0.4));
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 11'><path d='M1.5 8.5 L5.5 2.5 L9.5 8.5 Z' fill='black'/></svg>");
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 11'><path d='M1.5 8.5 L5.5 2.5 L9.5 8.5 Z' fill='black'/></svg>");
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-position: center center;
-webkit-mask-position: center center;
mask-size: 100% 100%;
-webkit-mask-size: 100% 100%;
}
.xterm .xterm-scrollable-element > .scrollbar > .scra.xterm-arrow-down {
transform: rotate(180deg);
}
.xterm .xterm-scrollable-element > .visible {
+1
View File
@@ -275,6 +275,7 @@ function createTerminal(): Terminal {
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
term = new Terminal({
scrollbar: { showArrows: true },
allowProposedApi: true,
windowsPty: isWindows ? {
// In a real scenario, these values should be verified on the backend
+1
View File
@@ -56,6 +56,7 @@ export class Viewport extends Disposable {
horizontal: ScrollbarVisibility.HIDDEN,
useShadows: false,
mouseWheelSmoothScroll: true,
verticalHasArrows: this._optionsService.rawOptions.scrollbar?.showArrows ?? false,
...this._getChangeOptions()
}, scrollable));
this._register(this._optionsService.onMultipleOptionChange([
@@ -17,7 +17,7 @@ export class HorizontalScrollbar extends AbstractScrollbar {
lazyRender: options.lazyRender,
host: host,
scrollbarState: new ScrollbarState(
(options.horizontalHasArrows ? options.arrowSize : 0),
(options.horizontalHasArrows ? options.horizontalScrollbarSize : 0),
(options.horizontal === ScrollbarVisibility.HIDDEN ? 0 : options.horizontalScrollbarSize),
(options.vertical === ScrollbarVisibility.HIDDEN ? 0 : options.verticalScrollbarSize),
scrollDimensions.width,
@@ -572,7 +572,6 @@ function resolveOptions(opts: IScrollableElementCreationOptions): IScrollableEle
fastScrollSensitivity: (typeof opts.fastScrollSensitivity !== 'undefined' ? opts.fastScrollSensitivity : 5),
scrollPredominantAxis: (typeof opts.scrollPredominantAxis !== 'undefined' ? opts.scrollPredominantAxis : true),
mouseWheelSmoothScroll: (typeof opts.mouseWheelSmoothScroll !== 'undefined' ? opts.mouseWheelSmoothScroll : true),
arrowSize: (typeof opts.arrowSize !== 'undefined' ? opts.arrowSize : 11),
listenOnDomNode: (typeof opts.listenOnDomNode !== 'undefined' ? opts.listenOnDomNode : null),
@@ -67,11 +67,6 @@ export interface IScrollableElementCreationOptions {
* Defaults to true.
*/
scrollPredominantAxis?: boolean;
/**
* Height for vertical arrows (top/bottom) and width for horizontal arrows (left/right).
* Defaults to 11.
*/
arrowSize?: number;
/**
* The dom node events should be bound to.
* If no listenOnDomNode is provided, the constructor dom node is used.
@@ -151,7 +146,6 @@ export interface IScrollableElementResolvedOptions {
fastScrollSensitivity: number;
scrollPredominantAxis: boolean;
mouseWheelSmoothScroll: boolean;
arrowSize: number;
listenOnDomNode: HTMLElement | null;
horizontal: ScrollbarVisibility;
horizontalScrollbarSize: number;
+3 -7
View File
@@ -8,11 +8,6 @@ import { Widget } from './widget';
import { TimeoutTimer } from 'common/Async';
import * as dom from '../Dom';
/**
* The arrow image size.
*/
const ARROW_IMG_SIZE = 11;
export interface IScrollbarArrowOptions {
handleActivate: () => void;
className: string;
@@ -63,8 +58,9 @@ export class ScrollbarArrow extends Widget {
// this.domNode.classList.add(...ThemeIcon.asClassNameArray(opts.icon));
this.domNode.style.position = 'absolute';
this.domNode.style.width = ARROW_IMG_SIZE + 'px';
this.domNode.style.height = ARROW_IMG_SIZE + 'px';
const arrowSize = Math.min(opts.bgWidth, opts.bgHeight);
this.domNode.style.width = arrowSize + 'px';
this.domNode.style.height = arrowSize + 'px';
if (typeof opts.top !== 'undefined') {
this.domNode.style.top = opts.top + 'px';
}
+24 -2
View File
@@ -17,7 +17,7 @@ export class VerticalScrollbar extends AbstractScrollbar {
lazyRender: options.lazyRender,
host: host,
scrollbarState: new ScrollbarState(
(options.verticalHasArrows ? options.arrowSize : 0),
(options.verticalHasArrows ? options.verticalScrollbarSize : 0),
(options.vertical === ScrollbarVisibility.HIDDEN ? 0 : options.verticalScrollbarSize),
0,
scrollDimensions.height,
@@ -31,7 +31,24 @@ export class VerticalScrollbar extends AbstractScrollbar {
});
if (options.verticalHasArrows) {
throw new Error('horizontalHasArrows is not supported in xterm.js');
const arrowSize = options.verticalScrollbarSize;
const arrowDelta = 0;
this._createArrow({
className: 'scra xterm-arrow-up',
top: arrowDelta,
left: arrowDelta,
bgWidth: options.verticalScrollbarSize,
bgHeight: arrowSize,
handleActivate: () => this._arrowScroll(-arrowSize)
});
this._createArrow({
className: 'scra xterm-arrow-down',
bottom: arrowDelta,
left: arrowDelta,
bgWidth: options.verticalScrollbarSize,
bgHeight: arrowSize,
handleActivate: () => this._arrowScroll(arrowSize)
});
}
this._createSlider(0, Math.floor((options.verticalScrollbarSize - options.verticalSliderSize) / 2), options.verticalSliderSize, undefined);
@@ -76,6 +93,11 @@ export class VerticalScrollbar extends AbstractScrollbar {
target.scrollTop = scrollPosition;
}
private _arrowScroll(delta: number): void {
const currentPosition = this._scrollable.getCurrentScrollPosition();
this._scrollable.setScrollPositionNow({ scrollTop: currentPosition.scrollTop + delta });
}
public updateOptions(options: IScrollableElementResolvedOptions): void {
this.updateScrollbarSize(options.vertical === ScrollbarVisibility.HIDDEN ? 0 : options.verticalScrollbarSize);
this._scrollbarState.setOppositeScrollbarSize(0);
+1
View File
@@ -31,6 +31,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
logLevel: 'info',
logger: null,
scrollback: 1000,
scrollbar: {},
scrollOnEraseInDisplay: false,
scrollOnUserInput: true,
scrollSensitivity: 1,
+5
View File
@@ -266,6 +266,7 @@ export interface ITerminalOptions {
wordSeparator?: string;
overviewRuler?: IOverviewRulerOptions;
quirks?: ITerminalQuirks;
scrollbar?: IScrollbarOptions;
scrollOnEraseInDisplay?: boolean;
vtExtensions?: IVtExtensions;
@@ -309,6 +310,10 @@ export interface ITerminalQuirks {
allowSetCursorBlink?: boolean;
}
export interface IScrollbarOptions {
showArrows?: boolean;
}
export interface IVtExtensions {
kittyKeyboard?: boolean;
kittySgrBoldFaintControl?: boolean;
+16
View File
@@ -274,6 +274,11 @@ declare module '@xterm/xterm' {
*/
scrollSensitivity?: number;
/**
* Options for configuring the scrollbar.
*/
scrollbar?: IScrollbarOptions;
/**
* The duration to smoothly scroll between the origin and the target in
* milliseconds. Set to 0 to disable smooth scrolling and scroll instantly.
@@ -722,6 +727,17 @@ declare module '@xterm/xterm' {
showBottomBorder?: boolean;
}
/**
* Options for configuring the scrollbar.
*/
export interface IScrollbarOptions {
/**
* Whether to show arrows at the top and bottom of the scrollbar. Defaults
* to false.
*/
showArrows?: boolean;
}
/**
* Enable various window manipulation and report features
* (`CSI Ps ; Ps ; Ps t`).