@gooddata/sdk-ui-pluggable-host 11.51.0-alpha.5 → 11.51.0
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/esm/components/HostUiContainer.d.ts.map +1 -1
- package/esm/components/HostUiContainer.js +20 -6
- package/esm/components/navigationGuard.d.ts +10 -0
- package/esm/components/navigationGuard.d.ts.map +1 -0
- package/esm/components/navigationGuard.js +25 -0
- package/esm/loader/routing.d.ts +2 -0
- package/esm/loader/routing.d.ts.map +1 -1
- package/esm/loader/routing.js +2 -2
- package/esm/ui/HostChrome.d.ts.map +1 -1
- package/esm/ui/HostChrome.js +12 -3
- package/esm/ui/PluggableApplicationRenderer.d.ts +8 -2
- package/esm/ui/PluggableApplicationRenderer.d.ts.map +1 -1
- package/esm/ui/PluggableApplicationRenderer.js +32 -5
- package/esm/ui/appMenuItems.d.ts.map +1 -1
- package/esm/ui/appMenuItems.js +4 -10
- package/esm/ui/chromeHelpers.d.ts +9 -0
- package/esm/ui/chromeHelpers.d.ts.map +1 -1
- package/esm/ui/chromeHelpers.js +15 -0
- package/package.json +17 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HostUiContainer.d.ts","sourceRoot":"","sources":["../../src/components/HostUiContainer.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAA0B,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,
|
|
1
|
+
{"version":3,"file":"HostUiContainer.d.ts","sourceRoot":"","sources":["../../src/components/HostUiContainer.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAA0B,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAGH,KAAK,gBAAgB,EAExB,MAAM,2CAA2C,CAAC;AAiBnD,OAAO,wBAAwB,CAAC;AAGhC,MAAM,WAAW,qBAAqB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,IAAI,EAAE,gCAAgC,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,qBAAqB,2CA+S7F"}
|
|
@@ -12,6 +12,7 @@ import { HostIntlProvider } from "../ui/HostIntlProvider.js";
|
|
|
12
12
|
import { PluggableApplicationRenderer } from "../ui/PluggableApplicationRenderer.js";
|
|
13
13
|
import { resolveHostUiModule } from "../ui/resolveHostUiModule.js";
|
|
14
14
|
import "./HostUiContainer.scss";
|
|
15
|
+
import { runGuardedNavigation } from "./navigationGuard.js";
|
|
15
16
|
/**
|
|
16
17
|
* Mounts the host UI module into a container div, then renders the active
|
|
17
18
|
* pluggable application into the host's app slot. Handles all lifecycle
|
|
@@ -63,6 +64,7 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
|
|
|
63
64
|
// app mount handle) and read by HostChat's chat so embedded apps can handle link clicks in-app.
|
|
64
65
|
const appAiLinkClickRef = useRef(undefined);
|
|
65
66
|
const onAppLinkClick = useCallback((link) => appAiLinkClickRef.current?.(link) ?? false, []);
|
|
67
|
+
const appNavigationRequestRef = useRef(undefined);
|
|
66
68
|
const activeAppRef = useAutoupdateRef(activeInternalApplication);
|
|
67
69
|
const onHeaderChange = useCallback((appId, header) => {
|
|
68
70
|
if (activeAppRef.current?.id === appId) {
|
|
@@ -74,11 +76,19 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
|
|
|
74
76
|
setPageTitle(title);
|
|
75
77
|
}
|
|
76
78
|
}, [activeAppRef]);
|
|
77
|
-
// Stable navigation callbacks that always use the latest router navigate
|
|
79
|
+
// Stable navigation callbacks that always use the latest router navigate.
|
|
80
|
+
// Both must stay stable: the host UI module captures them once at mount, so a changing dependency
|
|
81
|
+
// would leave it calling a stale closure.
|
|
78
82
|
const navigateRef = useAutoupdateRef(routerNavigate);
|
|
83
|
+
// `replace` stays unguarded: it serves programmatic redirects, not user intent.
|
|
79
84
|
const navigate = useCallback((url) => {
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
runGuardedNavigation({
|
|
86
|
+
url,
|
|
87
|
+
currentPathname: latestMountStateRef.current.pathname,
|
|
88
|
+
guardRef: appNavigationRequestRef,
|
|
89
|
+
navigate: (target) => void navigateRef.current(target),
|
|
90
|
+
});
|
|
91
|
+
}, [navigateRef, latestMountStateRef]);
|
|
82
92
|
const replace = useCallback((url) => {
|
|
83
93
|
void navigateRef.current(url, { replace: true });
|
|
84
94
|
}, [navigateRef]);
|
|
@@ -130,8 +140,12 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
|
|
|
130
140
|
appRootRef.current = undefined;
|
|
131
141
|
const handle = handleRef.current;
|
|
132
142
|
handleRef.current = undefined;
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
// This cleanup runs synchronously inside the commit of the root that renders this
|
|
144
|
+
// component, and unmount() is synchronous — doing it here re-enters React mid-render.
|
|
145
|
+
queueMicrotask(() => {
|
|
146
|
+
appRoot?.unmount();
|
|
147
|
+
handle?.unmount();
|
|
148
|
+
});
|
|
135
149
|
};
|
|
136
150
|
// Mount only once; updates are pushed via handle
|
|
137
151
|
}, [latestMountStateRef, navigationMountRef]);
|
|
@@ -200,7 +214,7 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
|
|
|
200
214
|
return;
|
|
201
215
|
}
|
|
202
216
|
if (activeInternalApplication) {
|
|
203
|
-
appRootRef.current.render(_jsx(HostIntlProvider, { locale: resolveLocale(ctx.preferredLocale), children: _jsx(PluggableApplicationRenderer, { app: activeInternalApplication, ctx: ctx, pathname: pathname, aiAssistantOpen: aiAssistantOpen, onOpenAiAssistant: requestOpenAi, onCloseAiAssistant: requestCloseAi, onAiAssistantContext: setAiAssistantContext, aiLinkClickHandlerRef: appAiLinkClickRef, onHeaderChange: onHeaderChange, onDocumentTitleChange: onDocumentTitleChange }, activeInternalApplication.id) }));
|
|
217
|
+
appRootRef.current.render(_jsx(HostIntlProvider, { locale: resolveLocale(ctx.preferredLocale), children: _jsx(PluggableApplicationRenderer, { app: activeInternalApplication, ctx: ctx, pathname: pathname, aiAssistantOpen: aiAssistantOpen, onOpenAiAssistant: requestOpenAi, onCloseAiAssistant: requestCloseAi, onAiAssistantContext: setAiAssistantContext, aiLinkClickHandlerRef: appAiLinkClickRef, navigationRequestRef: appNavigationRequestRef, onHeaderChange: onHeaderChange, onDocumentTitleChange: onDocumentTitleChange }, activeInternalApplication.id) }));
|
|
204
218
|
}
|
|
205
219
|
else {
|
|
206
220
|
appRootRef.current.render(null);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
import { type IHostNavigationRequest } from "@gooddata/sdk-pluggable-application-model";
|
|
3
|
+
export declare function isSameDestination(targetUrl: string, currentPathname: string): boolean;
|
|
4
|
+
export declare function runGuardedNavigation(options: {
|
|
5
|
+
url: string;
|
|
6
|
+
currentPathname: string;
|
|
7
|
+
guardRef: RefObject<((request: IHostNavigationRequest) => boolean) | undefined>;
|
|
8
|
+
navigate: (url: string) => void;
|
|
9
|
+
}): void;
|
|
10
|
+
//# sourceMappingURL=navigationGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigationGuard.d.ts","sourceRoot":"","sources":["../../src/components/navigationGuard.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAKxF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAKrF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAChF,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,GAAG,IAAI,CAmBP"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { normalizePath, stripEmbedPrefix } from "../loader/routing.js";
|
|
3
|
+
// Pathname is the most the host can compare: it cannot see the module's in-app route.
|
|
4
|
+
export function isSameDestination(targetUrl, currentPathname) {
|
|
5
|
+
const targetPathname = targetUrl.split(/[?#]/)[0] ?? "";
|
|
6
|
+
return (normalizePath(stripEmbedPrefix(targetPathname)) === normalizePath(stripEmbedPrefix(currentPathname)));
|
|
7
|
+
}
|
|
8
|
+
export function runGuardedNavigation(options) {
|
|
9
|
+
const { url, currentPathname, guardRef, navigate } = options;
|
|
10
|
+
const guard = guardRef.current;
|
|
11
|
+
let consumed = false;
|
|
12
|
+
const proceed = () => {
|
|
13
|
+
// An application can retain `proceed`: a second call would push a duplicate history entry, and
|
|
14
|
+
// a call surviving into another mount would navigate on its behalf.
|
|
15
|
+
if (consumed || guardRef.current !== guard) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
consumed = true;
|
|
19
|
+
navigate(url);
|
|
20
|
+
};
|
|
21
|
+
if (!isSameDestination(url, currentPathname) && guard?.({ url, proceed })) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
proceed();
|
|
25
|
+
}
|
package/esm/loader/routing.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ApplicationScope, type PluggableApplicationRegistryItem } from "@gooddata/sdk-model";
|
|
2
2
|
import { type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
|
|
3
|
+
export declare function stripEmbedPrefix(pathname: string): string;
|
|
3
4
|
/**
|
|
4
5
|
* Returns the application scope for the given URL path, or undefined if the path
|
|
5
6
|
* does not match a known scope.
|
|
@@ -10,6 +11,7 @@ export declare function getApplicationScopeFromPath(path: string): ApplicationSc
|
|
|
10
11
|
* does not contain a workspace id.
|
|
11
12
|
*/
|
|
12
13
|
export declare function getWorkspaceIdFromPath(pathname: string | undefined): string | undefined;
|
|
14
|
+
export declare function normalizePath(path: string): string;
|
|
13
15
|
/**
|
|
14
16
|
* Substitutes `{workspaceId}` placeholders in an external app URL with the
|
|
15
17
|
* currently resolved workspace id. URLs with no placeholder pass through
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/loader/routing.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,gCAAgC,EAIxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/loader/routing.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,gCAAgC,EAIxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAIlF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAYtF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEvF;AAMD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAgCD;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC7C,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,gBAAgB,EACrB,QAAQ,CAAC,EAAE,MAAM,GAClB,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAC9B,GAAG,EAAE,gCAAgC,EACrC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,CAAC,EAAE,MAAM,GAClB,MAAM,CAWR;AAED,wBAAgB,wBAAwB,CACpC,GAAG,EAAE,gCAAgC,EACrC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,GACjB,OAAO,CAQT;AAED,wBAAgB,4BAA4B,CACxC,IAAI,EAAE,gCAAgC,EAAE,EACxC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,GACjB,gCAAgC,GAAG,SAAS,CAK9C"}
|
package/esm/loader/routing.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
2
|
import { isExternalPluggableApplicationRegistryItem, isLocalPluggableApplicationRegistryItem, isRemotePluggableApplicationRegistryItem, } from "@gooddata/sdk-model";
|
|
3
3
|
const WORKSPACE_PATH_PATTERN = /^\/workspace\/(?<workspaceId>[^/]+)(?:\/|$)/;
|
|
4
|
-
function stripEmbedPrefix(pathname) {
|
|
4
|
+
export function stripEmbedPrefix(pathname) {
|
|
5
5
|
return pathname.startsWith("/embedded/") ? pathname.slice("/embedded".length) : pathname;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
@@ -29,7 +29,7 @@ export function getWorkspaceIdFromPath(pathname) {
|
|
|
29
29
|
function ensureLeadingSlash(path) {
|
|
30
30
|
return path.startsWith("/") ? path : `/${path}`;
|
|
31
31
|
}
|
|
32
|
-
function normalizePath(path) {
|
|
32
|
+
export function normalizePath(path) {
|
|
33
33
|
const prefixed = ensureLeadingSlash(path);
|
|
34
34
|
if (prefixed === "/") {
|
|
35
35
|
return prefixed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EAEH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EACxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AA+BnD,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;
|
|
1
|
+
{"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EAEH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EACxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AA+BnD,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;AAelE,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,EACvB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,SAAS,EAAE,UAAU,EACrB,aAAa,EACb,YAAmB,EACnB,YAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,QAAQ,EACX,EAAE,gBAAgB,2CA0OlB"}
|
package/esm/ui/HostChrome.js
CHANGED
|
@@ -11,7 +11,7 @@ import { getAppLifecycleCallbacks, preloadPluggableApplication, } from "../loade
|
|
|
11
11
|
import { getActiveInternalApplication, getApplicationHref } from "../loader/routing.js";
|
|
12
12
|
import { getBackend } from "../platformContext/backend.js";
|
|
13
13
|
import { buildAppMenu, getLocalizedTitle } from "./appMenuItems.js";
|
|
14
|
-
import { getUserDisplayName, swapWorkspaceInPath } from "./chromeHelpers.js";
|
|
14
|
+
import { getUserDisplayName, isPlainLeftClick, swapWorkspaceInPath } from "./chromeHelpers.js";
|
|
15
15
|
import { b, e } from "./hostChromeBem.js";
|
|
16
16
|
import { HostIntlProvider } from "./HostIntlProvider.js";
|
|
17
17
|
import { HostNotificationDispatcher } from "./HostNotificationDispatcher.js";
|
|
@@ -35,6 +35,8 @@ const LOGOUT_MENU_ITEM_KEY = "gs.header.logout";
|
|
|
35
35
|
// favicon is configured (see faviconUrl below).
|
|
36
36
|
const initialFaviconUrl = (typeof document !== "undefined" && document.querySelector("link[rel~='icon']")?.getAttribute("href")) ||
|
|
37
37
|
"/favicon.ico";
|
|
38
|
+
// Switches the host scope to organization; the first organization app is then chosen by redirect logic.
|
|
39
|
+
const LOGO_HREF = "/organization";
|
|
38
40
|
export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, onReplace: _onReplace, headerOptions, notification = null, showChatItem = false, onChatToggle, onAskAiAssistant, appPageTitle, children, }) {
|
|
39
41
|
const locale = resolveLocale(ctx.preferredLocale);
|
|
40
42
|
const userName = getUserDisplayName(ctx.user);
|
|
@@ -100,6 +102,14 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
|
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
}, [resolvedApplications, ctx, pathname]);
|
|
105
|
+
// Without this the anchor would do a full page load instead of a client-side navigation.
|
|
106
|
+
const handleLogoClick = useCallback((e) => {
|
|
107
|
+
if (!isPlainLeftClick(e)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
onNavigate(LOGO_HREF);
|
|
112
|
+
}, [onNavigate]);
|
|
103
113
|
const handleWorkspaceSelect = useCallback((workspace) => {
|
|
104
114
|
const newId = workspace.id;
|
|
105
115
|
if (!newId) {
|
|
@@ -143,7 +153,6 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
|
|
|
143
153
|
const activeColor = ctx.theme?.header?.activeColor ?? defaultHeaderTheme.activeColor;
|
|
144
154
|
const hideChrome = ctx.embeddingMode === "iframe" || ctx.isExportMode === true;
|
|
145
155
|
return (_jsx(HostIntlProvider, { locale: locale, additionalMessages: appMessages, children: _jsx(BackendProvider, { backend: getBackend(), children: _jsx(ToastsCenterContextProvider, { children: _jsxs("div", { className: b(), children: [
|
|
146
|
-
_jsx(DocumentHeader, { pageTitle: documentPageTitle, brandTitle: documentBrandTitle, faviconUrl: faviconUrl, appleTouchIconUrl: ctx.whiteLabeling?.appleTouchIconUrl }), hideChrome ? null : (_jsx("div", { className: e("header"), onMouseOver: handleHeaderMouseOver, children: _jsx(AppHeader, { logoUrl: ctx.whiteLabeling?.logoUrl || defaultLogoUrl, logoHref:
|
|
147
|
-
, logoTitle: logoTitle, headerColor: headerColor, headerTextColor: headerTextColor, activeColor: activeColor, userName: userName, organizationName: ctx.organization?.title, isAccessibilityCompliant: true, workspacePicker: workspacePicker, menuItemsGroups: menuItemsGroups, helpMenuItems: helpMenuItems, accountMenuItems: accountMenuItems, onMenuItemClick: handleMenuItemClick, showUpsellButton: pricing.isTrial, onUpsellButtonClick: pricing.onUpsellButtonClick, expiredDate: pricing.isTrial ? pricing.expiredDate : undefined, search: search.element, showChatItem: showChatItem, onChatItemClick: onChatToggle, notificationsPanel: ({ isMobile, closeNotificationsOverlay }) => (_jsx(AppHeaderNotifications, { locale: locale, isMobile: isMobile, closeNotificationsOverlay: closeNotificationsOverlay, useAsOfDateParam: true, enableExportToDocumentStorage: ctx.userSettings.enableExportToDocumentStorage ?? false })) }) })), _jsx("main", { className: e("content"), children: children }), pricing.element, _jsx(HostNotificationDispatcher, { notification: notification })
|
|
156
|
+
_jsx(DocumentHeader, { pageTitle: documentPageTitle, brandTitle: documentBrandTitle, faviconUrl: faviconUrl, appleTouchIconUrl: ctx.whiteLabeling?.appleTouchIconUrl }), hideChrome ? null : (_jsx("div", { className: e("header"), onMouseOver: handleHeaderMouseOver, children: _jsx(AppHeader, { logoUrl: ctx.whiteLabeling?.logoUrl || defaultLogoUrl, logoHref: LOGO_HREF, onLogoClick: handleLogoClick, logoTitle: logoTitle, headerColor: headerColor, headerTextColor: headerTextColor, activeColor: activeColor, userName: userName, organizationName: ctx.organization?.title, isAccessibilityCompliant: true, workspacePicker: workspacePicker, menuItemsGroups: menuItemsGroups, helpMenuItems: helpMenuItems, accountMenuItems: accountMenuItems, onMenuItemClick: handleMenuItemClick, showUpsellButton: pricing.isTrial, onUpsellButtonClick: pricing.onUpsellButtonClick, expiredDate: pricing.isTrial ? pricing.expiredDate : undefined, search: search.element, showChatItem: showChatItem, onChatItemClick: onChatToggle, notificationsPanel: ({ isMobile, closeNotificationsOverlay }) => (_jsx(AppHeaderNotifications, { locale: locale, isMobile: isMobile, closeNotificationsOverlay: closeNotificationsOverlay, useAsOfDateParam: true, enableExportToDocumentStorage: ctx.userSettings.enableExportToDocumentStorage ?? false })) }) })), _jsx("main", { className: e("content"), children: children }), pricing.element, _jsx(HostNotificationDispatcher, { notification: notification })
|
|
148
157
|
] }) }) }) }));
|
|
149
158
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type RefObject } from "react";
|
|
2
2
|
import { type IGenAIUserContext, type PluggableApplicationRegistryItem } from "@gooddata/sdk-model";
|
|
3
|
-
import { type IAppHeaderOptions, type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
|
|
3
|
+
import { type IAppHeaderOptions, type IHostNavigationRequest, type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
|
|
4
4
|
import "./PluggableApplicationRenderer.scss";
|
|
5
5
|
export interface IPluggableApplicationRendererProps {
|
|
6
6
|
app: PluggableApplicationRegistryItem;
|
|
@@ -30,8 +30,14 @@ export interface IPluggableApplicationRendererProps {
|
|
|
30
30
|
itemUrl?: string;
|
|
31
31
|
newTab?: boolean;
|
|
32
32
|
}) => boolean) | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Ref the renderer populates with a handler that asks the active app's mount handle
|
|
35
|
+
* (`onHostNavigationRequested`) whether the host may navigate away. Must be referentially
|
|
36
|
+
* stable — a new ref object remounts the application.
|
|
37
|
+
*/
|
|
38
|
+
navigationRequestRef?: RefObject<((request: IHostNavigationRequest) => boolean) | undefined>;
|
|
33
39
|
onHeaderChange?: (appId: string, header: IAppHeaderOptions) => void;
|
|
34
40
|
onDocumentTitleChange?: (appId: string, pageTitle: string | undefined) => void;
|
|
35
41
|
}
|
|
36
|
-
export declare function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onOpenAiAssistant, onCloseAiAssistant, onAiAssistantContext, aiLinkClickHandlerRef, onHeaderChange, onDocumentTitleChange }: IPluggableApplicationRendererProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export declare function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onOpenAiAssistant, onCloseAiAssistant, onAiAssistantContext, aiLinkClickHandlerRef, navigationRequestRef, onHeaderChange, onDocumentTitleChange }: IPluggableApplicationRendererProps): import("react/jsx-runtime").JSX.Element;
|
|
37
43
|
//# sourceMappingURL=PluggableApplicationRenderer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluggableApplicationRenderer.d.ts","sourceRoot":"","sources":["../../src/ui/PluggableApplicationRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAqD,MAAM,OAAO,CAAC;AAI1F,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EASxB,MAAM,2CAA2C,CAAC;AAcnD,OAAO,qCAAqC,CAAC;AAkB7C,MAAM,WAAW,kCAAkC;IAC/C,GAAG,EAAE,gCAAgC,CAAC;IACtC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,CAChB,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,oHAAoH;IACpH,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,WAAW,CAAC,EAAE,iBAAiB,CAAC;KACnC,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,qBAAqB,CAAC,EAAE,SAAS,CAC7B,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,SAAS,CACtG,CAAC;IACF,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpE,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAClF;AAED,wBAAgB,4BAA4B,CAAC,EACzC,GAAG,EACH,GAAG,EACH,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,qBAAqB,EACxB,EAAE,kCAAkC,
|
|
1
|
+
{"version":3,"file":"PluggableApplicationRenderer.d.ts","sourceRoot":"","sources":["../../src/ui/PluggableApplicationRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAqD,MAAM,OAAO,CAAC;AAI1F,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EASxB,MAAM,2CAA2C,CAAC;AAcnD,OAAO,qCAAqC,CAAC;AAkB7C,MAAM,WAAW,kCAAkC;IAC/C,GAAG,EAAE,gCAAgC,CAAC;IACtC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,CAChB,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,oHAAoH;IACpH,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,WAAW,CAAC,EAAE,iBAAiB,CAAC;KACnC,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,qBAAqB,CAAC,EAAE,SAAS,CAC7B,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,SAAS,CACtG,CAAC;IACF;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC7F,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpE,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAClF;AAED,wBAAgB,4BAA4B,CAAC,EACzC,GAAG,EACH,GAAG,EACH,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACxB,EAAE,kCAAkC,2CA8RpC"}
|
|
@@ -20,7 +20,7 @@ const SECURITY_FAILURE_MESSAGES = {
|
|
|
20
20
|
"build-expired": defineMessage({ id: "gs.host.error.appBuildExpired" }),
|
|
21
21
|
"metadata-missing": defineMessage({ id: "gs.host.error.appSecurityMetadataMissing" }),
|
|
22
22
|
};
|
|
23
|
-
export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onOpenAiAssistant, onCloseAiAssistant, onAiAssistantContext, aiLinkClickHandlerRef, onHeaderChange, onDocumentTitleChange, }) {
|
|
23
|
+
export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onOpenAiAssistant, onCloseAiAssistant, onAiAssistantContext, aiLinkClickHandlerRef, navigationRequestRef, onHeaderChange, onDocumentTitleChange, }) {
|
|
24
24
|
const intl = useIntl();
|
|
25
25
|
const intlRef = useAutoupdateRef(intl);
|
|
26
26
|
const ctxRef = useAutoupdateRef(ctx);
|
|
@@ -83,12 +83,14 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
|
|
|
83
83
|
let cancelled = false;
|
|
84
84
|
const mountId = `${app.id}:${Date.now()}`;
|
|
85
85
|
const totalStart = now();
|
|
86
|
+
// Identity must change per mount — the host rejects a `proceed` retained from an earlier one.
|
|
87
|
+
const navigationRequest = (request) => mountHandleRef.current?.onHostNavigationRequested?.(request) ?? false;
|
|
86
88
|
const prevHandle = mountHandleRef.current;
|
|
87
89
|
mountHandleRef.current = undefined;
|
|
88
90
|
mountedAppRef.current = undefined;
|
|
89
91
|
setViewState({ state: "loading" });
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
+
// unmount() is synchronous, so calling it inline can re-enter React while the parent render
|
|
93
|
+
// that swapped this application out is still in progress.
|
|
92
94
|
if (prevHandle) {
|
|
93
95
|
queueMicrotask(() => prevHandle.unmount());
|
|
94
96
|
}
|
|
@@ -126,6 +128,9 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
|
|
|
126
128
|
onHeaderChange: (header) => onHeaderChangeRef.current?.(app.id, header),
|
|
127
129
|
});
|
|
128
130
|
mountedAppRef.current = { app, loadedApp };
|
|
131
|
+
if (navigationRequestRef) {
|
|
132
|
+
navigationRequestRef.current = navigationRequest;
|
|
133
|
+
}
|
|
129
134
|
lifecycle?.onMountCompleted?.(app.id, now() - mountStart);
|
|
130
135
|
lifecycle?.onRendered?.(app.id, now() - totalStart);
|
|
131
136
|
setViewState({ state: "ready", validUntil: getSecuredRemoteAppValidUntil(app, loadedApp) });
|
|
@@ -134,6 +139,11 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
|
|
|
134
139
|
if (cancelled) {
|
|
135
140
|
return;
|
|
136
141
|
}
|
|
142
|
+
// An application shown as failed must not veto navigation — its dialog would render
|
|
143
|
+
// into a hidden container.
|
|
144
|
+
if (navigationRequestRef?.current === navigationRequest) {
|
|
145
|
+
navigationRequestRef.current = undefined;
|
|
146
|
+
}
|
|
137
147
|
const errorMessage = mountError instanceof Error ? mountError.message : "Unknown module loading error.";
|
|
138
148
|
console.error(`[host-runtime/renderer] Failed to mount app "${app.id}".`, mountError);
|
|
139
149
|
lifecycle?.onLoadFailed?.(app.id, errorMessage);
|
|
@@ -144,6 +154,10 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
|
|
|
144
154
|
});
|
|
145
155
|
return () => {
|
|
146
156
|
cancelled = true;
|
|
157
|
+
// Only ever clear this mount's own registration, never a newer mount's.
|
|
158
|
+
if (navigationRequestRef?.current === navigationRequest) {
|
|
159
|
+
navigationRequestRef.current = undefined;
|
|
160
|
+
}
|
|
147
161
|
const handle = mountHandleRef.current;
|
|
148
162
|
if (handle) {
|
|
149
163
|
mountHandleRef.current = undefined;
|
|
@@ -154,7 +168,17 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
|
|
|
154
168
|
});
|
|
155
169
|
}
|
|
156
170
|
};
|
|
157
|
-
}, [
|
|
171
|
+
}, [
|
|
172
|
+
app,
|
|
173
|
+
appBasePath,
|
|
174
|
+
ctxRef,
|
|
175
|
+
intlRef,
|
|
176
|
+
onHeaderChangeRef,
|
|
177
|
+
onTelemetryEvent,
|
|
178
|
+
lifecycle,
|
|
179
|
+
onEvent,
|
|
180
|
+
navigationRequestRef,
|
|
181
|
+
]);
|
|
158
182
|
useEffect(() => {
|
|
159
183
|
const mounted = mountedAppRef.current;
|
|
160
184
|
const handle = mountHandleRef.current;
|
|
@@ -171,6 +195,9 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
|
|
|
171
195
|
console.error(`[host-runtime/renderer] Unmounting app "${mounted.app.id}" after context change: ${securityFailure.kind}.`, securityFailure);
|
|
172
196
|
mountHandleRef.current = undefined;
|
|
173
197
|
mountedAppRef.current = undefined;
|
|
198
|
+
if (navigationRequestRef) {
|
|
199
|
+
navigationRequestRef.current = undefined;
|
|
200
|
+
}
|
|
174
201
|
handle.unmount();
|
|
175
202
|
lifecycle?.onUnmounted?.(mounted.app.id);
|
|
176
203
|
lifecycle?.onLoadFailed?.(mounted.app.id, securityFailure.kind);
|
|
@@ -178,7 +205,7 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
|
|
|
178
205
|
return;
|
|
179
206
|
}
|
|
180
207
|
handle.updateContext?.(ctx);
|
|
181
|
-
}, [ctx, intlRef, lifecycle]);
|
|
208
|
+
}, [ctx, intlRef, lifecycle, navigationRequestRef]);
|
|
182
209
|
// Forward the host-owned chat open-state to the mounted app once it is ready, and on every
|
|
183
210
|
// change, so app-side assistant controls stay aligned with the real (host) state (LX-2544).
|
|
184
211
|
useEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appMenuItems.d.ts","sourceRoot":"","sources":["../../src/ui/appMenuItems.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,OAAO,EACZ,KAAK,gCAAgC,EAExC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AACxG,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"appMenuItems.d.ts","sourceRoot":"","sources":["../../src/ui/appMenuItems.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,OAAO,EACZ,KAAK,gCAAgC,EAExC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AACxG,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA2B5D,wBAAgB,iBAAiB,CAC7B,GAAG,EAAE,gCAAgC,EACrC,MAAM,EAAE,OAAO,GAAG,SAAS,GAC5B,MAAM,CAQR;AAED,MAAM,WAAW,cAAc;IAC3B,eAAe,EAAE,eAAe,EAAE,EAAE,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAeD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CACxB,IAAI,EAAE,gCAAgC,EAAE,EACxC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,GAAG,SAAS,GAC5B,cAAc,CA4BhB"}
|
package/esm/ui/appMenuItems.js
CHANGED
|
@@ -2,18 +2,12 @@
|
|
|
2
2
|
import { isExternalPluggableApplicationRegistryItem, } from "@gooddata/sdk-model";
|
|
3
3
|
import { DefaultApplicationId } from "@gooddata/sdk-pluggable-application-model";
|
|
4
4
|
import { getApplicationHref, isInternalAppRouteActive } from "../loader/routing.js";
|
|
5
|
+
import { isPlainLeftClick } from "./chromeHelpers.js";
|
|
5
6
|
function handleExternalAppClick(href) {
|
|
6
7
|
return (event) => {
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// external apps live outside the host SPA, so React Router can't reach them.
|
|
11
|
-
if (event.defaultPrevented ||
|
|
12
|
-
event.button !== 0 ||
|
|
13
|
-
event.metaKey ||
|
|
14
|
-
event.ctrlKey ||
|
|
15
|
-
event.shiftKey ||
|
|
16
|
-
event.altKey) {
|
|
8
|
+
// Suppresses the parent menu's SPA navigation in favour of a full page load: external
|
|
9
|
+
// apps live outside the host SPA, so React Router cannot reach them.
|
|
10
|
+
if (!isPlainLeftClick(event)) {
|
|
17
11
|
return;
|
|
18
12
|
}
|
|
19
13
|
event.preventDefault();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MouseEvent } from "react";
|
|
1
2
|
import { type IUser } from "@gooddata/sdk-model";
|
|
2
3
|
/**
|
|
3
4
|
* Builds the display name shown for the current user in the host header.
|
|
@@ -15,4 +16,12 @@ export declare function getUserDisplayName(user: IUser): string;
|
|
|
15
16
|
* an organization-scoped route).
|
|
16
17
|
*/
|
|
17
18
|
export declare function swapWorkspaceInPath(pathname: string, newWorkspaceId: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether a click on a link may be taken over with `preventDefault`, rather than left to the
|
|
21
|
+
* browser's own handling of the href.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* Keyboard activation of a focused link reports button 0, so it counts as a plain left click.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isPlainLeftClick(event: MouseEvent<Element>): boolean;
|
|
18
27
|
//# sourceMappingURL=chromeHelpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chromeHelpers.d.ts","sourceRoot":"","sources":["../../src/ui/chromeHelpers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,CAStD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAGpF"}
|
|
1
|
+
{"version":3,"file":"chromeHelpers.d.ts","sourceRoot":"","sources":["../../src/ui/chromeHelpers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAExC,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,CAStD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAGpF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,OAAO,CASpE"}
|
package/esm/ui/chromeHelpers.js
CHANGED
|
@@ -27,3 +27,18 @@ export function swapWorkspaceInPath(pathname, newWorkspaceId) {
|
|
|
27
27
|
const replaced = pathname.replace(/^\/workspace\/[^/]+/, `/workspace/${newWorkspaceId}`);
|
|
28
28
|
return replaced === pathname ? `/workspace/${newWorkspaceId}` : replaced;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Whether a click on a link may be taken over with `preventDefault`, rather than left to the
|
|
32
|
+
* browser's own handling of the href.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* Keyboard activation of a focused link reports button 0, so it counts as a plain left click.
|
|
36
|
+
*/
|
|
37
|
+
export function isPlainLeftClick(event) {
|
|
38
|
+
return (!event.defaultPrevented &&
|
|
39
|
+
event.button === 0 &&
|
|
40
|
+
!event.metaKey &&
|
|
41
|
+
!event.ctrlKey &&
|
|
42
|
+
!event.shiftKey &&
|
|
43
|
+
!event.altKey);
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-pluggable-host",
|
|
3
|
-
"version": "11.51.0
|
|
3
|
+
"version": "11.51.0",
|
|
4
4
|
"description": "GoodData SDK runtime for hosting pluggable applications — registry, loader, routing, platform context, default UI chrome",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "GoodData Corporation",
|
|
@@ -29,20 +29,20 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@module-federation/runtime": "2.6.0",
|
|
31
31
|
"lodash-es": "^4.17.23",
|
|
32
|
-
"@gooddata/sdk-backend-
|
|
33
|
-
"@gooddata/sdk-
|
|
34
|
-
"@gooddata/sdk-
|
|
35
|
-
"@gooddata/sdk-backend-tiger": "11.51.0
|
|
36
|
-
"@gooddata/sdk-model": "11.51.0
|
|
37
|
-
"@gooddata/sdk-
|
|
38
|
-
"@gooddata/sdk-
|
|
39
|
-
"@gooddata/sdk-ui-
|
|
40
|
-
"@gooddata/sdk-ui-
|
|
41
|
-
"@gooddata/sdk-ui
|
|
42
|
-
"@gooddata/sdk-ui-
|
|
43
|
-
"@gooddata/
|
|
44
|
-
"@gooddata/sdk-ui-semantic-search": "11.51.0
|
|
45
|
-
"@gooddata/
|
|
32
|
+
"@gooddata/sdk-backend-base": "11.51.0",
|
|
33
|
+
"@gooddata/sdk-embedding": "11.51.0",
|
|
34
|
+
"@gooddata/sdk-backend-spi": "11.51.0",
|
|
35
|
+
"@gooddata/sdk-backend-tiger": "11.51.0",
|
|
36
|
+
"@gooddata/sdk-model": "11.51.0",
|
|
37
|
+
"@gooddata/sdk-pluggable-application-model": "11.51.0",
|
|
38
|
+
"@gooddata/sdk-ui-application-header": "11.51.0",
|
|
39
|
+
"@gooddata/sdk-ui-ext": "11.51.0",
|
|
40
|
+
"@gooddata/sdk-ui-gen-ai": "11.51.0",
|
|
41
|
+
"@gooddata/sdk-ui": "11.51.0",
|
|
42
|
+
"@gooddata/sdk-ui-kit": "11.51.0",
|
|
43
|
+
"@gooddata/sdk-ui-theme-provider": "11.51.0",
|
|
44
|
+
"@gooddata/sdk-ui-semantic-search": "11.51.0",
|
|
45
|
+
"@gooddata/util": "11.51.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@microsoft/api-documenter": "^7.17.0",
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
"vite": "8.0.16",
|
|
86
86
|
"vitest": "4.1.8",
|
|
87
87
|
"vitest-dom": "0.1.1",
|
|
88
|
-
"@gooddata/eslint-config": "11.51.0
|
|
89
|
-
"@gooddata/oxlint-config": "11.51.0
|
|
88
|
+
"@gooddata/eslint-config": "11.51.0",
|
|
89
|
+
"@gooddata/oxlint-config": "11.51.0"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
92
92
|
"react": ">=18.3.1",
|