ui5-lib-guard-router 1.0.1 → 1.1.1
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/LICENSE +21 -21
- package/README.md +181 -46
- package/dist/.ui5/build-manifest.json +75 -0
- package/dist/index.d.ts +10 -4
- package/dist/resources/ui5/guard/router/.library +17 -17
- package/dist/resources/ui5/guard/router/Router-dbg.js +479 -430
- package/dist/resources/ui5/guard/router/Router-dbg.js.map +1 -1
- package/dist/resources/ui5/guard/router/Router.d.ts +137 -6
- package/dist/resources/ui5/guard/router/Router.d.ts.map +1 -1
- package/dist/resources/ui5/guard/router/Router.js +1 -1
- package/dist/resources/ui5/guard/router/Router.js.map +1 -1
- package/dist/resources/ui5/guard/router/library-dbg.js +16 -16
- package/dist/resources/ui5/guard/router/library-dbg.js.map +1 -1
- package/dist/resources/ui5/guard/router/library-preload.js +3 -3
- package/dist/resources/ui5/guard/router/library-preload.js.map +1 -1
- package/dist/resources/ui5/guard/router/library.d.ts.map +1 -1
- package/dist/resources/ui5/guard/router/library.js +1 -1
- package/dist/resources/ui5/guard/router/library.js.map +1 -1
- package/dist/resources/ui5/guard/router/manifest.json +1 -1
- package/dist/resources/ui5/guard/router/types-dbg.js +1 -1
- package/dist/resources/ui5/guard/router/types-dbg.js.map +1 -1
- package/dist/resources/ui5/guard/router/types.d.ts +10 -40
- package/dist/resources/ui5/guard/router/types.d.ts.map +1 -1
- package/package.json +11 -4
- package/src/.library +17 -17
- package/src/Router.ts +187 -127
- package/src/manifest.json +1 -1
- package/src/types.ts +10 -54
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Marco
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Marco
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# ui5-lib-guard-router
|
|
2
2
|
|
|
3
|
-
Drop-in replacement for `sap.m.routing.Router` that intercepts navigation **before** route matching, target loading, or view creation
|
|
3
|
+
Drop-in replacement for `sap.m.routing.Router` that intercepts navigation **before** route matching, target loading, or view creation, preventing flashes of unauthorized content and polluted browser history.
|
|
4
4
|
|
|
5
5
|
> Born from [SAP/openui5#3411](https://github.com/SAP/openui5/issues/3411), an open request since 2021 for native navigation guard support in UI5.
|
|
6
6
|
>
|
|
7
7
|
> **Related resources**:
|
|
8
8
|
>
|
|
9
9
|
> - [Stack Overflow: Preventing router from navigating](https://stackoverflow.com/questions/29165700/preventing-router-from-navigating/29167292#29167292) (native NavContainer `navigate` event, sync-only, fires after route match)
|
|
10
|
-
> - [Research: Native NavContainer navigate event](../../docs/research
|
|
10
|
+
> - [Research: Native NavContainer navigate event](../../docs/research/native-router-navigate-event.md) (detailed comparison with this library)
|
|
11
11
|
|
|
12
12
|
> [!WARNING]
|
|
13
|
-
> This library is **experimental**. It is not battle-tested in production environments, and the API may change without notice. If you choose to consume it, you do so at your own risk
|
|
13
|
+
> This library is **experimental**. It is not battle-tested in production environments, and the API may change without notice. If you choose to consume it, you do so at your own risk. Make sure to pin your version and review changes before upgrading.
|
|
14
14
|
|
|
15
15
|
## Why
|
|
16
16
|
|
|
17
|
-
UI5's router has no way to block or redirect navigation before views render. The usual workaround
|
|
17
|
+
UI5's router has no way to block or redirect navigation before views render. The usual workaround, scattering guard logic across `attachPatternMatched` callbacks, causes flashes of unauthorized content, polluted browser history, and scattered guard logic across controllers.
|
|
18
18
|
|
|
19
19
|
This library solves all three by intercepting at the router level, before any route matching begins.
|
|
20
20
|
|
|
@@ -24,6 +24,90 @@ This library solves all three by intercepting at the router level, before any ro
|
|
|
24
24
|
npm install ui5-lib-guard-router
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
### TypeScript (optional)
|
|
28
|
+
|
|
29
|
+
If your app uses TypeScript and does not already depend on the UI5 typings, install them too (`@sapui5/types` works as well):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -D @openui5/types
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Add both packages to `compilerOptions.types`:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"compilerOptions": {
|
|
40
|
+
"types": ["@openui5/types", "ui5-lib-guard-router"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then import types from the UI5 module path as needed:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Core: router interface and guard function signatures
|
|
49
|
+
import type { GuardRouter, GuardFn, LeaveGuardFn } from "ui5/guard/router/types";
|
|
50
|
+
|
|
51
|
+
// Guard pipeline: context passed to guards, and the result union they return
|
|
52
|
+
import type { GuardContext, GuardResult } from "ui5/guard/router/types";
|
|
53
|
+
|
|
54
|
+
// Advanced: object form for redirect-with-parameters and enter+leave registration
|
|
55
|
+
import type { GuardRedirect, RouteGuardConfig } from "ui5/guard/router/types";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Serving the library
|
|
59
|
+
|
|
60
|
+
The npm package ships both pre-built distributables (`dist/`) and TypeScript sources (`src/`). There are three ways to serve the library in your app:
|
|
61
|
+
|
|
62
|
+
#### Option A: Pre-built (recommended)
|
|
63
|
+
|
|
64
|
+
The package includes a [UI5 build manifest](https://github.com/SAP/ui5-tooling/blob/main/rfcs/0006-local-dependency-resolution.md) (`dist/.ui5/build-manifest.json`). UI5 Tooling v4+ detects it automatically and serves the pre-built JavaScript from `dist/` with no extra configuration:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install ui5-lib-guard-router
|
|
68
|
+
# That's it. `ui5 serve` picks up the build manifest.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
No transpile tooling, no middleware, no additional `ui5.yaml` changes.
|
|
72
|
+
|
|
73
|
+
#### Option B: Transpile from source
|
|
74
|
+
|
|
75
|
+
If you prefer to serve from TypeScript sources (e.g. for debugging with source maps), install [`ui5-tooling-transpile`](https://github.com/nicholasmackey/ui5-tooling-transpile) and enable `transpileDependencies` in your app's `ui5.yaml`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install -D ui5-tooling-transpile
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
# ui5.yaml
|
|
83
|
+
server:
|
|
84
|
+
customMiddleware:
|
|
85
|
+
- name: ui5-tooling-transpile-middleware
|
|
86
|
+
afterMiddleware: compression
|
|
87
|
+
configuration:
|
|
88
|
+
transpileDependencies: true
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This transpiles the library's `.ts` sources on the fly during `ui5 serve`.
|
|
92
|
+
|
|
93
|
+
#### Option C: Static serving (workaround)
|
|
94
|
+
|
|
95
|
+
If neither option works for your setup, you can mount the pre-built resources manually using [`ui5-middleware-servestatic`](https://github.com/nicholasmackey/ui5-middleware-servestatic) (or a similar community middleware) and point it at the `dist/resources` folder in `node_modules`:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm install -D ui5-middleware-servestatic
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
# ui5.yaml
|
|
103
|
+
server:
|
|
104
|
+
customMiddleware:
|
|
105
|
+
- name: ui5-middleware-servestatic
|
|
106
|
+
afterMiddleware: compression
|
|
107
|
+
configuration:
|
|
108
|
+
rootPath: node_modules/ui5-lib-guard-router/dist/resources
|
|
109
|
+
```
|
|
110
|
+
|
|
27
111
|
## Setup
|
|
28
112
|
|
|
29
113
|
**1. Add the library dependency and set the router class in your `manifest.json`:**
|
|
@@ -61,14 +145,14 @@ export default class Component extends UIComponent {
|
|
|
61
145
|
|
|
62
146
|
init(): void {
|
|
63
147
|
super.init();
|
|
64
|
-
const router = this.getRouter() as
|
|
148
|
+
const router = this.getRouter() as GuardRouter;
|
|
65
149
|
|
|
66
|
-
// Route-specific guard
|
|
150
|
+
// Route-specific guard: redirect when not logged in
|
|
67
151
|
router.addRouteGuard("protected", (context) => {
|
|
68
152
|
return isLoggedIn() ? true : "home";
|
|
69
153
|
});
|
|
70
154
|
|
|
71
|
-
// Global guard
|
|
155
|
+
// Global guard: runs for every navigation
|
|
72
156
|
router.addGuard((context) => {
|
|
73
157
|
if (context.toRoute === "admin" && !isAdmin()) {
|
|
74
158
|
return "home";
|
|
@@ -115,37 +199,51 @@ All methods return `this` for chaining.
|
|
|
115
199
|
|
|
116
200
|
Every guard receives a `GuardContext` object:
|
|
117
201
|
|
|
118
|
-
| Property | Type | Description
|
|
119
|
-
| ------------- | -------------------------------------------------- |
|
|
120
|
-
| `toRoute` | `string` | Target route name (empty if no match)
|
|
121
|
-
| `toHash` | `string` | Raw hash being navigated to
|
|
122
|
-
| `toArguments` | `Record<string, string \| Record<string, string>>` | Parsed route parameters
|
|
123
|
-
| `fromRoute` | `string` | Current route name (empty on first navigation)
|
|
124
|
-
| `fromHash` | `string` | Current hash
|
|
125
|
-
| `signal` | `AbortSignal` | Aborted when
|
|
202
|
+
| Property | Type | Description |
|
|
203
|
+
| ------------- | -------------------------------------------------- | ----------------------------------------------------------------- |
|
|
204
|
+
| `toRoute` | `string` | Target route name (empty if no match) |
|
|
205
|
+
| `toHash` | `string` | Raw hash being navigated to |
|
|
206
|
+
| `toArguments` | `Record<string, string \| Record<string, string>>` | Parsed route parameters |
|
|
207
|
+
| `fromRoute` | `string` | Current route name (empty on first navigation) |
|
|
208
|
+
| `fromHash` | `string` | Current hash |
|
|
209
|
+
| `signal` | `AbortSignal` | Aborted when navigation is superseded, or on `stop()`/`destroy()` |
|
|
210
|
+
|
|
211
|
+
### Return values (`GuardResult`)
|
|
212
|
+
|
|
213
|
+
Enter guards return `GuardResult`, a union of four outcomes:
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
GuardResult = boolean | string | GuardRedirect
|
|
217
|
+
```
|
|
126
218
|
|
|
127
|
-
|
|
219
|
+
| Return | Type | When to use | Effect |
|
|
220
|
+
| ---------------------------------------------- | --------------- | ------------------------------------------------------- | ----------------------------------------------- |
|
|
221
|
+
| `true` | `boolean` | Guard condition passes | Allow navigation |
|
|
222
|
+
| `false` | `boolean` | Guard condition fails, no specific destination | Block (stay on current route, no history entry) |
|
|
223
|
+
| `"routeName"` | `string` | Redirect to a fixed route (no parameters needed) | Redirect to named route (replaces history) |
|
|
224
|
+
| `{ route, parameters?, componentTargetInfo? }` | `GuardRedirect` | Redirect and pass route parameters or component targets | Redirect with parameters (replaces history) |
|
|
128
225
|
|
|
129
|
-
|
|
226
|
+
`GuardRedirect` is the object form of a redirect. Use it when you need to pass route parameters (`parameters`) or nested component targets (`componentTargetInfo`). For simple redirects without parameters, the string shorthand (`return "home"`) is equivalent and shorter.
|
|
130
227
|
|
|
131
|
-
|
|
132
|
-
| ---------------------------------------------- | ----------------------------------------------- |
|
|
133
|
-
| `true` | Allow navigation |
|
|
134
|
-
| `false` | Block (stay on current route, no history entry) |
|
|
135
|
-
| `"routeName"` | Redirect to named route (replaces history) |
|
|
136
|
-
| `{ route, parameters?, componentTargetInfo? }` | Redirect with route parameters |
|
|
137
|
-
| anything else (`null`, `undefined`) | Treated as block |
|
|
228
|
+
Any other value (`null`, `undefined`, `0`, etc.) is treated as a block. Only strict `true` allows navigation -- there is no truthy coercion.
|
|
138
229
|
|
|
139
|
-
|
|
230
|
+
On first load, blocking a non-empty hash restores `""` and continues with the app's default route. Blocking the default route itself stays blocked. If you need a specific denied-first-load destination such as `login`, return a redirect instead of `false`.
|
|
140
231
|
|
|
141
|
-
**Leave guards** (`addLeaveGuard`):
|
|
232
|
+
**Leave guards** (`addLeaveGuard`) return `boolean` only:
|
|
142
233
|
|
|
143
234
|
| Return | Effect |
|
|
144
235
|
| --------------------------------- | ------------------------------- |
|
|
145
236
|
| `true` | Allow leaving the current route |
|
|
146
237
|
| `false` (or any non-`true` value) | Block |
|
|
147
238
|
|
|
148
|
-
Leave guards cannot redirect. For redirection logic, use enter guards on the target route.
|
|
239
|
+
Leave guards answer "can I leave?" and cannot redirect. For redirection logic, use enter guards on the target route.
|
|
240
|
+
|
|
241
|
+
### Lifecycle
|
|
242
|
+
|
|
243
|
+
| Method | Behavior |
|
|
244
|
+
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
245
|
+
| `stop()` | Cancels pending async guards (aborts the `AbortSignal`), resets guard state. A subsequent `initialize()` re-parses the current hash and fires `routeMatched`, matching native router behavior |
|
|
246
|
+
| `destroy()` | Clears all registered guards (global, enter, leave), cancels pending async guards, then calls `super.destroy()` |
|
|
149
247
|
|
|
150
248
|
### Execution order
|
|
151
249
|
|
|
@@ -159,19 +257,23 @@ Leave guards cannot redirect. For redirection logic, use enter guards on the tar
|
|
|
159
257
|
### Async guard with AbortSignal
|
|
160
258
|
|
|
161
259
|
```typescript
|
|
162
|
-
|
|
260
|
+
import type { GuardContext, GuardResult } from "ui5/guard/router/types";
|
|
261
|
+
|
|
262
|
+
router.addRouteGuard("dashboard", async (context: GuardContext): Promise<GuardResult> => {
|
|
163
263
|
const res = await fetch(`/api/access/${context.toRoute}`, {
|
|
164
|
-
signal: context.signal,
|
|
264
|
+
signal: context.signal, // cancelled when a newer navigation supersedes this one
|
|
165
265
|
});
|
|
166
266
|
const { allowed } = await res.json();
|
|
167
267
|
return allowed ? true : "forbidden";
|
|
168
268
|
});
|
|
169
269
|
```
|
|
170
270
|
|
|
171
|
-
### Redirect with parameters
|
|
271
|
+
### Redirect with parameters (GuardRedirect)
|
|
172
272
|
|
|
173
273
|
```typescript
|
|
174
|
-
|
|
274
|
+
import type { GuardRedirect } from "ui5/guard/router/types";
|
|
275
|
+
|
|
276
|
+
router.addGuard((context): GuardRedirect | true => {
|
|
175
277
|
if (context.toRoute === "old-detail") {
|
|
176
278
|
return {
|
|
177
279
|
route: "detail",
|
|
@@ -202,13 +304,18 @@ export function createDirtyFormGuard(formModel: JSONModel): LeaveGuardFn {
|
|
|
202
304
|
}
|
|
203
305
|
```
|
|
204
306
|
|
|
205
|
-
### Object form
|
|
307
|
+
### Object form with RouteGuardConfig
|
|
206
308
|
|
|
207
309
|
```typescript
|
|
208
|
-
router
|
|
310
|
+
import type { RouteGuardConfig } from "ui5/guard/router/types";
|
|
311
|
+
|
|
312
|
+
const orderGuards: RouteGuardConfig = {
|
|
209
313
|
beforeEnter: createAuthGuard(authModel),
|
|
210
314
|
beforeLeave: createDirtyFormGuard(formModel),
|
|
211
|
-
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
router.addRouteGuard("editOrder", orderGuards);
|
|
318
|
+
// later: router.removeRouteGuard("editOrder", orderGuards);
|
|
212
319
|
```
|
|
213
320
|
|
|
214
321
|
### Dynamic guard registration
|
|
@@ -239,13 +346,13 @@ export default class EditOrderController extends Controller {
|
|
|
239
346
|
const formModel = new JSONModel({ isDirty: false });
|
|
240
347
|
this.getView()!.setModel(formModel, "form");
|
|
241
348
|
|
|
242
|
-
const router =
|
|
349
|
+
const router = UIComponent.getRouterFor(this) as GuardRouter;
|
|
243
350
|
this._leaveGuard = createDirtyFormGuard(formModel);
|
|
244
351
|
router.addLeaveGuard("editOrder", this._leaveGuard);
|
|
245
352
|
}
|
|
246
353
|
|
|
247
354
|
onExit(): void {
|
|
248
|
-
const router =
|
|
355
|
+
const router = UIComponent.getRouterFor(this) as GuardRouter;
|
|
249
356
|
router.removeLeaveGuard("editOrder", this._leaveGuard);
|
|
250
357
|
}
|
|
251
358
|
}
|
|
@@ -278,10 +385,10 @@ sap.ushell.Container.setDirtyFlag(false); // clear after save
|
|
|
278
385
|
|
|
279
386
|
```typescript
|
|
280
387
|
const dirtyProvider = (navigationContext) => {
|
|
281
|
-
if (navigationContext?.isCrossAppNavigation) {
|
|
282
|
-
return
|
|
388
|
+
if (navigationContext?.isCrossAppNavigation === false) {
|
|
389
|
+
return false; // let in-app routing handle it
|
|
283
390
|
}
|
|
284
|
-
return
|
|
391
|
+
return formModel.getProperty("/isDirty") === true;
|
|
285
392
|
};
|
|
286
393
|
sap.ushell.Container.registerDirtyStateProvider(dirtyProvider);
|
|
287
394
|
|
|
@@ -291,12 +398,34 @@ sap.ushell.Container.deregisterDirtyStateProvider(dirtyProvider);
|
|
|
291
398
|
|
|
292
399
|
> **Note**: `getDirtyFlag()` is deprecated since UI5 1.120. FLP internally uses `getDirtyFlagsAsync()` (private) which combines the flag with all registered providers. The synchronous `getDirtyFlag()` still works but should not be relied upon in new code.
|
|
293
400
|
|
|
294
|
-
|
|
401
|
+
#### Combining leave guards with FLP dirty-state protection
|
|
402
|
+
|
|
403
|
+
When you use both a route leave guard and `registerDirtyStateProvider`, the two handle separate scopes and do not need to coordinate in application code:
|
|
404
|
+
|
|
405
|
+
- **Leave guard** protects **in-app** navigation (route to route within your app)
|
|
406
|
+
- **Dirty-state provider** protects **cross-app** navigation (shell home, other tiles, browser close)
|
|
407
|
+
|
|
408
|
+
In production FLP, `ShellNavigationHashChanger` intercepts cross-app navigation **before** it reaches the app router, so the leave guard never runs for cross-app hashes. The two mechanisms never overlap:
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
// 1. Leave guard: blocks in-app navigation when dirty
|
|
412
|
+
router.addRouteGuard("editOrder", {
|
|
413
|
+
beforeLeave: () => formModel.getProperty("/isDirty") !== true,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// 2. Dirty-state provider: tells FLP about unsaved changes for cross-app
|
|
417
|
+
const dirtyProvider = (navigationContext) => {
|
|
418
|
+
if (navigationContext?.isCrossAppNavigation === false) {
|
|
419
|
+
return false; // in-app navigation handled by leave guard
|
|
420
|
+
}
|
|
421
|
+
return formModel.getProperty("/isDirty") === true;
|
|
422
|
+
};
|
|
423
|
+
sap.ushell.Container.registerDirtyStateProvider(dirtyProvider);
|
|
424
|
+
```
|
|
295
425
|
|
|
296
|
-
-
|
|
297
|
-
- Use **`setDirtyFlag`** or **`registerDirtyStateProvider`** for FLP-level navigation (cross-app, browser close, home button)
|
|
426
|
+
No `toRoute` check or FLP detection is needed in the leave guard. Cross-app navigation via `toExternal()` operates at the shell level in both production and the FLP sandbox, so the leave guard never runs for cross-app hashes. The leave guard protects in-app route changes; the FLP dirty-state provider protects cross-app navigation, browser close, and the shell home button.
|
|
298
427
|
|
|
299
|
-
See the [FLP Dirty State Research](../../docs/research
|
|
428
|
+
See the [FLP Dirty State Research](../../docs/research/flp-dirty-state.md) for a detailed analysis of the FLP internals.
|
|
300
429
|
|
|
301
430
|
## Limitations
|
|
302
431
|
|
|
@@ -373,10 +502,16 @@ This follows the same pattern as [TanStack Router's `pendingComponent`](https://
|
|
|
373
502
|
## Compatibility
|
|
374
503
|
|
|
375
504
|
> [!IMPORTANT]
|
|
376
|
-
> **
|
|
505
|
+
> **Shipped UI5 baseline: 1.144.0**
|
|
377
506
|
>
|
|
378
|
-
> The
|
|
507
|
+
> The published package declares `minUI5Version: 1.144.0`, and the full CI suite runs on that shipped baseline. In addition, CI runs the library QUnit suite against OpenUI5 `1.118.0` as a compatibility lane for the core router implementation. That extra lane does not change the published manifest baseline, but it provides a concrete verification signal for consumers evaluating older runtimes.
|
|
508
|
+
|
|
509
|
+
If you maintain an app on an older UI5 stack and want to validate locally, run the dedicated compatibility check from the monorepo root:
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
npm run test:qunit:compat:118
|
|
513
|
+
```
|
|
379
514
|
|
|
380
515
|
## License
|
|
381
516
|
|
|
382
|
-
[MIT](
|
|
517
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project": {
|
|
3
|
+
"specVersion": "4.0",
|
|
4
|
+
"type": "library",
|
|
5
|
+
"metadata": {
|
|
6
|
+
"name": "ui5.guard.router"
|
|
7
|
+
},
|
|
8
|
+
"resources": {
|
|
9
|
+
"configuration": {
|
|
10
|
+
"paths": {
|
|
11
|
+
"src": "resources",
|
|
12
|
+
"test": "test-resources"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"buildManifest": {
|
|
18
|
+
"manifestVersion": "0.2",
|
|
19
|
+
"timestamp": "2026-03-16T11:10:25.198Z",
|
|
20
|
+
"versions": {
|
|
21
|
+
"builderVersion": "4.1.4",
|
|
22
|
+
"projectVersion": "4.0.13",
|
|
23
|
+
"fsVersion": "4.0.5"
|
|
24
|
+
},
|
|
25
|
+
"buildConfig": {
|
|
26
|
+
"selfContained": false,
|
|
27
|
+
"cssVariables": false,
|
|
28
|
+
"jsdoc": false,
|
|
29
|
+
"createBuildManifest": true,
|
|
30
|
+
"outputStyle": "Default",
|
|
31
|
+
"includedTasks": [],
|
|
32
|
+
"excludedTasks": []
|
|
33
|
+
},
|
|
34
|
+
"version": "1.1.1",
|
|
35
|
+
"namespace": "ui5/guard/router",
|
|
36
|
+
"tags": {
|
|
37
|
+
"/resources/ui5/guard/router/library-dbg.js": {
|
|
38
|
+
"ui5:IsDebugVariant": true
|
|
39
|
+
},
|
|
40
|
+
"/resources/ui5/guard/router/library-dbg.js.map": {
|
|
41
|
+
"ui5:IsDebugVariant": true
|
|
42
|
+
},
|
|
43
|
+
"/resources/ui5/guard/router/library.js": {
|
|
44
|
+
"ui5:HasDebugVariant": true
|
|
45
|
+
},
|
|
46
|
+
"/resources/ui5/guard/router/library.js.map": {
|
|
47
|
+
"ui5:HasDebugVariant": true
|
|
48
|
+
},
|
|
49
|
+
"/resources/ui5/guard/router/Router-dbg.js": {
|
|
50
|
+
"ui5:IsDebugVariant": true
|
|
51
|
+
},
|
|
52
|
+
"/resources/ui5/guard/router/Router-dbg.js.map": {
|
|
53
|
+
"ui5:IsDebugVariant": true
|
|
54
|
+
},
|
|
55
|
+
"/resources/ui5/guard/router/Router.js": {
|
|
56
|
+
"ui5:HasDebugVariant": true
|
|
57
|
+
},
|
|
58
|
+
"/resources/ui5/guard/router/Router.js.map": {
|
|
59
|
+
"ui5:HasDebugVariant": true
|
|
60
|
+
},
|
|
61
|
+
"/resources/ui5/guard/router/types-dbg.js": {
|
|
62
|
+
"ui5:IsDebugVariant": true
|
|
63
|
+
},
|
|
64
|
+
"/resources/ui5/guard/router/types-dbg.js.map": {
|
|
65
|
+
"ui5:IsDebugVariant": true
|
|
66
|
+
},
|
|
67
|
+
"/resources/ui5/guard/router/types.js": {
|
|
68
|
+
"ui5:HasDebugVariant": true
|
|
69
|
+
},
|
|
70
|
+
"/resources/ui5/guard/router/types.js.map": {
|
|
71
|
+
"ui5:HasDebugVariant": true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
// Generated with TypeScript 5.7.3 / OpenUI5 1.144.0 using:
|
|
2
2
|
// - yargs-parser@20.2.9
|
|
3
3
|
// - typescript@5.7.3
|
|
4
|
+
// - url@0.11.4
|
|
4
5
|
// - string_decoder@1.3.0
|
|
5
|
-
// -
|
|
6
|
+
// - punycode@1.4.1
|
|
6
7
|
// - events@3.3.0
|
|
7
|
-
// -
|
|
8
|
+
// - buffer@6.0.3
|
|
9
|
+
// - undici-types@7.18.2
|
|
8
10
|
// - @types/yauzl@2.10.3
|
|
9
11
|
// - @types/yargs-parser@21.0.3
|
|
10
12
|
// - @types/yargs@17.0.35
|
|
11
13
|
// - @types/ws@8.18.1
|
|
12
14
|
// - @types/which@2.0.2
|
|
15
|
+
// - @types/three@0.125.3
|
|
13
16
|
// - @types/stack-utils@2.0.3
|
|
14
17
|
// - @types/sizzle@2.3.10
|
|
15
18
|
// - @types/sinonjs__fake-timers@8.1.5
|
|
19
|
+
// - @types/shimmer@1.2.0
|
|
16
20
|
// - @types/qunit@2.5.4
|
|
21
|
+
// - @types/offscreencanvas@2019.6.4
|
|
17
22
|
// - @types/normalize-package-data@2.4.4
|
|
18
|
-
// - @types/node@
|
|
23
|
+
// - @types/node@25.5.0
|
|
19
24
|
// - @types/mocha@10.0.10
|
|
25
|
+
// - @types/minimatch@3.0.5
|
|
20
26
|
// - @types/jquery@3.5.13
|
|
21
27
|
// - @types/istanbul-reports@3.0.4
|
|
22
28
|
// - @types/istanbul-lib-report@3.0.3
|
|
23
29
|
// - @types/istanbul-lib-coverage@2.0.6
|
|
24
30
|
// - @types/qunit@2.19.13
|
|
25
|
-
/// <reference path="./resources/ui5/guard/router/library.d.ts"/>
|
|
26
31
|
/// <reference path="./resources/ui5/guard/router/types.d.ts"/>
|
|
32
|
+
/// <reference path="./resources/ui5/guard/router/library.d.ts"/>
|
|
27
33
|
/// <reference path="./resources/ui5/guard/router/Router.d.ts"/>
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" ?>
|
|
2
|
-
<library xmlns="http://www.sap.com/sap.ui.library.xsd">
|
|
3
|
-
<name>ui5.guard.router</name>
|
|
4
|
-
<vendor>Marco</vendor>
|
|
5
|
-
<version>1.
|
|
6
|
-
<copyright></copyright>
|
|
7
|
-
<title>UI5 Router extension with async navigation guards</title>
|
|
8
|
-
<documentation>Extends sap.m.routing.Router with async navigation guards, running before route matching begins.</documentation>
|
|
9
|
-
<dependencies>
|
|
10
|
-
<dependency>
|
|
11
|
-
<libraryName>sap.ui.core</libraryName>
|
|
12
|
-
</dependency>
|
|
13
|
-
<dependency>
|
|
14
|
-
<libraryName>sap.m</libraryName>
|
|
15
|
-
</dependency>
|
|
16
|
-
</dependencies>
|
|
17
|
-
</library>
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
2
|
+
<library xmlns="http://www.sap.com/sap.ui.library.xsd">
|
|
3
|
+
<name>ui5.guard.router</name>
|
|
4
|
+
<vendor>Marco</vendor>
|
|
5
|
+
<version>1.1.1</version>
|
|
6
|
+
<copyright></copyright>
|
|
7
|
+
<title>UI5 Router extension with async navigation guards</title>
|
|
8
|
+
<documentation>Extends sap.m.routing.Router with async navigation guards, running before route matching begins.</documentation>
|
|
9
|
+
<dependencies>
|
|
10
|
+
<dependency>
|
|
11
|
+
<libraryName>sap.ui.core</libraryName>
|
|
12
|
+
</dependency>
|
|
13
|
+
<dependency>
|
|
14
|
+
<libraryName>sap.m</libraryName>
|
|
15
|
+
</dependency>
|
|
16
|
+
</dependencies>
|
|
17
|
+
</library>
|