|
@@ -0,0 +1,129 @@
|
|
|
+// Copyright (c) Jupyter Development Team.
|
|
|
+// Distributed under the terms of the Modified BSD License.
|
|
|
+
|
|
|
+import {
|
|
|
+ CodeEditor
|
|
|
+} from '../codeeditor';
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * The class name added to all hover boxes.
|
|
|
+ */
|
|
|
+const HOVERBOX_CLASS = 'jp-HoverBox';
|
|
|
+
|
|
|
+/**
|
|
|
+ * The class name added to a hovering node that is scrolled out of view.
|
|
|
+ */
|
|
|
+const OUTOFVIEW_CLASS = 'jp-mod-outofview';
|
|
|
+
|
|
|
+/**
|
|
|
+ * The minimum height of a hover box.
|
|
|
+ */
|
|
|
+const MIN_HEIGHT = 20;
|
|
|
+
|
|
|
+/**
|
|
|
+ * The maximum height of a hover box.
|
|
|
+ *
|
|
|
+ * #### Notes
|
|
|
+ * This value is only used if a CSS max-height attribute is not set for the
|
|
|
+ * hover box. It is a fallback value.
|
|
|
+ */
|
|
|
+const MAX_HEIGHT = 200;
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * A namespace for `HoverBox` members.
|
|
|
+ */
|
|
|
+export
|
|
|
+namespace HoverBox {
|
|
|
+ /**
|
|
|
+ * Options for setting the geometry of a hovering node and its anchor node.
|
|
|
+ */
|
|
|
+ export
|
|
|
+ interface IOptions {
|
|
|
+ anchor: HTMLElement;
|
|
|
+ anchorPoint: number;
|
|
|
+ charWidth: number;
|
|
|
+ coords: CodeEditor.ICoordinate;
|
|
|
+ cursor: { start: number; end: number; };
|
|
|
+ lineHeight: number;
|
|
|
+ node: HTMLElement;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set the visible dimensions of a hovering box anchored to a scrollable node.
|
|
|
+ */
|
|
|
+ export
|
|
|
+ function setGeometry(options: IOptions): void {
|
|
|
+ let {
|
|
|
+ anchor, anchorPoint, charWidth, coords, cursor, lineHeight, node
|
|
|
+ } = options;
|
|
|
+
|
|
|
+ // Add hover box class if it does not exist.
|
|
|
+ node.classList.add(HOVERBOX_CLASS);
|
|
|
+
|
|
|
+ // Clear any previously set max-height.
|
|
|
+ node.style.maxHeight = '';
|
|
|
+
|
|
|
+ // Clear any programmatically set margin-top.
|
|
|
+ node.style.marginTop = '';
|
|
|
+
|
|
|
+ // Make sure the node is visible.
|
|
|
+ node.classList.remove(OUTOFVIEW_CLASS);
|
|
|
+
|
|
|
+ // Always use the original coordinates to calculate box position.
|
|
|
+ let style = window.getComputedStyle(node);
|
|
|
+ let innerHeight = window.innerHeight;
|
|
|
+ let scrollDelta = anchorPoint - anchor.scrollTop;
|
|
|
+ let spaceAbove = coords.top + scrollDelta;
|
|
|
+ let spaceBelow = innerHeight - coords.bottom - scrollDelta;
|
|
|
+ let marginTop = parseInt(style.marginTop, 10) || 0;
|
|
|
+ let maxHeight = parseInt(style.maxHeight, 10) || MAX_HEIGHT;
|
|
|
+ let minHeight = parseInt(style.minHeight, 10) || MIN_HEIGHT;
|
|
|
+ let anchorRect = anchor.getBoundingClientRect();
|
|
|
+ let top: number;
|
|
|
+
|
|
|
+ // If the whole box fits below or if there is more space below, then
|
|
|
+ // rendering the box below the text being typed is privileged so that
|
|
|
+ // the code above is not obscured.
|
|
|
+ let renderBelow = spaceBelow >= maxHeight || spaceBelow >= spaceAbove;
|
|
|
+ if (renderBelow) {
|
|
|
+ maxHeight = Math.min(spaceBelow - marginTop, maxHeight);
|
|
|
+ } else {
|
|
|
+ maxHeight = Math.min(spaceAbove, maxHeight);
|
|
|
+ // If the box renders above the text, its top margin is irrelevant.
|
|
|
+ node.style.marginTop = '0px';
|
|
|
+ }
|
|
|
+ node.style.maxHeight = `${maxHeight}px`;
|
|
|
+
|
|
|
+ // Make sure the box ought to be visible.
|
|
|
+ let withinBounds = maxHeight > minHeight &&
|
|
|
+ spaceBelow >= lineHeight &&
|
|
|
+ spaceAbove >= anchorRect.top;
|
|
|
+ if (!withinBounds) {
|
|
|
+ node.classList.add(OUTOFVIEW_CLASS);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let borderLeftWidth = style.borderLeftWidth;
|
|
|
+ let left = coords.left + (parseInt(borderLeftWidth, 10) || 0);
|
|
|
+ let { start, end } = cursor;
|
|
|
+ let nodeRect = node.getBoundingClientRect();
|
|
|
+
|
|
|
+ // Position the box vertically.
|
|
|
+ top = renderBelow ? innerHeight - spaceBelow : spaceAbove - nodeRect.height;
|
|
|
+ node.style.top = `${Math.floor(top)}px`;
|
|
|
+
|
|
|
+ // Move box to the start of the blob being completed.
|
|
|
+ left -= charWidth * (end - start);
|
|
|
+ node.style.left = `${Math.ceil(left)}px`;
|
|
|
+ node.style.width = 'auto';
|
|
|
+
|
|
|
+ // Expand the menu width by the scrollbar size, if present.
|
|
|
+ if (node.scrollHeight >= maxHeight) {
|
|
|
+ node.style.width = `${2 * node.offsetWidth - node.clientWidth}`;
|
|
|
+ node.scrollTop = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|