@equinor/fusion-framework-module-navigation 6.0.1 → 7.0.0-next.2

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 (159) hide show
  1. package/CHANGELOG.md +83 -5
  2. package/README.md +694 -0
  3. package/dist/esm/NavigationConfigurator.interface.js +2 -0
  4. package/dist/esm/NavigationConfigurator.interface.js.map +1 -0
  5. package/dist/esm/NavigationConfigurator.js +144 -0
  6. package/dist/esm/NavigationConfigurator.js.map +1 -0
  7. package/dist/esm/NavigationProvider.interface.js +2 -0
  8. package/dist/esm/NavigationProvider.interface.js.map +1 -0
  9. package/dist/esm/NavigationProvider.js +256 -0
  10. package/dist/esm/NavigationProvider.js.map +1 -0
  11. package/dist/esm/enable-navigation.js +52 -0
  12. package/dist/esm/enable-navigation.js.map +1 -0
  13. package/dist/esm/events.js +23 -0
  14. package/dist/esm/events.js.map +1 -0
  15. package/dist/esm/index.js +6 -5
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/lib/BaseHistory.js +189 -0
  18. package/dist/esm/lib/BaseHistory.js.map +1 -0
  19. package/dist/esm/lib/BrowserHistory.js +91 -0
  20. package/dist/esm/lib/BrowserHistory.js.map +1 -0
  21. package/dist/esm/lib/BrowserHistoryHashStack.js +53 -0
  22. package/dist/esm/lib/BrowserHistoryHashStack.js.map +1 -0
  23. package/dist/esm/lib/BrowserHistoryStack.js +77 -0
  24. package/dist/esm/lib/BrowserHistoryStack.js.map +1 -0
  25. package/dist/esm/lib/MemoryHistory.js +47 -0
  26. package/dist/esm/lib/MemoryHistory.js.map +1 -0
  27. package/dist/esm/lib/MemoryStack.js +98 -0
  28. package/dist/esm/lib/MemoryStack.js.map +1 -0
  29. package/dist/esm/lib/create-history.js +48 -0
  30. package/dist/esm/lib/create-history.js.map +1 -0
  31. package/dist/esm/lib/index.js +8 -1
  32. package/dist/esm/lib/index.js.map +1 -1
  33. package/dist/esm/lib/state/history.actions.js +71 -0
  34. package/dist/esm/lib/state/history.actions.js.map +1 -0
  35. package/dist/esm/lib/state/history.flows.js +212 -0
  36. package/dist/esm/lib/state/history.flows.js.map +1 -0
  37. package/dist/esm/lib/state/history.reducer.js +59 -0
  38. package/dist/esm/lib/state/history.reducer.js.map +1 -0
  39. package/dist/esm/lib/state/history.state.js +37 -0
  40. package/dist/esm/lib/state/history.state.js.map +1 -0
  41. package/dist/esm/lib/state/index.js +4 -0
  42. package/dist/esm/lib/state/index.js.map +1 -0
  43. package/dist/esm/lib/types.js +25 -0
  44. package/dist/esm/lib/types.js.map +1 -0
  45. package/dist/esm/lib/utils/encode-trailing-whitespace.js +19 -0
  46. package/dist/esm/lib/utils/encode-trailing-whitespace.js.map +1 -0
  47. package/dist/esm/lib/utils/has-protocol.js +22 -0
  48. package/dist/esm/lib/utils/has-protocol.js.map +1 -0
  49. package/dist/esm/lib/utils/index.js +6 -0
  50. package/dist/esm/lib/utils/index.js.map +1 -0
  51. package/dist/esm/lib/utils/path-to-string.js +23 -0
  52. package/dist/esm/lib/utils/path-to-string.js.map +1 -0
  53. package/dist/esm/lib/utils/path-to-url.js +47 -0
  54. package/dist/esm/lib/utils/path-to-url.js.map +1 -0
  55. package/dist/esm/lib/utils/resolve-browser-location.js +53 -0
  56. package/dist/esm/lib/utils/resolve-browser-location.js.map +1 -0
  57. package/dist/esm/lib/utils/resolve-path.js +21 -0
  58. package/dist/esm/lib/utils/resolve-path.js.map +1 -0
  59. package/dist/esm/module.js +34 -25
  60. package/dist/esm/module.js.map +1 -1
  61. package/dist/esm/version.js +1 -1
  62. package/dist/esm/version.js.map +1 -1
  63. package/dist/tsconfig.tsbuildinfo +1 -1
  64. package/dist/types/NavigationConfigurator.d.ts +60 -0
  65. package/dist/types/NavigationConfigurator.interface.d.ts +17 -0
  66. package/dist/types/NavigationProvider.d.ts +112 -0
  67. package/dist/types/NavigationProvider.interface.d.ts +75 -0
  68. package/dist/types/enable-navigation.d.ts +40 -0
  69. package/dist/types/events.d.ts +48 -0
  70. package/dist/types/index.d.ts +13 -6
  71. package/dist/types/lib/BaseHistory.d.ts +122 -0
  72. package/dist/types/lib/BrowserHistory.d.ts +53 -0
  73. package/dist/types/lib/BrowserHistoryHashStack.d.ts +41 -0
  74. package/dist/types/lib/BrowserHistoryStack.d.ts +51 -0
  75. package/dist/types/lib/MemoryHistory.d.ts +27 -0
  76. package/dist/types/lib/MemoryStack.d.ts +57 -0
  77. package/dist/types/lib/create-history.d.ts +35 -0
  78. package/dist/types/lib/index.d.ts +7 -1
  79. package/dist/types/lib/state/history.actions.d.ts +61 -0
  80. package/dist/types/lib/state/history.flows.d.ts +102 -0
  81. package/dist/types/lib/state/history.reducer.d.ts +85 -0
  82. package/dist/types/lib/state/history.state.d.ts +27 -0
  83. package/dist/types/lib/state/index.d.ts +3 -0
  84. package/dist/types/lib/types.d.ts +145 -0
  85. package/dist/types/lib/utils/encode-trailing-whitespace.d.ts +16 -0
  86. package/dist/types/lib/utils/has-protocol.d.ts +19 -0
  87. package/dist/types/lib/utils/index.d.ts +5 -0
  88. package/dist/types/lib/utils/path-to-string.d.ts +17 -0
  89. package/dist/types/lib/utils/path-to-url.d.ts +32 -0
  90. package/dist/types/lib/utils/resolve-browser-location.d.ts +42 -0
  91. package/dist/types/lib/utils/resolve-path.d.ts +17 -0
  92. package/dist/types/module.d.ts +33 -5
  93. package/dist/types/version.d.ts +1 -1
  94. package/package.json +25 -7
  95. package/src/NavigationConfigurator.interface.ts +18 -0
  96. package/src/NavigationConfigurator.ts +184 -0
  97. package/src/NavigationProvider.interface.ts +86 -0
  98. package/src/NavigationProvider.ts +313 -0
  99. package/src/__tests__/BrowserHistory.test.ts +151 -0
  100. package/src/__tests__/HashHistory.test.ts +121 -0
  101. package/src/__tests__/MemoryHistory.test.ts +185 -0
  102. package/src/__tests__/NavigationProvider.test.ts +60 -0
  103. package/src/__tests__/setup.ts +8 -0
  104. package/src/enable-navigation.ts +59 -0
  105. package/src/events.ts +65 -0
  106. package/src/index.ts +27 -6
  107. package/src/lib/BaseHistory.ts +229 -0
  108. package/src/lib/BrowserHistory.ts +123 -0
  109. package/src/lib/BrowserHistoryHashStack.ts +56 -0
  110. package/src/lib/BrowserHistoryStack.ts +82 -0
  111. package/src/lib/MemoryHistory.ts +66 -0
  112. package/src/lib/MemoryStack.ts +106 -0
  113. package/src/lib/create-history.ts +63 -0
  114. package/src/lib/index.ts +22 -1
  115. package/src/lib/state/history.actions.ts +108 -0
  116. package/src/lib/state/history.flows.ts +279 -0
  117. package/src/lib/state/history.reducer.ts +70 -0
  118. package/src/lib/state/history.state.ts +62 -0
  119. package/src/lib/state/index.ts +3 -0
  120. package/src/lib/types.ts +159 -0
  121. package/src/lib/utils/encode-trailing-whitespace.ts +18 -0
  122. package/src/lib/utils/has-protocol.ts +21 -0
  123. package/src/lib/utils/index.ts +5 -0
  124. package/src/lib/utils/path-to-string.ts +24 -0
  125. package/src/lib/utils/path-to-url.ts +52 -0
  126. package/src/lib/utils/resolve-browser-location.ts +60 -0
  127. package/src/lib/utils/resolve-path.ts +22 -0
  128. package/src/module.ts +53 -37
  129. package/src/version.ts +1 -1
  130. package/tsconfig.json +10 -1
  131. package/vitest.config.ts +14 -0
  132. package/dist/esm/configurator.js +0 -5
  133. package/dist/esm/configurator.js.map +0 -1
  134. package/dist/esm/createHistory.js +0 -18
  135. package/dist/esm/createHistory.js.map +0 -1
  136. package/dist/esm/lib/provider/INavigationProvider.js +0 -2
  137. package/dist/esm/lib/provider/INavigationProvider.js.map +0 -1
  138. package/dist/esm/lib/provider/NavigationProvider.js +0 -82
  139. package/dist/esm/lib/provider/NavigationProvider.js.map +0 -1
  140. package/dist/esm/lib/provider/index.js +0 -2
  141. package/dist/esm/lib/provider/index.js.map +0 -1
  142. package/dist/esm/navigator.js +0 -158
  143. package/dist/esm/navigator.js.map +0 -1
  144. package/dist/esm/types.js +0 -2
  145. package/dist/esm/types.js.map +0 -1
  146. package/dist/types/configurator.d.ts +0 -9
  147. package/dist/types/createHistory.d.ts +0 -4
  148. package/dist/types/lib/provider/INavigationProvider.d.ts +0 -38
  149. package/dist/types/lib/provider/NavigationProvider.d.ts +0 -22
  150. package/dist/types/lib/provider/index.d.ts +0 -2
  151. package/dist/types/navigator.d.ts +0 -105
  152. package/dist/types/types.d.ts +0 -3
  153. package/src/configurator.ts +0 -11
  154. package/src/createHistory.ts +0 -21
  155. package/src/lib/provider/INavigationProvider.ts +0 -46
  156. package/src/lib/provider/NavigationProvider.ts +0 -117
  157. package/src/lib/provider/index.ts +0 -2
  158. package/src/navigator.ts +0 -204
  159. package/src/types.ts +0 -4
package/README.md ADDED
@@ -0,0 +1,694 @@
1
+ # Fusion Framework Navigation Module
2
+
3
+ > __The Fusion Framework Navigation Module provides routing and navigation capabilities with observable state management.__
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.
6
+
7
+ ## Concept
8
+
9
+ The Navigation Module abstracts the complexity of managing navigation state in a Fusion Framework application. It provides:
10
+
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
30
+ ```
31
+
32
+
33
+
34
+
35
+ ## Quick Start
36
+
37
+ ```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();
42
+
43
+ // Simple usage - just provide basename
44
+ enableNavigation(configurator, '/apps/my-app');
45
+
46
+ // Advanced usage - full configuration
47
+ enableNavigation(configurator, {
48
+ configure: (config) => {
49
+ config.setBasename('/apps/my-app');
50
+ 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
+ }
85
+ });
86
+ ```
87
+
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 |
124
+
125
+ If not provided, defaults to browser history.
126
+
127
+ **Example:**
128
+ ```ts
129
+ // Browser history (default)
130
+ config.setHistory(createHistory('browser'));
131
+
132
+ // Hash history
133
+ config.setHistory(createHistory('hash'));
134
+
135
+ // Memory history (for testing)
136
+ config.setHistory(createHistory('memory'));
137
+ ```
138
+
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.
144
+
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
+ ```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
+ ```
170
+
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'
279
+ });
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
+
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'
304
+ });
305
+ console.log(hrefWithSearch); // '/apps/my-app/users?id=1#section'
306
+ ```
307
+
308
+ #### createURL(to?)
309
+
310
+ Creates a full URL object with basename included. Similar to `createHref` but returns a URL object.
311
+
312
+ ```ts
313
+ const url = navigation.createURL('/users');
314
+ console.log(url.pathname); // '/apps/my-app/users'
315
+ console.log(url.href); // Full URL string
316
+
317
+ // Create URL for current path
318
+ const currentUrl = navigation.createURL();
319
+ ```
320
+
321
+ #### createRouter(routes)
322
+
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.
324
+
325
+ ```ts
326
+ import { type AgnosticRouteObject } from '@remix-run/router';
327
+
328
+ const routes: AgnosticRouteObject[] = [
329
+ { path: '/', element: <Home /> },
330
+ { path: '/users', element: <Users /> },
331
+ { path: '/users/:id', element: <UserDetail /> }
332
+ ];
333
+
334
+ 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
+ ```
434
+
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
441
+
442
+ ## TypeScript Support
443
+
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
538
+
539
+ ```ts
540
+ 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
+
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!
574
+ ```
575
+
576
+ ### Navigation Blocking
577
+
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
+ ```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
593
+ });
594
+ } else {
595
+ unblock(); // Remove the block
596
+ transition.retry(); // Allow navigation immediately
597
+ }
598
+ });
599
+
600
+ // Clean up when done
601
+ unblock();
602
+ ```
603
+
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
+ ## API Reference
619
+
620
+ ### enableNavigation
621
+
622
+ Helper function to enable the navigation module.
623
+
624
+ ```ts
625
+ enableNavigation(
626
+ configurator: IModulesConfigurator,
627
+ basenameOrOptions?: string | {
628
+ configure: (config: NavigationConfigurator, ref?: unknown) => void
629
+ }
630
+ ): void
631
+ ```
632
+
633
+ **Parameters:**
634
+ - `configurator`: The modules configurator to add navigation to
635
+ - `basenameOrOptions`: Either a basename string or configuration object with `configure` callback
636
+
637
+ **Examples:**
638
+ ```ts
639
+ // Simple usage
640
+ enableNavigation(configurator, '/apps/my-app');
641
+
642
+ // Advanced usage
643
+ enableNavigation(configurator, {
644
+ configure: (config) => {
645
+ config.setBasename('/apps/my-app');
646
+ config.setHistory(createHistory('browser'));
647
+ }
648
+ });
649
+ ```
650
+
651
+ ### createHistory
652
+
653
+ Creates a history instance based on the specified type.
654
+
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`
681
+
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
689
+
690
+ ## See Also
691
+
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)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=NavigationConfigurator.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavigationConfigurator.interface.js","sourceRoot":"","sources":["../../src/NavigationConfigurator.interface.ts"],"names":[],"mappings":""}