@babylonjs/shared-ui-components 9.9.1 → 9.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fluent/hoc/childWindow.js +45 -130
- package/fluent/hoc/childWindow.js.map +1 -1
- package/fluent/hoc/popupWindow.d.ts +75 -0
- package/fluent/hoc/popupWindow.js +156 -0
- package/fluent/hoc/popupWindow.js.map +1 -0
- package/fluent/primitives/button.d.ts +12 -0
- package/fluent/primitives/button.js +2 -2
- package/fluent/primitives/button.js.map +1 -1
- package/fluent/primitives/dialog.js +1 -1
- package/fluent/primitives/dialog.js.map +1 -1
- package/fluent/primitives/toggleButton.d.ts +2 -0
- package/fluent/primitives/toggleButton.js +2 -2
- package/fluent/primitives/toggleButton.js.map +1 -1
- package/fluent/primitives/tooltip.d.ts +8 -0
- package/fluent/primitives/tooltip.js +2 -2
- package/fluent/primitives/tooltip.js.map +1 -1
- package/modularTool/components/errorBoundary.js +3 -0
- package/modularTool/components/errorBoundary.js.map +1 -1
- package/modularTool/components/theme.d.ts +5 -0
- package/modularTool/components/theme.js +27 -3
- package/modularTool/components/theme.js.map +1 -1
- package/modularTool/contexts/dialogContext.d.ts +14 -0
- package/modularTool/contexts/dialogContext.js +13 -0
- package/modularTool/contexts/dialogContext.js.map +1 -0
- package/modularTool/modularTool.js +64 -3
- package/modularTool/modularTool.js.map +1 -1
- package/modularTool/services/dialogService.d.ts +22 -0
- package/modularTool/services/dialogService.js +5 -0
- package/modularTool/services/dialogService.js.map +1 -0
- package/modularTool/services/shellService.js +15 -0
- package/modularTool/services/shellService.js.map +1 -1
- package/nodeGraphSystem/graphCanvas.d.ts +5 -1
- package/nodeGraphSystem/graphCanvas.js +6 -2
- package/nodeGraphSystem/graphCanvas.js.map +1 -1
- package/nodeGraphSystem/graphNode.d.ts +5 -1
- package/nodeGraphSystem/graphNode.js +13 -5
- package/nodeGraphSystem/graphNode.js.map +1 -1
- package/nodeGraphSystem/nodeLink.d.ts +6 -1
- package/nodeGraphSystem/nodeLink.js +9 -2
- package/nodeGraphSystem/nodeLink.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,26 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createDOMRenderer, FluentProvider, Portal, RendererProvider } from "@fluentui/react-components";
|
|
3
3
|
import { useCallback, useEffect, useImperativeHandle, useState } from "react";
|
|
4
|
-
import { Logger } from "@babylonjs/core/Misc/logger.js";
|
|
5
4
|
import { ToastProvider } from "../primitives/toast.js";
|
|
6
|
-
|
|
7
|
-
const { defaultWidth, defaultHeight, defaultLeft, defaultTop } = options;
|
|
8
|
-
const features = [];
|
|
9
|
-
if (defaultWidth !== undefined) {
|
|
10
|
-
features.push({ key: "width", value: defaultWidth.toString() });
|
|
11
|
-
}
|
|
12
|
-
if (defaultHeight !== undefined) {
|
|
13
|
-
features.push({ key: "height", value: defaultHeight.toString() });
|
|
14
|
-
}
|
|
15
|
-
if (defaultLeft !== undefined) {
|
|
16
|
-
features.push({ key: "left", value: defaultLeft.toString() });
|
|
17
|
-
}
|
|
18
|
-
if (defaultTop !== undefined) {
|
|
19
|
-
features.push({ key: "top", value: defaultTop.toString() });
|
|
20
|
-
}
|
|
21
|
-
features.push({ key: "location", value: "no" });
|
|
22
|
-
return features.map((feature) => `${feature.key}=${feature.value}`).join(",");
|
|
23
|
-
}
|
|
5
|
+
import { OpenPopupWindow } from "./popupWindow.js";
|
|
24
6
|
/**
|
|
25
7
|
* Allows displaying a child window that can contain child components.
|
|
26
8
|
* @param props Props for the child window.
|
|
@@ -29,133 +11,66 @@ function ToFeaturesString(options) {
|
|
|
29
11
|
export const ChildWindow = (props) => {
|
|
30
12
|
const { id, children, onOpenChange, imperativeRef: imperativeRef } = props;
|
|
31
13
|
const [windowState, setWindowState] = useState();
|
|
32
|
-
const [
|
|
33
|
-
const storageKey = id ? `Babylon/Settings/ChildWindow/${id}/Bounds` : null;
|
|
14
|
+
const [popupHandle, setPopupHandle] = useState();
|
|
34
15
|
// This function is just for creating the child window itself. It is a function because
|
|
35
16
|
// it must be called synchronously in response to a user interaction (e.g. button click),
|
|
36
17
|
// otherwise the browser will block it as a scripted popup.
|
|
37
18
|
const createWindow = useCallback((options = {}) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
// Half width by default.
|
|
64
|
-
if (!options.defaultWidth) {
|
|
65
|
-
options.defaultWidth = window.innerWidth * (2 / 3);
|
|
66
|
-
}
|
|
67
|
-
// Half height by default.
|
|
68
|
-
if (!options.defaultHeight) {
|
|
69
|
-
options.defaultHeight = window.innerHeight * (2 / 3);
|
|
70
|
-
}
|
|
71
|
-
// Horizontally centered by default.
|
|
72
|
-
if (!options.defaultLeft) {
|
|
73
|
-
options.defaultLeft = window.screenX + (window.innerWidth - options.defaultWidth) * (2 / 3);
|
|
74
|
-
}
|
|
75
|
-
// Vertically centered by default.
|
|
76
|
-
if (!options.defaultTop) {
|
|
77
|
-
options.defaultTop = window.screenY + (window.innerHeight - options.defaultHeight) * (2 / 3);
|
|
78
|
-
}
|
|
79
|
-
// Try to create the child window (can be null if popups are blocked).
|
|
80
|
-
const newChildWindow = window.open("", "", ToFeaturesString(options));
|
|
81
|
-
if (newChildWindow) {
|
|
82
|
-
// Set the title if provided.
|
|
83
|
-
newChildWindow.document.title = options.title ?? id ?? "";
|
|
84
|
-
// Set the child window state.
|
|
85
|
-
setChildWindow((current) => {
|
|
86
|
-
// But first close any existing child window.
|
|
87
|
-
current?.close();
|
|
88
|
-
return newChildWindow;
|
|
19
|
+
const handle = OpenPopupWindow({
|
|
20
|
+
id,
|
|
21
|
+
title: options.title ?? id,
|
|
22
|
+
defaultWidth: options.defaultWidth,
|
|
23
|
+
defaultHeight: options.defaultHeight,
|
|
24
|
+
defaultLeft: options.defaultLeft,
|
|
25
|
+
defaultTop: options.defaultTop,
|
|
26
|
+
onClose: () => {
|
|
27
|
+
// Triggered when the popup is closed for any reason (user dismissal, parent unload,
|
|
28
|
+
// or programmatic dispose). Clear the popup handle so the effect cleanup runs and
|
|
29
|
+
// `onOpenChange(false)` is propagated up the tree (which lets a parent shell re-dock
|
|
30
|
+
// a previously-undocked pane). teardown() is idempotent so calling dispose() again
|
|
31
|
+
// from the effect cleanup is a safe no-op.
|
|
32
|
+
setPopupHandle(undefined);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (handle) {
|
|
36
|
+
setPopupHandle((current) => {
|
|
37
|
+
// Close any existing child window before adopting the new one.
|
|
38
|
+
current?.dispose();
|
|
39
|
+
return handle;
|
|
89
40
|
});
|
|
90
41
|
}
|
|
91
|
-
}, [
|
|
42
|
+
}, [id]);
|
|
92
43
|
useImperativeHandle(imperativeRef, () => {
|
|
93
44
|
return {
|
|
94
45
|
open: createWindow,
|
|
95
|
-
close: () =>
|
|
46
|
+
close: () => {
|
|
47
|
+
setPopupHandle((current) => {
|
|
48
|
+
current?.dispose();
|
|
49
|
+
return undefined;
|
|
50
|
+
});
|
|
51
|
+
},
|
|
96
52
|
};
|
|
97
53
|
}, [createWindow]);
|
|
98
|
-
// This side effect runs any time the
|
|
54
|
+
// This side effect runs any time the popup handle changes. It does the rest of the child window
|
|
99
55
|
// setup work, including creating resources and state needed to properly render the content of the child window.
|
|
100
56
|
useEffect(() => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const body = childWindow.document.body;
|
|
104
|
-
body.style.width = "100%";
|
|
105
|
-
body.style.height = "100%";
|
|
106
|
-
body.style.margin = "0";
|
|
107
|
-
body.style.padding = "0";
|
|
108
|
-
body.style.display = "flex";
|
|
109
|
-
body.style.overflow = "hidden";
|
|
110
|
-
// Setup the window state, including creating a Fluent/Griffel "renderer" for managing runtime styles/classes in the child window.
|
|
111
|
-
setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });
|
|
112
|
-
onOpenChange?.(true);
|
|
113
|
-
// Track the most recently observed window bounds. In some browsers (e.g. Firefox), accessing
|
|
114
|
-
// properties like screenX on a closed window throws, so we cache the last known good values
|
|
115
|
-
// to use as a fallback when the dispose runs after the window has already been closed.
|
|
116
|
-
const getBounds = () => ({
|
|
117
|
-
left: childWindow.screenX,
|
|
118
|
-
top: childWindow.screenY,
|
|
119
|
-
width: childWindow.innerWidth,
|
|
120
|
-
height: childWindow.innerHeight,
|
|
121
|
-
});
|
|
122
|
-
let lastBounds = getBounds();
|
|
123
|
-
// When the child window is closed for any reason, transition back to a closed state.
|
|
124
|
-
const onChildWindowUnload = () => {
|
|
125
|
-
setWindowState(undefined);
|
|
126
|
-
setChildWindow(undefined);
|
|
127
|
-
onOpenChange?.(false);
|
|
128
|
-
};
|
|
129
|
-
childWindow.addEventListener("unload", onChildWindowUnload, { once: true });
|
|
130
|
-
disposeActions.push(() => childWindow.removeEventListener("unload", onChildWindowUnload));
|
|
131
|
-
// Capture bounds before the window is unloaded, while its properties are still safe to read.
|
|
132
|
-
const onChildWindowBeforeUnload = () => {
|
|
133
|
-
lastBounds = getBounds();
|
|
134
|
-
};
|
|
135
|
-
childWindow.addEventListener("beforeunload", onChildWindowBeforeUnload);
|
|
136
|
-
disposeActions.push(() => childWindow.removeEventListener("beforeunload", onChildWindowBeforeUnload));
|
|
137
|
-
// If the main window closes, close any open child windows as well (don't leave them orphaned).
|
|
138
|
-
const onParentWindowUnload = () => {
|
|
139
|
-
childWindow.close();
|
|
140
|
-
};
|
|
141
|
-
window.addEventListener("unload", onParentWindowUnload, { once: true });
|
|
142
|
-
disposeActions.push(() => window.removeEventListener("unload", onParentWindowUnload));
|
|
143
|
-
// On dispose, close the child window.
|
|
144
|
-
disposeActions.push(() => childWindow.close());
|
|
145
|
-
// On dispose, save the window bounds.
|
|
146
|
-
disposeActions.push(() => {
|
|
147
|
-
if (storageKey) {
|
|
148
|
-
if (!childWindow.closed) {
|
|
149
|
-
lastBounds = getBounds();
|
|
150
|
-
}
|
|
151
|
-
localStorage.setItem(storageKey, JSON.stringify(lastBounds));
|
|
152
|
-
}
|
|
153
|
-
});
|
|
57
|
+
if (!popupHandle) {
|
|
58
|
+
return undefined;
|
|
154
59
|
}
|
|
60
|
+
const popupDocument = popupHandle.popupWindow.document;
|
|
61
|
+
// Use the popup's hostElement as the React mount point. OpenPopupWindow configures it as a
|
|
62
|
+
// flex column that fills the popup body, so the FluentProvider (also a flex column with
|
|
63
|
+
// flex-grow: 1) inside it can fill the available space.
|
|
64
|
+
setWindowState({ mountNode: popupHandle.hostElement, renderer: createDOMRenderer(popupDocument) });
|
|
65
|
+
onOpenChange?.(true);
|
|
155
66
|
return () => {
|
|
156
|
-
|
|
67
|
+
// Tear down the popup. The cached handle's dispose() handles bounds saving and
|
|
68
|
+
// listener cleanup; React state is reset so the Portal/Provider tree unmounts.
|
|
69
|
+
popupHandle.dispose();
|
|
70
|
+
setWindowState(undefined);
|
|
71
|
+
onOpenChange?.(false);
|
|
157
72
|
};
|
|
158
|
-
}, [
|
|
73
|
+
}, [popupHandle]);
|
|
159
74
|
if (!windowState) {
|
|
160
75
|
return null;
|
|
161
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"childWindow.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hoc/childWindow.tsx"],"names":[],"mappings":"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","sourcesContent":["import { type GriffelRenderer, createDOMRenderer, FluentProvider, Portal, RendererProvider } from \"@fluentui/react-components\";\r\nimport { type FunctionComponent, type PropsWithChildren, type Ref, useCallback, useEffect, useImperativeHandle, useState } from \"react\";\r\n\r\nimport { Logger } from \"core/Misc/logger\";\r\nimport { ToastProvider } from \"../primitives/toast\";\r\n\r\nfunction ToFeaturesString(options: ChildWindowOptions) {\r\n const { defaultWidth, defaultHeight, defaultLeft, defaultTop } = options;\r\n\r\n const features: { key: string; value: string }[] = [];\r\n\r\n if (defaultWidth !== undefined) {\r\n features.push({ key: \"width\", value: defaultWidth.toString() });\r\n }\r\n if (defaultHeight !== undefined) {\r\n features.push({ key: \"height\", value: defaultHeight.toString() });\r\n }\r\n if (defaultLeft !== undefined) {\r\n features.push({ key: \"left\", value: defaultLeft.toString() });\r\n }\r\n if (defaultTop !== undefined) {\r\n features.push({ key: \"top\", value: defaultTop.toString() });\r\n }\r\n features.push({ key: \"location\", value: \"no\" });\r\n\r\n return features.map((feature) => `${feature.key}=${feature.value}`).join(\",\");\r\n}\r\n\r\nexport type ChildWindowOptions = {\r\n /**\r\n * The default width of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultWidth?: number;\r\n\r\n /**\r\n * The default height of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultHeight?: number;\r\n\r\n /**\r\n * The default left position of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultLeft?: number;\r\n\r\n /**\r\n * The default top position of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultTop?: number;\r\n\r\n /**\r\n * The title of the child window.\r\n * @remarks If not provided, the id will be used instead (if any).\r\n */\r\n title?: string;\r\n};\r\n\r\nexport type ChildWindow = {\r\n /**\r\n * Opens the child window.\r\n * @param options Options for opening the child window.\r\n */\r\n open: (options?: ChildWindowOptions) => void;\r\n\r\n /**\r\n * Closes the child window.\r\n */\r\n close: () => void;\r\n};\r\n\r\nexport type ChildWindowProps = {\r\n /**\r\n * An optional unique identity for the child window.\r\n * @remarks If provided, the child window's bounds will be saved/restored using this identity.\r\n */\r\n id?: string;\r\n\r\n /**\r\n * Called when the open state of the child window changes.\r\n * @param isOpen Whether the child window is open.\r\n */\r\n onOpenChange?: (isOpen: boolean) => void;\r\n\r\n /**\r\n * A ref that exposes the ChildWindow imperative API.\r\n */\r\n imperativeRef?: Ref<ChildWindow>;\r\n};\r\n\r\n/**\r\n * Allows displaying a child window that can contain child components.\r\n * @param props Props for the child window.\r\n * @returns The child window component.\r\n */\r\nexport const ChildWindow: FunctionComponent<PropsWithChildren<ChildWindowProps>> = (props) => {\r\n const { id, children, onOpenChange, imperativeRef: imperativeRef } = props;\r\n\r\n const [windowState, setWindowState] = useState<{ mountNode: HTMLElement; renderer: GriffelRenderer }>();\r\n const [childWindow, setChildWindow] = useState<Window>();\r\n\r\n const storageKey = id ? `Babylon/Settings/ChildWindow/${id}/Bounds` : null;\r\n\r\n // This function is just for creating the child window itself. It is a function because\r\n // it must be called synchronously in response to a user interaction (e.g. button click),\r\n // otherwise the browser will block it as a scripted popup.\r\n const createWindow = useCallback(\r\n (options: ChildWindowOptions = {}) => {\r\n if (storageKey) {\r\n // If we are persisting window bounds, but the window is already open, just use the existing bounds.\r\n // Otherwise, try to load bounds from storage.\r\n if (childWindow) {\r\n options.defaultLeft = childWindow.screenX;\r\n options.defaultTop = childWindow.screenY;\r\n options.defaultWidth = childWindow.innerWidth;\r\n options.defaultHeight = childWindow.innerHeight;\r\n } else {\r\n const savedBounds = localStorage.getItem(storageKey);\r\n if (savedBounds) {\r\n try {\r\n const bounds = JSON.parse(savedBounds);\r\n options.defaultLeft = bounds.left;\r\n options.defaultTop = bounds.top;\r\n options.defaultWidth = bounds.width;\r\n options.defaultHeight = bounds.height;\r\n } catch {\r\n Logger.Warn(`Could not parse saved bounds for child window with key ${storageKey}`);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Half width by default.\r\n if (!options.defaultWidth) {\r\n options.defaultWidth = window.innerWidth * (2 / 3);\r\n }\r\n // Half height by default.\r\n if (!options.defaultHeight) {\r\n options.defaultHeight = window.innerHeight * (2 / 3);\r\n }\r\n // Horizontally centered by default.\r\n if (!options.defaultLeft) {\r\n options.defaultLeft = window.screenX + (window.innerWidth - options.defaultWidth) * (2 / 3);\r\n }\r\n // Vertically centered by default.\r\n if (!options.defaultTop) {\r\n options.defaultTop = window.screenY + (window.innerHeight - options.defaultHeight) * (2 / 3);\r\n }\r\n\r\n // Try to create the child window (can be null if popups are blocked).\r\n const newChildWindow = window.open(\"\", \"\", ToFeaturesString(options));\r\n if (newChildWindow) {\r\n // Set the title if provided.\r\n newChildWindow.document.title = options.title ?? id ?? \"\";\r\n\r\n // Set the child window state.\r\n setChildWindow((current) => {\r\n // But first close any existing child window.\r\n current?.close();\r\n return newChildWindow;\r\n });\r\n }\r\n },\r\n [childWindow, storageKey]\r\n );\r\n\r\n useImperativeHandle(imperativeRef, () => {\r\n return {\r\n open: createWindow,\r\n close: () => setChildWindow(undefined),\r\n };\r\n }, [createWindow]);\r\n\r\n // This side effect runs any time the child window instance changes. It does the rest of the child window\r\n // setup work, including creating resources and state needed to properly render the content of the child window.\r\n useEffect(() => {\r\n const disposeActions: (() => void)[] = [];\r\n\r\n if (childWindow) {\r\n const body = childWindow.document.body;\r\n body.style.width = \"100%\";\r\n body.style.height = \"100%\";\r\n body.style.margin = \"0\";\r\n body.style.padding = \"0\";\r\n body.style.display = \"flex\";\r\n body.style.overflow = \"hidden\";\r\n\r\n // Setup the window state, including creating a Fluent/Griffel \"renderer\" for managing runtime styles/classes in the child window.\r\n setWindowState({ mountNode: body, renderer: createDOMRenderer(childWindow.document) });\r\n onOpenChange?.(true);\r\n\r\n // Track the most recently observed window bounds. In some browsers (e.g. Firefox), accessing\r\n // properties like screenX on a closed window throws, so we cache the last known good values\r\n // to use as a fallback when the dispose runs after the window has already been closed.\r\n const getBounds = () => ({\r\n left: childWindow.screenX,\r\n top: childWindow.screenY,\r\n width: childWindow.innerWidth,\r\n height: childWindow.innerHeight,\r\n });\r\n let lastBounds = getBounds();\r\n\r\n // When the child window is closed for any reason, transition back to a closed state.\r\n const onChildWindowUnload = () => {\r\n setWindowState(undefined);\r\n setChildWindow(undefined);\r\n onOpenChange?.(false);\r\n };\r\n childWindow.addEventListener(\"unload\", onChildWindowUnload, { once: true });\r\n disposeActions.push(() => childWindow.removeEventListener(\"unload\", onChildWindowUnload));\r\n\r\n // Capture bounds before the window is unloaded, while its properties are still safe to read.\r\n const onChildWindowBeforeUnload = () => {\r\n lastBounds = getBounds();\r\n };\r\n childWindow.addEventListener(\"beforeunload\", onChildWindowBeforeUnload);\r\n disposeActions.push(() => childWindow.removeEventListener(\"beforeunload\", onChildWindowBeforeUnload));\r\n\r\n // If the main window closes, close any open child windows as well (don't leave them orphaned).\r\n const onParentWindowUnload = () => {\r\n childWindow.close();\r\n };\r\n window.addEventListener(\"unload\", onParentWindowUnload, { once: true });\r\n disposeActions.push(() => window.removeEventListener(\"unload\", onParentWindowUnload));\r\n\r\n // On dispose, close the child window.\r\n disposeActions.push(() => childWindow.close());\r\n\r\n // On dispose, save the window bounds.\r\n disposeActions.push(() => {\r\n if (storageKey) {\r\n if (!childWindow.closed) {\r\n lastBounds = getBounds();\r\n }\r\n localStorage.setItem(storageKey, JSON.stringify(lastBounds));\r\n }\r\n });\r\n }\r\n\r\n return () => {\r\n disposeActions.reverse().forEach((dispose) => dispose());\r\n };\r\n }, [childWindow]);\r\n\r\n if (!windowState) {\r\n return null;\r\n }\r\n\r\n const { mountNode, renderer } = windowState;\r\n\r\n return (\r\n // Portal targets the body of the child window.\r\n <Portal mountNode={mountNode}>\r\n {/* RenderProvider manages Fluent style/class state. */}\r\n <RendererProvider renderer={renderer} targetDocument={mountNode.ownerDocument}>\r\n {/* Fluent Provider is needed for managing other Fluent state and applying the current theme mode. */}\r\n <FluentProvider\r\n style={{\r\n display: \"flex\",\r\n flexGrow: 1,\r\n flexDirection: \"column\",\r\n overflow: \"hidden\",\r\n }}\r\n applyStylesToPortals={false}\r\n targetDocument={mountNode.ownerDocument}\r\n >\r\n <ToastProvider>{children}</ToastProvider>\r\n </FluentProvider>\r\n </RendererProvider>\r\n </Portal>\r\n );\r\n};\r\n"]}
|
|
1
|
+
{"version":3,"file":"childWindow.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hoc/childWindow.tsx"],"names":[],"mappings":"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","sourcesContent":["import { type GriffelRenderer, createDOMRenderer, FluentProvider, Portal, RendererProvider } from \"@fluentui/react-components\";\r\nimport { type FunctionComponent, type PropsWithChildren, type Ref, useCallback, useEffect, useImperativeHandle, useState } from \"react\";\r\n\r\nimport { ToastProvider } from \"../primitives/toast\";\r\nimport { OpenPopupWindow, type PopupWindowHandle } from \"./popupWindow\";\r\n\r\nexport type ChildWindowOptions = {\r\n /**\r\n * The default width of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultWidth?: number;\r\n\r\n /**\r\n * The default height of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultHeight?: number;\r\n\r\n /**\r\n * The default left position of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultLeft?: number;\r\n\r\n /**\r\n * The default top position of the child window in pixels.\r\n * @remarks Ignored if the ChildWindow was passed an id and previous bounds were saved.\r\n */\r\n defaultTop?: number;\r\n\r\n /**\r\n * The title of the child window.\r\n * @remarks If not provided, the id will be used instead (if any).\r\n */\r\n title?: string;\r\n};\r\n\r\nexport type ChildWindow = {\r\n /**\r\n * Opens the child window.\r\n * @param options Options for opening the child window.\r\n */\r\n open: (options?: ChildWindowOptions) => void;\r\n\r\n /**\r\n * Closes the child window.\r\n */\r\n close: () => void;\r\n};\r\n\r\nexport type ChildWindowProps = {\r\n /**\r\n * An optional unique identity for the child window.\r\n * @remarks If provided, the child window's bounds will be saved/restored using this identity.\r\n */\r\n id?: string;\r\n\r\n /**\r\n * Called when the open state of the child window changes.\r\n * @param isOpen Whether the child window is open.\r\n */\r\n onOpenChange?: (isOpen: boolean) => void;\r\n\r\n /**\r\n * A ref that exposes the ChildWindow imperative API.\r\n */\r\n imperativeRef?: Ref<ChildWindow>;\r\n};\r\n\r\n/**\r\n * Allows displaying a child window that can contain child components.\r\n * @param props Props for the child window.\r\n * @returns The child window component.\r\n */\r\nexport const ChildWindow: FunctionComponent<PropsWithChildren<ChildWindowProps>> = (props) => {\r\n const { id, children, onOpenChange, imperativeRef: imperativeRef } = props;\r\n\r\n const [windowState, setWindowState] = useState<{ mountNode: HTMLElement; renderer: GriffelRenderer }>();\r\n const [popupHandle, setPopupHandle] = useState<PopupWindowHandle>();\r\n\r\n // This function is just for creating the child window itself. It is a function because\r\n // it must be called synchronously in response to a user interaction (e.g. button click),\r\n // otherwise the browser will block it as a scripted popup.\r\n const createWindow = useCallback(\r\n (options: ChildWindowOptions = {}) => {\r\n const handle = OpenPopupWindow({\r\n id,\r\n title: options.title ?? id,\r\n defaultWidth: options.defaultWidth,\r\n defaultHeight: options.defaultHeight,\r\n defaultLeft: options.defaultLeft,\r\n defaultTop: options.defaultTop,\r\n onClose: () => {\r\n // Triggered when the popup is closed for any reason (user dismissal, parent unload,\r\n // or programmatic dispose). Clear the popup handle so the effect cleanup runs and\r\n // `onOpenChange(false)` is propagated up the tree (which lets a parent shell re-dock\r\n // a previously-undocked pane). teardown() is idempotent so calling dispose() again\r\n // from the effect cleanup is a safe no-op.\r\n setPopupHandle(undefined);\r\n },\r\n });\r\n\r\n if (handle) {\r\n setPopupHandle((current) => {\r\n // Close any existing child window before adopting the new one.\r\n current?.dispose();\r\n return handle;\r\n });\r\n }\r\n },\r\n [id]\r\n );\r\n\r\n useImperativeHandle(imperativeRef, () => {\r\n return {\r\n open: createWindow,\r\n close: () => {\r\n setPopupHandle((current) => {\r\n current?.dispose();\r\n return undefined;\r\n });\r\n },\r\n };\r\n }, [createWindow]);\r\n\r\n // This side effect runs any time the popup handle changes. It does the rest of the child window\r\n // setup work, including creating resources and state needed to properly render the content of the child window.\r\n useEffect(() => {\r\n if (!popupHandle) {\r\n return undefined;\r\n }\r\n\r\n const popupDocument = popupHandle.popupWindow.document;\r\n\r\n // Use the popup's hostElement as the React mount point. OpenPopupWindow configures it as a\r\n // flex column that fills the popup body, so the FluentProvider (also a flex column with\r\n // flex-grow: 1) inside it can fill the available space.\r\n setWindowState({ mountNode: popupHandle.hostElement, renderer: createDOMRenderer(popupDocument) });\r\n onOpenChange?.(true);\r\n\r\n return () => {\r\n // Tear down the popup. The cached handle's dispose() handles bounds saving and\r\n // listener cleanup; React state is reset so the Portal/Provider tree unmounts.\r\n popupHandle.dispose();\r\n setWindowState(undefined);\r\n onOpenChange?.(false);\r\n };\r\n }, [popupHandle]);\r\n\r\n if (!windowState) {\r\n return null;\r\n }\r\n\r\n const { mountNode, renderer } = windowState;\r\n\r\n return (\r\n // Portal targets the body of the child window.\r\n <Portal mountNode={mountNode}>\r\n {/* RenderProvider manages Fluent style/class state. */}\r\n <RendererProvider renderer={renderer} targetDocument={mountNode.ownerDocument}>\r\n {/* Fluent Provider is needed for managing other Fluent state and applying the current theme mode. */}\r\n <FluentProvider\r\n style={{\r\n display: \"flex\",\r\n flexGrow: 1,\r\n flexDirection: \"column\",\r\n overflow: \"hidden\",\r\n }}\r\n applyStylesToPortals={false}\r\n targetDocument={mountNode.ownerDocument}\r\n >\r\n <ToastProvider>{children}</ToastProvider>\r\n </FluentProvider>\r\n </RendererProvider>\r\n </Portal>\r\n );\r\n};\r\n"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for opening a popup browser window via {@link OpenPopupWindow}.
|
|
3
|
+
*/
|
|
4
|
+
export type PopupWindowOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Title set on the popup document.
|
|
7
|
+
*/
|
|
8
|
+
title?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Default width of the popup in pixels.
|
|
11
|
+
* @remarks Ignored if `id` is provided and a previously saved width exists.
|
|
12
|
+
*/
|
|
13
|
+
defaultWidth?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Default height of the popup in pixels.
|
|
16
|
+
* @remarks Ignored if `id` is provided and a previously saved height exists.
|
|
17
|
+
*/
|
|
18
|
+
defaultHeight?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Default screen-X position of the popup in pixels.
|
|
21
|
+
* @remarks Ignored if `id` is provided and a previously saved position exists.
|
|
22
|
+
*/
|
|
23
|
+
defaultLeft?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Default screen-Y position of the popup in pixels.
|
|
26
|
+
* @remarks Ignored if `id` is provided and a previously saved position exists.
|
|
27
|
+
*/
|
|
28
|
+
defaultTop?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Optional unique identity. When provided, the popup's bounds are saved to and
|
|
31
|
+
* restored from `localStorage` under the key `Babylon/Settings/PopupWindow/{id}/Bounds`.
|
|
32
|
+
*/
|
|
33
|
+
id?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Optional callback invoked when the popup is closed externally — e.g. the user dismisses
|
|
36
|
+
* the popup, or the browser tab/window itself is unloaded. NOT called when the consumer
|
|
37
|
+
* closes the popup via {@link PopupWindowHandle.dispose} (the consumer already knows about
|
|
38
|
+
* that closure).
|
|
39
|
+
*/
|
|
40
|
+
onClose?: () => void;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Handle returned from {@link OpenPopupWindow}.
|
|
44
|
+
*/
|
|
45
|
+
export type PopupWindowHandle = {
|
|
46
|
+
/**
|
|
47
|
+
* The popup `Window` object. May become `closed` if the user dismisses the popup.
|
|
48
|
+
*/
|
|
49
|
+
readonly popupWindow: Window;
|
|
50
|
+
/**
|
|
51
|
+
* A flex container element appended to the popup body. Render the tool into this element.
|
|
52
|
+
*/
|
|
53
|
+
readonly hostElement: HTMLDivElement;
|
|
54
|
+
/**
|
|
55
|
+
* Closes the popup window and removes any listeners installed on the parent.
|
|
56
|
+
* Safe to call multiple times.
|
|
57
|
+
*/
|
|
58
|
+
dispose: () => void;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Opens a new browser popup window suitable for hosting a Fluent-based modular tool.
|
|
62
|
+
*
|
|
63
|
+
* The popup body is configured for full-bleed flex layout and a host `<div>` is appended
|
|
64
|
+
* for the tool to render into. Fluent style targeting (Griffel `RendererProvider`,
|
|
65
|
+
* `FluentProvider` with `targetDocument`) is the caller's responsibility — typically wired
|
|
66
|
+
* up by `MakeModularTool`, which derives `targetDocument` from `containerElement.ownerDocument`.
|
|
67
|
+
*
|
|
68
|
+
* **Must be called synchronously in response to a user interaction** (e.g. button click) —
|
|
69
|
+
* otherwise the browser will block the popup as a scripted popup.
|
|
70
|
+
*
|
|
71
|
+
* @param options Window options. See {@link PopupWindowOptions}.
|
|
72
|
+
* @returns A handle to the popup window and its host element, plus a `dispose` to close it.
|
|
73
|
+
* `null` if the popup was blocked by the browser.
|
|
74
|
+
*/
|
|
75
|
+
export declare function OpenPopupWindow(options?: PopupWindowOptions): PopupWindowHandle | null;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Logger } from "@babylonjs/core/Misc/logger.js";
|
|
2
|
+
const StorageKeyPrefix = "Babylon/Settings/PopupWindow";
|
|
3
|
+
function LoadSavedBounds(id) {
|
|
4
|
+
const stored = localStorage.getItem(`${StorageKeyPrefix}/${id}/Bounds`);
|
|
5
|
+
if (!stored) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(stored);
|
|
10
|
+
return parsed;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
Logger.Warn(`Could not parse saved bounds for popup window with id ${id}`);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function SaveBounds(id, bounds) {
|
|
18
|
+
try {
|
|
19
|
+
localStorage.setItem(`${StorageKeyPrefix}/${id}/Bounds`, JSON.stringify(bounds));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Storage may be full / disabled — bounds simply won't persist.
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function ResolveBounds(options) {
|
|
26
|
+
const saved = options.id ? LoadSavedBounds(options.id) : null;
|
|
27
|
+
const width = options.defaultWidth ?? saved?.width ?? Math.floor(window.innerWidth * (2 / 3));
|
|
28
|
+
const height = options.defaultHeight ?? saved?.height ?? Math.floor(window.innerHeight * (2 / 3));
|
|
29
|
+
const left = options.defaultLeft ?? saved?.left ?? Math.floor(window.screenX + (window.innerWidth - width) / 2);
|
|
30
|
+
const top = options.defaultTop ?? saved?.top ?? Math.floor(window.screenY + (window.innerHeight - height) / 2);
|
|
31
|
+
// When the caller passes explicit width/height/left/top, always honour them; otherwise fall
|
|
32
|
+
// back to the (saved or computed) values above. The order above already gives explicit
|
|
33
|
+
// options precedence, so just build the resulting bounds now.
|
|
34
|
+
return { left, top, width, height };
|
|
35
|
+
}
|
|
36
|
+
function ToFeaturesString(bounds) {
|
|
37
|
+
return `width=${bounds.width},height=${bounds.height},left=${bounds.left},top=${bounds.top},location=no`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Opens a new browser popup window suitable for hosting a Fluent-based modular tool.
|
|
41
|
+
*
|
|
42
|
+
* The popup body is configured for full-bleed flex layout and a host `<div>` is appended
|
|
43
|
+
* for the tool to render into. Fluent style targeting (Griffel `RendererProvider`,
|
|
44
|
+
* `FluentProvider` with `targetDocument`) is the caller's responsibility — typically wired
|
|
45
|
+
* up by `MakeModularTool`, which derives `targetDocument` from `containerElement.ownerDocument`.
|
|
46
|
+
*
|
|
47
|
+
* **Must be called synchronously in response to a user interaction** (e.g. button click) —
|
|
48
|
+
* otherwise the browser will block the popup as a scripted popup.
|
|
49
|
+
*
|
|
50
|
+
* @param options Window options. See {@link PopupWindowOptions}.
|
|
51
|
+
* @returns A handle to the popup window and its host element, plus a `dispose` to close it.
|
|
52
|
+
* `null` if the popup was blocked by the browser.
|
|
53
|
+
*/
|
|
54
|
+
export function OpenPopupWindow(options = {}) {
|
|
55
|
+
const bounds = ResolveBounds(options);
|
|
56
|
+
const popupWindow = window.open("", "", ToFeaturesString(bounds));
|
|
57
|
+
if (!popupWindow) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (options.title) {
|
|
61
|
+
popupWindow.document.title = options.title;
|
|
62
|
+
}
|
|
63
|
+
const popupBody = popupWindow.document.body;
|
|
64
|
+
popupBody.style.width = "100%";
|
|
65
|
+
popupBody.style.height = "100%";
|
|
66
|
+
popupBody.style.margin = "0";
|
|
67
|
+
popupBody.style.padding = "0";
|
|
68
|
+
popupBody.style.display = "flex";
|
|
69
|
+
popupBody.style.overflow = "hidden";
|
|
70
|
+
const hostElement = popupWindow.document.createElement("div");
|
|
71
|
+
hostElement.style.display = "flex";
|
|
72
|
+
hostElement.style.flexDirection = "column";
|
|
73
|
+
hostElement.style.flexGrow = "1";
|
|
74
|
+
hostElement.style.width = "100%";
|
|
75
|
+
hostElement.style.height = "100%";
|
|
76
|
+
hostElement.style.margin = "0";
|
|
77
|
+
hostElement.style.padding = "0";
|
|
78
|
+
hostElement.style.overflow = "hidden";
|
|
79
|
+
popupBody.appendChild(hostElement);
|
|
80
|
+
// Track the most recently observed window bounds. In some browsers (e.g. Firefox), accessing
|
|
81
|
+
// properties like screenX on a closed window throws, so we cache the last known good values
|
|
82
|
+
// to use as a fallback when saving after the window has already been closed.
|
|
83
|
+
const getBounds = () => ({
|
|
84
|
+
left: popupWindow.screenX,
|
|
85
|
+
top: popupWindow.screenY,
|
|
86
|
+
width: popupWindow.innerWidth,
|
|
87
|
+
height: popupWindow.innerHeight,
|
|
88
|
+
});
|
|
89
|
+
let lastBounds = bounds;
|
|
90
|
+
const onPopupBeforeUnload = () => {
|
|
91
|
+
try {
|
|
92
|
+
lastBounds = getBounds();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Use the cached lastBounds.
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
popupWindow.addEventListener("beforeunload", onPopupBeforeUnload);
|
|
99
|
+
let disposed = false;
|
|
100
|
+
// Internal cleanup: tears down listeners, persists bounds, closes the popup.
|
|
101
|
+
// Does NOT invoke `options.onClose` — that's reserved for popup unload events
|
|
102
|
+
// that originate from outside our own teardown (e.g. user dismissed the popup).
|
|
103
|
+
const cleanup = () => {
|
|
104
|
+
if (disposed) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
disposed = true;
|
|
108
|
+
if (options.id) {
|
|
109
|
+
try {
|
|
110
|
+
if (!popupWindow.closed) {
|
|
111
|
+
lastBounds = getBounds();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Use the cached lastBounds.
|
|
116
|
+
}
|
|
117
|
+
SaveBounds(options.id, lastBounds);
|
|
118
|
+
}
|
|
119
|
+
popupWindow.removeEventListener("beforeunload", onPopupBeforeUnload);
|
|
120
|
+
// Remove the unload listener so that any pending unload event triggered by our
|
|
121
|
+
// own popupWindow.close() below cannot reach back into onPopupUnload after we've
|
|
122
|
+
// already torn down. This avoids a race where, during a programmatic open-while-open
|
|
123
|
+
// swap, the old popup's unload would otherwise fire onClose and clear React state
|
|
124
|
+
// pointing at the new popup.
|
|
125
|
+
popupWindow.removeEventListener("unload", onPopupUnload);
|
|
126
|
+
window.removeEventListener("unload", onParentUnload);
|
|
127
|
+
if (!popupWindow.closed) {
|
|
128
|
+
popupWindow.close();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const onPopupUnload = () => {
|
|
132
|
+
if (disposed) {
|
|
133
|
+
// Already torn down programmatically; the popup is just finishing its unload.
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
cleanup();
|
|
137
|
+
// Notify the consumer only for externally-triggered closures (user dismissed the popup,
|
|
138
|
+
// tab/browser closed). Programmatic disposal calls cleanup() directly and intentionally
|
|
139
|
+
// skips this so callers don't get a re-entrant onClose for the close they themselves issued.
|
|
140
|
+
options.onClose?.();
|
|
141
|
+
};
|
|
142
|
+
popupWindow.addEventListener("unload", onPopupUnload, { once: true });
|
|
143
|
+
// If the parent window is unloaded (page refresh / navigation), don't leave the popup orphaned.
|
|
144
|
+
const onParentUnload = () => {
|
|
145
|
+
if (!popupWindow.closed) {
|
|
146
|
+
popupWindow.close();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
window.addEventListener("unload", onParentUnload, { once: true });
|
|
150
|
+
return {
|
|
151
|
+
popupWindow,
|
|
152
|
+
hostElement,
|
|
153
|
+
dispose: cleanup,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=popupWindow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"popupWindow.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hoc/popupWindow.ts"],"names":[],"mappings":"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","sourcesContent":["import { Logger } from \"core/Misc/logger\";\r\n\r\n/**\r\n * Options for opening a popup browser window via {@link OpenPopupWindow}.\r\n */\r\nexport type PopupWindowOptions = {\r\n /**\r\n * Title set on the popup document.\r\n */\r\n title?: string;\r\n\r\n /**\r\n * Default width of the popup in pixels.\r\n * @remarks Ignored if `id` is provided and a previously saved width exists.\r\n */\r\n defaultWidth?: number;\r\n\r\n /**\r\n * Default height of the popup in pixels.\r\n * @remarks Ignored if `id` is provided and a previously saved height exists.\r\n */\r\n defaultHeight?: number;\r\n\r\n /**\r\n * Default screen-X position of the popup in pixels.\r\n * @remarks Ignored if `id` is provided and a previously saved position exists.\r\n */\r\n defaultLeft?: number;\r\n\r\n /**\r\n * Default screen-Y position of the popup in pixels.\r\n * @remarks Ignored if `id` is provided and a previously saved position exists.\r\n */\r\n defaultTop?: number;\r\n\r\n /**\r\n * Optional unique identity. When provided, the popup's bounds are saved to and\r\n * restored from `localStorage` under the key `Babylon/Settings/PopupWindow/{id}/Bounds`.\r\n */\r\n id?: string;\r\n\r\n /**\r\n * Optional callback invoked when the popup is closed externally — e.g. the user dismisses\r\n * the popup, or the browser tab/window itself is unloaded. NOT called when the consumer\r\n * closes the popup via {@link PopupWindowHandle.dispose} (the consumer already knows about\r\n * that closure).\r\n */\r\n onClose?: () => void;\r\n};\r\n\r\n/**\r\n * Handle returned from {@link OpenPopupWindow}.\r\n */\r\nexport type PopupWindowHandle = {\r\n /**\r\n * The popup `Window` object. May become `closed` if the user dismisses the popup.\r\n */\r\n readonly popupWindow: Window;\r\n\r\n /**\r\n * A flex container element appended to the popup body. Render the tool into this element.\r\n */\r\n readonly hostElement: HTMLDivElement;\r\n\r\n /**\r\n * Closes the popup window and removes any listeners installed on the parent.\r\n * Safe to call multiple times.\r\n */\r\n dispose: () => void;\r\n};\r\n\r\nconst StorageKeyPrefix = \"Babylon/Settings/PopupWindow\";\r\n\r\ntype SavedBounds = { left: number; top: number; width: number; height: number };\r\n\r\nfunction LoadSavedBounds(id: string): Partial<SavedBounds> | null {\r\n const stored = localStorage.getItem(`${StorageKeyPrefix}/${id}/Bounds`);\r\n if (!stored) {\r\n return null;\r\n }\r\n try {\r\n const parsed = JSON.parse(stored) as Partial<SavedBounds>;\r\n return parsed;\r\n } catch {\r\n Logger.Warn(`Could not parse saved bounds for popup window with id ${id}`);\r\n return null;\r\n }\r\n}\r\n\r\nfunction SaveBounds(id: string, bounds: SavedBounds) {\r\n try {\r\n localStorage.setItem(`${StorageKeyPrefix}/${id}/Bounds`, JSON.stringify(bounds));\r\n } catch {\r\n // Storage may be full / disabled — bounds simply won't persist.\r\n }\r\n}\r\n\r\nfunction ResolveBounds(options: PopupWindowOptions): SavedBounds {\r\n const saved = options.id ? LoadSavedBounds(options.id) : null;\r\n\r\n const width = options.defaultWidth ?? saved?.width ?? Math.floor(window.innerWidth * (2 / 3));\r\n const height = options.defaultHeight ?? saved?.height ?? Math.floor(window.innerHeight * (2 / 3));\r\n const left = options.defaultLeft ?? saved?.left ?? Math.floor(window.screenX + (window.innerWidth - width) / 2);\r\n const top = options.defaultTop ?? saved?.top ?? Math.floor(window.screenY + (window.innerHeight - height) / 2);\r\n\r\n // When the caller passes explicit width/height/left/top, always honour them; otherwise fall\r\n // back to the (saved or computed) values above. The order above already gives explicit\r\n // options precedence, so just build the resulting bounds now.\r\n return { left, top, width, height };\r\n}\r\n\r\nfunction ToFeaturesString(bounds: SavedBounds): string {\r\n return `width=${bounds.width},height=${bounds.height},left=${bounds.left},top=${bounds.top},location=no`;\r\n}\r\n\r\n/**\r\n * Opens a new browser popup window suitable for hosting a Fluent-based modular tool.\r\n *\r\n * The popup body is configured for full-bleed flex layout and a host `<div>` is appended\r\n * for the tool to render into. Fluent style targeting (Griffel `RendererProvider`,\r\n * `FluentProvider` with `targetDocument`) is the caller's responsibility — typically wired\r\n * up by `MakeModularTool`, which derives `targetDocument` from `containerElement.ownerDocument`.\r\n *\r\n * **Must be called synchronously in response to a user interaction** (e.g. button click) —\r\n * otherwise the browser will block the popup as a scripted popup.\r\n *\r\n * @param options Window options. See {@link PopupWindowOptions}.\r\n * @returns A handle to the popup window and its host element, plus a `dispose` to close it.\r\n * `null` if the popup was blocked by the browser.\r\n */\r\nexport function OpenPopupWindow(options: PopupWindowOptions = {}): PopupWindowHandle | null {\r\n const bounds = ResolveBounds(options);\r\n\r\n const popupWindow = window.open(\"\", \"\", ToFeaturesString(bounds));\r\n if (!popupWindow) {\r\n return null;\r\n }\r\n\r\n if (options.title) {\r\n popupWindow.document.title = options.title;\r\n }\r\n\r\n const popupBody = popupWindow.document.body;\r\n popupBody.style.width = \"100%\";\r\n popupBody.style.height = \"100%\";\r\n popupBody.style.margin = \"0\";\r\n popupBody.style.padding = \"0\";\r\n popupBody.style.display = \"flex\";\r\n popupBody.style.overflow = \"hidden\";\r\n\r\n const hostElement = popupWindow.document.createElement(\"div\");\r\n hostElement.style.display = \"flex\";\r\n hostElement.style.flexDirection = \"column\";\r\n hostElement.style.flexGrow = \"1\";\r\n hostElement.style.width = \"100%\";\r\n hostElement.style.height = \"100%\";\r\n hostElement.style.margin = \"0\";\r\n hostElement.style.padding = \"0\";\r\n hostElement.style.overflow = \"hidden\";\r\n popupBody.appendChild(hostElement);\r\n\r\n // Track the most recently observed window bounds. In some browsers (e.g. Firefox), accessing\r\n // properties like screenX on a closed window throws, so we cache the last known good values\r\n // to use as a fallback when saving after the window has already been closed.\r\n const getBounds = (): SavedBounds => ({\r\n left: popupWindow.screenX,\r\n top: popupWindow.screenY,\r\n width: popupWindow.innerWidth,\r\n height: popupWindow.innerHeight,\r\n });\r\n let lastBounds = bounds;\r\n\r\n const onPopupBeforeUnload = () => {\r\n try {\r\n lastBounds = getBounds();\r\n } catch {\r\n // Use the cached lastBounds.\r\n }\r\n };\r\n popupWindow.addEventListener(\"beforeunload\", onPopupBeforeUnload);\r\n\r\n let disposed = false;\r\n // Internal cleanup: tears down listeners, persists bounds, closes the popup.\r\n // Does NOT invoke `options.onClose` — that's reserved for popup unload events\r\n // that originate from outside our own teardown (e.g. user dismissed the popup).\r\n const cleanup = () => {\r\n if (disposed) {\r\n return;\r\n }\r\n disposed = true;\r\n\r\n if (options.id) {\r\n try {\r\n if (!popupWindow.closed) {\r\n lastBounds = getBounds();\r\n }\r\n } catch {\r\n // Use the cached lastBounds.\r\n }\r\n SaveBounds(options.id, lastBounds);\r\n }\r\n\r\n popupWindow.removeEventListener(\"beforeunload\", onPopupBeforeUnload);\r\n // Remove the unload listener so that any pending unload event triggered by our\r\n // own popupWindow.close() below cannot reach back into onPopupUnload after we've\r\n // already torn down. This avoids a race where, during a programmatic open-while-open\r\n // swap, the old popup's unload would otherwise fire onClose and clear React state\r\n // pointing at the new popup.\r\n popupWindow.removeEventListener(\"unload\", onPopupUnload);\r\n window.removeEventListener(\"unload\", onParentUnload);\r\n\r\n if (!popupWindow.closed) {\r\n popupWindow.close();\r\n }\r\n };\r\n\r\n const onPopupUnload = () => {\r\n if (disposed) {\r\n // Already torn down programmatically; the popup is just finishing its unload.\r\n return;\r\n }\r\n cleanup();\r\n // Notify the consumer only for externally-triggered closures (user dismissed the popup,\r\n // tab/browser closed). Programmatic disposal calls cleanup() directly and intentionally\r\n // skips this so callers don't get a re-entrant onClose for the close they themselves issued.\r\n options.onClose?.();\r\n };\r\n popupWindow.addEventListener(\"unload\", onPopupUnload, { once: true });\r\n\r\n // If the parent window is unloaded (page refresh / navigation), don't leave the popup orphaned.\r\n const onParentUnload = () => {\r\n if (!popupWindow.closed) {\r\n popupWindow.close();\r\n }\r\n };\r\n window.addEventListener(\"unload\", onParentUnload, { once: true });\r\n\r\n return {\r\n popupWindow,\r\n hostElement,\r\n dispose: cleanup,\r\n };\r\n}\r\n"]}
|
|
@@ -2,14 +2,26 @@ import { type MouseEvent } from "react";
|
|
|
2
2
|
import { type FluentIcon } from "@fluentui/react-icons";
|
|
3
3
|
import { type BasePrimitiveProps } from "./primitive.js";
|
|
4
4
|
export type ButtonProps = BasePrimitiveProps & {
|
|
5
|
+
/** Callback invoked when the button is clicked. */
|
|
5
6
|
onClick?: (e?: MouseEvent<HTMLButtonElement>) => unknown | Promise<unknown>;
|
|
7
|
+
/** Optional icon rendered inside the button. */
|
|
6
8
|
icon?: FluentIcon;
|
|
9
|
+
/** Fluent button appearance. */
|
|
7
10
|
appearance?: "subtle" | "transparent" | "primary" | "secondary";
|
|
11
|
+
/** Optional visible button label. */
|
|
8
12
|
label?: string;
|
|
13
|
+
/** Optional accessible label when the visible label is absent or insufficient. */
|
|
14
|
+
ariaLabel?: string;
|
|
9
15
|
};
|
|
10
16
|
export declare const Button: import("react").ForwardRefExoticComponent<BasePrimitiveProps & {
|
|
17
|
+
/** Callback invoked when the button is clicked. */
|
|
11
18
|
onClick?: (e?: MouseEvent<HTMLButtonElement>) => unknown | Promise<unknown>;
|
|
19
|
+
/** Optional icon rendered inside the button. */
|
|
12
20
|
icon?: FluentIcon;
|
|
21
|
+
/** Fluent button appearance. */
|
|
13
22
|
appearance?: "subtle" | "transparent" | "primary" | "secondary";
|
|
23
|
+
/** Optional visible button label. */
|
|
14
24
|
label?: string;
|
|
25
|
+
/** Optional accessible label when the visible label is absent or insufficient. */
|
|
26
|
+
ariaLabel?: string;
|
|
15
27
|
} & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -16,7 +16,7 @@ export const Button = forwardRef((props, ref) => {
|
|
|
16
16
|
const { size } = useContext(ToolContext);
|
|
17
17
|
const classes = useButtonStyles();
|
|
18
18
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
19
|
-
const { icon: Icon, label, onClick, disabled, className, title, ...buttonProps } = props;
|
|
19
|
+
const { icon: Icon, label, onClick, disabled, className, title, ariaLabel, ...buttonProps } = props;
|
|
20
20
|
const [isOnClickBusy, setIsOnClickBusy] = useState(false);
|
|
21
21
|
const handleOnClick = useCallback(async (e) => {
|
|
22
22
|
const result = onClick?.(e);
|
|
@@ -31,7 +31,7 @@ export const Button = forwardRef((props, ref) => {
|
|
|
31
31
|
}
|
|
32
32
|
}, [onClick]);
|
|
33
33
|
const iconClass = size === "small" ? classes.smallIcon : classes.mediumIcon;
|
|
34
|
-
return (_jsx(Tooltip, { content: title ?? "", children: _jsx(FluentButton, { ref: ref, iconPosition: "after", ...buttonProps, className: className, size: size, icon: isOnClickBusy ? _jsx(Spinner, { size: "extra-tiny" }) : Icon && _jsx(Icon, { className: iconClass }), onClick: handleOnClick, disabled: disabled || isOnClickBusy, children: label && props.label }) }));
|
|
34
|
+
return (_jsx(Tooltip, { content: title ?? "", children: _jsx(FluentButton, { ref: ref, iconPosition: "after", ...buttonProps, className: className, size: size, "aria-label": ariaLabel ?? (!label ? title : undefined), icon: isOnClickBusy ? _jsx(Spinner, { size: "extra-tiny" }) : Icon && _jsx(Icon, { className: iconClass }), onClick: handleOnClick, disabled: disabled || isOnClickBusy, children: label && props.label }) }));
|
|
35
35
|
});
|
|
36
36
|
Button.displayName = "Button";
|
|
37
37
|
//# sourceMappingURL=button.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/button.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACzF,OAAO,EAAmB,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvF,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,eAAe,GAAG,UAAU,CAAC;IAC/B,SAAS,EAAE;QACP,QAAQ,EAAE,QAAQ,CAAC,IAAI;KAC1B;IACD,UAAU,EAAE;QACR,QAAQ,EAAE,QAAQ,CAAC,IAAI;KAC1B;CACJ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"button.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/button.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACzF,OAAO,EAAmB,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvF,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,eAAe,GAAG,UAAU,CAAC;IAC/B,SAAS,EAAE;QACP,QAAQ,EAAE,QAAQ,CAAC,IAAI;KAC1B;IACD,UAAU,EAAE;QACR,QAAQ,EAAE,QAAQ,CAAC,IAAI;KAC1B;CACJ,CAAC,CAAC;AAeH,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAiC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IAC5E,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,eAAe,EAAE,CAAC;IAClC,gEAAgE;IAChE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,GAAG,KAAK,CAAC;IAEpG,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1D,MAAM,aAAa,GAAG,WAAW,CAC7B,KAAK,EAAE,CAAgC,EAAE,EAAE;QACvC,MAAM,MAAM,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;YAC5B,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACvB,IAAI,CAAC;gBACD,MAAM,MAAM,CAAC;YACjB,CAAC;oBAAS,CAAC;gBACP,gBAAgB,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;QACL,CAAC;IACL,CAAC,EACD,CAAC,OAAO,CAAC,CACZ,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;IAE5E,OAAO,CACH,KAAC,OAAO,IAAC,OAAO,EAAE,KAAK,IAAI,EAAE,YACzB,KAAC,YAAY,IACT,GAAG,EAAE,GAAG,EACR,YAAY,EAAC,OAAO,KAChB,WAAW,EACf,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,IAAI,gBACE,SAAS,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,EACrD,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,KAAC,OAAO,IAAC,IAAI,EAAC,YAAY,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,KAAC,IAAI,IAAC,SAAS,EAAE,SAAS,GAAI,EAC5F,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,QAAQ,IAAI,aAAa,YAElC,KAAK,IAAI,KAAK,CAAC,KAAK,GACV,GACT,CACb,CAAC;AACN,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC","sourcesContent":["import { Button as FluentButton, makeStyles, Spinner } from \"@fluentui/react-components\";\r\nimport { type MouseEvent, forwardRef, useCallback, useContext, useState } from \"react\";\r\nimport { type FluentIcon } from \"@fluentui/react-icons\";\r\nimport { type BasePrimitiveProps } from \"./primitive\";\r\nimport { ToolContext } from \"../hoc/fluentToolWrapper\";\r\nimport { TokenMap } from \"./utils\";\r\nimport { Tooltip } from \"./tooltip\";\r\n\r\nconst useButtonStyles = makeStyles({\r\n smallIcon: {\r\n fontSize: TokenMap.px16,\r\n },\r\n mediumIcon: {\r\n fontSize: TokenMap.px20,\r\n },\r\n});\r\n\r\nexport type ButtonProps = BasePrimitiveProps & {\r\n /** Callback invoked when the button is clicked. */\r\n onClick?: (e?: MouseEvent<HTMLButtonElement>) => unknown | Promise<unknown>;\r\n /** Optional icon rendered inside the button. */\r\n icon?: FluentIcon;\r\n /** Fluent button appearance. */\r\n appearance?: \"subtle\" | \"transparent\" | \"primary\" | \"secondary\";\r\n /** Optional visible button label. */\r\n label?: string;\r\n /** Optional accessible label when the visible label is absent or insufficient. */\r\n ariaLabel?: string;\r\n};\r\n\r\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {\r\n const { size } = useContext(ToolContext);\r\n const classes = useButtonStyles();\r\n // eslint-disable-next-line @typescript-eslint/naming-convention\r\n const { icon: Icon, label, onClick, disabled, className, title, ariaLabel, ...buttonProps } = props;\r\n\r\n const [isOnClickBusy, setIsOnClickBusy] = useState(false);\r\n const handleOnClick = useCallback(\r\n async (e: MouseEvent<HTMLButtonElement>) => {\r\n const result = onClick?.(e);\r\n if (result instanceof Promise) {\r\n setIsOnClickBusy(true);\r\n try {\r\n await result;\r\n } finally {\r\n setIsOnClickBusy(false);\r\n }\r\n }\r\n },\r\n [onClick]\r\n );\r\n\r\n const iconClass = size === \"small\" ? classes.smallIcon : classes.mediumIcon;\r\n\r\n return (\r\n <Tooltip content={title ?? \"\"}>\r\n <FluentButton\r\n ref={ref}\r\n iconPosition=\"after\"\r\n {...buttonProps}\r\n className={className}\r\n size={size}\r\n aria-label={ariaLabel ?? (!label ? title : undefined)}\r\n icon={isOnClickBusy ? <Spinner size=\"extra-tiny\" /> : Icon && <Icon className={iconClass} />}\r\n onClick={handleOnClick}\r\n disabled={disabled || isOnClickBusy}\r\n >\r\n {label && props.label}\r\n </FluentButton>\r\n </Tooltip>\r\n );\r\n});\r\n\r\nButton.displayName = \"Button\";\r\n"]}
|
|
@@ -29,6 +29,6 @@ export const Dialog = (props) => {
|
|
|
29
29
|
if (!data.open && onDismiss) {
|
|
30
30
|
onDismiss();
|
|
31
31
|
}
|
|
32
|
-
}, children: _jsx(DialogSurface, { children: _jsxs(DialogBody, { children: [_jsx(DialogTitle, { action: onDismiss ? (_jsx(DialogTrigger, { action: "close", children: _jsx(Button, { appearance: "subtle",
|
|
32
|
+
}, children: _jsx(DialogSurface, { children: _jsxs(DialogBody, { children: [_jsx(DialogTitle, { action: onDismiss ? (_jsx(DialogTrigger, { action: "close", children: _jsx(Button, { appearance: "subtle", ariaLabel: "close", icon: DismissRegular }) })) : undefined, children: title }), _jsx(DialogContent, { children: children }), actions && actions.length > 0 && (_jsx(DialogActions, { children: actions.map((action, index) => (_jsx(Button, { appearance: action.appearance ?? "secondary", onClick: action.onClick, label: action.label }, index))) }))] }) }) }));
|
|
33
33
|
};
|
|
34
34
|
//# sourceMappingURL=dialog.js.map
|