monaco-editor-core
Version:
A browser based code editor
449 lines (448 loc) • 19.5 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { $, append, createStyleSheet, EventHelper, getWindow, isHTMLElement } from '../../dom.js';
import { DomEmitter } from '../../event.js';
import { EventType, Gesture } from '../../touch.js';
import { Delayer } from '../../../common/async.js';
import { memoize } from '../../../common/decorators.js';
import { Emitter } from '../../../common/event.js';
import { Disposable, DisposableStore, toDisposable } from '../../../common/lifecycle.js';
import { isMacintosh } from '../../../common/platform.js';
import './sash.css';
/**
* Allow the sashes to be visible at runtime.
* @remark Use for development purposes only.
*/
const DEBUG = false;
export var OrthogonalEdge;
(function (OrthogonalEdge) {
OrthogonalEdge["North"] = "north";
OrthogonalEdge["South"] = "south";
OrthogonalEdge["East"] = "east";
OrthogonalEdge["West"] = "west";
})(OrthogonalEdge || (OrthogonalEdge = {}));
let globalSize = 4;
const onDidChangeGlobalSize = new Emitter();
let globalHoverDelay = 300;
const onDidChangeHoverDelay = new Emitter();
class MouseEventFactory {
constructor(el) {
this.el = el;
this.disposables = new DisposableStore();
}
get onPointerMove() {
return this.disposables.add(new DomEmitter(getWindow(this.el), 'mousemove')).event;
}
get onPointerUp() {
return this.disposables.add(new DomEmitter(getWindow(this.el), 'mouseup')).event;
}
dispose() {
this.disposables.dispose();
}
}
__decorate([
memoize
], MouseEventFactory.prototype, "onPointerMove", null);
__decorate([
memoize
], MouseEventFactory.prototype, "onPointerUp", null);
class GestureEventFactory {
get onPointerMove() {
return this.disposables.add(new DomEmitter(this.el, EventType.Change)).event;
}
get onPointerUp() {
return this.disposables.add(new DomEmitter(this.el, EventType.End)).event;
}
constructor(el) {
this.el = el;
this.disposables = new DisposableStore();
}
dispose() {
this.disposables.dispose();
}
}
__decorate([
memoize
], GestureEventFactory.prototype, "onPointerMove", null);
__decorate([
memoize
], GestureEventFactory.prototype, "onPointerUp", null);
class OrthogonalPointerEventFactory {
get onPointerMove() {
return this.factory.onPointerMove;
}
get onPointerUp() {
return this.factory.onPointerUp;
}
constructor(factory) {
this.factory = factory;
}
dispose() {
// noop
}
}
__decorate([
memoize
], OrthogonalPointerEventFactory.prototype, "onPointerMove", null);
__decorate([
memoize
], OrthogonalPointerEventFactory.prototype, "onPointerUp", null);
const PointerEventsDisabledCssClass = 'pointer-events-disabled';
/**
* The {@link Sash} is the UI component which allows the user to resize other
* components. It's usually an invisible horizontal or vertical line which, when
* hovered, becomes highlighted and can be dragged along the perpendicular dimension
* to its direction.
*
* Features:
* - Touch event handling
* - Corner sash support
* - Hover with different mouse cursor support
* - Configurable hover size
* - Linked sash support, for 2x2 corner sashes
*/
export class Sash extends Disposable {
get state() { return this._state; }
get orthogonalStartSash() { return this._orthogonalStartSash; }
get orthogonalEndSash() { return this._orthogonalEndSash; }
/**
* The state of a sash defines whether it can be interacted with by the user
* as well as what mouse cursor to use, when hovered.
*/
set state(state) {
if (this._state === state) {
return;
}
this.el.classList.toggle('disabled', state === 0 /* SashState.Disabled */);
this.el.classList.toggle('minimum', state === 1 /* SashState.AtMinimum */);
this.el.classList.toggle('maximum', state === 2 /* SashState.AtMaximum */);
this._state = state;
this.onDidEnablementChange.fire(state);
}
/**
* A reference to another sash, perpendicular to this one, which
* aligns at the start of this one. A corner sash will be created
* automatically at that location.
*
* The start of a horizontal sash is its left-most position.
* The start of a vertical sash is its top-most position.
*/
set orthogonalStartSash(sash) {
if (this._orthogonalStartSash === sash) {
return;
}
this.orthogonalStartDragHandleDisposables.clear();
this.orthogonalStartSashDisposables.clear();
if (sash) {
const onChange = (state) => {
this.orthogonalStartDragHandleDisposables.clear();
if (state !== 0 /* SashState.Disabled */) {
this._orthogonalStartDragHandle = append(this.el, $('.orthogonal-drag-handle.start'));
this.orthogonalStartDragHandleDisposables.add(toDisposable(() => this._orthogonalStartDragHandle.remove()));
this.orthogonalStartDragHandleDisposables.add(new DomEmitter(this._orthogonalStartDragHandle, 'mouseenter')).event(() => Sash.onMouseEnter(sash), undefined, this.orthogonalStartDragHandleDisposables);
this.orthogonalStartDragHandleDisposables.add(new DomEmitter(this._orthogonalStartDragHandle, 'mouseleave')).event(() => Sash.onMouseLeave(sash), undefined, this.orthogonalStartDragHandleDisposables);
}
};
this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange.event(onChange, this));
onChange(sash.state);
}
this._orthogonalStartSash = sash;
}
/**
* A reference to another sash, perpendicular to this one, which
* aligns at the end of this one. A corner sash will be created
* automatically at that location.
*
* The end of a horizontal sash is its right-most position.
* The end of a vertical sash is its bottom-most position.
*/
set orthogonalEndSash(sash) {
if (this._orthogonalEndSash === sash) {
return;
}
this.orthogonalEndDragHandleDisposables.clear();
this.orthogonalEndSashDisposables.clear();
if (sash) {
const onChange = (state) => {
this.orthogonalEndDragHandleDisposables.clear();
if (state !== 0 /* SashState.Disabled */) {
this._orthogonalEndDragHandle = append(this.el, $('.orthogonal-drag-handle.end'));
this.orthogonalEndDragHandleDisposables.add(toDisposable(() => this._orthogonalEndDragHandle.remove()));
this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalEndDragHandle, 'mouseenter')).event(() => Sash.onMouseEnter(sash), undefined, this.orthogonalEndDragHandleDisposables);
this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalEndDragHandle, 'mouseleave')).event(() => Sash.onMouseLeave(sash), undefined, this.orthogonalEndDragHandleDisposables);
}
};
this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange.event(onChange, this));
onChange(sash.state);
}
this._orthogonalEndSash = sash;
}
constructor(container, layoutProvider, options) {
super();
this.hoverDelay = globalHoverDelay;
this.hoverDelayer = this._register(new Delayer(this.hoverDelay));
this._state = 3 /* SashState.Enabled */;
this.onDidEnablementChange = this._register(new Emitter());
this._onDidStart = this._register(new Emitter());
this._onDidChange = this._register(new Emitter());
this._onDidReset = this._register(new Emitter());
this._onDidEnd = this._register(new Emitter());
this.orthogonalStartSashDisposables = this._register(new DisposableStore());
this.orthogonalStartDragHandleDisposables = this._register(new DisposableStore());
this.orthogonalEndSashDisposables = this._register(new DisposableStore());
this.orthogonalEndDragHandleDisposables = this._register(new DisposableStore());
/**
* An event which fires whenever the user starts dragging this sash.
*/
this.onDidStart = this._onDidStart.event;
/**
* An event which fires whenever the user moves the mouse while
* dragging this sash.
*/
this.onDidChange = this._onDidChange.event;
/**
* An event which fires whenever the user double clicks this sash.
*/
this.onDidReset = this._onDidReset.event;
/**
* An event which fires whenever the user stops dragging this sash.
*/
this.onDidEnd = this._onDidEnd.event;
/**
* A linked sash will be forwarded the same user interactions and events
* so it moves exactly the same way as this sash.
*
* Useful in 2x2 grids. Not meant for widespread usage.
*/
this.linkedSash = undefined;
this.el = append(container, $('.monaco-sash'));
if (options.orthogonalEdge) {
this.el.classList.add(`orthogonal-edge-${options.orthogonalEdge}`);
}
if (isMacintosh) {
this.el.classList.add('mac');
}
const onMouseDown = this._register(new DomEmitter(this.el, 'mousedown')).event;
this._register(onMouseDown(e => this.onPointerStart(e, new MouseEventFactory(container)), this));
const onMouseDoubleClick = this._register(new DomEmitter(this.el, 'dblclick')).event;
this._register(onMouseDoubleClick(this.onPointerDoublePress, this));
const onMouseEnter = this._register(new DomEmitter(this.el, 'mouseenter')).event;
this._register(onMouseEnter(() => Sash.onMouseEnter(this)));
const onMouseLeave = this._register(new DomEmitter(this.el, 'mouseleave')).event;
this._register(onMouseLeave(() => Sash.onMouseLeave(this)));
this._register(Gesture.addTarget(this.el));
const onTouchStart = this._register(new DomEmitter(this.el, EventType.Start)).event;
this._register(onTouchStart(e => this.onPointerStart(e, new GestureEventFactory(this.el)), this));
const onTap = this._register(new DomEmitter(this.el, EventType.Tap)).event;
let doubleTapTimeout = undefined;
this._register(onTap(event => {
if (doubleTapTimeout) {
clearTimeout(doubleTapTimeout);
doubleTapTimeout = undefined;
this.onPointerDoublePress(event);
return;
}
clearTimeout(doubleTapTimeout);
doubleTapTimeout = setTimeout(() => doubleTapTimeout = undefined, 250);
}, this));
if (typeof options.size === 'number') {
this.size = options.size;
if (options.orientation === 0 /* Orientation.VERTICAL */) {
this.el.style.width = `${this.size}px`;
}
else {
this.el.style.height = `${this.size}px`;
}
}
else {
this.size = globalSize;
this._register(onDidChangeGlobalSize.event(size => {
this.size = size;
this.layout();
}));
}
this._register(onDidChangeHoverDelay.event(delay => this.hoverDelay = delay));
this.layoutProvider = layoutProvider;
this.orthogonalStartSash = options.orthogonalStartSash;
this.orthogonalEndSash = options.orthogonalEndSash;
this.orientation = options.orientation || 0 /* Orientation.VERTICAL */;
if (this.orientation === 1 /* Orientation.HORIZONTAL */) {
this.el.classList.add('horizontal');
this.el.classList.remove('vertical');
}
else {
this.el.classList.remove('horizontal');
this.el.classList.add('vertical');
}
this.el.classList.toggle('debug', DEBUG);
this.layout();
}
onPointerStart(event, pointerEventFactory) {
EventHelper.stop(event);
let isMultisashResize = false;
if (!event.__orthogonalSashEvent) {
const orthogonalSash = this.getOrthogonalSash(event);
if (orthogonalSash) {
isMultisashResize = true;
event.__orthogonalSashEvent = true;
orthogonalSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory));
}
}
if (this.linkedSash && !event.__linkedSashEvent) {
event.__linkedSashEvent = true;
this.linkedSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory));
}
if (!this.state) {
return;
}
const iframes = this.el.ownerDocument.getElementsByTagName('iframe');
for (const iframe of iframes) {
iframe.classList.add(PointerEventsDisabledCssClass); // disable mouse events on iframes as long as we drag the sash
}
const startX = event.pageX;
const startY = event.pageY;
const altKey = event.altKey;
const startEvent = { startX, currentX: startX, startY, currentY: startY, altKey };
this.el.classList.add('active');
this._onDidStart.fire(startEvent);
// fix https://github.com/microsoft/vscode/issues/21675
const style = createStyleSheet(this.el);
const updateStyle = () => {
let cursor = '';
if (isMultisashResize) {
cursor = 'all-scroll';
}
else if (this.orientation === 1 /* Orientation.HORIZONTAL */) {
if (this.state === 1 /* SashState.AtMinimum */) {
cursor = 's-resize';
}
else if (this.state === 2 /* SashState.AtMaximum */) {
cursor = 'n-resize';
}
else {
cursor = isMacintosh ? 'row-resize' : 'ns-resize';
}
}
else {
if (this.state === 1 /* SashState.AtMinimum */) {
cursor = 'e-resize';
}
else if (this.state === 2 /* SashState.AtMaximum */) {
cursor = 'w-resize';
}
else {
cursor = isMacintosh ? 'col-resize' : 'ew-resize';
}
}
style.textContent = `* { cursor: ${cursor} !important; }`;
};
const disposables = new DisposableStore();
updateStyle();
if (!isMultisashResize) {
this.onDidEnablementChange.event(updateStyle, null, disposables);
}
const onPointerMove = (e) => {
EventHelper.stop(e, false);
const event = { startX, currentX: e.pageX, startY, currentY: e.pageY, altKey };
this._onDidChange.fire(event);
};
const onPointerUp = (e) => {
EventHelper.stop(e, false);
style.remove();
this.el.classList.remove('active');
this._onDidEnd.fire();
disposables.dispose();
for (const iframe of iframes) {
iframe.classList.remove(PointerEventsDisabledCssClass);
}
};
pointerEventFactory.onPointerMove(onPointerMove, null, disposables);
pointerEventFactory.onPointerUp(onPointerUp, null, disposables);
disposables.add(pointerEventFactory);
}
onPointerDoublePress(e) {
const orthogonalSash = this.getOrthogonalSash(e);
if (orthogonalSash) {
orthogonalSash._onDidReset.fire();
}
if (this.linkedSash) {
this.linkedSash._onDidReset.fire();
}
this._onDidReset.fire();
}
static onMouseEnter(sash, fromLinkedSash = false) {
if (sash.el.classList.contains('active')) {
sash.hoverDelayer.cancel();
sash.el.classList.add('hover');
}
else {
sash.hoverDelayer.trigger(() => sash.el.classList.add('hover'), sash.hoverDelay).then(undefined, () => { });
}
if (!fromLinkedSash && sash.linkedSash) {
Sash.onMouseEnter(sash.linkedSash, true);
}
}
static onMouseLeave(sash, fromLinkedSash = false) {
sash.hoverDelayer.cancel();
sash.el.classList.remove('hover');
if (!fromLinkedSash && sash.linkedSash) {
Sash.onMouseLeave(sash.linkedSash, true);
}
}
/**
* Forcefully stop any user interactions with this sash.
* Useful when hiding a parent component, while the user is still
* interacting with the sash.
*/
clearSashHoverState() {
Sash.onMouseLeave(this);
}
/**
* Layout the sash. The sash will size and position itself
* based on its provided {@link ISashLayoutProvider layout provider}.
*/
layout() {
if (this.orientation === 0 /* Orientation.VERTICAL */) {
const verticalProvider = this.layoutProvider;
this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (this.size / 2) + 'px';
if (verticalProvider.getVerticalSashTop) {
this.el.style.top = verticalProvider.getVerticalSashTop(this) + 'px';
}
if (verticalProvider.getVerticalSashHeight) {
this.el.style.height = verticalProvider.getVerticalSashHeight(this) + 'px';
}
}
else {
const horizontalProvider = this.layoutProvider;
this.el.style.top = horizontalProvider.getHorizontalSashTop(this) - (this.size / 2) + 'px';
if (horizontalProvider.getHorizontalSashLeft) {
this.el.style.left = horizontalProvider.getHorizontalSashLeft(this) + 'px';
}
if (horizontalProvider.getHorizontalSashWidth) {
this.el.style.width = horizontalProvider.getHorizontalSashWidth(this) + 'px';
}
}
}
getOrthogonalSash(e) {
const target = e.initialTarget ?? e.target;
if (!target || !(isHTMLElement(target))) {
return undefined;
}
if (target.classList.contains('orthogonal-drag-handle')) {
return target.classList.contains('start') ? this.orthogonalStartSash : this.orthogonalEndSash;
}
return undefined;
}
dispose() {
super.dispose();
this.el.remove();
}
}