@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.
- package/esm/hostNavigation.d.ts +54 -0
- package/esm/hostNavigation.d.ts.map +1 -0
- package/esm/hostNavigation.js +75 -0
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/sdk-ui-pluggable-application.d.ts +56 -0
- package/package.json +10 -10
|
@@ -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
|
package/esm/index.d.ts.map
CHANGED
|
@@ -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
|
+
"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.
|
|
28
|
-
"@gooddata/sdk-backend-
|
|
29
|
-
"@gooddata/sdk-backend-
|
|
30
|
-
"@gooddata/sdk-model": "11.55.0-alpha.
|
|
31
|
-
"@gooddata/sdk-pluggable-application-model": "11.55.0-alpha.
|
|
32
|
-
"@gooddata/sdk-ui": "11.55.0-alpha.
|
|
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.
|
|
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.
|
|
63
|
-
"@gooddata/oxlint-config": "11.55.0-alpha.
|
|
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",
|