@gooddata/sdk-ui-pluggable-host 11.51.0-alpha.4 → 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.
@@ -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,EAEH,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,2CAqS7F"}
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"}
@@ -10,8 +10,9 @@ import { getActiveInternalApplication } from "../loader/routing.js";
10
10
  import { HostChat, } from "../ui/HostChat.js";
11
11
  import { HostIntlProvider } from "../ui/HostIntlProvider.js";
12
12
  import { PluggableApplicationRenderer } from "../ui/PluggableApplicationRenderer.js";
13
- import "./HostUiContainer.scss";
14
13
  import { resolveHostUiModule } from "../ui/resolveHostUiModule.js";
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,10 +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, []);
66
- // The active app's event handler, populated by PluggableApplicationRenderer (which holds the
67
- // app mount handle) and read by HostChat's chat so embedded apps can handle events in-app.
68
- const appEventReceiveRef = useRef(undefined);
69
- const onAppEventReceive = useCallback((event) => appEventReceiveRef.current?.(event), []);
67
+ const appNavigationRequestRef = useRef(undefined);
70
68
  const activeAppRef = useAutoupdateRef(activeInternalApplication);
71
69
  const onHeaderChange = useCallback((appId, header) => {
72
70
  if (activeAppRef.current?.id === appId) {
@@ -78,11 +76,19 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
78
76
  setPageTitle(title);
79
77
  }
80
78
  }, [activeAppRef]);
81
- // 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.
82
82
  const navigateRef = useAutoupdateRef(routerNavigate);
83
+ // `replace` stays unguarded: it serves programmatic redirects, not user intent.
83
84
  const navigate = useCallback((url) => {
84
- void navigateRef.current(url);
85
- }, [navigateRef]);
85
+ runGuardedNavigation({
86
+ url,
87
+ currentPathname: latestMountStateRef.current.pathname,
88
+ guardRef: appNavigationRequestRef,
89
+ navigate: (target) => void navigateRef.current(target),
90
+ });
91
+ }, [navigateRef, latestMountStateRef]);
86
92
  const replace = useCallback((url) => {
87
93
  void navigateRef.current(url, { replace: true });
88
94
  }, [navigateRef]);
@@ -134,8 +140,12 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
134
140
  appRootRef.current = undefined;
135
141
  const handle = handleRef.current;
136
142
  handleRef.current = undefined;
137
- appRoot?.unmount();
138
- handle?.unmount();
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
+ });
139
149
  };
140
150
  // Mount only once; updates are pushed via handle
141
151
  }, [latestMountStateRef, navigationMountRef]);
@@ -204,7 +214,7 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
204
214
  return;
205
215
  }
206
216
  if (activeInternalApplication) {
207
- 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, aiEventReceiveRef: appEventReceiveRef, 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) }));
208
218
  }
209
219
  else {
210
220
  appRootRef.current.render(null);
@@ -222,5 +232,5 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
222
232
  setAiAssistantContext,
223
233
  ]);
224
234
  return (_jsxs(_Fragment, { children: [
225
- _jsx("div", { ref: containerRef, className: "gd-host-ui-container" }), hostReady && !ctx.isExportMode ? (_jsx(HostChat, { ctx: ctx, resolvedApplications: apps, pathname: pathname, activeAppId: activeInternalApplication?.id, visibility: aiVisibility, context: aiContext, onOpenChange: setAiAssistantOpen, onChatStateChange: onChatStateChange, onAppLinkClick: onAppLinkClick, onAppEventReceive: onAppEventReceive })) : null] }));
235
+ _jsx("div", { ref: containerRef, className: "gd-host-ui-container" }), hostReady && !ctx.isExportMode ? (_jsx(HostChat, { ctx: ctx, resolvedApplications: apps, pathname: pathname, activeAppId: activeInternalApplication?.id, visibility: aiVisibility, context: aiContext, onOpenChange: setAiAssistantOpen, onChatStateChange: onChatStateChange, onAppLinkClick: onAppLinkClick })) : null] }));
226
236
  }
@@ -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
+ }
@@ -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;AAQlF;;;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;AA4CD;;;;;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"}
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"}
@@ -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":"GenAIChat.d.ts","sourceRoot":"","sources":["../../src/ui/GenAIChat.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GenAIChat.d.ts","sourceRoot":"","sources":["../../src/ui/GenAIChat.tsx"],"names":[],"mappings":"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"}
@@ -26,20 +26,15 @@ export function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, a
26
26
  // type (only visualizations did anything). Non-embedded mode uses the connected wrapper's default
27
27
  // open/navigate handler instead.
28
28
  const onLinkClick = useCallback((link) => {
29
- if (!embedded) {
30
- const currentUrl = window.location.pathname + window.location.hash;
31
- const areUrlsEqual = currentUrl === link.itemUrl;
32
- if (link.newTab) {
33
- window.open(link.itemUrl, "_blank");
34
- }
35
- else if (!areUrlsEqual) {
36
- window.location.assign(link.itemUrl);
37
- }
38
- }
39
- onAppLinkClick?.(link);
29
+ onAppLinkClick?.({
30
+ type: link.type,
31
+ id: link.id,
32
+ itemUrl: link.itemUrl,
33
+ newTab: link.newTab,
34
+ });
40
35
  link.preventDefault();
41
- return link.itemUrl;
42
- }, [onAppLinkClick, embedded]);
36
+ return undefined;
37
+ }, [onAppLinkClick]);
43
38
  const handleEvent = useCallback((event) => {
44
39
  switch (event.name) {
45
40
  case "opened":
@@ -84,5 +79,5 @@ export function GenAIChat({ workspaceId, open, onOpen, onClose, askedQuestion, a
84
79
  break;
85
80
  }
86
81
  }, [addError, addSuccess, intl, onEvent]);
87
- return (_jsx(GenAIChatDialogConnected, { backend: backend, workspace: workspaceId, locale: intl.locale, isOpen: open, onOpen: onOpen, onClose: onClose, askedQuestion: askedQuestion, askSeq: askSeq, userContext: userContext, appendToChat: appendToChat, agentId: agentId, replaceUserContext: replaceUserContext, ambientUserContext: ambientUserContext, includeTags: includeTags, excludeTags: excludeTags, canManage: canManageProject, canAnalyze: canAnalyzeProject, canFullControl: canFullControl, settings: settings, dialogPosition: dialogPosition, className: embedded ? EMBEDDED_CHAT_CLASS : undefined, allowNativeLinks: false, onLinkClick: onLinkClick, onEvent: handleEvent, returnFocusTo: embedded ? EMBEDDED_AI_TRIGGER_ID : HEADER_CHAT_BUTTON_ID }));
82
+ return (_jsx(GenAIChatDialogConnected, { backend: backend, workspace: workspaceId, locale: intl.locale, isOpen: open, onOpen: onOpen, onClose: onClose, askedQuestion: askedQuestion, askSeq: askSeq, userContext: userContext, appendToChat: appendToChat, agentId: agentId, replaceUserContext: replaceUserContext, ambientUserContext: ambientUserContext, includeTags: includeTags, excludeTags: excludeTags, canManage: canManageProject, canAnalyze: canAnalyzeProject, canFullControl: canFullControl, settings: settings, dialogPosition: dialogPosition, allowNativeLinks: embedded ? false : undefined, className: embedded ? EMBEDDED_CHAT_CLASS : undefined, onLinkClick: embedded ? onLinkClick : undefined, onEvent: handleEvent, returnFocusTo: embedded ? EMBEDDED_AI_TRIGGER_ID : HEADER_CHAT_BUTTON_ID }));
88
83
  }
@@ -1,6 +1,5 @@
1
1
  import { type IGenAIUserContext, type PluggableApplicationRegistryItem } from "@gooddata/sdk-model";
2
2
  import { type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
3
- import { GenAIChatEvent } from "./GenAIChat.js";
4
3
  import "@gooddata/sdk-ui-gen-ai/styles/css/main.css";
5
4
  /**
6
5
  * AI-assistant open/close/toggle request. `seq` changes on every request so an identical repeat
@@ -59,10 +58,6 @@ export interface IHostChatProps {
59
58
  * visualization as an in-place overlay). Returns true if the app handled it.
60
59
  */
61
60
  onAppLinkClick?: (link: IHostChatLink) => boolean;
62
- /**
63
- * Delegates a chat event receive to the active application.
64
- */
65
- onAppEventReceive?: (event: GenAIChatEvent) => void;
66
61
  }
67
62
  /**
68
63
  * Owns and renders the host's single GenAI chat, decoupled from the host UI module.
@@ -73,5 +68,5 @@ export interface IHostChatProps {
73
68
  * (embedded/export). The header chat button and app-side controls drive it through the `visibility`
74
69
  * and `context` props; it reports its open-state and button state back via callbacks.
75
70
  */
76
- export declare function HostChat({ ctx, resolvedApplications, pathname, activeAppId, visibility, context, onOpenChange, onChatStateChange, onAppLinkClick, onAppEventReceive }: IHostChatProps): import("react/jsx-runtime").JSX.Element;
71
+ export declare function HostChat({ ctx, resolvedApplications, pathname, activeAppId, visibility, context, onOpenChange, onChatStateChange, onAppLinkClick }: IHostChatProps): import("react/jsx-runtime").JSX.Element;
77
72
  //# sourceMappingURL=HostChat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HostChat.d.ts","sourceRoot":"","sources":["../../src/ui/HostChat.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAOlF,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIhD,OAAO,6CAA6C,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;CACf;AAED,+FAA+F;AAC/F,MAAM,WAAW,aAAa;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+FAA+F;IAC/F,UAAU,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACxC,oFAAoF;IACpF,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,mFAAmF;IACnF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,YAAY,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAChF;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC;IAClD;;OAEG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CACvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACrB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,WAAW,EACX,UAAiB,EACjB,OAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACpB,EAAE,cAAc,2CA2FhB"}
1
+ {"version":3,"file":"HostChat.d.ts","sourceRoot":"","sources":["../../src/ui/HostChat.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAUlF,OAAO,6CAA6C,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;CACf;AAED,+FAA+F;AAC/F,MAAM,WAAW,aAAa;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+FAA+F;IAC/F,UAAU,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACxC,oFAAoF;IACpF,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,mFAAmF;IACnF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,YAAY,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAChF;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC;CACrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACrB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,WAAW,EACX,UAAiB,EACjB,OAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACjB,EAAE,cAAc,2CA0FhB"}
@@ -18,7 +18,7 @@ import "@gooddata/sdk-ui-gen-ai/styles/css/main.css";
18
18
  * (embedded/export). The header chat button and app-side controls drive it through the `visibility`
19
19
  * and `context` props; it reports its open-state and button state back via callbacks.
20
20
  */
21
- export function HostChat({ ctx, resolvedApplications, pathname, activeAppId, visibility = null, context = null, onOpenChange, onChatStateChange, onAppLinkClick, onAppEventReceive, }) {
21
+ export function HostChat({ ctx, resolvedApplications, pathname, activeAppId, visibility = null, context = null, onOpenChange, onChatStateChange, onAppLinkClick, }) {
22
22
  const features = useHostChromeWorkspaceFeatures(resolvedApplications, ctx, pathname);
23
23
  const shellTelemetry = useMemo(() => getAppLifecycleCallbacks()?.createTelemetryCallbacks?.("host-ui"), []);
24
24
  const chat = useHostChromeChat({
@@ -28,7 +28,6 @@ export function HostChat({ ctx, resolvedApplications, pathname, activeAppId, vis
28
28
  dialogPosition: context?.dialogPosition,
29
29
  embedded: context?.embedded,
30
30
  onAppLinkClick,
31
- onAppEventReceive,
32
31
  });
33
32
  const { askAiAssistant: chatAskAiAssistant, openAiAssistant: chatOpenAiAssistant, open: chatOpen, close: chatClose, toggle: chatToggle, setTags: chatSetTags, setAmbientUserContext: chatSetAmbientUserContext, isOpen: chatIsOpen, showChatItem, } = chat;
34
33
  // Report the chat open-state outward so the runtime can forward it to the active pluggable
@@ -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;AAYlE,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,2CA6NlB"}
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"}
@@ -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: "/organization" // switch the host scope to organization, the first org app will be chosen
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,8 +1,7 @@
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
- import { GenAIChatEvent } from "./GenAIChat.js";
6
5
  export interface IPluggableApplicationRendererProps {
7
6
  app: PluggableApplicationRegistryItem;
8
7
  ctx: IPlatformContext;
@@ -32,12 +31,13 @@ export interface IPluggableApplicationRendererProps {
32
31
  newTab?: boolean;
33
32
  }) => boolean) | undefined>;
34
33
  /**
35
- * Ref the renderer populates with a handler that delegates a host-chat event to the active
36
- * app's mount handle (`onAiAssistantEventReceive`), so an embedded app can handle it in-app.
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
37
  */
38
- aiEventReceiveRef?: RefObject<((event: GenAIChatEvent) => void) | undefined>;
38
+ navigationRequestRef?: RefObject<((request: IHostNavigationRequest) => boolean) | undefined>;
39
39
  onHeaderChange?: (appId: string, header: IAppHeaderOptions) => void;
40
40
  onDocumentTitleChange?: (appId: string, pageTitle: string | undefined) => void;
41
41
  }
42
- export declare function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOpen, onOpenAiAssistant, onCloseAiAssistant, onAiAssistantContext, aiLinkClickHandlerRef, aiEventReceiveRef, 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;
43
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;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAkBhD,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;;;OAGG;IACH,iBAAiB,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;IAC7E,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,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACxB,EAAE,kCAAkC,2CA+QpC"}
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, aiEventReceiveRef, 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
- // Defer unmount of the previous app to avoid synchronously unmounting
91
- // a React root while React is already rendering (race in strict mode).
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
- }, [app, appBasePath, ctxRef, intlRef, onHeaderChangeRef, onTelemetryEvent, lifecycle, onEvent]);
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(() => {
@@ -200,19 +227,6 @@ export function PluggableApplicationRenderer({ app, ctx, pathname, aiAssistantOp
200
227
  ref.current = undefined;
201
228
  };
202
229
  }, [aiLinkClickHandlerRef]);
203
- // Expose a host-chat event delegate that defers to the active app's mount handle, so an
204
- // embedded app can handle events in-app. Read through the ref at call time so it
205
- // always targets the currently mounted app.
206
- useEffect(() => {
207
- const ref = aiEventReceiveRef;
208
- if (!ref) {
209
- return;
210
- }
211
- ref.current = (event) => mountHandleRef.current?.onAiAssistantEventReceive?.(event);
212
- return () => {
213
- ref.current = undefined;
214
- };
215
- }, [aiEventReceiveRef]);
216
230
  return (_jsxs("section", { className: b(), children: [viewState.state === "loading" ? (_jsx("div", { className: e("loading"), children: _jsx(LoadingComponent, { height: 40 }) })) : null, viewState.state === "error" ? (_jsxs("div", { className: e("error"), children: [
217
231
  _jsx("h2", { children: _jsx(FormattedMessage, { id: "gs.host.error.applicationFailedToLoad" }) }), _jsx("p", { children: viewState.message })
218
232
  ] })) : null, _jsx("div", { ref: containerRef, className: e("container", { visible: viewState.state === "ready" }) }), viewState.state === "ready" && viewState.validUntil !== undefined ? (_jsx("div", { className: e("demoBadge"), role: "status", children: _jsx(FormattedMessage, { id: "gs.host.demoApp.validUntil", values: {
@@ -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;AAkC5D,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"}
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"}
@@ -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
- // Let the browser handle modifier-click and non-primary buttons natively
8
- // (opens in a new tab/window). Only intercept a plain left-click so we can
9
- // suppress the parent menu's SPA navigation and do a full-page load instead —
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"}
@@ -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
+ }
@@ -1,7 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import { type IGenAIUserContext } from "@gooddata/sdk-model";
3
3
  import { type IPlatformContext, type IPluggableAppTelemetryCallbacks } from "@gooddata/sdk-pluggable-application-model";
4
- import { type GenAIChatEvent } from "./GenAIChat.js";
5
4
  import { type IHostChromeWorkspaceFeatures } from "./useHostChromeWorkspaceFeatures.js";
6
5
  export interface IHostChromeChat {
7
6
  /** The `<GenAIChat>` element to mount, or `null` when chat is gated off. */
@@ -55,8 +54,6 @@ export interface IUseHostChromeChatArgs {
55
54
  itemUrl?: string;
56
55
  newTab?: boolean;
57
56
  }) => boolean;
58
- /** Delegates a chat event receive to the active app. */
59
- onAppEventReceive?: (event: GenAIChatEvent) => void;
60
57
  }
61
58
  /**
62
59
  * Owns the GenAI chat state and renders the `<GenAIChat>` element for the host chrome.
@@ -65,5 +62,5 @@ export interface IUseHostChromeChatArgs {
65
62
  * points, the runtime availability probe (`useGenAiChatAvailability`) and forwarding chat
66
63
  * events to the host telemetry callbacks.
67
64
  */
68
- export declare function useHostChromeChat({ features, ctx, telemetry, dialogPosition, embedded, onAppLinkClick, onAppEventReceive }: IUseHostChromeChatArgs): IHostChromeChat;
65
+ export declare function useHostChromeChat({ features, ctx, telemetry, dialogPosition, embedded, onAppLinkClick }: IUseHostChromeChatArgs): IHostChromeChat;
69
66
  //# sourceMappingURL=useHostChromeChat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useHostChromeChat.d.ts","sourceRoot":"","sources":["../../src/ui/useHostChromeChat.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAyB,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACvC,MAAM,2CAA2C,CAAC;AAKnD,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAExF,MAAM,WAAW,eAAe;IAC5B,4EAA4E;IAC5E,OAAO,EAAE,SAAS,CAAC;IACnB,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,6EAA2E;IAC3E,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB;;;OAGG;IACH,cAAc,EAAE,CACZ,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,eAAe,EAAE,CACb,WAAW,CAAC,EAAE,iBAAiB,EAC/B,kBAAkB,CAAC,EAAE,OAAO,EAC5B,OAAO,CAAC,EAAE,MAAM,KACf,IAAI,CAAC;IACV;;;OAGG;IACH,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAClE;;;OAGG;IACH,qBAAqB,EAAE,CAAC,WAAW,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACpE;AAED,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,4BAA4B,CAAC;IACvC,GAAG,EAAE,gBAAgB,CAAC;IACtB,SAAS,EAAE,+BAA+B,GAAG,SAAS,CAAC;IACvD,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,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;IACvG,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CACvD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,QAAQ,EACR,GAAG,EACH,SAAS,EACT,cAAc,EACd,QAAQ,EACR,cAAc,EACd,iBAAiB,EACpB,EAAE,sBAAsB,GAAG,eAAe,CAyH1C"}
1
+ {"version":3,"file":"useHostChromeChat.d.ts","sourceRoot":"","sources":["../../src/ui/useHostChromeChat.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAyB,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACvC,MAAM,2CAA2C,CAAC;AAMnD,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAExF,MAAM,WAAW,eAAe;IAC5B,4EAA4E;IAC5E,OAAO,EAAE,SAAS,CAAC;IACnB,iDAAiD;IACjD,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,6EAA2E;IAC3E,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB;;;OAGG;IACH,cAAc,EAAE,CACZ,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,eAAe,EAAE,CACb,WAAW,CAAC,EAAE,iBAAiB,EAC/B,kBAAkB,CAAC,EAAE,OAAO,EAC5B,OAAO,CAAC,EAAE,MAAM,KACf,IAAI,CAAC;IACV;;;OAGG;IACH,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAClE;;;OAGG;IACH,qBAAqB,EAAE,CAAC,WAAW,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACpE;AAED,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,4BAA4B,CAAC;IACvC,GAAG,EAAE,gBAAgB,CAAC;IACtB,SAAS,EAAE,+BAA+B,GAAG,SAAS,CAAC;IACvD,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,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;CAC1G;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,QAAQ,EACR,GAAG,EACH,SAAS,EACT,cAAc,EACd,QAAQ,EACR,cAAc,EACjB,EAAE,sBAAsB,GAAG,eAAe,CAwH1C"}
@@ -11,7 +11,7 @@ import { GenAIChat } from "./GenAIChat.js";
11
11
  * points, the runtime availability probe (`useGenAiChatAvailability`) and forwarding chat
12
12
  * events to the host telemetry callbacks.
13
13
  */
14
- export function useHostChromeChat({ features, ctx, telemetry, dialogPosition, embedded, onAppLinkClick, onAppEventReceive, }) {
14
+ export function useHostChromeChat({ features, ctx, telemetry, dialogPosition, embedded, onAppLinkClick, }) {
15
15
  const [isChatOpen, setIsChatOpen] = useState(false);
16
16
  const [askedQuestion, setAskedQuestion] = useState(null);
17
17
  const [appendToChat, setAppendToChat] = useState(false);
@@ -61,8 +61,7 @@ export function useHostChromeChat({ features, ctx, telemetry, dialogPosition, em
61
61
  }, []);
62
62
  const handleChatEvent = useCallback((event) => {
63
63
  telemetry?.trackEvent(event.name, event.payload);
64
- onAppEventReceive?.(event);
65
- }, [telemetry, onAppEventReceive]);
64
+ }, [telemetry]);
66
65
  const showChatItem = useGenAiChatAvailability(getBackend(), features.workspaceId, features.showChat, features.canManageProject);
67
66
  const element = features.showChat && features.workspaceId ? (_jsx(GenAIChat, { workspaceId: features.workspaceId, open: isChatOpen, onOpen: open, onClose: close, askedQuestion: askedQuestion, appendToChat: appendToChat, agentId: agentId, replaceUserContext: replaceUserContext, askSeq: askSeq, userContext: userContext, ambientUserContext: ambientUserContext, includeTags: includeTags, excludeTags: excludeTags, canManageProject: features.canManageProject, canAnalyzeProject: features.canCreateVisualization, canFullControl: features.canFullControl, settings: ctx.workspaceSettings, dialogPosition: dialogPosition, embedded: embedded, onAppLinkClick: onAppLinkClick, onEvent: handleChatEvent })) : null;
68
67
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-pluggable-host",
3
- "version": "11.51.0-alpha.4",
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-base": "11.51.0-alpha.4",
33
- "@gooddata/sdk-backend-spi": "11.51.0-alpha.4",
34
- "@gooddata/sdk-backend-tiger": "11.51.0-alpha.4",
35
- "@gooddata/sdk-model": "11.51.0-alpha.4",
36
- "@gooddata/sdk-embedding": "11.51.0-alpha.4",
37
- "@gooddata/sdk-pluggable-application-model": "11.51.0-alpha.4",
38
- "@gooddata/sdk-ui": "11.51.0-alpha.4",
39
- "@gooddata/sdk-ui-application-header": "11.51.0-alpha.4",
40
- "@gooddata/sdk-ui-ext": "11.51.0-alpha.4",
41
- "@gooddata/sdk-ui-gen-ai": "11.51.0-alpha.4",
42
- "@gooddata/sdk-ui-kit": "11.51.0-alpha.4",
43
- "@gooddata/sdk-ui-semantic-search": "11.51.0-alpha.4",
44
- "@gooddata/sdk-ui-theme-provider": "11.51.0-alpha.4",
45
- "@gooddata/util": "11.51.0-alpha.4"
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-alpha.4",
89
- "@gooddata/oxlint-config": "11.51.0-alpha.4"
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",