diff --git a/core/dropdowndiv.ts b/core/dropdowndiv.ts index c90661c4ea7..cbbbca7f168 100644 --- a/core/dropdowndiv.ts +++ b/core/dropdowndiv.ts @@ -55,15 +55,15 @@ let animateOutTimer: ReturnType | null = null; /** Callback for when the drop-down is hidden. */ let onHide: Function | null = null; +/** A class name representing the current owner's workspace container. */ +const containerClassName = 'blocklyDropDownDiv'; + /** A class name representing the current owner's workspace renderer. */ let renderedClassName = ''; /** A class name representing the current owner's workspace theme. */ let themeClassName = ''; -/** The content element. */ -let div: HTMLDivElement; - /** The content element. */ let content: HTMLDivElement; @@ -107,17 +107,26 @@ export interface PositionMetrics { arrowVisible: boolean; } +/** + * Returns the HTML container for dropdown div. + * + * @returns The editor widget container. + */ +export function getDiv(): HTMLDivElement | null { + return document.querySelector('.' + containerClassName); +} + /** * Create and insert the DOM element for this div. * * @internal */ export function createDom() { - if (document.querySelector('.blocklyDropDownDiv')) { + if (getDiv()) { return; // Already created. } - div = document.createElement('div'); - div.className = 'blocklyDropDownDiv'; + const div = document.createElement('div'); + div.className = containerClassName; const parentDiv = common.getParentContainer() || document.body; parentDiv.appendChild(div); @@ -183,6 +192,7 @@ export function clearContent() { * @param borderColour Any CSS colour for the border. */ export function setColour(backgroundColour: string, borderColour: string) { + const div = getDiv()!; div.style.backgroundColor = backgroundColour; div.style.borderColor = borderColour; } @@ -338,6 +348,11 @@ export function show( ): boolean { owner = newOwner as Field; onHide = opt_onHide || null; + const div = getDiv(); + if (!div) { + return false; + } + // Set direction. div.style.direction = rtl ? 'rtl' : 'ltr'; @@ -401,7 +416,7 @@ const internal = { secondaryY: number, ): PositionMetrics { const boundsInfo = internal.getBoundsInfo(); - const divSize = style.getSize(div as Element); + const divSize = style.getSize(getDiv()! as Element); // Can we fit in-bounds below the target? if (primaryY + divSize.height < boundsInfo.bottom) { @@ -628,6 +643,11 @@ export function hideIfOwner( /** Hide the menu, triggering animation. */ export function hide() { + const div = getDiv(); + if (!div) { + return; + } + // Start the animation by setting the translation and fading out. // Reset to (initialX, initialY) - i.e., no translation. div.style.transform = 'translate(0, 0)'; @@ -650,6 +670,10 @@ export function hideWithoutAnimation() { if (animateOutTimer) { clearTimeout(animateOutTimer); } + const div = getDiv(); + if (!div) { + return; + } // Reset style properties in case this gets called directly // instead of hide() - see discussion on #2551. @@ -694,6 +718,11 @@ function positionInternal( secondaryX: number, secondaryY: number, ): boolean { + const div = getDiv(); + if (!div) { + return false; + } + const metrics = internal.getPositionMetrics( primaryX, primaryY, diff --git a/core/tooltip.ts b/core/tooltip.ts index 0478b91fd55..e6a5f3dc420 100644 --- a/core/tooltip.ts +++ b/core/tooltip.ts @@ -125,8 +125,8 @@ export const HOVER_MS = 750; */ export const MARGINS = 5; -/** The HTML container. Set once by createDom. */ -let containerDiv: HTMLDivElement | null = null; +/** A class name representing the HTML tooltip container. */ +const containerClassName = 'blocklyTooltipDiv'; /** * Returns the HTML tooltip container. @@ -134,7 +134,7 @@ let containerDiv: HTMLDivElement | null = null; * @returns The HTML tooltip container. */ export function getDiv(): HTMLDivElement | null { - return containerDiv; + return document.querySelector('.' + containerClassName); } /** @@ -184,12 +184,12 @@ function getTargetObject( * Create the tooltip div and inject it onto the page. */ export function createDom() { - if (document.querySelector('.blocklyTooltipDiv')) { + if (getDiv()) { return; // Already created. } // Create an HTML container for popup overlays (e.g. editor widgets). - containerDiv = document.createElement('div'); - containerDiv.className = 'blocklyTooltipDiv'; + const containerDiv = document.createElement('div'); + containerDiv.className = containerClassName; const container = common.getParentContainer() || document.body; container.appendChild(containerDiv); } @@ -339,6 +339,7 @@ export function dispose() { export function hide() { if (visible) { visible = false; + const containerDiv = getDiv(); if (containerDiv) { containerDiv.style.display = 'none'; } @@ -372,6 +373,7 @@ export function unblock() { /** Renders the tooltip content into the tooltip div. */ function renderContent() { + const containerDiv = getDiv(); if (!containerDiv || !element) { // This shouldn't happen, but if it does, we can't render. return; @@ -392,7 +394,7 @@ function renderDefaultContent() { for (let i = 0; i < lines.length; i++) { const div = document.createElement('div'); div.appendChild(document.createTextNode(lines[i])); - containerDiv!.appendChild(div); + getDiv()!.appendChild(div); } } @@ -404,21 +406,22 @@ function renderDefaultContent() { * @returns Coordinates at which the tooltip div should be placed. */ function getPosition(rtl: boolean): {x: number; y: number} { + const containerDiv = getDiv()!; // Position the tooltip just below the cursor. const windowWidth = document.documentElement.clientWidth; const windowHeight = document.documentElement.clientHeight; let anchorX = lastX; if (rtl) { - anchorX -= OFFSET_X + containerDiv!.offsetWidth; + anchorX -= OFFSET_X + containerDiv.offsetWidth; } else { anchorX += OFFSET_X; } let anchorY = lastY + OFFSET_Y; - if (anchorY + containerDiv!.offsetHeight > windowHeight + window.scrollY) { + if (anchorY + containerDiv.offsetHeight > windowHeight + window.scrollY) { // Falling off the bottom of the screen; shift the tooltip up. - anchorY -= containerDiv!.offsetHeight + 2 * OFFSET_Y; + anchorY -= containerDiv.offsetHeight + 2 * OFFSET_Y; } if (rtl) { @@ -426,12 +429,12 @@ function getPosition(rtl: boolean): {x: number; y: number} { anchorX = Math.max(MARGINS - window.scrollX, anchorX); } else { if ( - anchorX + containerDiv!.offsetWidth > + anchorX + containerDiv.offsetWidth > windowWidth + window.scrollX - 2 * MARGINS ) { // Falling off the right edge of the screen; // clamp the tooltip on the edge. - anchorX = windowWidth - containerDiv!.offsetWidth - 2 * MARGINS; + anchorX = windowWidth - containerDiv.offsetWidth - 2 * MARGINS; } } @@ -445,6 +448,7 @@ function show() { return; } poisonedElement = element; + const containerDiv = getDiv(); if (!containerDiv) { return; } diff --git a/core/widgetdiv.ts b/core/widgetdiv.ts index 2742fa3bf9b..aa3bb3b4665 100644 --- a/core/widgetdiv.ts +++ b/core/widgetdiv.ts @@ -19,22 +19,22 @@ let owner: unknown = null; /** Optional cleanup function set by whichever object uses the widget. */ let dispose: (() => void) | null = null; +/** A class name representing the current owner's workspace container. */ +const containerClassName = 'blocklyWidgetDiv'; + /** A class name representing the current owner's workspace renderer. */ let rendererClassName = ''; /** A class name representing the current owner's workspace theme. */ let themeClassName = ''; -/** The HTML container for popup overlays (e.g. editor widgets). */ -let containerDiv: HTMLDivElement | null; - /** * Returns the HTML container for editor widgets. * * @returns The editor widget container. */ export function getDiv(): HTMLDivElement | null { - return containerDiv; + return document.querySelector('.' + containerClassName); } /** @@ -44,19 +44,28 @@ export function getDiv(): HTMLDivElement | null { * @internal */ export function testOnly_setDiv(newDiv: HTMLDivElement | null) { - containerDiv = newDiv; + const div = getDiv(); + if (!div) { + return; + } + + if (newDiv === null) { + div.remove(); + } else { + div.replaceWith(newDiv); + } } /** * Create the widget div and inject it onto the page. */ export function createDom() { - if (document.querySelector('.blocklyWidgetDiv')) { + if (getDiv()) { return; // Already created. } - containerDiv = document.createElement('div') as HTMLDivElement; - containerDiv.className = 'blocklyWidgetDiv'; + const containerDiv = document.createElement('div') as HTMLDivElement; + containerDiv.className = containerClassName; const container = common.getParentContainer() || document.body; container.appendChild(containerDiv); } @@ -73,8 +82,10 @@ export function show(newOwner: unknown, rtl: boolean, newDispose: () => void) { hide(); owner = newOwner; dispose = newDispose; - const div = containerDiv; - if (!div) return; + const div = getDiv(); + if (!div) { + return; + } div.style.direction = rtl ? 'rtl' : 'ltr'; div.style.display = 'block'; const mainWorkspace = common.getMainWorkspace() as WorkspaceSvg; @@ -97,7 +108,7 @@ export function hide() { } owner = null; - const div = containerDiv; + const div = getDiv(); if (!div) return; div.style.display = 'none'; div.style.left = ''; @@ -146,9 +157,10 @@ export function hideIfOwner(oldOwner: unknown) { * @param height The height of the widget div (pixels). */ function positionInternal(x: number, y: number, height: number) { - containerDiv!.style.left = x + 'px'; - containerDiv!.style.top = y + 'px'; - containerDiv!.style.height = height + 'px'; + const div = getDiv()!; + div.style.left = x + 'px'; + div.style.top = y + 'px'; + div.style.height = height + 'px'; } /**