@equinor/fusion-framework-module-navigation 7.0.0-next.2 → 7.0.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +42 -19
  2. package/README.md +97 -614
  3. package/dist/esm/NavigationConfigurator.js +29 -6
  4. package/dist/esm/NavigationConfigurator.js.map +1 -1
  5. package/dist/esm/NavigationProvider.js +57 -23
  6. package/dist/esm/NavigationProvider.js.map +1 -1
  7. package/dist/esm/events.js +20 -3
  8. package/dist/esm/events.js.map +1 -1
  9. package/dist/esm/index.js +23 -0
  10. package/dist/esm/index.js.map +1 -1
  11. package/dist/esm/lib/BaseHistory.js +23 -6
  12. package/dist/esm/lib/BaseHistory.js.map +1 -1
  13. package/dist/esm/lib/BrowserHistoryStack.js +5 -6
  14. package/dist/esm/lib/BrowserHistoryStack.js.map +1 -1
  15. package/dist/esm/lib/MemoryHistory.js +13 -6
  16. package/dist/esm/lib/MemoryHistory.js.map +1 -1
  17. package/dist/esm/lib/MemoryStack.js +1 -0
  18. package/dist/esm/lib/MemoryStack.js.map +1 -1
  19. package/dist/esm/lib/ProxyHistory.js +114 -0
  20. package/dist/esm/lib/ProxyHistory.js.map +1 -0
  21. package/dist/esm/lib/create-history.js +7 -13
  22. package/dist/esm/lib/create-history.js.map +1 -1
  23. package/dist/esm/lib/index.js +10 -0
  24. package/dist/esm/lib/index.js.map +1 -1
  25. package/dist/esm/lib/state/history.flows.js +7 -12
  26. package/dist/esm/lib/state/history.flows.js.map +1 -1
  27. package/dist/esm/lib/state/history.reducer.js +8 -4
  28. package/dist/esm/lib/state/history.reducer.js.map +1 -1
  29. package/dist/esm/lib/state/history.state.js +9 -3
  30. package/dist/esm/lib/state/history.state.js.map +1 -1
  31. package/dist/esm/lib/utils/resolve-browser-location.js +2 -2
  32. package/dist/esm/lib/utils/resolve-browser-location.js.map +1 -1
  33. package/dist/esm/module.js +4 -4
  34. package/dist/esm/version.js +1 -1
  35. package/dist/esm/version.js.map +1 -1
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/dist/types/NavigationConfigurator.d.ts +10 -1
  38. package/dist/types/NavigationConfigurator.interface.d.ts +25 -5
  39. package/dist/types/NavigationProvider.d.ts +57 -23
  40. package/dist/types/NavigationProvider.interface.d.ts +20 -7
  41. package/dist/types/events.d.ts +20 -3
  42. package/dist/types/index.d.ts +24 -1
  43. package/dist/types/lib/BaseHistory.d.ts +23 -6
  44. package/dist/types/lib/BrowserHistoryStack.d.ts +5 -6
  45. package/dist/types/lib/MemoryHistory.d.ts +5 -5
  46. package/dist/types/lib/ProxyHistory.d.ts +68 -0
  47. package/dist/types/lib/create-history.d.ts +7 -13
  48. package/dist/types/lib/index.d.ts +10 -0
  49. package/dist/types/lib/state/history.flows.d.ts +6 -11
  50. package/dist/types/lib/state/history.reducer.d.ts +8 -4
  51. package/dist/types/lib/state/history.state.d.ts +9 -3
  52. package/dist/types/lib/types.d.ts +39 -8
  53. package/dist/types/version.d.ts +1 -1
  54. package/package.json +12 -13
  55. package/src/NavigationConfigurator.interface.ts +28 -5
  56. package/src/NavigationConfigurator.ts +47 -15
  57. package/src/NavigationProvider.interface.ts +20 -7
  58. package/src/NavigationProvider.ts +57 -23
  59. package/src/__tests__/ProxyHistory.test.ts +149 -0
  60. package/src/events.ts +20 -3
  61. package/src/index.ts +25 -1
  62. package/src/lib/BaseHistory.ts +23 -6
  63. package/src/lib/BrowserHistoryStack.ts +5 -6
  64. package/src/lib/MemoryHistory.ts +13 -6
  65. package/src/lib/MemoryStack.ts +1 -0
  66. package/src/lib/ProxyHistory.ts +144 -0
  67. package/src/lib/create-history.ts +7 -13
  68. package/src/lib/index.ts +11 -0
  69. package/src/lib/state/history.flows.ts +7 -12
  70. package/src/lib/state/history.reducer.ts +8 -4
  71. package/src/lib/state/history.state.ts +9 -3
  72. package/src/lib/types.ts +39 -8
  73. package/src/lib/utils/resolve-browser-location.ts +2 -2
  74. package/src/module.ts +4 -4
  75. package/src/version.ts +1 -1
package/README.md CHANGED
@@ -1,694 +1,177 @@
1
- # Fusion Framework Navigation Module
1
+ # @equinor/fusion-framework-module-navigation
2
2
 
3
- > __The Fusion Framework Navigation Module provides routing and navigation capabilities with observable state management.__
3
+ Routing and navigation module for **Fusion Framework** with observable state management and automatic basename localization.
4
4
 
5
- The Navigation Module provides a reactive API based on RxJS observables while handling the complexities of path localization and basename management. It ensures that your application works seamlessly whether served from a root path or a subdirectory, and provides consistent navigation state across your entire application.
5
+ ## Features
6
6
 
7
- ## Concept
7
+ - **Path localization** — consumers work with clean paths (`/users`) while the history stack receives full paths (`/apps/my-app/users`).
8
+ - **Observable state** — navigation state exposed as an RxJS observable (`state$`) with `shareReplay` semantics.
9
+ - **Multiple history types** — browser (pathname), hash (`#/path`), and memory (no URL changes).
10
+ - **Router compatibility** — `createRouter()` creates `@remix-run/router` instances wired to the framework history.
11
+ - **Navigation blocking** — intercept navigations with `history.block()` and optionally retry.
12
+ - **Telemetry & events** — dispatches `NavigateEvent` / `NavigatedEvent` and tracks actions via the telemetry module.
8
13
 
9
- The Navigation Module abstracts the complexity of managing navigation state in a Fusion Framework application. It provides:
14
+ ## Installation
10
15
 
11
- 1. **Path Localization**: Automatically handles basename prefixes, so consumers work with clean paths like `/users` while the underlying history receives full paths like `/apps/my-app/users`
12
-
13
- 2. **Observable State**: Exposes navigation state as an RxJS observable, enabling reactive programming patterns throughout your application
14
-
15
- 3. **History Abstraction**: Supports multiple history types (browser, hash, memory) with a unified API, making it easy to switch between deployment scenarios
16
-
17
- 4. **Router Compatibility**: Provides a `createRouter()` method that creates router instances compatible with industry-standard routers (Remix/React Router)
18
-
19
- ### How It Works
20
-
21
- ```mermaid
22
- flowchart TD
23
- A[Your Application<br/>Clean paths: /users] -->|push/replace| B[NavigationProvider]
24
- B -->|Localizes paths<br/>Adds basename| C[History<br/>Browser/Hash/Memory]
25
- C -->|Full paths| D[Router<br/>/apps/my-app/users]
26
-
27
- B -->|state$ observable| A
28
- B -->|path getter| A
29
- C -->|Navigation events| B
16
+ ```sh
17
+ pnpm add @equinor/fusion-framework-module-navigation
30
18
  ```
31
19
 
20
+ ## Usage
32
21
 
33
-
34
-
35
- ## Quick Start
22
+ ### Enable the module
36
23
 
37
24
  ```ts
38
- import { enableNavigation, createHistory } from '@equinor/fusion-framework-module-navigation';
39
- import { ModulesConfigurator } from '@equinor/fusion-framework-module';
40
-
41
- const configurator = new ModulesConfigurator();
25
+ import { enableNavigation } from '@equinor/fusion-framework-module-navigation';
42
26
 
43
- // Simple usage - just provide basename
27
+ // Minimal basename only
44
28
  enableNavigation(configurator, '/apps/my-app');
45
29
 
46
- // Advanced usage - full configuration
30
+ // Advanced full configuration
47
31
  enableNavigation(configurator, {
48
32
  configure: (config) => {
49
33
  config.setBasename('/apps/my-app');
50
34
  config.setHistory(createHistory('browser'));
51
- }
52
- });
53
- ```
54
-
55
- ## Configuration
56
-
57
- ### Basic Configuration
58
-
59
- The simplest way to enable navigation is by providing a basename string:
60
-
61
- ```ts
62
- enableNavigation(configurator, '/apps/my-app');
63
- ```
64
-
65
- This will:
66
- - Use browser history (default)
67
- - Configure basename for path localization
68
- - Set up the navigation module with sensible defaults
69
-
70
- ### Advanced Configuration
71
-
72
- For more control, use the configuration callback:
73
-
74
- ```ts
75
- import { enableNavigation, createHistory } from '@equinor/fusion-framework-module-navigation';
76
-
77
- enableNavigation(configurator, {
78
- configure: (config) => {
79
- // Set the basename (required when app is served from subdirectory)
80
- config.setBasename('/apps/my-app');
81
-
82
- // Set custom history type
83
- config.setHistory(createHistory('browser')); // or 'hash', 'memory'
84
- }
35
+ },
85
36
  });
86
37
  ```
87
38
 
88
- ### Configuration Options
89
-
90
- #### basename
91
-
92
- The base pathname for your application. This is **required** when your app is served from a subdirectory.
93
-
94
- **Why basename matters:**
95
- - The URL pathname must start with the basename when your app is served from a subdirectory
96
- - The navigation module automatically handles adding/removing the basename prefix
97
- - Consumers receive localized paths (basename removed), while the underlying history receives full paths
98
-
99
- **Example:**
100
- ```ts
101
- // If your app is served at: https://example.com/apps/my-app
102
- config.setBasename('/apps/my-app');
103
-
104
- // When you navigate to '/users':
105
- // - Consumer sees: '/users'
106
- // - History receives: '/apps/my-app/users'
107
- // - Browser URL: 'https://example.com/apps/my-app/users'
108
- ```
109
-
110
- **When to set basename:**
111
- - ✅ App is served from a subdirectory (e.g., `/apps/my-app`)
112
- - ✅ App is part of a larger portal or micro-frontend architecture
113
- - ❌ App is served from root (e.g., `https://example.com/`) - leave basename undefined
114
-
115
- #### history
116
-
117
- The history instance to use for navigation. The module provides three history types:
118
-
119
- | Type | Description | Use Case |
120
- |------|-------------|----------|
121
- | `'browser'` | Uses browser History API with normal URLs | **Default** - Most web applications |
122
- | `'hash'` | Uses hash-based routing (e.g., `#/path`) | Legacy browser support, static hosting without server config |
123
- | `'memory'` | Uses in-memory history (no URL changes) | **Widget applications**, testing, SSR, or when URL shouldn't change |
39
+ ### Programmatic navigation
124
40
 
125
- If not provided, defaults to browser history.
126
-
127
- **Example:**
128
41
  ```ts
129
- // Browser history (default)
130
- config.setHistory(createHistory('browser'));
42
+ const navigation = framework.modules.navigation;
131
43
 
132
- // Hash history
133
- config.setHistory(createHistory('hash'));
44
+ navigation.push('/users'); // adds history entry
45
+ navigation.replace('/login'); // replaces current entry
46
+ navigation.push('/users', { id: 1 }); // with state
134
47
 
135
- // Memory history (for testing)
136
- config.setHistory(createHistory('memory'));
48
+ console.log(navigation.path.pathname); // '/users' basename removed
137
49
  ```
138
50
 
139
- ## History Types Explained
140
-
141
- ### Browser History
142
-
143
- Uses the browser's native History API with clean URLs. This is the default and recommended option for most applications.
51
+ ### Observable navigation state
144
52
 
145
- **Characteristics:**
146
- - Clean URLs: `https://example.com/apps/my-app/users`
147
- - Requires server configuration for client-side routing
148
- - Full browser history support (back/forward buttons work)
149
- - Best SEO and user experience
150
-
151
- **When to use:**
152
- - ✅ Modern web applications
153
- - ✅ Server can be configured for client-side routing
154
- - ✅ You want clean, SEO-friendly URLs
155
-
156
- **Example:**
157
53
  ```ts
158
- config.setHistory(createHistory('browser'));
159
- // Navigates to: https://example.com/apps/my-app/users
160
- ```
161
-
162
- **Server Configuration:**
163
- For browser history to work, your server must serve `index.html` for all routes. Example nginx config:
164
-
165
- ```nginx
166
- location / {
167
- try_files $uri $uri/ /index.html;
168
- }
169
- ```
54
+ import { filter } from 'rxjs';
170
55
 
171
- ### Hash History
172
-
173
- Uses hash-based routing where the path is stored in the URL hash fragment.
174
-
175
- **Characteristics:**
176
- - URLs include hash: `https://example.com/#/apps/my-app/users`
177
- - No server configuration needed
178
- - Works with static hosting
179
- - Slightly less clean URLs
180
-
181
- **When to use:**
182
- - ✅ Static hosting without server configuration
183
- - ✅ Legacy browser support required
184
- - ✅ You can't configure server routing rules
185
- - ❌ SEO is important (hash fragments are not crawled)
186
-
187
- **Example:**
188
- ```ts
189
- config.setHistory(createHistory('hash'));
190
- // Navigates to: https://example.com/#/apps/my-app/users
191
- ```
192
-
193
- ### Memory History
194
-
195
- Uses in-memory history storage without modifying the browser URL.
196
-
197
- **Characteristics:**
198
- - No URL changes (URL stays the same)
199
- - History stored in memory only
200
- - Perfect for testing
201
- - Ideal for widget/embedded applications
202
-
203
- **When to use:**
204
- - ✅ **Widget applications** - When serving an application as a widget embedded in another page
205
- - ✅ Testing (unit tests, integration tests)
206
- - ✅ SSR scenarios where URL shouldn't change
207
- - ✅ Embedded applications where URL changes are not desired
208
- - ❌ Production web applications (use browser or hash instead)
209
-
210
- **Example:**
211
- ```ts
212
- // Widget application - history in memory, no URL changes
213
- config.setHistory(createHistory('memory'));
214
- // No URL changes, history managed in memory
215
- ```
216
-
217
- ## Usage
218
-
219
- ### Accessing Navigation Provider
220
-
221
- Once configured, access the navigation provider through the framework:
222
-
223
- ```ts
224
- import { useFramework } from '@equinor/fusion-framework-react';
225
-
226
- function MyComponent() {
227
- const framework = useFramework();
228
- const navigation = framework.navigation;
229
-
230
- // Get current path (localized, basename removed)
231
- const currentPath = navigation.path;
232
- console.log(currentPath.pathname); // '/users' (not '/apps/my-app/users')
233
-
234
- // Navigate programmatically
235
- navigation.push('/users');
236
- navigation.replace('/dashboard');
237
- }
238
- ```
239
-
240
- ### Navigation Methods
241
-
242
- #### push(to, state?)
243
-
244
- Pushes a new navigation entry onto the history stack. Adds a new entry to the browser history, so the back button will return to the previous location.
245
-
246
- ```ts
247
- // Navigate to a new path
248
- navigation.push('/users');
249
-
250
- // Navigate with state data
251
- navigation.push('/users', { userId: 123 });
252
-
253
- // Navigate with Path object (includes search/hash)
254
- navigation.push({
255
- pathname: '/users',
256
- search: '?sort=name',
257
- hash: '#section'
258
- });
259
-
260
- // Use current path (no argument)
261
- navigation.push(); // Pushes current path again
262
- ```
263
-
264
- #### replace(to, state?)
265
-
266
- Replaces the current history entry. Unlike `push`, this doesn't add a new entry to history, so the back button won't return to the previous location.
267
-
268
- ```ts
269
- // Replace current location
270
- navigation.replace('/login');
271
-
272
- // Replace with state
273
- navigation.replace('/login', { from: '/dashboard' });
274
-
275
- // Replace with Path object
276
- navigation.replace({
277
- pathname: '/login',
278
- search: '?redirect=/dashboard'
56
+ navigation.state$.subscribe(({ action, location }) => {
57
+ console.log(action, location.pathname); // 'PUSH' '/users'
279
58
  });
280
- ```
281
-
282
- **When to use `replace` vs `push`:**
283
- - Use `push` when you want to add to history (normal navigation)
284
- - Use `replace` when you don't want to add to history (redirects, auth flows)
285
59
 
286
- #### createHref(to?)
287
-
288
- Creates a localized href string with basename included. Useful for creating links that work with the navigation system.
289
-
290
- ```ts
291
- // Create href for a path
292
- const href = navigation.createHref('/users');
293
- console.log(href); // '/apps/my-app/users'
294
-
295
- // Create href for current path
296
- const currentHref = navigation.createHref();
297
- console.log(currentHref); // '/apps/my-app/users' (current path)
298
-
299
- // Create href with Path object
300
- const hrefWithSearch = navigation.createHref({
301
- pathname: '/users',
302
- search: '?id=1',
303
- hash: '#section'
60
+ navigation.state$.pipe(
61
+ filter(({ action }) => action === 'POP'),
62
+ ).subscribe(({ location }) => {
63
+ console.log('Back/forward to', location.pathname);
304
64
  });
305
- console.log(hrefWithSearch); // '/apps/my-app/users?id=1#section'
306
65
  ```
307
66
 
308
- #### createURL(to?)
309
-
310
- Creates a full URL object with basename included. Similar to `createHref` but returns a URL object.
67
+ ### Create href / URL
311
68
 
312
69
  ```ts
313
- const url = navigation.createURL('/users');
314
- console.log(url.pathname); // '/apps/my-app/users'
315
- console.log(url.href); // Full URL string
70
+ // basename = '/apps/my-app'
71
+ navigation.createHref('/users');
72
+ // '/apps/my-app/users'
316
73
 
317
- // Create URL for current path
318
- const currentUrl = navigation.createURL();
74
+ navigation.createURL('/users');
75
+ // URL { pathname: '/apps/my-app/users', ... }
319
76
  ```
320
77
 
321
- #### createRouter(routes)
78
+ ### Router integration (legacy)
322
79
 
323
- Creates a router instance from route configuration that is compatible with industry-standard routers (Remix/React Router). This is the recommended way to create routers in Fusion Framework applications.
80
+ > **Note:** Prefer `@equinor/fusion-framework-react-router` for new applications.
324
81
 
325
82
  ```ts
326
- import { type AgnosticRouteObject } from '@remix-run/router';
83
+ import type { AgnosticRouteObject } from '@remix-run/router';
327
84
 
328
85
  const routes: AgnosticRouteObject[] = [
329
86
  { path: '/', element: <Home /> },
330
- { path: '/users', element: <Users /> },
331
- { path: '/users/:id', element: <UserDetail /> }
87
+ { path: '/users/:id', element: <UserDetail /> },
332
88
  ];
333
89
 
334
90
  const router = navigation.createRouter(routes);
335
-
336
- // Use the router with your routing library
337
- router.subscribe((state) => {
338
- // Handle route state
339
- });
340
- ```
341
-
342
- > [!CAUTION]
343
- > **Important**: Always use `navigation.createRouter()` instead of creating router instances directly. Creating routers directly will create their own history wrapper that is **NOT** connected to the framework, causing unpredictable behavior and broken links.
344
-
345
- ## Path Localization
346
-
347
- Path localization is a core feature of the Navigation Module. It ensures that:
348
-
349
- 1. **Consumers work with clean paths**: Your application code uses paths like `/users` without worrying about basename
350
- 2. **History receives full paths**: The underlying history gets paths like `/apps/my-app/users` with basename included
351
- 3. **Automatic translation**: The module automatically adds/removes basename as needed
352
-
353
- ### How Path Localization Works
354
-
355
- ```ts
356
- // Configuration
357
- config.setBasename('/apps/my-app');
358
-
359
- // Consumer code (your application)
360
- navigation.push('/users');
361
- // → NavigationProvider adds basename
362
- // → History receives: '/apps/my-app/users'
363
- // → Browser URL: 'https://example.com/apps/my-app/users'
364
-
365
- // Reading path (consumer receives localized version)
366
- console.log(navigation.path.pathname); // '/users' (basename removed)
367
- console.log(history.location.pathname); // '/apps/my-app/users' (full path)
368
-
369
- // Observable state (localized paths)
370
- navigation.state$.subscribe(({ location }) => {
371
- console.log(location.pathname); // '/users' (basename removed)
372
- });
373
- ```
374
-
375
- ### Path Localization Rules
376
-
377
- 1. **When pushing/replacing**: Paths are automatically prefixed with basename
378
- 2. **When reading paths**: The `path` getter returns localized paths (basename removed)
379
- 3. **In state$ observable**: All emitted paths are localized
380
- 4. **In createHref/createURL**: Basename is automatically included
381
-
382
- ### Path Localization Examples
383
-
384
- ```ts
385
- // Example: Basename is '/apps/my-app'
386
-
387
- // Push navigation
388
- navigation.push('/users');
389
- // → History: '/apps/my-app/users'
390
- // → navigation.path: '/users'
391
-
392
- // Push with search and hash
393
- navigation.push('/users?id=1#section');
394
- // → History: '/apps/my-app/users?id=1#section'
395
- // → navigation.path: '/users?id=1#section'
396
-
397
- // Push root path
398
- navigation.push('/');
399
- // → History: '/apps/my-app'
400
- // → navigation.path: '/' or ''
401
-
402
- // Create href
403
- const href = navigation.createHref('/users');
404
- // → Returns: '/apps/my-app/users'
405
- ```
406
-
407
- ## Observable Navigation State
408
-
409
- The navigation module provides an observable stream of navigation updates using RxJS:
410
-
411
- ```ts
412
- import { map, filter } from 'rxjs';
413
-
414
- // Subscribe to all navigation changes
415
- navigation.state$.subscribe(({ action, location }) => {
416
- console.log('Action:', action); // 'PUSH', 'REPLACE', or 'POP'
417
- console.log('Location:', location.pathname); // Localized path
418
- });
419
-
420
- // Filter for specific actions
421
- navigation.state$.pipe(
422
- filter(({ action }) => action === 'POP')
423
- ).subscribe(({ location }) => {
424
- console.log('User navigated back to:', location.pathname);
425
- });
426
-
427
- // Map to pathname only
428
- navigation.state$.pipe(
429
- map(({ location }) => location.pathname)
430
- ).subscribe(pathname => {
431
- console.log('Current path:', pathname);
432
- });
433
91
  ```
434
92
 
435
- ### Observable Characteristics
436
-
437
- - **Shareable**: Multiple subscribers share the same observable (uses `shareReplay`)
438
- - **Replays last value**: Late subscribers immediately receive the last navigation state
439
- - **Localized paths**: All emitted paths have basename removed
440
- - **Filtered by basename**: Only paths within the basename scope are emitted
93
+ > Always use `navigation.createRouter()` instead of `createBrowserRouter()` directly—creating a router outside the provider breaks basename handling and state synchronisation.
441
94
 
442
- ## TypeScript Support
95
+ ### History types
443
96
 
444
- The module is fully typed and provides comprehensive TypeScript definitions. Most types are inferred automatically, but you can import them when needed:
445
-
446
- ```ts
447
- import type { INavigationProvider } from '@equinor/fusion-framework-module-navigation';
448
-
449
- // Type-safe navigation usage
450
- function navigateToUsers(navigation: INavigationProvider) {
451
- navigation.push('/users'); // ✅ Type-safe
452
- // navigation.push(123); // ❌ Type error
453
- }
454
- ```
455
-
456
- **Available types:**
457
- - `INavigationProvider` - Navigation provider interface
458
- - `INavigationConfigurator` - Configuration interface (for helpers)
459
- - `History` - History instance type
460
- - `Path` - Path object type
461
- - `To` - Navigation destination type
462
-
463
- ## Examples
464
-
465
- ### Complete Application Setup
466
-
467
- ```ts
468
- import { enableNavigation, createHistory } from '@equinor/fusion-framework-module-navigation';
469
- import { ModulesConfigurator } from '@equinor/fusion-framework-module';
470
-
471
- export const configure = async (configurator: ModulesConfigurator) => {
472
- enableNavigation(configurator, {
473
- configure: (config) => {
474
- // Set basename if app is served from subdirectory
475
- config.setBasename('/apps/my-app');
476
-
477
- // Use browser history (default)
478
- config.setHistory(createHistory('browser'));
479
- }
480
- });
481
- };
482
- ```
483
-
484
- ### React Component Usage
485
-
486
- ```tsx
487
- import { useEffect, useState } from 'react';
488
-
489
- function NavigationExample() {
490
- const navigation = useModule('navigation')
491
- const [currentPath, setCurrentPath] = useState(navigation.path.pathname);
492
-
493
- useEffect(() => {
494
- const subscription = navigation.state$.subscribe(({ location }) => {
495
- setCurrentPath(location.pathname);
496
- });
497
-
498
- return () => subscription.unsubscribe();
499
- }, [navigation]);
500
-
501
- return (
502
- <div>
503
- <p>Current path: {currentPath}</p>
504
- <button onClick={() => navigation.push('/users')}>
505
- Go to Users
506
- </button>
507
- <button onClick={() => navigation.replace('/dashboard')}>
508
- Go to Dashboard
509
- </button>
510
- </div>
511
- );
512
- }
513
- ```
514
-
515
- ### Router Integration
516
-
517
- ```tsx
518
- import { useFramework } from '@equinor/fusion-framework-react';
519
- import { RouterProvider } from '@remix-run/react';
520
-
521
- function App() {
522
- const framework = useFramework();
523
- const navigation = framework.navigation;
524
-
525
- const routes = [
526
- { path: '/', element: <Home /> },
527
- { path: '/users', element: <Users /> },
528
- { path: '/users/:id', element: <UserDetail /> }
529
- ];
530
-
531
- const router = navigation.createRouter(routes);
532
-
533
- return <RouterProvider router={router} />;
534
- }
535
- ```
536
-
537
- ### Testing with Memory History
97
+ | Factory argument | Class | Description |
98
+ |---|---|---|
99
+ | `'browser'` | `BrowserHistory` | Pathname-based routing via the History API. **Default.** |
100
+ | `'hash'` | `BrowserHistory` (hash stack) | Hash-fragment routing (`#/path`). No server config needed. |
101
+ | `'memory'` | `MemoryHistory` | In-memory history. Ideal for widgets, tests, and SSR. |
538
102
 
539
103
  ```ts
540
104
  import { createHistory } from '@equinor/fusion-framework-module-navigation';
541
- import { enableNavigation } from '@equinor/fusion-framework-module-navigation';
542
-
543
- // In tests, use memory history
544
- const configurator = new ModulesConfigurator();
545
- enableNavigation(configurator, {
546
- configure: (config) => {
547
- config.setHistory(createHistory('memory'));
548
- }
549
- });
550
- ```
551
-
552
- ## Important Notes
553
-
554
- ### ⚠️ Router Integration
555
-
556
- > [!CAUTION]
557
- > **Never create router instances directly** in Fusion Framework applications. Always use `navigation.createRouter()` to ensure proper integration with the framework's navigation system.
558
105
 
559
- **Why?**
560
- - Creating routers directly creates their own history wrapper
561
- - This breaks the connection to the framework's navigation provider
562
- - Links and navigation will behave unpredictably
563
- - State synchronization issues will occur
564
-
565
- **✅ Correct:**
566
- ```ts
567
- const router = navigation.createRouter(routes);
568
- ```
569
-
570
- **❌ Incorrect:**
571
- ```ts
572
- import { createBrowserRouter } from '@remix-run/router';
573
- const router = createBrowserRouter(routes); // Don't do this!
106
+ const browserHistory = createHistory('browser');
107
+ const hashHistory = createHistory('hash');
108
+ const memoryHistory = createHistory('memory');
574
109
  ```
575
110
 
576
- ### Navigation Blocking
111
+ ### Navigation blocking
577
112
 
578
- The `history.block()` method is available for intercepting navigation attempts. It uses a callback-based approach where the blocker receives a transition object with a `retry()` method.
579
-
580
- **Important:** You must unblock before retrying. The block must be removed before the navigation can proceed.
581
-
582
- **Example:**
583
113
  ```ts
584
- const framework = useFramework();
585
- const history = framework.navigation.history;
586
-
587
- const unblock = history.block((transition) => {
588
- if (shouldPreventNavigation(transition)) {
589
- // Show confirmation dialog
590
- showConfirmationDialog(() => {
591
- unblock(); // Remove the block first
592
- transition.retry(); // Then allow navigation to proceed
114
+ const unblock = navigation.history.block((transition) => {
115
+ if (hasUnsavedChanges) {
116
+ showConfirmDialog(() => {
117
+ unblock(); // remove blocker first
118
+ transition.retry(); // then retry navigation
593
119
  });
594
120
  } else {
595
- unblock(); // Remove the block
596
- transition.retry(); // Allow navigation immediately
121
+ unblock();
122
+ transition.retry();
597
123
  }
598
124
  });
599
-
600
- // Clean up when done
601
- unblock();
602
125
  ```
603
126
 
604
- ### Path Localization Best Practices
605
-
606
- 1. **Always use the provider's methods**: Use `navigation.push()` instead of directly calling `history.push()`
607
- 2. **Read paths from the provider**: Use `navigation.path` instead of `history.location` for consumer-facing code
608
- 3. **Subscribe to state$**: Use `navigation.state$` instead of listening to history events directly
609
- 4. **Create routers properly**: Always use `navigation.createRouter()` for router integration
610
-
611
- ### Basename Configuration
612
-
613
- - **Required when**: App is served from a subdirectory
614
- - **Not required when**: App is served from root (`/`)
615
- - **Must match**: The URL path prefix where your app is served
616
- - **Example**: If app is at `https://example.com/apps/my-app`, basename should be `/apps/my-app`
617
-
618
127
  ## API Reference
619
128
 
620
- ### enableNavigation
129
+ ### Functions
621
130
 
622
- Helper function to enable the navigation module.
131
+ | Export | Description |
132
+ |---|---|
133
+ | `enableNavigation(configurator, opts?)` | Registers the navigation module on a configurator. |
134
+ | `createHistory(type, ...args)` | Factory for `BrowserHistory`, hash-based `BrowserHistory`, or `MemoryHistory`. |
623
135
 
624
- ```ts
625
- enableNavigation(
626
- configurator: IModulesConfigurator,
627
- basenameOrOptions?: string | {
628
- configure: (config: NavigationConfigurator, ref?: unknown) => void
629
- }
630
- ): void
631
- ```
136
+ ### Classes
632
137
 
633
- **Parameters:**
634
- - `configurator`: The modules configurator to add navigation to
635
- - `basenameOrOptions`: Either a basename string or configuration object with `configure` callback
138
+ | Export | Description |
139
+ |---|---|
140
+ | `NavigationProvider` | Module provider manages state, localization, and lifecycle. |
141
+ | `NavigationConfigurator` | Fluent config builder with Zod validation. |
142
+ | `BrowserHistory` | History backed by browser `pushState` / `replaceState`. |
143
+ | `MemoryHistory` | History backed by in-memory storage. |
636
144
 
637
- **Examples:**
638
- ```ts
639
- // Simple usage
640
- enableNavigation(configurator, '/apps/my-app');
145
+ ### Interfaces & Types
641
146
 
642
- // Advanced usage
643
- enableNavigation(configurator, {
644
- configure: (config) => {
645
- config.setBasename('/apps/my-app');
646
- config.setHistory(createHistory('browser'));
647
- }
648
- });
649
- ```
147
+ | Export | Description |
148
+ |---|---|
149
+ | `INavigationProvider` | Public contract for the navigation provider. |
150
+ | `INavigationConfigurator` | Configuration shape (`basename`, `history`, `telemetry`, `eventProvider`). |
151
+ | `History` | History instance contract (observable state + navigation methods). |
152
+ | `Path` | `{ pathname, search, hash }` |
153
+ | `Location` | `Path` extended with `state` and `key`. |
154
+ | `To` | `string \| Partial<Path>` — target for navigation operations. |
155
+ | `Action` | Enum: `Pop`, `Push`, `Replace`. |
156
+ | `NavigationUpdate` | `{ delta, action, location }` emitted by `state$`. |
650
157
 
651
- ### createHistory
158
+ ### Events
652
159
 
653
- Creates a history instance based on the specified type.
160
+ | Event | When | Cancelable |
161
+ |---|---|---|
162
+ | `NavigateEvent` (`onNavigate`) | Before navigation | Yes |
163
+ | `NavigatedEvent` (`onNavigated`) | After navigation | No |
654
164
 
655
- ```ts
656
- createHistory(type?: 'browser' | 'hash' | 'memory'): History
657
- ```
658
-
659
- **Parameters:**
660
- - `type`: The type of history to create (defaults to `'browser'`)
661
-
662
- **Returns:**
663
- - A `History` instance for navigation management
664
-
665
- **Examples:**
666
- ```ts
667
- const browserHistory = createHistory('browser');
668
- const hashHistory = createHistory('hash');
669
- const memoryHistory = createHistory('memory');
670
- ```
671
-
672
- ### NavigationProvider
673
-
674
- The main provider class that manages navigation state and provides navigation methods.
675
-
676
- **Properties:**
677
- - `state$`: Observable stream of navigation updates
678
- - `path`: Current localized path (basename removed)
679
- - `history`: The underlying history instance
680
- - `navigator`: (deprecated) Alias for `history`
165
+ ## Configuration
681
166
 
682
- **Methods:**
683
- - `push(to, state?)`: Push new navigation entry
684
- - `replace(to, state?)`: Replace current navigation entry
685
- - `createHref(to?)`: Create href string with basename
686
- - `createURL(to?)`: Create URL object with basename
687
- - `createRouter(routes)`: Create router instance compatible with industry-standard routers
688
- - `dispose()`: Clean up resources
167
+ | Option | Type | Default | Description |
168
+ |---|---|---|---|
169
+ | `basename` | `string` | `undefined` | URL path prefix stripped from / prepended to consumer paths. |
170
+ | `history` | `History` | Browser history (or memory in Node) | History instance created via `createHistory()`. |
171
+ | `telemetry` | `ITelemetryProvider` | Auto-resolved | Tracks navigation events and errors. |
172
+ | `eventProvider` | `IEventModuleProvider` | Auto-resolved | Dispatches `onNavigate` / `onNavigated` events. |
689
173
 
690
174
  ## See Also
691
175
 
692
- - [Fusion Framework Documentation](https://github.com/equinor/fusion-framework)
693
- - [Navigation Module Cookbook](https://github.com/equinor/fusion-framework/tree/main/cookbooks/app-react-router)
694
- - [RxJS Documentation](https://rxjs.dev/) (for observable patterns)
176
+ - [`@equinor/fusion-framework-react-router`](https://github.com/equinor/fusion-framework/tree/main/packages/react/router) — React Router integration
177
+ - [Navigation Cookbook](https://github.com/equinor/fusion-framework/tree/main/cookbooks/app-react-router) — Example application with routing