@equinor/fusion-framework-module-navigation 7.0.3 → 7.0.5

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 (142) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/esm/NavigateEvent.js +24 -0
  3. package/dist/esm/NavigateEvent.js.map +1 -0
  4. package/dist/esm/NavigatedEvent.js +20 -0
  5. package/dist/esm/NavigatedEvent.js.map +1 -0
  6. package/dist/esm/NavigationConfigurator.js +13 -1
  7. package/dist/esm/NavigationConfigurator.js.map +1 -1
  8. package/dist/esm/NavigationProvider.js +15 -3
  9. package/dist/esm/NavigationProvider.js.map +1 -1
  10. package/dist/esm/enable-navigation.js +1 -0
  11. package/dist/esm/enable-navigation.js.map +1 -1
  12. package/dist/esm/index.js +2 -1
  13. package/dist/esm/index.js.map +1 -1
  14. package/dist/esm/lib/BaseHistory.js +9 -0
  15. package/dist/esm/lib/BaseHistory.js.map +1 -1
  16. package/dist/esm/lib/BrowserHistory.js +1 -0
  17. package/dist/esm/lib/BrowserHistory.js.map +1 -1
  18. package/dist/esm/lib/BrowserHistoryHashStack.js +1 -0
  19. package/dist/esm/lib/BrowserHistoryHashStack.js.map +1 -1
  20. package/dist/esm/lib/BrowserHistoryStack.js +15 -0
  21. package/dist/esm/lib/BrowserHistoryStack.js.map +1 -1
  22. package/dist/esm/lib/MemoryHistory.js +4 -1
  23. package/dist/esm/lib/MemoryHistory.js.map +1 -1
  24. package/dist/esm/lib/{MemoryStack.js → MemoryHistoryStack.js} +12 -1
  25. package/dist/esm/lib/MemoryHistoryStack.js.map +1 -0
  26. package/dist/esm/lib/ProxyHistory.js +1 -0
  27. package/dist/esm/lib/ProxyHistory.js.map +1 -1
  28. package/dist/esm/lib/create-history.js +1 -0
  29. package/dist/esm/lib/create-history.js.map +1 -1
  30. package/dist/esm/lib/index.js +1 -1
  31. package/dist/esm/lib/index.js.map +1 -1
  32. package/dist/esm/lib/state/{history.actions.js → actions.js} +1 -1
  33. package/dist/esm/lib/state/actions.js.map +1 -0
  34. package/dist/esm/lib/state/check-blockers.js +36 -0
  35. package/dist/esm/lib/state/check-blockers.js.map +1 -0
  36. package/dist/esm/lib/state/create-flow.js +27 -0
  37. package/dist/esm/lib/state/create-flow.js.map +1 -0
  38. package/dist/esm/lib/state/{history.reducer.js → create-history-reducer.js} +5 -2
  39. package/dist/esm/lib/state/create-history-reducer.js.map +1 -0
  40. package/dist/esm/lib/state/create-store.js +26 -0
  41. package/dist/esm/lib/state/create-store.js.map +1 -0
  42. package/dist/esm/lib/state/flow-creators.js +7 -0
  43. package/dist/esm/lib/state/flow-creators.js.map +1 -0
  44. package/dist/esm/lib/state/go.js +12 -0
  45. package/dist/esm/lib/state/go.js.map +1 -0
  46. package/dist/esm/lib/state/history.state.js +1 -39
  47. package/dist/esm/lib/state/history.state.js.map +1 -1
  48. package/dist/esm/lib/state/index.js +3 -3
  49. package/dist/esm/lib/state/index.js.map +1 -1
  50. package/dist/esm/lib/state/navigate.js +43 -0
  51. package/dist/esm/lib/state/navigate.js.map +1 -0
  52. package/dist/esm/lib/state/pop.js +14 -0
  53. package/dist/esm/lib/state/pop.js.map +1 -0
  54. package/dist/esm/lib/state/validate-current-location.js +26 -0
  55. package/dist/esm/lib/state/validate-current-location.js.map +1 -0
  56. package/dist/esm/lib/utils/index.js +2 -2
  57. package/dist/esm/lib/utils/index.js.map +1 -1
  58. package/dist/esm/lib/utils/resolve-browser-location.js +1 -52
  59. package/dist/esm/lib/utils/resolve-browser-location.js.map +1 -1
  60. package/dist/esm/lib/utils/resolve-hash-location.js +20 -0
  61. package/dist/esm/lib/utils/resolve-hash-location.js.map +1 -0
  62. package/dist/esm/lib/utils/resolve-window-location.js +18 -0
  63. package/dist/esm/lib/utils/resolve-window-location.js.map +1 -0
  64. package/dist/esm/version.js +1 -1
  65. package/dist/tsconfig.tsbuildinfo +1 -1
  66. package/dist/types/NavigateEvent.d.ts +36 -0
  67. package/dist/types/NavigatedEvent.d.ts +28 -0
  68. package/dist/types/NavigationConfigurator.d.ts +3 -0
  69. package/dist/types/NavigationProvider.d.ts +3 -0
  70. package/dist/types/index.d.ts +2 -1
  71. package/dist/types/lib/BaseHistory.d.ts +8 -0
  72. package/dist/types/lib/BrowserHistoryHashStack.d.ts +1 -0
  73. package/dist/types/lib/BrowserHistoryStack.d.ts +14 -0
  74. package/dist/types/lib/MemoryHistory.d.ts +3 -0
  75. package/dist/types/lib/{MemoryStack.d.ts → MemoryHistoryStack.d.ts} +6 -0
  76. package/dist/types/lib/ProxyHistory.d.ts +1 -1
  77. package/dist/types/lib/index.d.ts +1 -1
  78. package/dist/types/lib/state/check-blockers.d.ts +3 -0
  79. package/dist/types/lib/state/create-flow.d.ts +10 -0
  80. package/dist/types/lib/state/{history.reducer.d.ts → create-history-reducer.d.ts} +32 -32
  81. package/dist/types/lib/state/create-store.d.ts +17 -0
  82. package/dist/types/lib/state/flow-creators.d.ts +7 -0
  83. package/dist/types/lib/state/go.d.ts +3 -0
  84. package/dist/types/lib/state/history.state.d.ts +2 -23
  85. package/dist/types/lib/state/index.d.ts +4 -3
  86. package/dist/types/lib/state/navigate.d.ts +9 -0
  87. package/dist/types/lib/state/pop.d.ts +3 -0
  88. package/dist/types/lib/state/validate-current-location.d.ts +3 -0
  89. package/dist/types/lib/types.d.ts +1 -1
  90. package/dist/types/lib/utils/index.d.ts +2 -2
  91. package/dist/types/lib/utils/resolve-browser-location.d.ts +1 -42
  92. package/dist/types/lib/utils/resolve-hash-location.d.ts +10 -0
  93. package/dist/types/lib/utils/resolve-window-location.d.ts +10 -0
  94. package/dist/types/version.d.ts +1 -1
  95. package/package.json +8 -8
  96. package/src/NavigateEvent.ts +47 -0
  97. package/src/NavigatedEvent.ts +35 -0
  98. package/src/NavigationConfigurator.ts +13 -3
  99. package/src/NavigationProvider.interface.ts +1 -1
  100. package/src/NavigationProvider.ts +15 -3
  101. package/src/__tests__/NavigationProvider.test.ts +11 -0
  102. package/src/enable-navigation.ts +1 -0
  103. package/src/index.ts +2 -6
  104. package/src/lib/BaseHistory.ts +9 -0
  105. package/src/lib/BrowserHistory.ts +1 -0
  106. package/src/lib/BrowserHistoryHashStack.ts +1 -0
  107. package/src/lib/BrowserHistoryStack.ts +15 -0
  108. package/src/lib/MemoryHistory.ts +4 -1
  109. package/src/lib/{MemoryStack.ts → MemoryHistoryStack.ts} +11 -0
  110. package/src/lib/ProxyHistory.ts +2 -1
  111. package/src/lib/create-history.ts +1 -0
  112. package/src/lib/index.ts +1 -1
  113. package/src/lib/state/check-blockers.ts +52 -0
  114. package/src/lib/state/create-flow.ts +36 -0
  115. package/src/lib/state/{history.reducer.ts → create-history-reducer.ts} +4 -1
  116. package/src/lib/state/create-store.ts +39 -0
  117. package/src/lib/state/flow-creators.ts +7 -0
  118. package/src/lib/state/go.ts +20 -0
  119. package/src/lib/state/history.state.ts +2 -56
  120. package/src/lib/state/index.ts +4 -3
  121. package/src/lib/state/navigate.ts +58 -0
  122. package/src/lib/state/pop.ts +24 -0
  123. package/src/lib/state/validate-current-location.ts +33 -0
  124. package/src/lib/types.ts +1 -1
  125. package/src/lib/utils/index.ts +2 -2
  126. package/src/lib/utils/resolve-browser-location.ts +1 -60
  127. package/src/lib/utils/resolve-hash-location.ts +26 -0
  128. package/src/lib/utils/resolve-window-location.ts +24 -0
  129. package/src/version.ts +1 -1
  130. package/dist/esm/events.js +0 -40
  131. package/dist/esm/events.js.map +0 -1
  132. package/dist/esm/lib/MemoryStack.js.map +0 -1
  133. package/dist/esm/lib/state/history.actions.js.map +0 -1
  134. package/dist/esm/lib/state/history.flows.js +0 -207
  135. package/dist/esm/lib/state/history.flows.js.map +0 -1
  136. package/dist/esm/lib/state/history.reducer.js.map +0 -1
  137. package/dist/types/events.d.ts +0 -65
  138. package/dist/types/lib/state/history.flows.d.ts +0 -97
  139. package/src/events.ts +0 -82
  140. package/src/lib/state/history.flows.ts +0 -274
  141. /package/dist/types/lib/state/{history.actions.d.ts → actions.d.ts} +0 -0
  142. /package/src/lib/state/{history.actions.ts → actions.ts} +0 -0
@@ -0,0 +1,39 @@
1
+ import { createState, type ReducerWithInitialState } from '@equinor/fusion-observable';
2
+ import { actions, type Actions } from './actions';
3
+ import { flowCreators } from './flow-creators';
4
+ import { createFlow } from './create-flow';
5
+ import type { HistoryFlowCreator } from './navigate';
6
+ import type { HistoryStack, LocationState } from '../types';
7
+ import type { HistoryState } from './history.state';
8
+
9
+ /**
10
+ * Creates a history store with the specified stack and reducer.
11
+ * @param stack - The history stack implementation.
12
+ * @param reducer - The reducer that handles state transitions.
13
+ * @param options - Optional flow configuration.
14
+ * @returns A fully-initialized history state with flows attached.
15
+ */
16
+ export const createStore = (
17
+ stack: HistoryStack,
18
+ reducer: ReducerWithInitialState<LocationState, Actions>,
19
+ options?: {
20
+ flows?: HistoryFlowCreator[];
21
+ skipBlockCheck?: boolean;
22
+ validateCurrentLocation?: boolean;
23
+ },
24
+ ): HistoryState => {
25
+ // Attach the stack onto the created state so flows can reach it without prop-drilling.
26
+ const state = Object.assign(createState(actions, reducer), { stack }) as HistoryState;
27
+ const flows = new Set<HistoryFlowCreator>(
28
+ options?.flows ?? [flowCreators.navigate, flowCreators.go, flowCreators.pop],
29
+ );
30
+ // Validation is opt-in since it re-checks the current location on every relevant action.
31
+ if (options?.validateCurrentLocation) {
32
+ flows.add(flowCreators.validateCurrentLocation);
33
+ }
34
+ const flow = createFlow([...flows], {
35
+ skipBlockCheck: options?.skipBlockCheck,
36
+ });
37
+ state.subject.addFlow(flow(stack));
38
+ return state;
39
+ };
@@ -0,0 +1,7 @@
1
+ import { go } from './go';
2
+ import { navigate } from './navigate';
3
+ import { pop } from './pop';
4
+ import { validateCurrentLocation } from './validate-current-location';
5
+
6
+ /** Collection of flow creators for history state management. */
7
+ export const flowCreators = { navigate, go, pop, validateCurrentLocation };
@@ -0,0 +1,20 @@
1
+ import type { Observable } from 'rxjs';
2
+ import { map, withLatestFrom } from 'rxjs/operators';
3
+ import type { HistoryFlowCreator } from './navigate';
4
+ import { actions, type Actions } from './actions';
5
+ import { filterAction } from '@equinor/fusion-observable/operators';
6
+ import type { LocationState } from '../types';
7
+ import { Action } from '../types';
8
+
9
+ /** Flow creator for handling go back and forward actions. */
10
+ export const go: HistoryFlowCreator =
11
+ (stack) => (action$: Observable<Actions>, state$: Observable<LocationState>) =>
12
+ // Move the stack by the requested delta and emit the resulting location.
13
+ action$.pipe(
14
+ filterAction(actions.go.type),
15
+ withLatestFrom(state$),
16
+ map(([action, state]) => {
17
+ stack.go(action.payload.delta, state);
18
+ return actions.go.success({ delta: 0, action: Action.Pop, location: stack.current });
19
+ }),
20
+ );
@@ -1,10 +1,5 @@
1
- import {
2
- createState,
3
- type FlowState,
4
- type ReducerWithInitialState,
5
- } from '@equinor/fusion-observable';
6
- import { actions, type Actions } from './history.actions';
7
- import { flowCreators, createFlow, type HistoryFlowCreator } from './history.flows';
1
+ import type { FlowState } from '@equinor/fusion-observable';
2
+ import type { actions } from './actions';
8
3
  import type { HistoryStack, LocationState } from '../types';
9
4
 
10
5
  /**
@@ -17,52 +12,3 @@ export type HistoryState = FlowState<LocationState, typeof actions> & {
17
12
  /**
18
13
  * Default flows for history state management.
19
14
  */
20
- export const defaultFlows = [flowCreators.navigate, flowCreators.go, flowCreators.pop];
21
-
22
- /**
23
- * Creates a history store with the specified stack and reducer.
24
- *
25
- * Combines the reactive state container with a {@link HistoryStack} and wires
26
- * up navigation flows for action processing.
27
- *
28
- * @param stack - The history stack implementation (browser, hash, or memory)
29
- * @param reducer - The reducer that handles state transitions
30
- * @param options - Optional configuration options
31
- * @param options.flows - Custom flow creators (defaults to {@link defaultFlows})
32
- * @param options.skipBlockCheck - Skip blocker checking in flows
33
- * @param options.validateCurrentLocation - Add location validation flow
34
- * @returns A fully-initialized {@link HistoryState} with flows attached
35
- */
36
- export const createStore = (
37
- stack: HistoryStack,
38
- reducer: ReducerWithInitialState<LocationState, Actions>,
39
- options?: {
40
- flows?: HistoryFlowCreator[];
41
- skipBlockCheck?: boolean;
42
- validateCurrentLocation?: boolean;
43
- },
44
- ): HistoryState => {
45
- // Create base state with actions and reducer, then add stack property
46
- // The stack is stored alongside the flow state for use in flows
47
- const state = Object.assign(createState(actions, reducer), { stack }) as HistoryState;
48
-
49
- // Build set of flows to use (default flows or custom flows)
50
- // Use Set to ensure no duplicates if validateCurrentLocation is added
51
- const flows = new Set<HistoryFlowCreator>(options?.flows ?? defaultFlows);
52
- // Optionally add validation flow to check location consistency
53
- if (options?.validateCurrentLocation) {
54
- flows.add(flowCreators.validateCurrentLocation);
55
- }
56
-
57
- // Create combined flow from all flow creators
58
- // This merges all flows so they process actions in parallel
59
- const flow = createFlow([...flows], {
60
- skipBlockCheck: options?.skipBlockCheck,
61
- });
62
-
63
- // Initialize the flow with the stack and add it to the state subject
64
- // The flow processes actions and updates state through the reducer
65
- state.subject.addFlow(flow(stack));
66
-
67
- return state;
68
- };
@@ -1,3 +1,4 @@
1
- export { createStore, type HistoryState } from './history.state';
2
- export { createHistoryReducer } from './history.reducer';
3
- export { actions, type Actions } from './history.actions';
1
+ export { createStore } from './create-store';
2
+ export type { HistoryState } from './history.state';
3
+ export { createHistoryReducer } from './create-history-reducer';
4
+ export { actions, type Actions } from './actions';
@@ -0,0 +1,58 @@
1
+ import type { Observable } from 'rxjs';
2
+ import { map } from 'rxjs/operators';
3
+ import type { Flow } from '@equinor/fusion-observable';
4
+ import { filterAction } from '@equinor/fusion-observable/operators';
5
+ import { actions, type Actions } from './actions';
6
+ import { resolvePath } from '../utils';
7
+ import { type Location, type LocationState, type HistoryStack, Action } from '../types';
8
+
9
+ /** History flow for processing navigation actions. */
10
+ export type HistoryFlow = Flow<Actions, LocationState>;
11
+
12
+ /** Factory function that creates a history flow from a stack. */
13
+ export type HistoryFlowCreator = (stack: HistoryStack) => HistoryFlow;
14
+
15
+ const compareLocation = (a: Location, b: Location): boolean => {
16
+ return (
17
+ a.hash === b.hash &&
18
+ a.search === b.search &&
19
+ a.pathname === b.pathname &&
20
+ JSON.stringify(a.state) === JSON.stringify(b.state)
21
+ );
22
+ };
23
+
24
+ /** Flow creator for handling navigate actions. */
25
+ export const navigate: HistoryFlowCreator =
26
+ (stack: HistoryStack): HistoryFlow =>
27
+ (action$: Observable<Actions>) => {
28
+ const { navigate } = actions;
29
+ // Transform matching navigation actions into stack updates and success actions.
30
+ return action$.pipe(
31
+ filterAction(navigate.type),
32
+ map((action) => {
33
+ const { payload, meta } = action;
34
+ const path = resolvePath(payload.to);
35
+ const nextLocation = {
36
+ ...path,
37
+ key: meta.key,
38
+ state: payload.options.state,
39
+ unstable_mask: undefined,
40
+ } satisfies Location;
41
+ // Abort duplicate navigations to prevent a feedback loop in history state.
42
+ if (compareLocation(nextLocation, stack.current)) {
43
+ return actions.abortNavigate('Location is the same as the current location');
44
+ }
45
+ // Apply replacement or append semantics selected by the navigation request.
46
+ if (payload.options.replace) {
47
+ stack.replace(nextLocation);
48
+ } else {
49
+ stack.push(nextLocation);
50
+ }
51
+ return navigate.success({
52
+ delta: 0,
53
+ action: payload.options.replace ? Action.Replace : Action.Push,
54
+ location: nextLocation,
55
+ });
56
+ }),
57
+ );
58
+ };
@@ -0,0 +1,24 @@
1
+ import { Observable } from 'rxjs';
2
+ import { concatMap } from 'rxjs/operators';
3
+ import type { HistoryFlowCreator } from './navigate';
4
+ import { actions, type Actions } from './actions';
5
+ import { filterAction } from '@equinor/fusion-observable/operators';
6
+ import type { HistoryStack } from '../types';
7
+ import { Action } from '../types';
8
+
9
+ /** Flow creator for handling POP actions from browser navigation. */
10
+ export const pop: HistoryFlowCreator = (stack: HistoryStack) => (action$: Observable<Actions>) =>
11
+ // Translate a browser-driven POP into a success action carrying its location.
12
+ action$.pipe(
13
+ filterAction(actions.pop.type),
14
+ concatMap(
15
+ (action) =>
16
+ new Observable<Actions>((subscriber) => {
17
+ const currentLocation = action.payload.update?.location ?? stack.current;
18
+ subscriber.next(
19
+ actions.pop.success({ delta: 0, action: Action.Pop, location: currentLocation }),
20
+ );
21
+ subscriber.complete();
22
+ }),
23
+ ),
24
+ );
@@ -0,0 +1,33 @@
1
+ import type { Observable } from 'rxjs';
2
+ import { map, withLatestFrom } from 'rxjs/operators';
3
+ import type { HistoryFlowCreator } from './navigate';
4
+ import { actions, type Actions } from './actions';
5
+ import { filterAction } from '@equinor/fusion-observable/operators';
6
+ import { Action, type LocationState, type HistoryStack } from '../types';
7
+
8
+ /** Flow creator that validates the current stack location against reducer state. */
9
+ export const validateCurrentLocation: HistoryFlowCreator =
10
+ (stack: HistoryStack) => (action$: Observable<Actions>, state$: Observable<LocationState>) =>
11
+ // Confirm the browser stack's current key still matches reducer state.
12
+ action$.pipe(
13
+ filterAction(actions.validateLocation.type),
14
+ withLatestFrom(state$),
15
+ map(([, state]) => {
16
+ const currentLocation = stack.current;
17
+ // Find the reducer entry matching the stack key before comparing location state.
18
+ const record = state.history.find(({ location }) => location?.key === currentLocation?.key);
19
+ // Reject validation when the browser stack key is absent from reducer state.
20
+ if (!record) {
21
+ return actions.validateLocation.failure(new Error('Stack state not found'));
22
+ }
23
+ // Reject validation when the stack and reducer carry different location state.
24
+ if (record.location?.state !== currentLocation?.state) {
25
+ return actions.validateLocation.failure(new Error('Stack state mismatch'));
26
+ }
27
+ return actions.validateLocation.success({
28
+ delta: 0,
29
+ action: Action.Pop,
30
+ location: currentLocation,
31
+ });
32
+ }),
33
+ );
package/src/lib/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Observable } from 'rxjs';
2
- import type { Actions } from './state/history.actions';
2
+ import type { Actions } from './state/actions';
3
3
 
4
4
  /**
5
5
  * Actions represent the type of change to a location value.
@@ -1,5 +1,5 @@
1
1
  export { pathToString } from './path-to-string';
2
2
  export { pathToUrl } from './path-to-url';
3
3
  export { resolvePath } from './resolve-path';
4
- export { resolveWindowLocation } from './resolve-browser-location';
5
- export { resolveHashLocation } from './resolve-browser-location';
4
+ export { resolveWindowLocation } from './resolve-window-location';
5
+ export { resolveHashLocation } from './resolve-hash-location';
@@ -1,60 +1 @@
1
- import type { Location } from '../types';
2
- import { resolvePath } from './resolve-path';
3
-
4
- const resolveState = (target?: { state: unknown }): { state: unknown; key: string } => {
5
- const { value, key = 'unknown' } =
6
- (target ?? window.history).state ??
7
- ({} as {
8
- value: unknown;
9
- key?: string;
10
- });
11
- return { state: value, key };
12
- };
13
-
14
- /**
15
- * Resolves the current location from window.location.
16
- *
17
- * @param window - The window object to extract location from
18
- * @param target - Optional target object with state
19
- * @returns The current location with pathname, search, hash, state, and key
20
- *
21
- * @example
22
- * ```ts
23
- * // If window.location is 'https://example.com/users?id=1#section'
24
- * resolveWindowLocation(window)
25
- * // { pathname: '/users', search: '?id=1', hash: '#section', state: undefined, key: 'unknown' }
26
- *
27
- * resolveWindowLocation(window, { state: { userId: 123 } })
28
- * // { pathname: '/users', search: '?id=1', hash: '#section', state: { userId: 123 }, key: 'abc123' }
29
- * ```
30
- */
31
- export const resolveWindowLocation = (window: Window, target?: { state: unknown }): Location => {
32
- const { pathname, search, hash } = resolvePath(window.location);
33
- const { state, key } = resolveState(target);
34
- return { pathname, search, hash, state, key, unstable_mask: undefined };
35
- };
36
-
37
- /**
38
- * Resolves the current location from window.location.hash.
39
- *
40
- * @param window - The window object to extract location from
41
- * @param target - Optional target object with state
42
- * @returns The current location with pathname, search, hash, state, and key
43
- *
44
- * @example
45
- * ```ts
46
- * // If window.location.hash is '#/users?id=1#section'
47
- * resolveHashLocation(window)
48
- * // { pathname: '/users', search: '?id=1', hash: '#section', state: undefined, key: 'unknown' }
49
- *
50
- * // If window.location.hash is '#/dashboard'
51
- * resolveHashLocation(window)
52
- * // { pathname: '/dashboard', search: '', hash: '', state: undefined, key: 'unknown' }
53
- * ```
54
- */
55
- export const resolveHashLocation = (window: Window, target?: { state: unknown }): Location => {
56
- const location = resolveWindowLocation(window, target);
57
- const { pathname, search, hash } = resolvePath(location.hash?.replace('#', '') ?? '');
58
- const { state, key } = resolveState(target);
59
- return { pathname, search, hash, state, key, unstable_mask: undefined };
60
- };
1
+ export { resolveWindowLocation } from './resolve-window-location';
@@ -0,0 +1,26 @@
1
+ import type { Location } from '../types';
2
+ import { resolvePath } from './resolve-path';
3
+ import { resolveWindowLocation } from './resolve-window-location';
4
+
5
+ const resolveState = (target?: { state: unknown }): { state: unknown; key: string } => {
6
+ const { value, key = 'unknown' } =
7
+ (target ?? window.history).state ??
8
+ ({} as {
9
+ value: unknown;
10
+ key?: string;
11
+ });
12
+ return { state: value, key };
13
+ };
14
+
15
+ /**
16
+ * Resolves the current location from window.location.hash.
17
+ * @param window - The window object to extract location from.
18
+ * @param target - Optional target object with state.
19
+ * @returns The current hash-based navigation location.
20
+ */
21
+ export const resolveHashLocation = (window: Window, target?: { state: unknown }): Location => {
22
+ const location = resolveWindowLocation(window, target);
23
+ const { pathname, search, hash } = resolvePath(location.hash?.replace('#', '') ?? '');
24
+ const { state, key } = resolveState(target);
25
+ return { pathname, search, hash, state, key, unstable_mask: undefined };
26
+ };
@@ -0,0 +1,24 @@
1
+ import type { Location } from '../types';
2
+ import { resolvePath } from './resolve-path';
3
+
4
+ const resolveState = (target?: { state: unknown }): { state: unknown; key: string } => {
5
+ const { value, key = 'unknown' } =
6
+ (target ?? window.history).state ??
7
+ ({} as {
8
+ value: unknown;
9
+ key?: string;
10
+ });
11
+ return { state: value, key };
12
+ };
13
+
14
+ /**
15
+ * Resolves the current location from window.location.
16
+ * @param window - The window object to extract location from.
17
+ * @param target - Optional target object with state.
18
+ * @returns The current browser navigation location.
19
+ */
20
+ export const resolveWindowLocation = (window: Window, target?: { state: unknown }): Location => {
21
+ const { pathname, search, hash } = resolvePath(window.location);
22
+ const { state, key } = resolveState(target);
23
+ return { pathname, search, hash, state, key, unstable_mask: undefined };
24
+ };
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '7.0.3';
2
+ export const version = '7.0.5';
@@ -1,40 +0,0 @@
1
- import { FrameworkEvent } from '@equinor/fusion-framework-module-event';
2
- /**
3
- * Event emitted before navigation occurs.
4
- * Can be canceled by calling `preventDefault()` to block the navigation.
5
- *
6
- * @example
7
- * ```ts
8
- * eventProvider.addEventListener('onNavigate', (event) => {
9
- * if (hasUnsavedChanges) {
10
- * event.preventDefault();
11
- * }
12
- * });
13
- * ```
14
- */
15
- export class NavigateEvent extends FrameworkEvent {
16
- constructor(detail, source) {
17
- super('onNavigate', {
18
- detail,
19
- source,
20
- });
21
- }
22
- }
23
- /**
24
- * Event emitted after navigation completes.
25
- * Contains the navigation action type and both current and previous locations.
26
- *
27
- * @example
28
- * ```ts
29
- * eventProvider.addEventListener('onNavigated', (event) => {
30
- * const { action, current, previous } = event.detail;
31
- * console.log(`${action}: ${previous.location.pathname} → ${current.location.pathname}`);
32
- * });
33
- * ```
34
- */
35
- export class NavigatedEvent extends FrameworkEvent {
36
- constructor(detail, source) {
37
- super('onNavigated', { detail, source });
38
- }
39
- }
40
- //# sourceMappingURL=events.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA2B,MAAM,wCAAwC,CAAC;AAejG;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,aAAc,SAAQ,cAElC;IACC,YAAY,MAA2B,EAAE,MAA2B;QAClE,KAAK,CAAC,YAAY,EAAE;YAClB,MAAM;YACN,MAAM;SACP,CAAC,CAAC;IACL,CAAC;CACF;AAcD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,cAAe,SAAQ,cAEnC;IACC,YAAY,MAA4B,EAAE,MAA2B;QACnE,KAAK,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C,CAAC;CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"MemoryStack.js","sourceRoot":"","sources":["../../../src/lib/MemoryStack.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,OAAO,kBAAkB;IAC7B,QAAQ,CAAW;IAEnB;;;OAGG;IACH,IAAI,MAAM;QACR,OAAO,WAAW,CAAC;IACrB,CAAC;IAED;;OAEG;IACH,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED;;;;;OAKG;IACH,YAAY,OAAwC;QAClD,IAAI,CAAC,QAAQ,GAAG,OAAO,EAAE,eAAe,IAAI;YAC1C,QAAQ,EAAE,GAAG;YACb,MAAM,EAAE,EAAE;YACV,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,IAAI;YACX,GAAG,EAAE,EAAE;YACP,aAAa,EAAE,SAAS;SACzB,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,IAAI,CAAC,QAAkB;QACrB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,QAAkB;QACxB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;;;;;;;OAQG;IACH,EAAE,CAAC,KAAa,EAAE,KAA8B;QAC9C,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;QACnC,MAAM,eAAe,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC;QAC1D,0CAA0C;QAC1C,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,KAAK,eAAe,CAAC,GAAG,CAAC,CAAC;QAE9F,gDAAgD;QAChD,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;YACxB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;YACvD,CAAC;YACD,OAAO;QACT,CAAC;QAED,kDAAkD;QAClD,MAAM,QAAQ,GAAG,YAAY,GAAG,KAAK,CAAC;QACtC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YACjB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC5C,CAAC;aAAM,IAAI,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC5C,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;QACnE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;QACnD,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,EAAM;QACd,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC7B,OAAO,IAAI,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"history.actions.js","sourceRoot":"","sources":["../../../../src/lib/state/history.actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAErF,OAAO,EAAE,EAAE,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AAGxC;;GAEG;AACH,MAAM,cAAc,GAAG,iBAAiB,CACtC,qBAAqB,EACrB,CAAC,EAAM,EAAE,OAAwB,EAAE,EAAE,CAAC,CAAC;IACrC,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;IACxB,IAAI,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE;CAC5B,CAAC,EACF,CAAC,MAAwB,EAAE,EAAE,CAAC,CAAC;IAC7B,OAAO,EAAE,EAAE,MAAM,EAAE;CACpB,CAAC,EACF,CAAC,KAAY,EAAE,EAAE,CAAC,CAAC;IACjB,OAAO,EAAE,EAAE,KAAK,EAAE;CACnB,CAAC,CACH,CAAC;AAEF,MAAM,mBAAmB,GAAG,YAAY,CAAC,4BAA4B,EAAE,CAAC,MAAe,EAAE,EAAE,CAAC,CAAC;IAC3F,OAAO,EAAE,EAAE,MAAM,EAAE;CACpB,CAAC,CAAC,CAAC;AAEJ;;GAEG;AACH,MAAM,aAAa,GAAG,iBAAiB,CACrC,eAAe,EACf,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC;IAClB,OAAO,EAAE,EAAE,KAAK,EAAE;CACnB,CAAC,EACF,CAAC,MAAwB,EAAE,EAAE,CAAC,CAAC;IAC7B,OAAO,EAAE,EAAE,MAAM,EAAE;CACpB,CAAC,EACF,CAAC,KAAY,EAAE,EAAE,CAAC,CAAC;IACjB,OAAO,EAAE,EAAE,KAAK,EAAE;CACnB,CAAC,CACH,CAAC;AAEF;;GAEG;AACH,MAAM,cAAc,GAAG,iBAAiB,CACtC,gBAAgB,EAChB,CAAC,MAAyB,EAAE,EAAE,CAAC,CAAC;IAC9B,OAAO,EAAE,EAAE,MAAM,EAAE;CACpB,CAAC,EACF,CAAC,MAAwB,EAAE,EAAE,CAAC,CAAC;IAC7B,OAAO,EAAE,EAAE,MAAM,EAAE;CACpB,CAAC,EACF,CAAC,KAAY,EAAE,EAAE,CAAC,CAAC;IACjB,OAAO,EAAE,EAAE,KAAK,EAAE;CACnB,CAAC,CACH,CAAC;AAEF;;GAEG;AACH,MAAM,sBAAsB,GAAG,iBAAiB,CAC9C,iCAAiC,EACjC,GAAG,EAAE,CAAC,CAAC;IACL,OAAO,EAAE,EAAE;CACZ,CAAC,EACF,CAAC,MAAwB,EAAE,EAAE,CAAC,CAAC;IAC7B,OAAO,EAAE,EAAE,MAAM,EAAE;CACpB,CAAC,EACF,CAAC,KAAY,EAAE,EAAE,CAAC,CAAC;IACjB,OAAO,EAAE,EAAE,KAAK,EAAE;CACnB,CAAC,CACH,CAAC;AAEF;;GAEG;AACH,MAAM,gBAAgB,GAAG,YAAY,CAAC,uBAAuB,EAAE,CAAC,OAA0B,EAAE,EAAE,CAAC,CAAC;IAC9F,OAAO,EAAE,EAAE,OAAO,EAAE;CACrB,CAAC,CAAC,CAAC;AAEJ;;GAEG;AACH,MAAM,mBAAmB,GAAG,YAAY,CACtC,0BAA0B,EAC1B,CAAC,OAA0B,EAAE,EAAE,CAAC,CAAC;IAC/B,OAAO,EAAE,EAAE,OAAO,EAAE;CACrB,CAAC,CACH,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,QAAQ,EAAE,cAAc;IACxB,aAAa,EAAE,mBAAmB;IAClC,EAAE,EAAE,aAAa;IACjB,GAAG,EAAE,cAAc;IACnB,gBAAgB,EAAE,sBAAsB;IACxC,UAAU,EAAE,gBAAgB;IAC5B,aAAa,EAAE,mBAAmB;CACnC,CAAC"}
@@ -1,207 +0,0 @@
1
- import { Observable, of, merge } from 'rxjs';
2
- import { concatMap, map, withLatestFrom } from 'rxjs/operators';
3
- import { filterAction } from '@equinor/fusion-observable/operators';
4
- import { actions } from './history.actions';
5
- import { resolvePath } from '../utils';
6
- import { Action } from '../types';
7
- const compareLocation = (a, b) => {
8
- return (a.hash === b.hash &&
9
- a.search === b.search &&
10
- a.pathname === b.pathname &&
11
- JSON.stringify(a.state) === JSON.stringify(b.state));
12
- };
13
- /**
14
- * Flow creator for handling navigate actions.
15
- *
16
- * Execution flow:
17
- * 1. Filters for navigate actions
18
- * 2. Resolves the target path from the action payload
19
- * 3. Creates a location object with the resolved path, key (from action meta), and optional state
20
- * 4. Checks if the new location is the same as current to prevent navigation loops
21
- * 5. Updates the stack based on replace option (replace updates current, otherwise pushes new entry)
22
- * 6. Returns success action with the new location and appropriate action type (PUSH/REPLACE)
23
- */
24
- export const navigate = (stack) => (action$) => {
25
- const { navigate } = actions;
26
- return action$.pipe(filterAction(navigate.type), map((action) => {
27
- const { payload, meta } = action;
28
- const path = resolvePath(payload.to);
29
- const key = meta.key;
30
- const { replace, state } = payload.options;
31
- const nextLocation = { ...path, key, state, unstable_mask: undefined };
32
- // prevent death loop navigation
33
- if (compareLocation(nextLocation, stack.current)) {
34
- return actions.abortNavigate('Location is the same as the current location');
35
- }
36
- if (replace) {
37
- stack.replace(nextLocation);
38
- }
39
- else {
40
- stack.push(nextLocation);
41
- }
42
- return navigate.success({
43
- delta: 0,
44
- action: replace ? Action.Replace : Action.Push,
45
- location: nextLocation,
46
- });
47
- }));
48
- };
49
- /**
50
- * Flow creator for handling go (back/forward) actions.
51
- *
52
- * Execution flow:
53
- * 1. Filters for go actions
54
- * 2. Combines with current state to get history entries
55
- * 3. Moves the stack index by the specified delta (negative for back, positive for forward)
56
- * 4. Returns success action with POP action type and the new current location
57
- */
58
- export const go = (stack) => (action$, state$) => {
59
- const { go } = actions;
60
- return action$.pipe(filterAction(go.type), withLatestFrom(state$), map(([action, state]) => {
61
- const { delta } = action.payload;
62
- stack.go(delta, state);
63
- return go.success({
64
- delta: 0,
65
- action: Action.Pop,
66
- location: stack.current,
67
- });
68
- }));
69
- };
70
- /**
71
- * Flow creator for handling pop (browser back/forward) actions.
72
- *
73
- * Execution flow:
74
- * 1. Filters for pop actions
75
- * 2. Uses the location from the action payload or falls back to stack.current
76
- * 3. Emits a success action with POP action type and the current location
77
- * 4. Completes the observable immediately (synchronous operation)
78
- */
79
- export const pop = (stack) => (action$) => {
80
- const { pop } = actions;
81
- return action$.pipe(filterAction(pop.type), concatMap((action) => new Observable((subscriber) => {
82
- const currentLocation = action.payload.update?.location ?? stack.current;
83
- // todo - we might need to check if the current location is in the history stack
84
- // for now we will just use the current location
85
- subscriber.next(pop.success({
86
- delta: 0,
87
- action: Action.Pop,
88
- location: currentLocation,
89
- }));
90
- subscriber.complete();
91
- })));
92
- };
93
- /**
94
- * Flow creator for checking navigation blockers before processing actions.
95
- *
96
- * Execution flow:
97
- * 1. Filters for navigation actions (navigate, go, pop, validateLocation)
98
- * 2. Determines the action type (PUSH/REPLACE for navigate, POP for others)
99
- * 3. Combines with current state to check for active blockers
100
- * 4. If no blockers exist, passes the action through immediately
101
- * 5. If blockers exist:
102
- * - Creates a transition object with action, location, and retry callback
103
- * - Calls each blocker asynchronously (wrapped in Promise to prevent blocking)
104
- * - Blockers can call retry() to allow navigation or prevent it by not calling retry()
105
- * - Waits for all blockers to complete before continuing
106
- * 6. Returns the original action if navigation is allowed, or completes without emitting if blocked
107
- */
108
- export const checkBlockers = (stack) => (action$, state$) => {
109
- const { pop, navigate, go, validateLocation } = actions;
110
- return action$.pipe(filterAction(navigate.type, pop.type, go.type, validateLocation.type), map((action) => {
111
- switch (action.type) {
112
- case navigate.type:
113
- return {
114
- replace: action.payload.options.replace,
115
- action: action,
116
- };
117
- case go.type:
118
- case pop.type:
119
- case validateLocation.type:
120
- return {
121
- type: 'POP',
122
- action: action,
123
- };
124
- }
125
- }), withLatestFrom(state$), concatMap(([{ action, replace }, state]) => {
126
- if (!state.blockers.length) {
127
- return of(action);
128
- }
129
- return new Observable((subscriber) => {
130
- const location = stack.current;
131
- const blockers = state.blockers.map((blocker) => Promise.resolve(blocker({
132
- delta: 0,
133
- action: replace ? Action.Replace : Action.Push,
134
- location,
135
- retry: () => {
136
- subscriber.next(action);
137
- },
138
- })));
139
- Promise.allSettled(blockers).then(() => {
140
- subscriber.complete();
141
- });
142
- });
143
- }));
144
- };
145
- /**
146
- * Flow creator for validating the current location against history state.
147
- *
148
- * Execution flow:
149
- * 1. Filters for validateLocation actions
150
- * 2. Combines with current state to access history entries
151
- * 3. Gets the current location from the stack
152
- * 4. Searches history for an entry matching the current location's key
153
- * 5. Validates that:
154
- * - The location key exists in history (returns failure if not found)
155
- * - The location state matches the history entry state (returns failure if mismatch)
156
- * 6. Returns success action with current location if validation passes, failure otherwise
157
- */
158
- export const validateCurrentLocation = (stack) => (action$, state$) => {
159
- const { validateLocation } = actions;
160
- return action$.pipe(filterAction(validateLocation.type), withLatestFrom(state$), map(([, state]) => {
161
- const currentLocation = stack.current;
162
- const record = state.history.find(({ location }) => location?.key === currentLocation?.key);
163
- if (!record) {
164
- return validateLocation.failure(new Error('Stack state not found'));
165
- }
166
- if (record?.location?.state !== currentLocation?.state) {
167
- return validateLocation.failure(new Error('Stack state mismatch'));
168
- }
169
- return validateLocation.success({
170
- delta: 0,
171
- action: Action.Pop,
172
- location: currentLocation,
173
- });
174
- }));
175
- };
176
- /**
177
- * Creates a combined history flow from multiple flow creators.
178
- *
179
- * Merges all provided flows so they process actions in parallel.
180
- * Optionally prepends a blocker-checking step that gates navigation
181
- * actions through registered {@link NavigationBlocker | blockers}.
182
- *
183
- * @param flowCreators - Array of flow creators to combine
184
- * @param options - Optional configuration
185
- * @param options.skipBlockCheck - If `true`, skip blocker checking
186
- * @returns A combined {@link HistoryFlowCreator} that merges all provided flows
187
- */
188
- export const createFlow = (flowCreators, options) => {
189
- return (stack) => {
190
- const preProcessActions = options?.skipBlockCheck
191
- ? (action$) => action$
192
- : checkBlockers(stack);
193
- const flows = (action$, state$) => {
194
- return merge(...flowCreators
195
- .map((initializer) => initializer(stack))
196
- .map((flow) => flow(action$, state$)));
197
- };
198
- return (action$, state$) => {
199
- return preProcessActions(action$, state$).pipe((source$) => flows(source$, state$));
200
- };
201
- };
202
- };
203
- /**
204
- * Collection of flow creators for history state management.
205
- */
206
- export const flowCreators = { navigate, go, pop, validateCurrentLocation };
207
- //# sourceMappingURL=history.flows.js.map