UNPKG

8.85 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2022 Ericsson and others.
4//
5// This program and the accompanying materials are made available under the
6// terms of the Eclipse Public License v. 2.0 which is available at
7// http://www.eclipse.org/legal/epl-2.0.
8//
9// This Source Code may also be made available under the following Secondary
10// Licenses when the conditions for such availability set forth in the Eclipse
11// Public License v. 2.0 are satisfied: GNU General Public License, version 2
12// with the GNU Classpath Exception which is available at
13// https://www.gnu.org/software/classpath/license.html.
14//
15// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16// *****************************************************************************
17var HoverService_1;
18Object.defineProperty(exports, "__esModule", { value: true });
19exports.HoverService = exports.HoverPosition = void 0;
20const tslib_1 = require("tslib");
21const inversify_1 = require("inversify");
22const common_1 = require("../common");
23const browser_1 = require("./browser");
24const markdown_renderer_1 = require("./markdown-rendering/markdown-renderer");
25const preferences_1 = require("./preferences");
26require("../../src/browser/style/hover-service.css");
27var HoverPosition;
28(function (HoverPosition) {
29 function invertIfNecessary(position, target, host, totalWidth, totalHeight) {
30 if (position === 'left') {
31 if (target.left - host.width - 5 < 0) {
32 return 'right';
33 }
34 }
35 else if (position === 'right') {
36 if (target.right + host.width + 5 > totalWidth) {
37 return 'left';
38 }
39 }
40 else if (position === 'top') {
41 if (target.top - host.height - 5 < 0) {
42 return 'bottom';
43 }
44 }
45 else if (position === 'bottom') {
46 if (target.bottom + host.height + 5 > totalHeight) {
47 return 'top';
48 }
49 }
50 return position;
51 }
52 HoverPosition.invertIfNecessary = invertIfNecessary;
53})(HoverPosition = exports.HoverPosition || (exports.HoverPosition = {}));
54let HoverService = HoverService_1 = class HoverService {
55 constructor() {
56 this.lastHidHover = Date.now();
57 this.disposeOnHide = new common_1.DisposableCollection();
58 }
59 get markdownRenderer() {
60 this._markdownRenderer || (this._markdownRenderer = this.markdownRendererFactory());
61 return this._markdownRenderer;
62 }
63 get hoverHost() {
64 if (!this._hoverHost) {
65 this._hoverHost = document.createElement('div');
66 this._hoverHost.classList.add(HoverService_1.hostClassName);
67 this._hoverHost.style.position = 'absolute';
68 }
69 return this._hoverHost;
70 }
71 requestHover(request) {
72 if (request.target !== this.hoverTarget) {
73 this.cancelHover();
74 this.pendingTimeout = (0, common_1.disposableTimeout)(() => this.renderHover(request), this.getHoverDelay());
75 }
76 }
77 getHoverDelay() {
78 return Date.now() - this.lastHidHover < 200
79 ? 0
80 : this.preferences.get('workbench.hover.delay', common_1.isOSX ? 1500 : 500);
81 }
82 async renderHover(request) {
83 const host = this.hoverHost;
84 let firstChild;
85 const { target, content, position, cssClasses } = request;
86 if (cssClasses) {
87 host.classList.add(...cssClasses);
88 }
89 this.hoverTarget = target;
90 if (content instanceof HTMLElement) {
91 host.appendChild(content);
92 firstChild = content;
93 }
94 else if (typeof content === 'string') {
95 host.textContent = content;
96 }
97 else {
98 const renderedContent = this.markdownRenderer.render(content);
99 this.disposeOnHide.push(renderedContent);
100 host.appendChild(renderedContent.element);
101 firstChild = renderedContent.element;
102 }
103 // browsers might insert linebreaks when the hover appears at the edge of the window
104 // resetting the position prevents that
105 host.style.left = '0px';
106 host.style.top = '0px';
107 document.body.append(host);
108 if (request.visualPreview) {
109 // If just a string is being rendered use the size of the outer box
110 const width = firstChild ? firstChild.offsetWidth : this.hoverHost.offsetWidth;
111 const visualPreview = request.visualPreview(width);
112 if (visualPreview) {
113 host.appendChild(visualPreview);
114 }
115 }
116 await (0, browser_1.animationFrame)(); // Allow the browser to size the host
117 const updatedPosition = this.setHostPosition(target, host, position);
118 this.disposeOnHide.push({
119 dispose: () => {
120 this.lastHidHover = Date.now();
121 host.classList.remove(updatedPosition);
122 if (cssClasses) {
123 host.classList.remove(...cssClasses);
124 }
125 }
126 });
127 this.listenForMouseOut();
128 }
129 setHostPosition(target, host, position) {
130 const targetDimensions = target.getBoundingClientRect();
131 const hostDimensions = host.getBoundingClientRect();
132 const documentWidth = document.body.getBoundingClientRect().width;
133 // document.body.getBoundingClientRect().height doesn't work as expected
134 // scrollHeight will always be accurate here: https://stackoverflow.com/a/44077777
135 const documentHeight = document.documentElement.scrollHeight;
136 position = HoverPosition.invertIfNecessary(position, targetDimensions, hostDimensions, documentWidth, documentHeight);
137 if (position === 'top' || position === 'bottom') {
138 const targetMiddleWidth = targetDimensions.left + (targetDimensions.width / 2);
139 const middleAlignment = targetMiddleWidth - (hostDimensions.width / 2);
140 const furthestRight = Math.min(documentWidth - hostDimensions.width, middleAlignment);
141 const left = Math.max(0, furthestRight);
142 const top = position === 'top'
143 ? targetDimensions.top - hostDimensions.height - 5
144 : targetDimensions.bottom + 5;
145 host.style.setProperty('--theia-hover-before-position', `${targetMiddleWidth - left - 5}px`);
146 host.style.top = `${top}px`;
147 host.style.left = `${left}px`;
148 }
149 else {
150 const targetMiddleHeight = targetDimensions.top + (targetDimensions.height / 2);
151 const middleAlignment = targetMiddleHeight - (hostDimensions.height / 2);
152 const furthestTop = Math.min(documentHeight - hostDimensions.height, middleAlignment);
153 const top = Math.max(0, furthestTop);
154 const left = position === 'left'
155 ? targetDimensions.left - hostDimensions.width - 5
156 : targetDimensions.right + 5;
157 host.style.setProperty('--theia-hover-before-position', `${targetMiddleHeight - top - 5}px`);
158 host.style.left = `${left}px`;
159 host.style.top = `${top}px`;
160 }
161 host.classList.add(position);
162 return position;
163 }
164 listenForMouseOut() {
165 const handleMouseMove = (e) => {
166 var _a;
167 if (e.target instanceof Node && !this.hoverHost.contains(e.target) && !((_a = this.hoverTarget) === null || _a === void 0 ? void 0 : _a.contains(e.target))) {
168 this.cancelHover();
169 }
170 };
171 document.addEventListener('mousemove', handleMouseMove);
172 this.disposeOnHide.push({ dispose: () => document.removeEventListener('mousemove', handleMouseMove) });
173 }
174 cancelHover() {
175 var _a;
176 (_a = this.pendingTimeout) === null || _a === void 0 ? void 0 : _a.dispose();
177 this.unRenderHover();
178 this.disposeOnHide.dispose();
179 this.hoverTarget = undefined;
180 }
181 unRenderHover() {
182 this.hoverHost.remove();
183 this.hoverHost.replaceChildren();
184 }
185};
186HoverService.hostClassName = 'theia-hover';
187HoverService.styleSheetId = 'theia-hover-style';
188(0, tslib_1.__decorate)([
189 (0, inversify_1.inject)(preferences_1.PreferenceService),
190 (0, tslib_1.__metadata)("design:type", Object)
191], HoverService.prototype, "preferences", void 0);
192(0, tslib_1.__decorate)([
193 (0, inversify_1.inject)(markdown_renderer_1.MarkdownRendererFactory),
194 (0, tslib_1.__metadata)("design:type", Function)
195], HoverService.prototype, "markdownRendererFactory", void 0);
196HoverService = HoverService_1 = (0, tslib_1.__decorate)([
197 (0, inversify_1.injectable)()
198], HoverService);
199exports.HoverService = HoverService;
200//# sourceMappingURL=hover-service.js.map
\No newline at end of file