UNPKG

monaco-editor-core

Version:
407 lines (393 loc) • 18.4 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { BrowserFeatures } from '../../canIUse.js'; import * as DOM from '../../dom.js'; import { createStyleSheet } from '../../domStylesheets.js'; import { Disposable, DisposableStore, toDisposable } from '../../../common/lifecycle.js'; import { layout2d } from '../../../common/layout.js'; import * as platform from '../../../common/platform.js'; import './contextview.css'; export function isAnchor(obj) { const anchor = obj; return !!anchor && typeof anchor.x === 'number' && typeof anchor.y === 'number'; } export const CONTEXT_VIEW_MENU_MOTION_CLASS = 'context-view-menu-motion'; export const CONTEXT_VIEW_MENU_MOTION_CLOSING_CLASS = 'context-view-menu-motion-closing'; export const CONTEXT_VIEW_MENU_MOTION_CLOSE_ANIMATION_DURATION = 150; export const CONTEXT_VIEW_MENU_MOTION_ANCESTOR_CLASSES = ['modern-ui', 'monaco-enable-motion']; export const CONTEXT_VIEW_CLOSE_ANIMATION_DURATION_VARIABLE = '--vscode-context-view-close-animation-duration'; export const CONTEXT_VIEW_MENU_MOTION_SHADOW_VARIABLE = '--vscode-context-view-menu-motion-shadow'; const CONTEXT_VIEW_MENU_MOTION_CLOSE_START_OPACITY_VARIABLE = '--vscode-context-view-menu-motion-close-start-opacity'; const CONTEXT_VIEW_MENU_MOTION_CLOSE_START_TRANSFORM_VARIABLE = '--vscode-context-view-menu-motion-close-start-transform'; const CONTEXT_VIEW_MENU_MOTION_OPEN_DURATION_MS = 250; const CONTEXT_VIEW_MENU_MOTION_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)'; export const contextViewMenuCloseAnimation = { className: CONTEXT_VIEW_MENU_MOTION_CLOSING_CLASS, duration: CONTEXT_VIEW_MENU_MOTION_CLOSE_ANIMATION_DURATION, requiredAncestorClasses: CONTEXT_VIEW_MENU_MOTION_ANCESTOR_CLASSES, }; function getContextViewMenuMotionCss(enabledSelectorPrefix) { return /* css */ ` ${enabledSelectorPrefix} .context-view.${CONTEXT_VIEW_MENU_MOTION_CLASS} { animation: none; box-shadow: none; overflow: visible; } ${enabledSelectorPrefix} .context-view.${CONTEXT_VIEW_MENU_MOTION_CLASS} > .monaco-scrollable-element { animation: context-view-menu-motion-open ${CONTEXT_VIEW_MENU_MOTION_OPEN_DURATION_MS}ms ${CONTEXT_VIEW_MENU_MOTION_EASING} backwards; box-shadow: var(${CONTEXT_VIEW_MENU_MOTION_SHADOW_VARIABLE}); transform-origin: top left; will-change: opacity; } ${enabledSelectorPrefix} .context-view.${CONTEXT_VIEW_MENU_MOTION_CLASS}.right > .monaco-scrollable-element { transform-origin: top right; } ${enabledSelectorPrefix} .context-view.${CONTEXT_VIEW_MENU_MOTION_CLASS}.top > .monaco-scrollable-element { transform-origin: bottom left; } ${enabledSelectorPrefix} .context-view.${CONTEXT_VIEW_MENU_MOTION_CLASS}.top.right > .monaco-scrollable-element { transform-origin: bottom right; } ${enabledSelectorPrefix} .context-view.${CONTEXT_VIEW_MENU_MOTION_CLASS}.${CONTEXT_VIEW_MENU_MOTION_CLOSING_CLASS} > .monaco-scrollable-element { animation: context-view-menu-motion-close var(${CONTEXT_VIEW_CLOSE_ANIMATION_DURATION_VARIABLE}) ${CONTEXT_VIEW_MENU_MOTION_EASING} both; pointer-events: none; } @keyframes context-view-menu-motion-open { 0% { opacity: 0; transform: scale(0.97); } 100% { opacity: 1; transform: scale(1); } } @keyframes context-view-menu-motion-close { 0% { opacity: var(${CONTEXT_VIEW_MENU_MOTION_CLOSE_START_OPACITY_VARIABLE}, 1); transform: var(${CONTEXT_VIEW_MENU_MOTION_CLOSE_START_TRANSFORM_VARIABLE}, scale(1)); } 100% { opacity: 0; transform: scale(0.99); } }`; } let contextViewMenuMotionStyleSheet; function ensureContextViewMenuMotionStyleSheet() { if (!contextViewMenuMotionStyleSheet) { contextViewMenuMotionStyleSheet = createStyleSheet(undefined, style => { style.textContent = getContextViewMenuMotionCss('.modern-ui.monaco-enable-motion'); }); } } export function getAnchorRect(anchor) { // Get the element's position and size (to anchor the view) if (DOM.isHTMLElement(anchor)) { const elementPosition = DOM.getDomNodePagePosition(anchor); // In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element // e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level. // Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Size Multiplier: 1.5 const zoom = DOM.getDomNodeZoomLevel(anchor); return { top: elementPosition.top * zoom, left: elementPosition.left * zoom, width: elementPosition.width * zoom, height: elementPosition.height * zoom }; } else if (isAnchor(anchor)) { return { top: anchor.y, left: anchor.x, width: anchor.width || 1, height: anchor.height || 2 }; } else { return { top: anchor.posy, left: anchor.posx, // We are about to position the context view where the mouse // cursor is. To prevent the view being exactly under the mouse // when showing and thus potentially triggering an action within, // we treat the mouse location like a small sized block element. width: 2, height: 2 }; } } export class ContextView extends Disposable { static { this.BUBBLE_UP_EVENTS = ['click', 'keydown', 'focus', 'blur']; } static { this.BUBBLE_DOWN_EVENTS = ['click']; } constructor(container, domPosition) { super(); this.container = null; this.useFixedPosition = false; this.useShadowDOM = false; this.delegate = null; this.toDisposeOnClean = Disposable.None; this.toDisposeOnSetContainer = Disposable.None; this.shadowRoot = null; this.shadowRootHostElement = null; ensureContextViewMenuMotionStyleSheet(); this.view = DOM.$('.context-view'); DOM.hide(this.view); this.setContainer(container, domPosition); this._register(toDisposable(() => this.setContainer(null, 1 /* ContextViewDOMPosition.ABSOLUTE */))); } setContainer(container, domPosition) { this.useFixedPosition = domPosition !== 1 /* ContextViewDOMPosition.ABSOLUTE */; const usedShadowDOM = this.useShadowDOM; this.useShadowDOM = domPosition === 3 /* ContextViewDOMPosition.FIXED_SHADOW */; if (container === this.container && usedShadowDOM === this.useShadowDOM) { return; // container is the same and no shadow DOM usage has changed } if (this.container) { this.toDisposeOnSetContainer.dispose(); this.view.remove(); if (this.shadowRoot) { this.shadowRoot = null; this.shadowRootHostElement?.remove(); this.shadowRootHostElement = null; } this.container = null; } if (container) { this.container = container; if (this.useShadowDOM) { this.shadowRootHostElement = DOM.$('.shadow-root-host'); Object.assign(this.shadowRootHostElement.style, { position: 'fixed', top: '0', left: '0', width: '0', height: '0' }); this.container.appendChild(this.shadowRootHostElement); this.shadowRoot = this.shadowRootHostElement.attachShadow({ mode: 'open' }); const style = document.createElement('style'); style.textContent = SHADOW_ROOT_CSS; this.shadowRoot.appendChild(style); this.shadowRoot.appendChild(this.view); this.shadowRoot.appendChild(DOM.$('slot')); } else { this.container.appendChild(this.view); } const toDisposeOnSetContainer = new DisposableStore(); ContextView.BUBBLE_UP_EVENTS.forEach(event => { toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container, event, e => { this.onDOMEvent(e, false); })); }); ContextView.BUBBLE_DOWN_EVENTS.forEach(event => { toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container, event, e => { this.onDOMEvent(e, true); }, true)); }); this.toDisposeOnSetContainer = toDisposeOnSetContainer; } } show(delegate) { this.completeHideAnimation(); if (this.isVisible()) { this.hide(undefined, true); } // Show static box DOM.clearNode(this.view); this.view.className = 'context-view monaco-component'; this.view.style.top = '0px'; this.view.style.left = '0px'; const zIndex = `${2575 + (delegate.layer ?? 0)}`; this.view.style.zIndex = zIndex; if (this.shadowRootHostElement) { this.shadowRootHostElement.style.zIndex = zIndex; } this.view.style.position = this.useFixedPosition ? 'fixed' : 'absolute'; DOM.show(this.view); // Render content this.toDisposeOnClean = delegate.render(this.view) || Disposable.None; // Set active delegate this.delegate = delegate; // Layout this.doLayout(); // Focus this.delegate.focus?.(); } getViewElement() { return this.view; } layout() { if (!this.isVisible()) { return; } if (this.delegate.canRelayout === false && !(platform.isIOS && BrowserFeatures.pointerEvents)) { this.hide(); return; } this.delegate?.layout?.(); this.doLayout(); } doLayout() { // Check that we still have a delegate - this.delegate.layout may have hidden if (!this.isVisible()) { return; } // Get anchor const anchor = getAnchorRect(this.delegate.getAnchor()); const containerWindow = this.container ? DOM.getWindow(this.container) : DOM.getActiveWindow(); const viewport = { top: containerWindow.pageYOffset, left: containerWindow.pageXOffset, width: containerWindow.innerWidth, height: containerWindow.innerHeight }; this.view.classList.toggle('fixed', this.useFixedPosition); this.view.style.top = '0px'; this.view.style.left = '0px'; const positioningOrigin = DOM.getDomNodePagePosition(this.view); const view = { width: DOM.getTotalWidth(this.view), height: DOM.getTotalHeight(this.view) }; const anchorPosition = this.delegate.anchorPosition; const anchorAlignment = this.delegate.anchorAlignment; const anchorAxisAlignment = this.delegate.anchorAxisAlignment; const layoutResult = layout2d(viewport, view, anchor, { anchorAlignment, anchorPosition, anchorAxisAlignment }); const { top, left } = layoutResult; this.view.classList.remove('top', 'bottom', 'left', 'right'); this.view.classList.add(layoutResult.anchorPosition === 0 /* AnchorPosition.BELOW */ ? 'bottom' : 'top'); this.view.classList.add(layoutResult.anchorAlignment === 0 /* AnchorAlignment.LEFT */ ? 'left' : 'right'); this.view.style.top = `${top - positioningOrigin.top}px`; this.view.style.left = `${left - positioningOrigin.left}px`; this.view.style.width = 'initial'; } hide(data, skipAnimation = false) { if (this.hidingContextView) { if (skipAnimation) { this.completeHideAnimation(); } return; } const delegate = this.delegate; this.delegate = null; if (!delegate) { return; } const toDispose = this.toDisposeOnClean; this.toDisposeOnClean = Disposable.None; delegate.onHide?.(data); const closeAnimation = delegate.closeAnimation; if (!skipAnimation && closeAnimation && closeAnimation.duration > 0 && this.hasRequiredAncestorClasses(closeAnimation.requiredAncestorClasses)) { this.view.style.setProperty(CONTEXT_VIEW_CLOSE_ANIMATION_DURATION_VARIABLE, `${closeAnimation.duration}ms`); this.prepareMenuCloseAnimation(); this.view.inert = true; this.view.classList.add(closeAnimation.className); const timeout = setTimeout(() => this.completeHideAnimation(), closeAnimation.duration); this.hidingContextView = { disposable: toDisposable(() => clearTimeout(timeout)), toDispose, className: closeAnimation.className }; return; } toDispose.dispose(); DOM.hide(this.view); } isVisible() { return !!this.delegate; } completeHideAnimation() { const hidingContextView = this.hidingContextView; if (!hidingContextView) { return; } this.hidingContextView = undefined; hidingContextView.disposable.dispose(); this.view.classList.remove(hidingContextView.className); this.view.style.removeProperty(CONTEXT_VIEW_CLOSE_ANIMATION_DURATION_VARIABLE); this.view.style.removeProperty(CONTEXT_VIEW_MENU_MOTION_CLOSE_START_OPACITY_VARIABLE); this.view.style.removeProperty(CONTEXT_VIEW_MENU_MOTION_CLOSE_START_TRANSFORM_VARIABLE); hidingContextView.toDispose.dispose(); DOM.hide(this.view); this.view.inert = false; } prepareMenuCloseAnimation() { if (!this.view.classList.contains(CONTEXT_VIEW_MENU_MOTION_CLASS)) { return; } const surface = Array.from(this.view.children).find(element => DOM.isHTMLElement(element) && element.classList.contains('monaco-scrollable-element')); if (!DOM.isHTMLElement(surface)) { return; } const computedStyle = DOM.getWindow(surface).getComputedStyle(surface); this.view.style.setProperty(CONTEXT_VIEW_MENU_MOTION_CLOSE_START_OPACITY_VARIABLE, computedStyle.opacity); this.view.style.setProperty(CONTEXT_VIEW_MENU_MOTION_CLOSE_START_TRANSFORM_VARIABLE, computedStyle.transform); } hasRequiredAncestorClasses(classNames) { if (!classNames?.length) { return true; } for (let candidate = this.view; candidate;) { const current = candidate; if (classNames.every(className => current.classList.contains(className))) { return true; } if (current.parentElement) { candidate = current.parentElement; } else { const root = current.getRootNode(); candidate = root instanceof ShadowRoot && DOM.isHTMLElement(root.host) ? root.host : null; } } return false; } onDOMEvent(e, onCapture) { if (this.delegate) { if (this.delegate.onDOMEvent) { this.delegate.onDOMEvent(e, DOM.getWindow(e).document.activeElement); } else if (onCapture && !DOM.isAncestor(e.target, this.container)) { this.hide(); } } } dispose() { this.hide(); this.completeHideAnimation(); super.dispose(); } } const SHADOW_ROOT_CSS = /* css */ ` :host { all: initial; /* 1st rule so subsequent properties are reset. */ } .codicon[class*='codicon-'] { font: normal normal normal 16px/1 codicon; display: inline-block; text-decoration: none; text-rendering: auto; text-align: center; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; user-select: none; -webkit-user-select: none; -ms-user-select: none; } :host { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", system-ui, "Ubuntu", "Droid Sans", sans-serif; } :host-context(.mac) { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } :host-context(.mac:lang(zh-Hans)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } :host-context(.mac:lang(zh-Hant)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } :host-context(.mac:lang(ja)) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; } :host-context(.mac:lang(ko)) { font-family: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Nanum Gothic", "AppleGothic", sans-serif; } :host-context(.windows) { font-family: "Segoe WPC", "Segoe UI", sans-serif; } :host-context(.windows:lang(zh-Hans)) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } :host-context(.windows:lang(zh-Hant)) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } :host-context(.windows:lang(ja)) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; } :host-context(.windows:lang(ko)) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } :host-context(.linux) { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; } :host-context(.linux:lang(zh-Hans)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } :host-context(.linux:lang(zh-Hant)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; } :host-context(.linux:lang(ja)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } :host-context(.linux:lang(ko)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } ${getContextViewMenuMotionCss(':host-context(.modern-ui.monaco-enable-motion)')} `; //# sourceMappingURL=contextview.js.map