diff --git a/client/browser/src/shared/code-hosts/gitlab/codeHost.ts b/client/browser/src/shared/code-hosts/gitlab/codeHost.ts index e61e78245a8..13c582df094 100644 --- a/client/browser/src/shared/code-hosts/gitlab/codeHost.ts +++ b/client/browser/src/shared/code-hosts/gitlab/codeHost.ts @@ -7,7 +7,7 @@ import { NotificationType } from '@sourcegraph/shared/src/api/extension/extensio import { toAbsoluteBlobURL } from '@sourcegraph/shared/src/util/url' import { background } from '../../../browser-extension/web-extension-api/runtime' -import { CodeHost } from '../shared/codeHost' +import { CodeHost, OverlayPosition } from '../shared/codeHost' import { CodeView } from '../shared/codeViews' import { createNotificationClassNameGetter } from '../shared/getNotificationClassName' import { getSelectionsFromHash, observeSelectionsFromHash } from '../shared/util/selections' @@ -24,21 +24,26 @@ export function checkIsGitlab(): boolean { return !!document.head.querySelector('meta[content="GitLab"]') } -const adjustOverlayPosition: CodeHost['adjustOverlayPosition'] = ({ top, left }) => { +const adjustOverlayPosition: CodeHost['adjustOverlayPosition'] = args => { + const topOrBottom = 'top' in args ? 'top' : 'bottom' + let topOrBottomValue = 'top' in args ? args.top : args.bottom + const header = document.querySelector('header') if (header) { - top += header.getBoundingClientRect().height + topOrBottomValue += header.getBoundingClientRect().height } // When running GitLab from source, we also need to take into account // the debug header shown at the top of the page. const debugHeader = document.querySelector('#js-peek.development') if (debugHeader) { - top += debugHeader.getBoundingClientRect().height + topOrBottomValue += debugHeader.getBoundingClientRect().height } + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return { - top, - left, - } + [topOrBottom]: topOrBottomValue, + left: args.left, + } as OverlayPosition } export const getToolbarMount = (codeView: HTMLElement, pageKind?: GitLabPageKind): HTMLElement => { diff --git a/client/browser/src/shared/code-hosts/shared/codeHost.tsx b/client/browser/src/shared/code-hosts/shared/codeHost.tsx index 96d77f35f2b..5c8bdd647a5 100644 --- a/client/browser/src/shared/code-hosts/shared/codeHost.tsx +++ b/client/browser/src/shared/code-hosts/shared/codeHost.tsx @@ -136,10 +136,7 @@ import styles from './codeHost.module.scss' registerHighlightContributions() -export interface OverlayPosition { - top: number - left: number -} +export type OverlayPosition = { left: number } & ({ top: number } | { bottom: number }) export type ObserveMutations = ( target: Node, diff --git a/client/codeintellify/src/hoverifier.ts b/client/codeintellify/src/hoverifier.ts index 744d1213887..af9a40ff09e 100644 --- a/client/codeintellify/src/hoverifier.ts +++ b/client/codeintellify/src/hoverifier.ts @@ -272,7 +272,7 @@ interface InternalHoverifierState { hoverOrError?: typeof LOADING | (HoverAttachment & D) | null | ErrorLike /** The desired position of the hover overlay */ - hoverOverlayPosition?: { left: number; top: number } + hoverOverlayPosition?: { left: number } & ({ top: number } | { bottom: number }) /** The currently hovered token */ hoveredToken?: HoveredToken & C diff --git a/client/codeintellify/src/overlayPosition.test.ts b/client/codeintellify/src/overlayPosition.test.ts index 54f9e99b73f..dbb73263463 100644 --- a/client/codeintellify/src/overlayPosition.test.ts +++ b/client/codeintellify/src/overlayPosition.test.ts @@ -31,7 +31,7 @@ describe('overlay_position', () => { target: rectangle(128, 220, 60, 16), hoverOverlayElement: rectangle(28, 38, 350, 150), }), - { left: 100, top: 50 } + { left: 100, bottom: 400 } ) }) @@ -62,7 +62,7 @@ describe('overlay_position', () => { target: rectangle(128, 220, 60, 16), hoverOverlayElement: rectangle(28, -362, 350, 150), }), - { left: 100, top: 450 } + { left: 100, bottom: 2400 } ) }) @@ -96,7 +96,7 @@ describe('overlay_position', () => { target: rectangle(128, 220, 60, 16), hoverOverlayElement: rectangle(28, 38, 350, 150), }), - { left: 100, top: 50 } + { left: 100, bottom: 400 } ) }) @@ -127,7 +127,7 @@ describe('overlay_position', () => { target: rectangle(128, 220, 60, 16), hoverOverlayElement: rectangle(-172, -362, 350, 150), }), - { left: 300, top: 450 } + { left: 300, bottom: 0 } ) }) diff --git a/client/codeintellify/src/overlayPosition.ts b/client/codeintellify/src/overlayPosition.ts index 7308b7f74df..7666a9b0021 100644 --- a/client/codeintellify/src/overlayPosition.ts +++ b/client/codeintellify/src/overlayPosition.ts @@ -12,12 +12,7 @@ export interface CalculateOverlayPositionOptions { hoverOverlayElement: HasGetBoundingClientRect } -export interface CSSOffsets { - /** Offset from the left in pixel */ - left: number - /** Offset from the top in pixel */ - top: number -} +export type CSSOffsets = { left: number } & ({ top: number } | { bottom: number }) /** * Calculates the desired position of the hover overlay depending on the container, @@ -35,19 +30,21 @@ export const calculateOverlayPosition = ({ // If the relativeElement is scrolled horizontally, we need to account for the offset (if not scrollLeft will be 0) const relativeHoverOverlayLeft = targetBounds.left + relativeElement.scrollLeft - relativeElementBounds.left - let relativeHoverOverlayTop: number // Check if the top of the hover overlay would be outside of the relative element or the viewport if (targetBounds.top - hoverOverlayBounds.height < Math.max(relativeElementBounds.top, 0)) { // Position it below the target // If the relativeElement is scrolled, we need to account for the offset (if not scrollTop will be 0) - relativeHoverOverlayTop = targetBounds.bottom - relativeElementBounds.top + relativeElement.scrollTop - } else { - // Else position it above the target - // Caculate the offset from the top of the relativeElement content to the top of the target - // If the relativeElement is scrolled, we need to account for the offset (if not scrollTop will be 0) - const relativeTargetTop = targetBounds.top - relativeElementBounds.top + relativeElement.scrollTop - relativeHoverOverlayTop = relativeTargetTop - hoverOverlayBounds.height + return { + left: relativeHoverOverlayLeft, + top: targetBounds.bottom - relativeElementBounds.top + relativeElement.scrollTop, + } } - return { left: relativeHoverOverlayLeft, top: relativeHoverOverlayTop } + // Else position it above the target + // If the relativeElement is scrolled, we need to account for the offset (if not scrollTop will be 0) + return { + left: relativeHoverOverlayLeft, + bottom: + relativeElementBounds.height - (targetBounds.top - relativeElementBounds.top + relativeElement.scrollTop), + } } diff --git a/client/codeintellify/src/types.ts b/client/codeintellify/src/types.ts index 20466c81713..0cb03e3c663 100644 --- a/client/codeintellify/src/types.ts +++ b/client/codeintellify/src/types.ts @@ -14,7 +14,7 @@ export interface HoverOverlayProps { hoverOrError?: typeof LOADING | (HoverAttachment & D) | null | ErrorLike /** The position of the tooltip (assigned to `style`) */ - overlayPosition?: { left: number; top: number } + overlayPosition?: { left: number } & ({ top: number } | { bottom: number }) /** * The hovered token (position and word). diff --git a/client/shared/src/hover/HoverOverlay.tsx b/client/shared/src/hover/HoverOverlay.tsx index 6ba95731b92..5c9dffa3071 100644 --- a/client/shared/src/hover/HoverOverlay.tsx +++ b/client/shared/src/hover/HoverOverlay.tsx @@ -62,18 +62,24 @@ export interface HoverOverlayProps useBrandedLogo?: boolean } -const getOverlayStyle = (overlayPosition: HoverOverlayProps['overlayPosition']): CSSProperties => - overlayPosition - ? { - opacity: 1, - visibility: 'visible', - left: `${overlayPosition.left}px`, - top: `${overlayPosition.top}px`, - } - : { - opacity: 0, - visibility: 'hidden', - } +const getOverlayStyle = (overlayPosition: HoverOverlayProps['overlayPosition']): CSSProperties => { + if (!overlayPosition) { + return { + opacity: 0, + visibility: 'hidden', + } + } + + const topOrBottom = 'top' in overlayPosition ? 'top' : 'bottom' + const topOrBottomValue = 'top' in overlayPosition ? overlayPosition.top : overlayPosition.bottom + + return { + opacity: 1, + visibility: 'visible', + left: `${overlayPosition.left}px`, + [topOrBottom]: `${topOrBottomValue}px`, + } +} export const HoverOverlay: React.FunctionComponent = props => { const {