diff --git a/core/block_svg.ts b/core/block_svg.ts index ca2ad181a..54a5bfaaa 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -599,7 +599,7 @@ export class BlockSvg const menuOptions = this.generateContextMenu(); if (menuOptions && menuOptions.length) { - ContextMenu.show(e, menuOptions, this.RTL); + ContextMenu.show(e, menuOptions, this.RTL, this.workspace); ContextMenu.setCurrentBlock(this); } } diff --git a/core/comments/rendered_workspace_comment.ts b/core/comments/rendered_workspace_comment.ts index b5818895f..1234d6ef8 100644 --- a/core/comments/rendered_workspace_comment.ts +++ b/core/comments/rendered_workspace_comment.ts @@ -274,7 +274,7 @@ export class RenderedWorkspaceComment ContextMenuRegistry.ScopeType.COMMENT, {comment: this}, ); - contextMenu.show(e, menuOptions, this.workspace.RTL); + contextMenu.show(e, menuOptions, this.workspace.RTL, this.workspace); } /** Snap this comment to the nearest grid point. */ diff --git a/core/contextmenu.ts b/core/contextmenu.ts index 939477b3c..e469c4335 100644 --- a/core/contextmenu.ts +++ b/core/contextmenu.ts @@ -23,6 +23,7 @@ import {Rect} from './utils/rect.js'; import * as serializationBlocks from './serialization/blocks.js'; import * as svgMath from './utils/svg_math.js'; import * as WidgetDiv from './widgetdiv.js'; +import type {WorkspaceSvg} from './workspace_svg.js'; import * as Xml from './xml.js'; import * as common from './common.js'; @@ -62,13 +63,15 @@ let menu_: Menu | null = null; * @param e Mouse event. * @param options Array of menu options. * @param rtl True if RTL, false if LTR. + * @param workspace The workspace associated with the context menu, if any. */ export function show( e: PointerEvent, options: (ContextMenuOption | LegacyContextMenuOption)[], rtl: boolean, + workspace?: WorkspaceSvg, ) { - WidgetDiv.show(dummyOwner, rtl, dispose); + WidgetDiv.show(dummyOwner, rtl, dispose, workspace); if (!options.length) { hide(); return; diff --git a/core/field_input.ts b/core/field_input.ts index 5c26f42b3..4f36ac92d 100644 --- a/core/field_input.ts +++ b/core/field_input.ts @@ -372,7 +372,12 @@ export abstract class FieldInput extends Field< if (!block) { throw new UnattachedFieldError(); } - WidgetDiv.show(this, block.RTL, this.widgetDispose_.bind(this)); + WidgetDiv.show( + this, + block.RTL, + this.widgetDispose_.bind(this), + this.workspace_, + ); this.htmlInput_ = this.widgetCreate_() as HTMLInputElement; this.isBeingEdited_ = true; this.valueWhenEditorWasOpened_ = this.value_; @@ -546,17 +551,17 @@ export abstract class FieldInput extends Field< */ protected onHtmlInputKeyDown_(e: KeyboardEvent) { if (e.key === 'Enter') { - WidgetDiv.hide(); + WidgetDiv.hideIfOwner(this); dropDownDiv.hideWithoutAnimation(); } else if (e.key === 'Escape') { this.setValue( this.htmlInput_!.getAttribute('data-untyped-default-value'), false, ); - WidgetDiv.hide(); + WidgetDiv.hideIfOwner(this); dropDownDiv.hideWithoutAnimation(); } else if (e.key === 'Tab') { - WidgetDiv.hide(); + WidgetDiv.hideIfOwner(this); dropDownDiv.hideWithoutAnimation(); (this.sourceBlock_ as BlockSvg).tab(this, !e.shiftKey); e.preventDefault(); diff --git a/core/flyout_horizontal.ts b/core/flyout_horizontal.ts index 9c9490b22..14c9b00b9 100644 --- a/core/flyout_horizontal.ts +++ b/core/flyout_horizontal.ts @@ -240,7 +240,7 @@ export class HorizontalFlyout extends Flyout { this.workspace_.scrollbar?.setX(pos); // When the flyout moves from a wheel event, hide WidgetDiv and // dropDownDiv. - WidgetDiv.hide(); + WidgetDiv.hideIfOwnerIsInWorkspace(this.workspace_); dropDownDiv.hideWithoutAnimation(); } // Don't scroll the page. diff --git a/core/flyout_vertical.ts b/core/flyout_vertical.ts index c9ce4f659..6aa6e3370 100644 --- a/core/flyout_vertical.ts +++ b/core/flyout_vertical.ts @@ -209,7 +209,7 @@ export class VerticalFlyout extends Flyout { this.workspace_.scrollbar?.setY(pos); // When the flyout moves from a wheel event, hide WidgetDiv and // dropDownDiv. - WidgetDiv.hide(); + WidgetDiv.hideIfOwnerIsInWorkspace(this.workspace_); dropDownDiv.hideWithoutAnimation(); } // Don't scroll the page. diff --git a/core/widgetdiv.ts b/core/widgetdiv.ts index e03cf0b25..9f58bb1c5 100644 --- a/core/widgetdiv.ts +++ b/core/widgetdiv.ts @@ -8,7 +8,7 @@ import * as common from './common.js'; import * as dom from './utils/dom.js'; -import type {Field} from './field.js'; +import {Field} from './field.js'; import type {Rect} from './utils/rect.js'; import type {Size} from './utils/size.js'; import type {WorkspaceSvg} from './workspace_svg.js'; @@ -16,6 +16,9 @@ import type {WorkspaceSvg} from './workspace_svg.js'; /** The object currently using this container. */ let owner: unknown = null; +/** The workspace associated with the owner currently using this container. */ +let ownerWorkspace: WorkspaceSvg | null = null; + /** Optional cleanup function set by whichever object uses the widget. */ let dispose: (() => void) | null = null; @@ -76,8 +79,14 @@ export function createDom() { * @param rtl Right-to-left (true) or left-to-right (false). * @param newDispose Optional cleanup function to be run when the widget is * closed. + * @param workspace The workspace associated with the widget owner. */ -export function show(newOwner: unknown, rtl: boolean, newDispose: () => void) { +export function show( + newOwner: unknown, + rtl: boolean, + newDispose: () => void, + workspace?: WorkspaceSvg | null, +) { hide(); owner = newOwner; dispose = newDispose; @@ -85,9 +94,16 @@ export function show(newOwner: unknown, rtl: boolean, newDispose: () => void) { if (!div) return; div.style.direction = rtl ? 'rtl' : 'ltr'; div.style.display = 'block'; - const mainWorkspace = common.getMainWorkspace() as WorkspaceSvg; - rendererClassName = mainWorkspace.getRenderer().getClassName(); - themeClassName = mainWorkspace.getTheme().getClassName(); + if (!workspace && newOwner instanceof Field) { + // For backward compatibility with plugin fields that do not provide a + // workspace to this function, attempt to derive it from the field. + workspace = (newOwner as Field).getSourceBlock()?.workspace as WorkspaceSvg; + } + ownerWorkspace = workspace ?? null; + const rendererWorkspace = + workspace ?? (common.getMainWorkspace() as WorkspaceSvg); + rendererClassName = rendererWorkspace.getRenderer().getClassName(); + themeClassName = rendererWorkspace.getTheme().getClassName(); if (rendererClassName) { dom.addClass(div, rendererClassName); } @@ -145,6 +161,19 @@ export function hideIfOwner(oldOwner: unknown) { hide(); } } + +/** + * Destroy the widget and hide the div if it is being used by an object in the + * specified workspace, or if it is used by an unknown workspace. + * + * @param oldOwnerWorkspace The workspace that was using this container. + */ +export function hideIfOwnerIsInWorkspace(oldOwnerWorkspace: WorkspaceSvg) { + if (ownerWorkspace === null || ownerWorkspace === oldOwnerWorkspace) { + hide(); + } +} + /** * Set the widget div's position and height. This function does nothing clever: * it will not ensure that your widget div ends up in the visible window. diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 14cc1101f..96d4b19a9 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -1686,7 +1686,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { this.configureContextMenu(menuOptions, e); } - ContextMenu.show(e, menuOptions, this.RTL); + ContextMenu.show(e, menuOptions, this.RTL, this); } /** @@ -2376,7 +2376,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { */ hideChaff(onlyClosePopups = false) { Tooltip.hide(); - WidgetDiv.hide(); + WidgetDiv.hideIfOwnerIsInWorkspace(this); dropDownDiv.hideWithoutAnimation(); this.hideComponents(onlyClosePopups);