@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.
- package/CHANGELOG.md +83 -5
- package/README.md +694 -0
- package/dist/esm/NavigationConfigurator.interface.js +2 -0
- package/dist/esm/NavigationConfigurator.interface.js.map +1 -0
- package/dist/esm/NavigationConfigurator.js +144 -0
- package/dist/esm/NavigationConfigurator.js.map +1 -0
- package/dist/esm/NavigationProvider.interface.js +2 -0
- package/dist/esm/NavigationProvider.interface.js.map +1 -0
- package/dist/esm/NavigationProvider.js +256 -0
- package/dist/esm/NavigationProvider.js.map +1 -0
- package/dist/esm/enable-navigation.js +52 -0
- package/dist/esm/enable-navigation.js.map +1 -0
- package/dist/esm/events.js +23 -0
- package/dist/esm/events.js.map +1 -0
- package/dist/esm/index.js +6 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/BaseHistory.js +189 -0
- package/dist/esm/lib/BaseHistory.js.map +1 -0
- package/dist/esm/lib/BrowserHistory.js +91 -0
- package/dist/esm/lib/BrowserHistory.js.map +1 -0
- package/dist/esm/lib/BrowserHistoryHashStack.js +53 -0
- package/dist/esm/lib/BrowserHistoryHashStack.js.map +1 -0
- package/dist/esm/lib/BrowserHistoryStack.js +77 -0
- package/dist/esm/lib/BrowserHistoryStack.js.map +1 -0
- package/dist/esm/lib/MemoryHistory.js +47 -0
- package/dist/esm/lib/MemoryHistory.js.map +1 -0
- package/dist/esm/lib/MemoryStack.js +98 -0
- package/dist/esm/lib/MemoryStack.js.map +1 -0
- package/dist/esm/lib/create-history.js +48 -0
- package/dist/esm/lib/create-history.js.map +1 -0
- package/dist/esm/lib/index.js +8 -1
- package/dist/esm/lib/index.js.map +1 -1
- package/dist/esm/lib/state/history.actions.js +71 -0
- package/dist/esm/lib/state/history.actions.js.map +1 -0
- package/dist/esm/lib/state/history.flows.js +212 -0
- package/dist/esm/lib/state/history.flows.js.map +1 -0
- package/dist/esm/lib/state/history.reducer.js +59 -0
- package/dist/esm/lib/state/history.reducer.js.map +1 -0
- package/dist/esm/lib/state/history.state.js +37 -0
- package/dist/esm/lib/state/history.state.js.map +1 -0
- package/dist/esm/lib/state/index.js +4 -0
- package/dist/esm/lib/state/index.js.map +1 -0
- package/dist/esm/lib/types.js +25 -0
- package/dist/esm/lib/types.js.map +1 -0
- package/dist/esm/lib/utils/encode-trailing-whitespace.js +19 -0
- package/dist/esm/lib/utils/encode-trailing-whitespace.js.map +1 -0
- package/dist/esm/lib/utils/has-protocol.js +22 -0
- package/dist/esm/lib/utils/has-protocol.js.map +1 -0
- package/dist/esm/lib/utils/index.js +6 -0
- package/dist/esm/lib/utils/index.js.map +1 -0
- package/dist/esm/lib/utils/path-to-string.js +23 -0
- package/dist/esm/lib/utils/path-to-string.js.map +1 -0
- package/dist/esm/lib/utils/path-to-url.js +47 -0
- package/dist/esm/lib/utils/path-to-url.js.map +1 -0
- package/dist/esm/lib/utils/resolve-browser-location.js +53 -0
- package/dist/esm/lib/utils/resolve-browser-location.js.map +1 -0
- package/dist/esm/lib/utils/resolve-path.js +21 -0
- package/dist/esm/lib/utils/resolve-path.js.map +1 -0
- package/dist/esm/module.js +34 -25
- package/dist/esm/module.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/NavigationConfigurator.d.ts +60 -0
- package/dist/types/NavigationConfigurator.interface.d.ts +17 -0
- package/dist/types/NavigationProvider.d.ts +112 -0
- package/dist/types/NavigationProvider.interface.d.ts +75 -0
- package/dist/types/enable-navigation.d.ts +40 -0
- package/dist/types/events.d.ts +48 -0
- package/dist/types/index.d.ts +13 -6
- package/dist/types/lib/BaseHistory.d.ts +122 -0
- package/dist/types/lib/BrowserHistory.d.ts +53 -0
- package/dist/types/lib/BrowserHistoryHashStack.d.ts +41 -0
- package/dist/types/lib/BrowserHistoryStack.d.ts +51 -0
- package/dist/types/lib/MemoryHistory.d.ts +27 -0
- package/dist/types/lib/MemoryStack.d.ts +57 -0
- package/dist/types/lib/create-history.d.ts +35 -0
- package/dist/types/lib/index.d.ts +7 -1
- package/dist/types/lib/state/history.actions.d.ts +61 -0
- package/dist/types/lib/state/history.flows.d.ts +102 -0
- package/dist/types/lib/state/history.reducer.d.ts +85 -0
- package/dist/types/lib/state/history.state.d.ts +27 -0
- package/dist/types/lib/state/index.d.ts +3 -0
- package/dist/types/lib/types.d.ts +145 -0
- package/dist/types/lib/utils/encode-trailing-whitespace.d.ts +16 -0
- package/dist/types/lib/utils/has-protocol.d.ts +19 -0
- package/dist/types/lib/utils/index.d.ts +5 -0
- package/dist/types/lib/utils/path-to-string.d.ts +17 -0
- package/dist/types/lib/utils/path-to-url.d.ts +32 -0
- package/dist/types/lib/utils/resolve-browser-location.d.ts +42 -0
- package/dist/types/lib/utils/resolve-path.d.ts +17 -0
- package/dist/types/module.d.ts +33 -5
- package/dist/types/version.d.ts +1 -1
- package/package.json +25 -7
- package/src/NavigationConfigurator.interface.ts +18 -0
- package/src/NavigationConfigurator.ts +184 -0
- package/src/NavigationProvider.interface.ts +86 -0
- package/src/NavigationProvider.ts +313 -0
- package/src/__tests__/BrowserHistory.test.ts +151 -0
- package/src/__tests__/HashHistory.test.ts +121 -0
- package/src/__tests__/MemoryHistory.test.ts +185 -0
- package/src/__tests__/NavigationProvider.test.ts +60 -0
- package/src/__tests__/setup.ts +8 -0
- package/src/enable-navigation.ts +59 -0
- package/src/events.ts +65 -0
- package/src/index.ts +27 -6
- package/src/lib/BaseHistory.ts +229 -0
- package/src/lib/BrowserHistory.ts +123 -0
- package/src/lib/BrowserHistoryHashStack.ts +56 -0
- package/src/lib/BrowserHistoryStack.ts +82 -0
- package/src/lib/MemoryHistory.ts +66 -0
- package/src/lib/MemoryStack.ts +106 -0
- package/src/lib/create-history.ts +63 -0
- package/src/lib/index.ts +22 -1
- package/src/lib/state/history.actions.ts +108 -0
- package/src/lib/state/history.flows.ts +279 -0
- package/src/lib/state/history.reducer.ts +70 -0
- package/src/lib/state/history.state.ts +62 -0
- package/src/lib/state/index.ts +3 -0
- package/src/lib/types.ts +159 -0
- package/src/lib/utils/encode-trailing-whitespace.ts +18 -0
- package/src/lib/utils/has-protocol.ts +21 -0
- package/src/lib/utils/index.ts +5 -0
- package/src/lib/utils/path-to-string.ts +24 -0
- package/src/lib/utils/path-to-url.ts +52 -0
- package/src/lib/utils/resolve-browser-location.ts +60 -0
- package/src/lib/utils/resolve-path.ts +22 -0
- package/src/module.ts +53 -37
- package/src/version.ts +1 -1
- package/tsconfig.json +10 -1
- package/vitest.config.ts +14 -0
- package/dist/esm/configurator.js +0 -5
- package/dist/esm/configurator.js.map +0 -1
- package/dist/esm/createHistory.js +0 -18
- package/dist/esm/createHistory.js.map +0 -1
- package/dist/esm/lib/provider/INavigationProvider.js +0 -2
- package/dist/esm/lib/provider/INavigationProvider.js.map +0 -1
- package/dist/esm/lib/provider/NavigationProvider.js +0 -82
- package/dist/esm/lib/provider/NavigationProvider.js.map +0 -1
- package/dist/esm/lib/provider/index.js +0 -2
- package/dist/esm/lib/provider/index.js.map +0 -1
- package/dist/esm/navigator.js +0 -158
- package/dist/esm/navigator.js.map +0 -1
- package/dist/esm/types.js +0 -2
- package/dist/esm/types.js.map +0 -1
- package/dist/types/configurator.d.ts +0 -9
- package/dist/types/createHistory.d.ts +0 -4
- package/dist/types/lib/provider/INavigationProvider.d.ts +0 -38
- package/dist/types/lib/provider/NavigationProvider.d.ts +0 -22
- package/dist/types/lib/provider/index.d.ts +0 -2
- package/dist/types/navigator.d.ts +0 -105
- package/dist/types/types.d.ts +0 -3
- package/src/configurator.ts +0 -11
- package/src/createHistory.ts +0 -21
- package/src/lib/provider/INavigationProvider.ts +0 -46
- package/src/lib/provider/NavigationProvider.ts +0 -117
- package/src/lib/provider/index.ts +0 -2
- package/src/navigator.ts +0 -204
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationConfigurator.interface.js","sourceRoot":"","sources":["../../src/NavigationConfigurator.interface.ts"],"names":[],"mappings":""}
|