@gooddata/sdk-ui-pluggable-application 11.55.0-alpha.3 → 11.55.0-alpha.4

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.
@@ -0,0 +1,54 @@
1
+ import { type IHostNavigationRequest } from "@gooddata/sdk-pluggable-application-model";
2
+ /**
3
+ * Mutable slot an application's router fills with its navigate function once it exists.
4
+ *
5
+ * @remarks
6
+ * The path handed to `navigate` is relative to the application's base path (starts with "/").
7
+ * The implementation must navigate with history REPLACE semantics: the takeover lets the host
8
+ * push first (see {@link createHostNavigationTakeover}), so the replace lands on the entry that
9
+ * push created and history gains exactly one entry, with the source entry preserved.
10
+ *
11
+ * An application whose navigation guard can cancel this navigation (an unsaved-changes blocker)
12
+ * is responsible for restoring the URL when the user cancels — the host push has already
13
+ * happened by then. gdc-reports' editor does this on its Stay choice.
14
+ *
15
+ * @alpha
16
+ */
17
+ export interface IPluggableAppNavigateRef {
18
+ current: ((path: string) => void) | null;
19
+ }
20
+ /**
21
+ * The in-app route a host navigation target stands for, or undefined when the target leaves
22
+ * the application.
23
+ *
24
+ * @remarks
25
+ * Query and hash ride along with the path.
26
+ *
27
+ * @alpha
28
+ */
29
+ export declare function inAppPath(url: string, basePath: string): string | undefined;
30
+ /**
31
+ * Builds an {@link @gooddata/sdk-pluggable-application-model#IPluggableApplicationMountHandle.onHostNavigationRequested}
32
+ * handler that takes over host navigations landing inside the application.
33
+ *
34
+ * @remarks
35
+ * The host chrome navigates through its own router, which the application's router does not
36
+ * observe — an unhandled chrome push targeting the application would change the URL and render
37
+ * nothing. The returned handler drives targets inside the base path through the application's
38
+ * own navigation (which also keeps its navigation guards in force) and leaves anything else to
39
+ * the host.
40
+ *
41
+ * The takeover still calls `proceed` before navigating: the host push keeps the host chrome's
42
+ * own location state (menu highlight, targets derived from the current path) in sync and
43
+ * preserves the source history entry; the application then renders by REPLACING that entry
44
+ * with the same URL, so history gains exactly one entry. Push-first is a deliberate trade-off:
45
+ * ordering the push after the application navigation would either destroy the source entry
46
+ * (replace) or duplicate the target (push), and gating it on the navigation committing loses
47
+ * the push when a navigation guard confirms later. The cost is that a guard cancelling the
48
+ * follow-up navigation leaves the pushed URL behind — which is why the guard owner restores
49
+ * the URL on cancel (see {@link IPluggableAppNavigateRef}).
50
+ *
51
+ * @alpha
52
+ */
53
+ export declare function createHostNavigationTakeover(basePath: string, navigateRef: IPluggableAppNavigateRef): (request: IHostNavigationRequest) => boolean;
54
+ //# sourceMappingURL=hostNavigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostNavigation.d.ts","sourceRoot":"","sources":["../src/hostNavigation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAExF;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,wBAAwB;IACrC,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5C;AAUD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAsB3E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,4BAA4B,CACxC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,wBAAwB,GACtC,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAW9C"}
@@ -0,0 +1,75 @@
1
+ // (C) 2026 GoodData Corporation
2
+ const RELATIVE_ORIGIN = "https://pluggable.invalid";
3
+ // The real origin when running in a browser, so same-origin absolute targets resolve as in-app;
4
+ // outside one the sentinel makes every absolute URL foreign, which is the only safe reading.
5
+ function resolutionOrigin() {
6
+ return typeof location === "object" && location?.origin ? location.origin : RELATIVE_ORIGIN;
7
+ }
8
+ /**
9
+ * The in-app route a host navigation target stands for, or undefined when the target leaves
10
+ * the application.
11
+ *
12
+ * @remarks
13
+ * Query and hash ride along with the path.
14
+ *
15
+ * @alpha
16
+ */
17
+ export function inAppPath(url, basePath) {
18
+ const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
19
+ const origin = resolutionOrigin();
20
+ // URL resolution collapses dot segments ("..", "%2e%2e"), which a raw prefix check would let
21
+ // slip past the base and take over a navigation that actually leaves the application.
22
+ let target;
23
+ try {
24
+ target = new URL(url, origin);
25
+ }
26
+ catch {
27
+ // A target that does not parse is not in-app; the host decides what to do with it.
28
+ return undefined;
29
+ }
30
+ // A cross-origin target is never in-app, whatever its pathname looks like — a navigation to
31
+ // another origin must stay the host's to perform.
32
+ if (target.origin !== origin) {
33
+ return undefined;
34
+ }
35
+ const { pathname } = target;
36
+ if (pathname !== base && !pathname.startsWith(`${base}/`)) {
37
+ return undefined;
38
+ }
39
+ return `${pathname.slice(base.length) || "/"}${target.search}${target.hash}`;
40
+ }
41
+ /**
42
+ * Builds an {@link @gooddata/sdk-pluggable-application-model#IPluggableApplicationMountHandle.onHostNavigationRequested}
43
+ * handler that takes over host navigations landing inside the application.
44
+ *
45
+ * @remarks
46
+ * The host chrome navigates through its own router, which the application's router does not
47
+ * observe — an unhandled chrome push targeting the application would change the URL and render
48
+ * nothing. The returned handler drives targets inside the base path through the application's
49
+ * own navigation (which also keeps its navigation guards in force) and leaves anything else to
50
+ * the host.
51
+ *
52
+ * The takeover still calls `proceed` before navigating: the host push keeps the host chrome's
53
+ * own location state (menu highlight, targets derived from the current path) in sync and
54
+ * preserves the source history entry; the application then renders by REPLACING that entry
55
+ * with the same URL, so history gains exactly one entry. Push-first is a deliberate trade-off:
56
+ * ordering the push after the application navigation would either destroy the source entry
57
+ * (replace) or duplicate the target (push), and gating it on the navigation committing loses
58
+ * the push when a navigation guard confirms later. The cost is that a guard cancelling the
59
+ * follow-up navigation leaves the pushed URL behind — which is why the guard owner restores
60
+ * the URL on cancel (see {@link IPluggableAppNavigateRef}).
61
+ *
62
+ * @alpha
63
+ */
64
+ export function createHostNavigationTakeover(basePath, navigateRef) {
65
+ return ({ url, proceed }) => {
66
+ const target = inAppPath(url, basePath);
67
+ const navigate = navigateRef.current;
68
+ if (target === undefined || navigate === null) {
69
+ return false;
70
+ }
71
+ proceed();
72
+ navigate(target);
73
+ return true;
74
+ };
75
+ }
package/esm/index.d.ts CHANGED
@@ -9,4 +9,5 @@ export { AppProviders, type IAppProvidersProps } from "./AppProviders.js";
9
9
  export { type IPluggableAppEventsContextValue, type IPluggableAppEventsProviderProps, PluggableAppEventsProvider, usePluggableAppEvents, } from "./events.js";
10
10
  export { type IModuleTelemetryMetadata, type IPluggableAppTelemetryProviderProps, enrichTelemetryCallbacks, PluggableAppTelemetryProvider, usePluggableAppTelemetry, } from "./telemetry.js";
11
11
  export { subscribeAiAssistantDefault } from "./ai.js";
12
+ export { type IPluggableAppNavigateRef, createHostNavigationTakeover, inAppPath } from "./hostNavigation.js";
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACH,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,KAAK,6BAA6B,EAClC,kBAAkB,EAClB,wBAAwB,GAC3B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,KAAK,8BAA8B,EAAE,MAAM,cAAc,CAAC;AAE3F,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EACH,KAAK,+BAA+B,EACpC,KAAK,gCAAgC,EACrC,0BAA0B,EAC1B,qBAAqB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACH,KAAK,wBAAwB,EAC7B,KAAK,mCAAmC,EACxC,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,GAC3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACH,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,KAAK,6BAA6B,EAClC,kBAAkB,EAClB,wBAAwB,GAC3B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,KAAK,8BAA8B,EAAE,MAAM,cAAc,CAAC;AAE3F,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EACH,KAAK,+BAA+B,EACpC,KAAK,gCAAgC,EACrC,0BAA0B,EAC1B,qBAAqB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACH,KAAK,wBAAwB,EAC7B,KAAK,mCAAmC,EACxC,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,GAC3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,KAAK,wBAAwB,EAAE,4BAA4B,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC"}
package/esm/index.js CHANGED
@@ -11,3 +11,4 @@ export { AppProviders } from "./AppProviders.js";
11
11
  export { PluggableAppEventsProvider, usePluggableAppEvents, } from "./events.js";
12
12
  export { enrichTelemetryCallbacks, PluggableAppTelemetryProvider, usePluggableAppTelemetry, } from "./telemetry.js";
13
13
  export { subscribeAiAssistantDefault } from "./ai.js";
14
+ export { createHostNavigationTakeover, inAppPath } from "./hostNavigation.js";
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { IAnalyticalBackend } from '@gooddata/sdk-backend-spi';
8
8
  import { IAuthCredentials } from '@gooddata/sdk-pluggable-application-model';
9
+ import { IHostNavigationRequest } from '@gooddata/sdk-pluggable-application-model';
9
10
  import { ILocale } from '@gooddata/sdk-model';
10
11
  import { IPlatformContext } from '@gooddata/sdk-pluggable-application-model';
11
12
  import { IPluggableAppEvent } from '@gooddata/sdk-pluggable-application-model';
@@ -68,6 +69,31 @@ export declare function AppProviders({ ctx, packageName, resolveMessages: resolv
68
69
  */
69
70
  export declare function createBackendForModule(auth: IAuthCredentials, options: ICreateBackendForModuleOptions): IAnalyticalBackend;
70
71
 
72
+ /**
73
+ * Builds an {@link @gooddata/sdk-pluggable-application-model#IPluggableApplicationMountHandle.onHostNavigationRequested}
74
+ * handler that takes over host navigations landing inside the application.
75
+ *
76
+ * @remarks
77
+ * The host chrome navigates through its own router, which the application's router does not
78
+ * observe — an unhandled chrome push targeting the application would change the URL and render
79
+ * nothing. The returned handler drives targets inside the base path through the application's
80
+ * own navigation (which also keeps its navigation guards in force) and leaves anything else to
81
+ * the host.
82
+ *
83
+ * The takeover still calls `proceed` before navigating: the host push keeps the host chrome's
84
+ * own location state (menu highlight, targets derived from the current path) in sync and
85
+ * preserves the source history entry; the application then renders by REPLACING that entry
86
+ * with the same URL, so history gains exactly one entry. Push-first is a deliberate trade-off:
87
+ * ordering the push after the application navigation would either destroy the source entry
88
+ * (replace) or duplicate the target (push), and gating it on the navigation committing loses
89
+ * the push when a navigation guard confirms later. The cost is that a guard cancelling the
90
+ * follow-up navigation leaves the pushed URL behind — which is why the guard owner restores
91
+ * the URL on cancel (see {@link IPluggableAppNavigateRef}).
92
+ *
93
+ * @alpha
94
+ */
95
+ export declare function createHostNavigationTakeover(basePath: string, navigateRef: IPluggableAppNavigateRef): (request: IHostNavigationRequest) => boolean;
96
+
71
97
  /**
72
98
  * Wraps host telemetry callbacks so every `trackEvent` / `trackPageView` / `trackTiming` call also reports
73
99
  * the module's runtime metadata.
@@ -180,6 +206,17 @@ export declare interface IModuleTelemetryMetadata {
180
206
  moduleSdkVersion?: string;
181
207
  }
182
208
 
209
+ /**
210
+ * The in-app route a host navigation target stands for, or undefined when the target leaves
211
+ * the application.
212
+ *
213
+ * @remarks
214
+ * Query and hash ride along with the path.
215
+ *
216
+ * @alpha
217
+ */
218
+ export declare function inAppPath(url: string, basePath: string): string | undefined;
219
+
183
220
  /**
184
221
  * Props for {@link PlatformContextProvider}.
185
222
  *
@@ -229,6 +266,25 @@ export declare interface IPluggableAppEventsProviderProps extends PropsWithChild
229
266
  onEvent?: (event: IPluggableAppEvent) => void;
230
267
  }
231
268
 
269
+ /**
270
+ * Mutable slot an application's router fills with its navigate function once it exists.
271
+ *
272
+ * @remarks
273
+ * The path handed to `navigate` is relative to the application's base path (starts with "/").
274
+ * The implementation must navigate with history REPLACE semantics: the takeover lets the host
275
+ * push first (see {@link createHostNavigationTakeover}), so the replace lands on the entry that
276
+ * push created and history gains exactly one entry, with the source entry preserved.
277
+ *
278
+ * An application whose navigation guard can cancel this navigation (an unsaved-changes blocker)
279
+ * is responsible for restoring the URL when the user cancels — the host push has already
280
+ * happened by then. gdc-reports' editor does this on its Stay choice.
281
+ *
282
+ * @alpha
283
+ */
284
+ export declare interface IPluggableAppNavigateRef {
285
+ current: ((path: string) => void) | null;
286
+ }
287
+
232
288
  /**
233
289
  * Props for {@link PluggableAppTelemetryProvider}.
234
290
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-pluggable-application",
3
- "version": "11.55.0-alpha.3",
3
+ "version": "11.55.0-alpha.4",
4
4
  "description": "GoodData SDK React helpers for pluggable applications",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -24,12 +24,12 @@
24
24
  "react-intl": "7.1.11",
25
25
  "ts-invariant": "0.10.3",
26
26
  "tslib": "2.8.1",
27
- "@gooddata/sdk-backend-base": "11.55.0-alpha.3",
28
- "@gooddata/sdk-backend-tiger": "11.55.0-alpha.3",
29
- "@gooddata/sdk-backend-spi": "11.55.0-alpha.3",
30
- "@gooddata/sdk-model": "11.55.0-alpha.3",
31
- "@gooddata/sdk-pluggable-application-model": "11.55.0-alpha.3",
32
- "@gooddata/sdk-ui": "11.55.0-alpha.3"
27
+ "@gooddata/sdk-backend-base": "11.55.0-alpha.4",
28
+ "@gooddata/sdk-backend-spi": "11.55.0-alpha.4",
29
+ "@gooddata/sdk-backend-tiger": "11.55.0-alpha.4",
30
+ "@gooddata/sdk-model": "11.55.0-alpha.4",
31
+ "@gooddata/sdk-pluggable-application-model": "11.55.0-alpha.4",
32
+ "@gooddata/sdk-ui": "11.55.0-alpha.4"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@microsoft/api-documenter": "7.30.10",
@@ -52,15 +52,15 @@
52
52
  "eslint-plugin-sonarjs": "3.0.6",
53
53
  "happy-dom": "20.9.0",
54
54
  "npm-run-all": "4.1.5",
55
- "oxfmt": "0.52.0",
55
+ "oxfmt": "0.58.0",
56
56
  "oxlint": "1.57.0",
57
57
  "oxlint-tsgolint": "0.15.0",
58
58
  "react": "19.1.1",
59
59
  "react-dom": "19.1.1",
60
60
  "typescript": "5.9.3",
61
61
  "vitest": "4.1.8",
62
- "@gooddata/eslint-config": "11.55.0-alpha.3",
63
- "@gooddata/oxlint-config": "11.55.0-alpha.3"
62
+ "@gooddata/eslint-config": "11.55.0-alpha.4",
63
+ "@gooddata/oxlint-config": "11.55.0-alpha.4"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "react": "^18.0.0 || ^19.0.0",