e920191367e71bb35ef26448c4533849daafe49341bc9b845768ee7d00339df490db4d630f44c46aacc5f4eb2cbf4cee62d38164245b44cc107ace3a43209a 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { BrowserFeatures } from '../../canIUse.js';
  6. import * as DOM from '../../dom.js';
  7. import { Disposable, DisposableStore, toDisposable } from '../../../common/lifecycle.js';
  8. import * as platform from '../../../common/platform.js';
  9. import { Range } from '../../../common/range.js';
  10. import './contextview.css';
  11. export var LayoutAnchorMode;
  12. (function (LayoutAnchorMode) {
  13. LayoutAnchorMode[LayoutAnchorMode["AVOID"] = 0] = "AVOID";
  14. LayoutAnchorMode[LayoutAnchorMode["ALIGN"] = 1] = "ALIGN";
  15. })(LayoutAnchorMode || (LayoutAnchorMode = {}));
  16. /**
  17. * Lays out a one dimensional view next to an anchor in a viewport.
  18. *
  19. * @returns The view offset within the viewport.
  20. */
  21. export function layout(viewportSize, viewSize, anchor) {
  22. const layoutAfterAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset : anchor.offset + anchor.size;
  23. const layoutBeforeAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset + anchor.size : anchor.offset;
  24. if (anchor.position === 0 /* LayoutAnchorPosition.Before */) {
  25. if (viewSize <= viewportSize - layoutAfterAnchorBoundary) {
  26. return layoutAfterAnchorBoundary; // happy case, lay it out after the anchor
  27. }
  28. if (viewSize <= layoutBeforeAnchorBoundary) {
  29. return layoutBeforeAnchorBoundary - viewSize; // ok case, lay it out before the anchor
  30. }
  31. return Math.max(viewportSize - viewSize, 0); // sad case, lay it over the anchor
  32. }
  33. else {
  34. if (viewSize <= layoutBeforeAnchorBoundary) {
  35. return layoutBeforeAnchorBoundary - viewSize; // happy case, lay it out before the anchor
  36. }
  37. if (viewSize <= viewportSize - layoutAfterAnchorBoundary) {
  38. return layoutAfterAnchorBoundary; // ok case, lay it out after the anchor
  39. }
  40. return 0; // sad case, lay it over the anchor
  41. }
  42. }
  43. export class ContextView extends Disposable {
  44. constructor(container, domPosition) {
  45. super();
  46. this.container = null;
  47. this.delegate = null;
  48. this.toDisposeOnClean = Disposable.None;
  49. this.toDisposeOnSetContainer = Disposable.None;
  50. this.shadowRoot = null;
  51. this.shadowRootHostElement = null;
  52. this.view = DOM.$('.context-view');
  53. this.useFixedPosition = false;
  54. this.useShadowDOM = false;
  55. DOM.hide(this.view);
  56. this.setContainer(container, domPosition);
  57. this._register(toDisposable(() => this.setContainer(null, 1 /* ContextViewDOMPosition.ABSOLUTE */)));
  58. }
  59. setContainer(container, domPosition) {
  60. var _a;
  61. if (this.container) {
  62. this.toDisposeOnSetContainer.dispose();
  63. if (this.shadowRoot) {
  64. this.shadowRoot.removeChild(this.view);
  65. this.shadowRoot = null;
  66. (_a = this.shadowRootHostElement) === null || _a === void 0 ? void 0 : _a.remove();
  67. this.shadowRootHostElement = null;
  68. }
  69. else {
  70. this.container.removeChild(this.view);
  71. }
  72. this.container = null;
  73. }
  74. if (container) {
  75. this.container = container;
  76. this.useFixedPosition = domPosition !== 1 /* ContextViewDOMPosition.ABSOLUTE */;
  77. this.useShadowDOM = domPosition === 3 /* ContextViewDOMPosition.FIXED_SHADOW */;
  78. if (this.useShadowDOM) {
  79. this.shadowRootHostElement = DOM.$('.shadow-root-host');
  80. this.container.appendChild(this.shadowRootHostElement);
  81. this.shadowRoot = this.shadowRootHostElement.attachShadow({ mode: 'open' });
  82. const style = document.createElement('style');
  83. style.textContent = SHADOW_ROOT_CSS;
  84. this.shadowRoot.appendChild(style);
  85. this.shadowRoot.appendChild(this.view);
  86. this.shadowRoot.appendChild(DOM.$('slot'));
  87. }
  88. else {
  89. this.container.appendChild(this.view);
  90. }
  91. const toDisposeOnSetContainer = new DisposableStore();
  92. ContextView.BUBBLE_UP_EVENTS.forEach(event => {
  93. toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container, event, (e) => {
  94. this.onDOMEvent(e, false);
  95. }));
  96. });
  97. ContextView.BUBBLE_DOWN_EVENTS.forEach(event => {
  98. toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container, event, (e) => {
  99. this.onDOMEvent(e, true);
  100. }, true));
  101. });
  102. this.toDisposeOnSetContainer = toDisposeOnSetContainer;
  103. }
  104. }
  105. show(delegate) {
  106. var _a, _b;
  107. if (this.isVisible()) {
  108. this.hide();
  109. }
  110. // Show static box
  111. DOM.clearNode(this.view);
  112. this.view.className = 'context-view';
  113. this.view.style.top = '0px';
  114. this.view.style.left = '0px';
  115. this.view.style.zIndex = '2575';
  116. this.view.style.position = this.useFixedPosition ? 'fixed' : 'absolute';
  117. DOM.show(this.view);
  118. // Render content
  119. this.toDisposeOnClean = delegate.render(this.view) || Disposable.None;
  120. // Set active delegate
  121. this.delegate = delegate;
  122. // Layout
  123. this.doLayout();
  124. // Focus
  125. (_b = (_a = this.delegate).focus) === null || _b === void 0 ? void 0 : _b.call(_a);
  126. }
  127. getViewElement() {
  128. return this.view;
  129. }
  130. layout() {
  131. if (!this.isVisible()) {
  132. return;
  133. }
  134. if (this.delegate.canRelayout === false && !(platform.isIOS && BrowserFeatures.pointerEvents)) {
  135. this.hide();
  136. return;
  137. }
  138. if (this.delegate.layout) {
  139. this.delegate.layout();
  140. }
  141. this.doLayout();
  142. }
  143. doLayout() {
  144. // Check that we still have a delegate - this.delegate.layout may have hidden
  145. if (!this.isVisible()) {
  146. return;
  147. }
  148. // Get anchor
  149. const anchor = this.delegate.getAnchor();
  150. // Compute around
  151. let around;
  152. // Get the element's position and size (to anchor the view)
  153. if (DOM.isHTMLElement(anchor)) {
  154. const elementPosition = DOM.getDomNodePagePosition(anchor);
  155. // In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element
  156. // e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level.
  157. // Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Size Multiplier: 1.5
  158. const zoom = DOM.getDomNodeZoomLevel(anchor);
  159. around = {
  160. top: elementPosition.top * zoom,
  161. left: elementPosition.left * zoom,
  162. width: elementPosition.width * zoom,
  163. height: elementPosition.height * zoom
  164. };
  165. }
  166. else {
  167. around = {
  168. top: anchor.y,
  169. left: anchor.x,
  170. width: anchor.width || 1,
  171. height: anchor.height || 2
  172. };
  173. }
  174. const viewSizeWidth = DOM.getTotalWidth(this.view);
  175. const viewSizeHeight = DOM.getTotalHeight(this.view);
  176. const anchorPosition = this.delegate.anchorPosition || 0 /* AnchorPosition.BELOW */;
  177. const anchorAlignment = this.delegate.anchorAlignment || 0 /* AnchorAlignment.LEFT */;
  178. const anchorAxisAlignment = this.delegate.anchorAxisAlignment || 0 /* AnchorAxisAlignment.VERTICAL */;
  179. let top;
  180. let left;
  181. if (anchorAxisAlignment === 0 /* AnchorAxisAlignment.VERTICAL */) {
  182. const verticalAnchor = { offset: around.top - window.pageYOffset, size: around.height, position: anchorPosition === 0 /* AnchorPosition.BELOW */ ? 0 /* LayoutAnchorPosition.Before */ : 1 /* LayoutAnchorPosition.After */ };
  183. const horizontalAnchor = { offset: around.left, size: around.width, position: anchorAlignment === 0 /* AnchorAlignment.LEFT */ ? 0 /* LayoutAnchorPosition.Before */ : 1 /* LayoutAnchorPosition.After */, mode: LayoutAnchorMode.ALIGN };
  184. top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset;
  185. // if view intersects vertically with anchor, we must avoid the anchor
  186. if (Range.intersects({ start: top, end: top + viewSizeHeight }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) {
  187. horizontalAnchor.mode = LayoutAnchorMode.AVOID;
  188. }
  189. left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor);
  190. }
  191. else {
  192. const horizontalAnchor = { offset: around.left, size: around.width, position: anchorAlignment === 0 /* AnchorAlignment.LEFT */ ? 0 /* LayoutAnchorPosition.Before */ : 1 /* LayoutAnchorPosition.After */ };
  193. const verticalAnchor = { offset: around.top, size: around.height, position: anchorPosition === 0 /* AnchorPosition.BELOW */ ? 0 /* LayoutAnchorPosition.Before */ : 1 /* LayoutAnchorPosition.After */, mode: LayoutAnchorMode.ALIGN };
  194. left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor);
  195. // if view intersects horizontally with anchor, we must avoid the anchor
  196. if (Range.intersects({ start: left, end: left + viewSizeWidth }, { start: horizontalAnchor.offset, end: horizontalAnchor.offset + horizontalAnchor.size })) {
  197. verticalAnchor.mode = LayoutAnchorMode.AVOID;
  198. }
  199. top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset;
  200. }
  201. this.view.classList.remove('top', 'bottom', 'left', 'right');
  202. this.view.classList.add(anchorPosition === 0 /* AnchorPosition.BELOW */ ? 'bottom' : 'top');
  203. this.view.classList.add(anchorAlignment === 0 /* AnchorAlignment.LEFT */ ? 'left' : 'right');
  204. this.view.classList.toggle('fixed', this.useFixedPosition);
  205. const containerPosition = DOM.getDomNodePagePosition(this.container);
  206. this.view.style.top = `${top - (this.useFixedPosition ? DOM.getDomNodePagePosition(this.view).top : containerPosition.top)}px`;
  207. this.view.style.left = `${left - (this.useFixedPosition ? DOM.getDomNodePagePosition(this.view).left : containerPosition.left)}px`;
  208. this.view.style.width = 'initial';
  209. }
  210. hide(data) {
  211. const delegate = this.delegate;
  212. this.delegate = null;
  213. if (delegate === null || delegate === void 0 ? void 0 : delegate.onHide) {
  214. delegate.onHide(data);
  215. }
  216. this.toDisposeOnClean.dispose();
  217. DOM.hide(this.view);
  218. }
  219. isVisible() {
  220. return !!this.delegate;
  221. }
  222. onDOMEvent(e, onCapture) {
  223. if (this.delegate) {
  224. if (this.delegate.onDOMEvent) {
  225. this.delegate.onDOMEvent(e, document.activeElement);
  226. }
  227. else if (onCapture && !DOM.isAncestor(e.target, this.container)) {
  228. this.hide();
  229. }
  230. }
  231. }
  232. dispose() {
  233. this.hide();
  234. super.dispose();
  235. }
  236. }
  237. ContextView.BUBBLE_UP_EVENTS = ['click', 'keydown', 'focus', 'blur'];
  238. ContextView.BUBBLE_DOWN_EVENTS = ['click'];
  239. const SHADOW_ROOT_CSS = /* css */ `
  240. :host {
  241. all: initial; /* 1st rule so subsequent properties are reset. */
  242. }
  243. @font-face {
  244. font-family: "codicon";
  245. font-display: block;
  246. src: url("./codicon.ttf?5d4d76ab2ce5108968ad644d591a16a6") format("truetype");
  247. }
  248. .codicon[class*='codicon-'] {
  249. font: normal normal normal 16px/1 codicon;
  250. display: inline-block;
  251. text-decoration: none;
  252. text-rendering: auto;
  253. text-align: center;
  254. -webkit-font-smoothing: antialiased;
  255. -moz-osx-font-smoothing: grayscale;
  256. user-select: none;
  257. -webkit-user-select: none;
  258. -ms-user-select: none;
  259. }
  260. :host {
  261. font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", system-ui, "Ubuntu", "Droid Sans", sans-serif;
  262. }
  263. :host-context(.mac) { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
  264. :host-context(.mac:lang(zh-Hans)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
  265. :host-context(.mac:lang(zh-Hant)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
  266. :host-context(.mac:lang(ja)) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
  267. :host-context(.mac:lang(ko)) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
  268. :host-context(.windows) { font-family: "Segoe WPC", "Segoe UI", sans-serif; }
  269. :host-context(.windows:lang(zh-Hans)) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
  270. :host-context(.windows:lang(zh-Hant)) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
  271. :host-context(.windows:lang(ja)) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
  272. :host-context(.windows:lang(ko)) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
  273. :host-context(.linux) { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
  274. :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; }
  275. :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; }
  276. :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; }
  277. :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; }
  278. `;