@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
@@ -0,0 +1,123 @@
1
+ import { fromEvent } from 'rxjs';
2
+ import { map } from 'rxjs/operators';
3
+ import { BaseHistory } from './BaseHistory';
4
+ import { BrowserHistoryStack } from './BrowserHistoryStack';
5
+ import { BrowserHistoryHashStack } from './BrowserHistoryHashStack';
6
+ import { resolveWindowLocation } from './utils';
7
+ import { Action, type HistoryStack, type NavigationBlocker } from './types';
8
+ import { createStore, createHistoryReducer, actions } from './state';
9
+
10
+ /**
11
+ * Handler for beforeunload events.
12
+ * Prevents page unload when navigation blockers are active.
13
+ */
14
+ const onBeforeUnload = (event: BeforeUnloadEvent) => {
15
+ event.preventDefault();
16
+ event.returnValue = '';
17
+ };
18
+ /**
19
+ * Constructor for a HistoryStack implementation.
20
+ *
21
+ * @param window - The window object to use for history operations
22
+ * @returns A new instance of the HistoryStack implementation
23
+ */
24
+ export interface StackConstructor {
25
+ new (window: Window): HistoryStack;
26
+ }
27
+
28
+ /**
29
+ * Options for configuring a BrowserHistory instance.
30
+ */
31
+ export type BrowserHistoryOptions = {
32
+ /** Optional window object (defaults to global window) */
33
+ window?: Window;
34
+ /** Optional stack constructor (defaults to BrowserHistoryStack, use BrowserHistoryHashStack for hash routing) */
35
+ stack?: StackConstructor;
36
+ };
37
+
38
+ /**
39
+ * Browser history implementation using native browser APIs.
40
+ *
41
+ * Uses the browser's History API (pushState/replaceState) for navigation.
42
+ * Automatically listens for popstate/hashchange events to detect browser back/forward navigation.
43
+ * Compatible with industry-standard routers (Remix/React Router).
44
+ */
45
+ export class BrowserHistory extends BaseHistory {
46
+ #window: Window;
47
+ /**
48
+ * Creates a browser history instance.
49
+ *
50
+ * Initializes with the current window location and sets up listeners for
51
+ * browser navigation events (popstate for regular routing, hashchange for hash routing).
52
+ *
53
+ * @param options - Configuration options
54
+ * @param options.window - Window object to use (defaults to global window)
55
+ * @param options.stack - Stack implementation (defaults to BrowserHistoryStack, use BrowserHistoryHashStack for hash routing)
56
+ * @throws {Error} If window is not available
57
+ */
58
+ constructor(options: BrowserHistoryOptions = {}) {
59
+ // Use provided stack or default to BrowserHistoryStack
60
+ const Stack: StackConstructor = options.stack ?? BrowserHistoryStack;
61
+ const browserWindow = options.window ?? document.defaultView;
62
+ if (!browserWindow) {
63
+ throw new Error('Window is required');
64
+ }
65
+
66
+ // Initialize state with current window location
67
+ const state = createStore(
68
+ new Stack(browserWindow),
69
+ createHistoryReducer({
70
+ delta: 0,
71
+ action: Action.Pop,
72
+ location: resolveWindowLocation(browserWindow, browserWindow.history),
73
+ }),
74
+ );
75
+ super(state);
76
+
77
+ this.#window = browserWindow;
78
+
79
+ // Determine event type based on stack implementation
80
+ // Hash routing uses 'hashchange', regular routing uses 'popstate'
81
+ const isHashHistory = state.stack instanceof BrowserHistoryHashStack;
82
+ const eventName = isHashHistory ? 'hashchange' : 'popstate';
83
+
84
+ // Listen for browser navigation events and dispatch POP actions
85
+ // This handles browser back/forward button clicks
86
+ this._addTeardown(
87
+ fromEvent(browserWindow, eventName)
88
+ .pipe(
89
+ map(() => {
90
+ const location = state.stack.current;
91
+ return actions.pop({ delta: 0, action: Action.Pop, location });
92
+ }),
93
+ )
94
+ .subscribe(this._dispatch.bind(this)),
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Registers a blocker to intercept navigation attempts.
100
+ *
101
+ * Adds a beforeunload event listener when blockers are active to prevent
102
+ * page unload (e.g., when user tries to close the tab). This provides
103
+ * browser-level protection in addition to in-app navigation blocking.
104
+ *
105
+ * @param blocker - Navigation blocker function to register
106
+ * @returns Function to unsubscribe the blocker
107
+ */
108
+ public override block(blocker: NavigationBlocker): VoidFunction {
109
+ const unblock = super.block(blocker);
110
+ // Add beforeunload listener when blockers are active
111
+ // This shows browser's "Leave site?" dialog on page unload
112
+ if (this.hasBlockers) {
113
+ window.addEventListener('beforeunload', onBeforeUnload);
114
+ }
115
+ return () => {
116
+ unblock();
117
+ // Remove beforeunload listener if no blockers remain
118
+ if (this.hasBlockers) {
119
+ window.removeEventListener('beforeunload', onBeforeUnload);
120
+ }
121
+ };
122
+ }
123
+ }
@@ -0,0 +1,56 @@
1
+ import { BrowserHistoryStack } from './BrowserHistoryStack';
2
+ import type { Location, To } from './types';
3
+ import { resolveHashLocation, resolvePath, pathToString } from './utils';
4
+
5
+ /**
6
+ * Browser history hash stack implementation using hash-based routing.
7
+ *
8
+ * Uses URL hash fragment (#) instead of pathname. The hash is not sent to the server,
9
+ * allowing routing without server configuration.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // Regular routing: https://example.com/users
14
+ * // Hash routing: https://example.com/#/users
15
+ * const stack = new BrowserHistoryHashStack(window);
16
+ * ```
17
+ */
18
+ export class BrowserHistoryHashStack extends BrowserHistoryStack {
19
+ /**
20
+ * Gets the current location from window.location.hash.
21
+ */
22
+ public get current(): Readonly<Location> {
23
+ return resolveHashLocation(this._window);
24
+ }
25
+
26
+ /**
27
+ * Creates a URL object for a given path with hash-based routing.
28
+ *
29
+ * Uses the current window location as the base and only modifies the hash fragment.
30
+ * The path is normalized to ensure it starts with '#' if not already present.
31
+ *
32
+ * @param to - The target path (string, Path object, or Location object)
33
+ * @returns A URL object with the path in the hash fragment
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // If current URL is 'https://example.com/app'
38
+ * createURL('/users?id=1')
39
+ * // URL { href: 'https://example.com/app#/users?id=1', ... }
40
+ *
41
+ * createURL({ pathname: '/dashboard', search: '?tab=settings' })
42
+ * // URL { href: 'https://example.com/app#/dashboard?tab=settings', ... }
43
+ * ```
44
+ */
45
+ public override createURL(to: To): URL {
46
+ const path = resolvePath(to);
47
+ const fullPath = pathToString(path);
48
+ // Normalize: ensure path starts with '#' for hash routing
49
+ const hashPath = fullPath.startsWith('#') ? fullPath : `#${fullPath}`;
50
+ // Use current location as base, only modify hash
51
+ const url = new URL(this._window.location.href, this.origin);
52
+ url.hash = hashPath;
53
+
54
+ return url;
55
+ }
56
+ }
@@ -0,0 +1,82 @@
1
+ import { pathToString, resolvePath, resolveWindowLocation } from './utils';
2
+ import type { HistoryStack, Location, To } from './types';
3
+
4
+ /**
5
+ * Browser history stack implementation using native browser APIs.
6
+ *
7
+ * Manages navigation state using the browser's History API (pushState/replaceState).
8
+ * Stores location state in history.state and uses relative paths for navigation.
9
+ * This is the base implementation for regular (non-hash) routing.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const stack = new BrowserHistoryStack(window);
14
+ * stack.push({ pathname: '/users', key: 'abc123', state: { userId: 1 } });
15
+ * // Updates URL to /users and adds entry to history
16
+ * ```
17
+ */
18
+ export class BrowserHistoryStack implements HistoryStack {
19
+ /**
20
+ * @param _window - The window object to use for history operations
21
+ */
22
+ public constructor(protected readonly _window: Window) {}
23
+
24
+ /**
25
+ * Gets the origin of the history stack.
26
+ */
27
+ public get origin(): string {
28
+ return this._window.location.origin;
29
+ }
30
+
31
+ /**
32
+ * Gets the current location.
33
+ */
34
+ public get current(): Location {
35
+ return resolveWindowLocation(this._window);
36
+ }
37
+
38
+ /**
39
+ * Pushes a new entry onto the history stack.
40
+ */
41
+ public push(location: Location): void {
42
+ this.navigate(location, 'PUSH');
43
+ }
44
+
45
+ /**
46
+ * Replaces the current entry in the history stack.
47
+ */
48
+ public replace(location: Location): void {
49
+ this.navigate(location, 'REPLACE');
50
+ }
51
+
52
+ /**
53
+ * Navigates to a location with the specified action.
54
+ */
55
+ public navigate(location: Location, action: 'PUSH' | 'REPLACE'): void {
56
+ const relativePath = this._createRelativePath(location);
57
+ const state = { value: location.state, key: location.key };
58
+ if (action === 'PUSH') {
59
+ this._window.history.pushState(state, '', relativePath);
60
+ } else {
61
+ this._window.history.replaceState(state, '', relativePath);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Navigates backward or forward in the history stack.
67
+ */
68
+ public go(delta: number): void {
69
+ this._window.history.go(delta);
70
+ }
71
+
72
+ /**
73
+ * Creates a URL object for a given path.
74
+ */
75
+ public createURL(to: To): URL {
76
+ return new URL(pathToString(resolvePath(to)), this.origin);
77
+ }
78
+
79
+ protected _createRelativePath(to: To): string {
80
+ return pathToString(this.createURL(to));
81
+ }
82
+ }
@@ -0,0 +1,66 @@
1
+ import { type To, type LocationState, type NavigationUpdate, Action } from './types';
2
+ import { pathToString, resolvePath } from './utils';
3
+ import { MemoryHistoryStack } from './MemoryStack';
4
+ import { BaseHistory } from './BaseHistory';
5
+ import { createHistoryReducer, createStore } from './state';
6
+
7
+ /**
8
+ * Default initial location for memory history.
9
+ */
10
+ const defaultInitialLocation: NavigationUpdate = {
11
+ delta: 0,
12
+ action: Action.Pop,
13
+ location: { pathname: '/', search: '', hash: '', key: 'unknown', state: null },
14
+ };
15
+
16
+ /**
17
+ * Options for configuring a MemoryHistory instance.
18
+ */
19
+ export type MemoryHistoryOptions = {
20
+ /** Optional initial location */
21
+ initialLocation?: NavigationUpdate;
22
+ /** Optional initial history entries */
23
+ initialHistory?: NavigationUpdate[];
24
+ };
25
+
26
+ /**
27
+ * Memory history implementation using in-memory storage.
28
+ *
29
+ * Useful for:
30
+ * - Testing: Control navigation state in tests without browser APIs
31
+ * - SSR: Server-side rendering where window is not available
32
+ * - Widgets: Serving applications as widgets without affecting the main browser history
33
+ * - Node.js environments: Environments without browser APIs
34
+ */
35
+ export class MemoryHistory extends BaseHistory {
36
+ public constructor(options?: MemoryHistoryOptions) {
37
+ const { initialLocation, initialHistory } = options ?? {};
38
+ const initial = initialLocation ?? defaultInitialLocation;
39
+
40
+ // create initial state for memory history
41
+ const initialState: LocationState = {
42
+ current: initial,
43
+ history: initialHistory ?? [initial],
44
+ blockers: [],
45
+ };
46
+
47
+ // create stack for memory history
48
+ const stack = new MemoryHistoryStack({ initialLocation: initialState.current.location });
49
+
50
+ // initialize state with stack and reducer
51
+ const state = createStore(
52
+ stack,
53
+ createHistoryReducer(() => initialState, { maxHistory: 100 }),
54
+ );
55
+
56
+ super(state);
57
+ }
58
+
59
+ /**
60
+ * Creates a URL object for a given path using memory:// origin.
61
+ */
62
+ public createURL(to: To): URL {
63
+ const path = pathToString(resolvePath(to));
64
+ return new URL(path, 'memory://');
65
+ }
66
+ }
@@ -0,0 +1,106 @@
1
+ import type { HistoryStack, Location, LocationState, To } from './types';
2
+ import { pathToString, resolvePath } from './utils';
3
+
4
+ /**
5
+ * Memory-based history stack implementation.
6
+ *
7
+ * Stores navigation state in memory instead of using browser APIs.
8
+ * Unlike BrowserHistoryStack, push/replace only update the current location
9
+ * (history entries are managed by the reducer, not the stack).
10
+ */
11
+ export class MemoryHistoryStack implements HistoryStack {
12
+ #current: Location;
13
+
14
+ /**
15
+ * Gets the origin of the history stack.
16
+ * Always returns 'memory://' for in-memory storage.
17
+ */
18
+ get origin(): string {
19
+ return 'memory://';
20
+ }
21
+
22
+ /**
23
+ * Gets the current location.
24
+ */
25
+ get current(): Location {
26
+ return this.#current;
27
+ }
28
+
29
+ /**
30
+ * Creates a memory history stack instance.
31
+ *
32
+ * @param options - Configuration options
33
+ * @param options.initialLocation - Optional initial location (defaults to '/')
34
+ */
35
+ constructor(options?: { initialLocation?: Location }) {
36
+ this.#current = options?.initialLocation ?? {
37
+ pathname: '/',
38
+ search: '',
39
+ hash: '',
40
+ state: null,
41
+ key: '',
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Pushes a new entry onto the history stack.
47
+ *
48
+ * Only updates the current location. History entries are managed by the reducer.
49
+ */
50
+ push(location: Location): void {
51
+ this.#current = location;
52
+ }
53
+
54
+ /**
55
+ * Replaces the current entry in the history stack.
56
+ *
57
+ * Only updates the current location. History entries are managed by the reducer.
58
+ */
59
+ replace(location: Location): void {
60
+ this.#current = location;
61
+ }
62
+
63
+ /**
64
+ * Navigates backward or forward in the history stack.
65
+ *
66
+ * Uses the history state to find the target location by index.
67
+ * Clamps to valid range (first or last entry if out of bounds).
68
+ *
69
+ * @param delta - Number of steps to move (negative for back, positive for forward)
70
+ * @param state - Current location state with history entries
71
+ */
72
+ go(delta: number, state: Readonly<LocationState>): void {
73
+ const { history, current } = state;
74
+ const currentLocation = current.location ?? this.#current;
75
+ // Find current location in history by key
76
+ const currentIndex = history.findIndex((entry) => entry.location.key === currentLocation.key);
77
+
78
+ // If current location not found, use last entry
79
+ if (currentIndex === -1) {
80
+ if (history.length > 0) {
81
+ this.#current = history[history.length - 1].location;
82
+ }
83
+ return;
84
+ }
85
+
86
+ // Calculate target index and clamp to valid range
87
+ const newIndex = currentIndex + delta;
88
+ if (newIndex < 0) {
89
+ this.#current = state.history[0].location;
90
+ } else if (newIndex >= state.history.length) {
91
+ this.#current = state.history[state.history.length - 1].location;
92
+ } else {
93
+ this.#current = state.history[newIndex].location;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Creates a URL object for a given path.
99
+ *
100
+ * All URLs use the 'memory://' origin since this is in-memory storage.
101
+ */
102
+ createURL(to: To): URL {
103
+ const path = resolvePath(to);
104
+ return new URL(pathToString(path), this.origin);
105
+ }
106
+ }
@@ -0,0 +1,63 @@
1
+ import { MemoryHistory, type MemoryHistoryOptions } from './MemoryHistory';
2
+ import { BrowserHistory, type BrowserHistoryOptions } from './BrowserHistory';
3
+ import { BrowserHistoryStack } from './BrowserHistoryStack';
4
+ import { BrowserHistoryHashStack } from './BrowserHistoryHashStack';
5
+
6
+ type HistoryCtorMap = {
7
+ memory: (options?: MemoryHistoryOptions) => MemoryHistory;
8
+ browser: (options?: Omit<BrowserHistoryOptions, 'stack'>) => BrowserHistory;
9
+ hash: (options?: Omit<BrowserHistoryOptions, 'stack'>) => BrowserHistory;
10
+ };
11
+
12
+ /**
13
+ * Creates a history instance based on the specified type.
14
+ *
15
+ * Factory function for creating different history implementations:
16
+ * - `'browser'`: Creates a {@link BrowserHistory} instance using regular routing (pathname-based)
17
+ * - `'hash'`: Creates a {@link BrowserHistory} instance using hash routing (hash-based)
18
+ * - `'memory'`: Creates a {@link MemoryHistory} instance for testing or SSR (in-memory)
19
+ *
20
+ * All returned instances are compatible with industry-standard routers (Remix/React Router).
21
+ *
22
+ * @param type - The type of history to create ('browser', 'hash', or 'memory')
23
+ * @param args - Optional arguments for the history type
24
+ * @returns A History instance compatible with industry-standard routers (Remix/React Router)
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // Regular browser routing
29
+ * const history = createHistory('browser');
30
+ *
31
+ * // Hash-based routing
32
+ * const hashHistory = createHistory('hash');
33
+ *
34
+ * // In-memory history for testing
35
+ * const memoryHistory = createHistory('memory', { initialLocation: { ... } });
36
+ * ```
37
+ */
38
+ export const createHistory = <T extends keyof HistoryCtorMap>(
39
+ type: T,
40
+ ...args: Parameters<HistoryCtorMap[T]>
41
+ ): ReturnType<HistoryCtorMap[T]> => {
42
+ switch (type) {
43
+ case 'memory':
44
+ return new MemoryHistory(...(args as [MemoryHistoryOptions])) as ReturnType<
45
+ HistoryCtorMap[T]
46
+ >;
47
+ case 'browser': {
48
+ const options = args[0] as Omit<BrowserHistoryOptions, 'stack'>;
49
+ return new BrowserHistory({ ...options, stack: BrowserHistoryStack }) as ReturnType<
50
+ HistoryCtorMap[T]
51
+ >;
52
+ }
53
+ case 'hash': {
54
+ const options = args[0] as Omit<BrowserHistoryOptions, 'stack'>;
55
+ return new BrowserHistory({ ...options, stack: BrowserHistoryHashStack }) as ReturnType<
56
+ HistoryCtorMap[T]
57
+ >;
58
+ }
59
+ default: {
60
+ throw new Error(`Invalid history type: ${type}`);
61
+ }
62
+ }
63
+ };
package/src/lib/index.ts CHANGED
@@ -1 +1,22 @@
1
- export * from './provider';
1
+ // History implementations
2
+ export { BaseHistory } from './BaseHistory';
3
+ export { BrowserHistory } from './BrowserHistory';
4
+ export { MemoryHistory } from './MemoryHistory';
5
+
6
+ // History stacks
7
+ export { BrowserHistoryStack } from './BrowserHistoryStack';
8
+ export { BrowserHistoryHashStack as HashHistoryStack } from './BrowserHistoryHashStack';
9
+ export { MemoryHistoryStack } from './MemoryStack';
10
+
11
+ // Types
12
+ export type {
13
+ Action,
14
+ History,
15
+ HistoryStack,
16
+ Location,
17
+ NavigateOptions,
18
+ NavigationListener,
19
+ NavigationUpdate,
20
+ Path,
21
+ To,
22
+ } from './types';
@@ -0,0 +1,108 @@
1
+ import { createAction, createAsyncAction } from '@equinor/fusion-observable/actions';
2
+ import type { ActionTypes } from '@equinor/fusion-observable/actions';
3
+ import { v7 as generateId } from 'uuid';
4
+ import type { To, NavigateOptions, NavigationBlocker, NavigationUpdate } from '../types';
5
+
6
+ /**
7
+ * Action for navigating to a new location (push or replace).
8
+ */
9
+ const navigateAction = createAsyncAction(
10
+ 'navigation/navigate',
11
+ (to: To, options: NavigateOptions) => ({
12
+ payload: { to, options },
13
+ meta: { key: generateId() },
14
+ }),
15
+ (update: NavigationUpdate) => ({
16
+ payload: { update },
17
+ }),
18
+ (error: Error) => ({
19
+ payload: { error },
20
+ }),
21
+ );
22
+
23
+ const abortNavigateAction = createAction('navigation/navigate::abort', (reason?: string) => ({
24
+ payload: { reason },
25
+ }));
26
+
27
+ /**
28
+ * Action for navigating backward or forward in history.
29
+ */
30
+ const goDeltaAction = createAsyncAction(
31
+ 'navigation/go',
32
+ (delta: number) => ({
33
+ payload: { delta },
34
+ }),
35
+ (update: NavigationUpdate) => ({
36
+ payload: { update },
37
+ }),
38
+ (error: Error) => ({
39
+ payload: { error },
40
+ }),
41
+ );
42
+
43
+ /**
44
+ * Action for handling browser back/forward navigation (popstate events).
45
+ */
46
+ const popStateAction = createAsyncAction(
47
+ 'navigation/pop',
48
+ (update?: NavigationUpdate) => ({
49
+ payload: { update },
50
+ }),
51
+ (update: NavigationUpdate) => ({
52
+ payload: { update },
53
+ }),
54
+ (error: Error) => ({
55
+ payload: { error },
56
+ }),
57
+ );
58
+
59
+ /**
60
+ * Action for validating the current location against history state.
61
+ */
62
+ const validateLocationAction = createAsyncAction(
63
+ 'navigation/navigationValidation',
64
+ () => ({
65
+ payload: {},
66
+ }),
67
+ (update: NavigationUpdate) => ({
68
+ payload: { update },
69
+ }),
70
+ (error: Error) => ({
71
+ payload: { error },
72
+ }),
73
+ );
74
+
75
+ /**
76
+ * Action for adding a navigation blocker.
77
+ */
78
+ const addBlockerAction = createAction('navigation/addBlocker', (blocker: NavigationBlocker) => ({
79
+ payload: { blocker },
80
+ }));
81
+
82
+ /**
83
+ * Action for removing a navigation blocker.
84
+ */
85
+ const removeBlockerAction = createAction(
86
+ 'navigation/removeBlocker',
87
+ (blocker: NavigationBlocker) => ({
88
+ payload: { blocker },
89
+ }),
90
+ );
91
+
92
+ /**
93
+ * Navigation actions for history state management.
94
+ */
95
+ export const actions = {
96
+ navigate: navigateAction,
97
+ abortNavigate: abortNavigateAction,
98
+ go: goDeltaAction,
99
+ pop: popStateAction,
100
+ validateLocation: validateLocationAction,
101
+ addBlocker: addBlockerAction,
102
+ removeBlocker: removeBlockerAction,
103
+ };
104
+
105
+ /**
106
+ * Union type of all navigation actions.
107
+ */
108
+ export type Actions = ActionTypes<typeof actions>;