UNPKG

monaco-editor-core

Version:

A browser based code editor

449 lines (448 loc) • 19.5 kB
/*--------------------------------------------------------------------------------------------- * 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(); } }