diff --git a/common/api/appui-react.api.md b/common/api/appui-react.api.md index ac2631dc49e..b9d5643662e 100644 --- a/common/api/appui-react.api.md +++ b/common/api/appui-react.api.md @@ -2266,7 +2266,7 @@ export class FrontstageDef { // (undocumented) get rightPanel(): StagePanelDef | undefined; // @internal (undocumented) - saveChildWindowSizeAndPosition(childWindowId: string, childWindow: Window): void; + saveChildWindowSizeAndPosition(childWindowId: string, childWindow: ChildWindow): void; setActiveContent(): Promise; setActiveView(newContent: ContentControl, oldContent?: ContentControl): void; setActiveViewFromViewport(viewport: ScreenViewport): boolean; diff --git a/common/changes/@itwin/appui-react/joeh-popoutSmallerOrLarger_2023-12-07-17-24.json b/common/changes/@itwin/appui-react/joeh-popoutSmallerOrLarger_2023-12-07-17-24.json new file mode 100644 index 00000000000..28d799049d1 --- /dev/null +++ b/common/changes/@itwin/appui-react/joeh-popoutSmallerOrLarger_2023-12-07-17-24.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/appui-react", + "comment": "Fixed bug where popouts would get incrementally smaller or larger each time opened", + "type": "none" + } + ], + "packageName": "@itwin/appui-react" +} \ No newline at end of file diff --git a/docs/changehistory/NextVersion.md b/docs/changehistory/NextVersion.md index 631954b9e5a..9240feb7a37 100644 --- a/docs/changehistory/NextVersion.md +++ b/docs/changehistory/NextVersion.md @@ -5,6 +5,7 @@ Table of contents: - [@itwin/appui-react](#itwinappui-react) - [Fixes](#fixes) - [Changes](#changes) + - [Fixes](#fixes) - [@itwin/components-react](#itwincomponents-react) - [Fixes](#fixes) - [Improvements](#improvements) @@ -21,6 +22,10 @@ Table of contents: - Promoted `FrameworkToolAdmin` to _beta_. #618 - Correctly handle `WidgetState.Unloaded` and keep widget content unmounted when widget is unloaded. #617 +### Fixes + +- Fixed popout widgets getting incrementally smaller or larger each time popped out. [#563](https://github.com/iTwin/appui/issues/563) + ## @itwin/components-react ### Fixes diff --git a/ui/appui-react/src/appui-react/childwindow/ChildWindowConfig.ts b/ui/appui-react/src/appui-react/childwindow/ChildWindowConfig.ts new file mode 100644 index 00000000000..d419e86f3bf --- /dev/null +++ b/ui/appui-react/src/appui-react/childwindow/ChildWindowConfig.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +/** + * An extension of a Window to be used for Popout Widgets. + * @internal + */ +export interface ChildWindow extends Window { + /** Width that child window wants to open to */ + expectedWidth?: number; + /** Height that child window wants to open to */ + expectedHeight?: number; + /** Difference between width child window actually opened to as compared to expected */ + deltaWidth?: number; + /** Difference between height child window actually opened to as compared to expected */ + deltaHeight?: number; + /** If outer size should be used for calculations instead of inner size */ + shouldUseOuterSized?: boolean; +} diff --git a/ui/appui-react/src/appui-react/childwindow/InternalChildWindowManager.tsx b/ui/appui-react/src/appui-react/childwindow/InternalChildWindowManager.tsx index ed3e360870b..c04c4b8cb22 100644 --- a/ui/appui-react/src/appui-react/childwindow/InternalChildWindowManager.tsx +++ b/ui/appui-react/src/appui-react/childwindow/InternalChildWindowManager.tsx @@ -25,6 +25,7 @@ import type { OpenChildWindowInfo, } from "../framework/FrameworkChildWindows"; import { createLayoutStore, NineZone } from "@itwin/appui-layout-react"; +import type { ChildWindow } from "./ChildWindowConfig"; const childHtml = ` @@ -141,12 +142,25 @@ export class InternalChildWindowManager implements FrameworkChildWindows { // istanbul ignore next: Used in `open` which is not tested. private renderChildWindowContents( - childWindow: Window, + childWindow: ChildWindow, childWindowId: string, content: React.ReactNode, title: string ) { childWindow.document.title = title; + if (childWindow.expectedHeight && childWindow.expectedWidth) { + childWindow.deltaWidth = + childWindow.expectedWidth - + (childWindow.shouldUseOuterSized + ? childWindow.outerWidth + : childWindow.innerWidth); + childWindow.deltaHeight = + childWindow.expectedHeight - + (childWindow.shouldUseOuterSized + ? childWindow.outerHeight + : childWindow.innerHeight); + } + const reactConnectionDiv = childWindow.document.getElementById("root"); if (reactConnectionDiv) { // set openChildWindows now so components can use it when they mount @@ -285,12 +299,19 @@ export class InternalChildWindowManager implements FrameworkChildWindows { location = this.adjustWidowLocation(location); const url = useDefaultPopoutUrl ? "/iTwinPopup.html" : ""; - const childWindow = window.open( + const childWindow: ChildWindow | null = window.open( url, "", `width=${location.width},height=${location.height},left=${location.left},top=${location.top},menubar=no,resizable=yes,scrollbars=no,status=no,location=no` ); if (!childWindow) return false; + childWindow.expectedHeight = location.height; + childWindow.expectedWidth = location.width; + + // Edge needs to use outer size + childWindow.shouldUseOuterSized = + navigator.userAgent.toLowerCase().indexOf("edg/") > -1; + if (0 === url.length) { childWindow.document.write(childHtml); this.renderChildWindowContents( diff --git a/ui/appui-react/src/appui-react/frontstage/FrontstageDef.tsx b/ui/appui-react/src/appui-react/frontstage/FrontstageDef.tsx index 35924ffdd60..0b903d74af0 100644 --- a/ui/appui-react/src/appui-react/frontstage/FrontstageDef.tsx +++ b/ui/appui-react/src/appui-react/frontstage/FrontstageDef.tsx @@ -47,7 +47,7 @@ import type { FrontstageProvider } from "./FrontstageProvider"; import { TimeTracker } from "../configurableui/TimeTracker"; import type { ChildWindowLocationProps } from "../framework/FrameworkChildWindows"; import { PopoutWidget } from "../childwindow/PopoutWidget"; -import { BentleyStatus, ProcessDetector } from "@itwin/core-bentley"; +import { BentleyStatus } from "@itwin/core-bentley"; import type { FrontstageConfig } from "./FrontstageConfig"; import type { StagePanelConfig } from "../stagepanels/StagePanelConfig"; import type { WidgetConfig } from "../widgets/WidgetConfig"; @@ -57,6 +57,7 @@ import { WidgetState } from "../widgets/WidgetState"; import { InternalFrontstageManager } from "./InternalFrontstageManager"; import { InternalContentDialogManager } from "../dialog/InternalContentDialogManager"; import type { XAndY } from "@itwin/core-geometry"; +import type { ChildWindow } from "../childwindow/ChildWindowConfig"; /** @internal */ export interface FrontstageEventArgs { @@ -900,7 +901,7 @@ export class FrontstageDef { /** @internal */ public saveChildWindowSizeAndPosition( childWindowId: string, - childWindow: Window + childWindow: ChildWindow ) { const state = this.nineZoneState; if (!state) return; @@ -913,11 +914,21 @@ export class FrontstageDef { const widgetDef = this.findWidgetDef(tabId); if (!widgetDef) return; - const adjustmentWidth = ProcessDetector.isElectronAppFrontend ? 16 : 0; - const adjustmentHeight = ProcessDetector.isElectronAppFrontend ? 39 : 0; + let width = childWindow.shouldUseOuterSized + ? childWindow.outerWidth + : childWindow.innerWidth; + let height = childWindow.shouldUseOuterSized + ? childWindow.outerHeight + : childWindow.innerHeight; + if (childWindow.deltaHeight) { + height = height + childWindow.deltaHeight; + if (height < 1) height = 100; + } + if (childWindow.deltaWidth) { + width = width + childWindow.deltaWidth; + if (width < 1) width = 100; + } - const width = childWindow.innerWidth + adjustmentWidth; - const height = childWindow.innerHeight + adjustmentHeight; const bounds = Rectangle.createFromSize({ width, height }).offset({ x: childWindow.screenX, y: childWindow.screenY,