@equinor/fusion-framework-module-navigation 7.0.9 → 7.0.10

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 (55) hide show
  1. package/dist/esm/version.js +1 -1
  2. package/dist/esm/version.js.map +1 -1
  3. package/dist/tsconfig.tsbuildinfo +1 -1
  4. package/dist/types/version.d.ts +1 -1
  5. package/package.json +10 -7
  6. package/CHANGELOG.md +0 -600
  7. package/src/NavigateEvent.ts +0 -47
  8. package/src/NavigatedEvent.ts +0 -35
  9. package/src/NavigationConfigurator.interface.ts +0 -41
  10. package/src/NavigationConfigurator.ts +0 -226
  11. package/src/NavigationProvider.interface.ts +0 -99
  12. package/src/NavigationProvider.ts +0 -436
  13. package/src/__tests__/BrowserHistory.test.ts +0 -151
  14. package/src/__tests__/HashHistory.test.ts +0 -121
  15. package/src/__tests__/MemoryHistory.test.ts +0 -185
  16. package/src/__tests__/NavigationProvider.test.ts +0 -302
  17. package/src/__tests__/ProxyHistory.test.ts +0 -149
  18. package/src/__tests__/setup.ts +0 -8
  19. package/src/enable-navigation.ts +0 -60
  20. package/src/index.ts +0 -51
  21. package/src/lib/BaseHistory.ts +0 -255
  22. package/src/lib/BrowserHistory.ts +0 -124
  23. package/src/lib/BrowserHistoryHashStack.ts +0 -57
  24. package/src/lib/BrowserHistoryStack.ts +0 -96
  25. package/src/lib/MemoryHistory.ts +0 -76
  26. package/src/lib/MemoryHistoryStack.ts +0 -118
  27. package/src/lib/ProxyHistory.ts +0 -145
  28. package/src/lib/create-history.ts +0 -58
  29. package/src/lib/index.ts +0 -33
  30. package/src/lib/state/actions.ts +0 -108
  31. package/src/lib/state/check-blockers.ts +0 -52
  32. package/src/lib/state/create-flow.ts +0 -36
  33. package/src/lib/state/create-history-reducer.ts +0 -77
  34. package/src/lib/state/create-store.ts +0 -39
  35. package/src/lib/state/flow-creators.ts +0 -7
  36. package/src/lib/state/go.ts +0 -20
  37. package/src/lib/state/history.state.ts +0 -14
  38. package/src/lib/state/index.ts +0 -4
  39. package/src/lib/state/navigate.ts +0 -58
  40. package/src/lib/state/pop.ts +0 -24
  41. package/src/lib/state/validate-current-location.ts +0 -33
  42. package/src/lib/types.ts +0 -190
  43. package/src/lib/utils/encode-trailing-whitespace.ts +0 -18
  44. package/src/lib/utils/has-protocol.ts +0 -21
  45. package/src/lib/utils/index.ts +0 -5
  46. package/src/lib/utils/path-to-string.ts +0 -24
  47. package/src/lib/utils/path-to-url.ts +0 -52
  48. package/src/lib/utils/resolve-browser-location.ts +0 -1
  49. package/src/lib/utils/resolve-hash-location.ts +0 -26
  50. package/src/lib/utils/resolve-path.ts +0 -22
  51. package/src/lib/utils/resolve-window-location.ts +0 -24
  52. package/src/module.ts +0 -80
  53. package/src/version.ts +0 -2
  54. package/tsconfig.json +0 -24
  55. package/vitest.config.ts +0 -14
@@ -1,52 +0,0 @@
1
- import { Observable, of } from 'rxjs';
2
- import { concatMap, 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 HistoryStack, type LocationState } from '../types';
7
-
8
- /** Flow creator that gates navigation actions through registered blockers. */
9
- export const checkBlockers: HistoryFlowCreator =
10
- (stack: HistoryStack) => (action$: Observable<Actions>, state$: Observable<LocationState>) =>
11
- // Normalize each navigation-related action into a common shape before gating on blockers.
12
- action$.pipe(
13
- filterAction(
14
- actions.navigate.type,
15
- actions.pop.type,
16
- actions.go.type,
17
- actions.validateLocation.type,
18
- ),
19
- map((action) => {
20
- // Only `navigate` carries a replace flag; the rest are treated as POP transitions.
21
- switch (action.type) {
22
- case actions.navigate.type:
23
- return { replace: action.payload.options.replace, action };
24
- case actions.go.type:
25
- case actions.pop.type:
26
- case actions.validateLocation.type:
27
- return { type: 'POP' as Action, action };
28
- }
29
- }),
30
- withLatestFrom(state$),
31
- concatMap(([{ action, replace }, state]) => {
32
- // Allow actions through immediately when no blocker can veto the transition.
33
- if (!state.blockers.length) {
34
- return of(action);
35
- }
36
- return new Observable<Actions>((subscriber) => {
37
- const location = stack.current;
38
- // Invoke all blockers and complete once every asynchronous decision settles.
39
- const blockers = state.blockers.map((blocker) =>
40
- Promise.resolve(
41
- blocker({
42
- delta: 0,
43
- action: replace ? Action.Replace : Action.Push,
44
- location,
45
- retry: () => subscriber.next(action),
46
- }),
47
- ),
48
- );
49
- Promise.allSettled(blockers).then(() => subscriber.complete());
50
- });
51
- }),
52
- );
@@ -1,36 +0,0 @@
1
- import { merge, type Observable } from 'rxjs';
2
- import type { HistoryFlow, HistoryFlowCreator } from './navigate';
3
- import { checkBlockers } from './check-blockers';
4
- import type { Actions } from './actions';
5
- import type { HistoryStack, LocationState } from '../types';
6
-
7
- /**
8
- * Creates a combined history flow from multiple flow creators.
9
- * @param flowCreators - Flow creators to combine.
10
- * @param options - Optional blocker configuration.
11
- * @returns A combined history flow creator.
12
- */
13
- export const createFlow = (
14
- flowCreators: HistoryFlowCreator[],
15
- options?: { skipBlockCheck?: boolean },
16
- ): HistoryFlowCreator => {
17
- return (stack: HistoryStack): HistoryFlow => {
18
- const preProcessActions = options?.skipBlockCheck
19
- ? (action$: Observable<Actions>) => action$
20
- : checkBlockers(stack);
21
- const flows: HistoryFlow = (
22
- action$: Observable<Actions>,
23
- state$: Observable<LocationState>,
24
- ) => {
25
- // Initialize each creator against the same stack before merging their action streams.
26
- const initializedFlows = flowCreators
27
- .map((initializer) => initializer(stack))
28
- .map((flow) => flow(action$, state$));
29
- return merge(...initializedFlows);
30
- };
31
- return (action$: Observable<Actions>, state$: Observable<LocationState>) => {
32
- // Gate actions before dispatching them to the parallel history flows.
33
- return preProcessActions(action$, state$).pipe((source$) => flows(source$, state$));
34
- };
35
- };
36
- };
@@ -1,77 +0,0 @@
1
- import { createReducer } from '@equinor/fusion-observable';
2
- import type { LocationState, NavigationUpdate } from '../types';
3
- import { actions, type Actions } from './actions';
4
- import { isSuccessAction } from '@equinor/fusion-observable/actions';
5
-
6
- /**
7
- * Finds the index of the current location in the history array.
8
- * Used to determine where to insert/replace entries for REPLACE actions.
9
- */
10
- const findIndex = (state: LocationState) => {
11
- // Locate the current entry by key rather than reference, since updates are cloned.
12
- return state.history.findIndex((update) => update.location.key === state.current.location.key);
13
- };
14
-
15
- /**
16
- * Creates a reducer for history state management.
17
- *
18
- * Handles navigate, go, and pop success actions by updating the
19
- * history array and current location. Enforces a maximum history
20
- * length to prevent unbounded memory growth.
21
- *
22
- * @param initial - Initial navigation update or factory returning initial state
23
- * @param options - Optional configuration
24
- * @param options.maxHistory - Maximum number of history entries to keep (default: `100`)
25
- * @returns A reducer with initial state for use with {@link createStore}
26
- */
27
- export const createHistoryReducer = (
28
- initial: NavigationUpdate | (() => LocationState),
29
- options?: { maxHistory?: number },
30
- ) => {
31
- const maxHistory = options?.maxHistory ?? 100;
32
-
33
- const initialState: LocationState =
34
- initial instanceof Function
35
- ? initial()
36
- : ({
37
- current: initial,
38
- history: [initial],
39
- blockers: [],
40
- } satisfies LocationState);
41
-
42
- return createReducer<LocationState, Actions>(initialState, (builder) => {
43
- // Handle navigate.success actions to update history array
44
- builder.addCase(actions.navigate.success, (state, action) => {
45
- const { update } = action.payload;
46
- // For REPLACE actions, remove all entries after the current location (the tail)
47
- // This ensures REPLACE doesn't add to history length when at the end,
48
- // and removes any forward history when not at the end
49
- if (update.action === 'REPLACE') {
50
- const currentIndex = findIndex(state);
51
- // Only trim when the current entry is actually present in history.
52
- if (currentIndex !== -1) {
53
- // Remove all entries from current position onwards
54
- state.history.splice(currentIndex, state.history.length - currentIndex);
55
- }
56
- }
57
- state.history.push(update);
58
- });
59
- // Update current location for all successful actions (navigate, go, pop, etc.)
60
- builder.addMatcher(isSuccessAction, (state, action) => {
61
- const { update } = action.payload;
62
- state.current = update;
63
- });
64
- // Limit history size to prevent unbounded growth
65
- // This matcher always runs (returns true) to check history length after every action
66
- builder.addMatcher(
67
- () => true,
68
- (state) => {
69
- // Trim only once history has actually grown past the configured cap.
70
- if (state.history.length > maxHistory) {
71
- // Keep only the most recent entries, removing oldest ones
72
- state.history = state.history.slice(-maxHistory);
73
- }
74
- },
75
- );
76
- });
77
- };
@@ -1,39 +0,0 @@
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
- };
@@ -1,7 +0,0 @@
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 };
@@ -1,20 +0,0 @@
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,14 +0,0 @@
1
- import type { FlowState } from '@equinor/fusion-observable';
2
- import type { actions } from './actions';
3
- import type { HistoryStack, LocationState } from '../types';
4
-
5
- /**
6
- * History state containing flow state and stack.
7
- */
8
- export type HistoryState = FlowState<LocationState, typeof actions> & {
9
- stack: HistoryStack;
10
- };
11
-
12
- /**
13
- * Default flows for history state management.
14
- */
@@ -1,4 +0,0 @@
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';
@@ -1,58 +0,0 @@
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
- };
@@ -1,24 +0,0 @@
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
- );
@@ -1,33 +0,0 @@
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 DELETED
@@ -1,190 +0,0 @@
1
- import type { Observable } from 'rxjs';
2
- import type { Actions } from './state/actions';
3
-
4
- /**
5
- * Actions represent the type of change to a location value.
6
- */
7
- export enum Action {
8
- /**
9
- * A POP indicates a change to an arbitrary index in the history stack, such
10
- * as a back or forward navigation. It does not describe the direction of the
11
- * navigation, only that the current index changed.
12
- *
13
- * Note: This is the default action for newly created history objects.
14
- */
15
- Pop = 'POP',
16
- /**
17
- * A PUSH indicates a new entry being added to the history stack, such as when
18
- * a link is clicked and a new page loads.
19
- */
20
- Push = 'PUSH',
21
- /**
22
- * A REPLACE indicates the entry at the current index in the history stack
23
- * being replaced by a new one.
24
- */
25
- Replace = 'REPLACE',
26
- }
27
-
28
- /**
29
- * Target path for navigation operations.
30
- * Can be a string path or a Path object.
31
- */
32
- export type To = string | Partial<Path>;
33
-
34
- /**
35
- * Path object containing pathname, optional search, and optional hash.
36
- */
37
- export type Path = {
38
- pathname: string;
39
- search: string;
40
- hash: string;
41
- };
42
-
43
- /**
44
- * Location object representing a navigation entry.
45
- * Extends {@link Path} with arbitrary state data and a unique key.
46
- *
47
- * @template T - The type of the state payload stored in this location entry
48
- */
49
- // biome-ignore lint/suspicious/noExplicitAny: necessary
50
- export type Location<T = any> = Path & {
51
- state: T;
52
- key: string;
53
- /** Masked path used by react-router 7.13+ for unstable view-transition masking. */
54
- unstable_mask: Path | undefined;
55
- };
56
-
57
- /**
58
- * Internal state for history management.
59
- */
60
- export type LocationState = {
61
- current: NavigationUpdate;
62
- history: NavigationUpdate[];
63
- blockers: NavigationBlocker[];
64
- };
65
-
66
- /**
67
- * Callback function for listening to navigation changes.
68
- */
69
- export type NavigationListener = (update: Readonly<NavigationUpdate>) => void;
70
-
71
- /**
72
- * Navigation update event containing the action, location, and stack delta.
73
- *
74
- * @template A - The type of navigation action (defaults to {@link Action})
75
- * @template T - The type of the state payload in the location
76
- */
77
- export type NavigationUpdate<A extends string = Action, T = unknown> = {
78
- delta: number;
79
- action: Readonly<A>;
80
- location: Readonly<Location<T>>;
81
- };
82
-
83
- /**
84
- * Navigation transition that was blocked.
85
- * Provides a retry method to allow the navigation to proceed.
86
- */
87
- export interface NavigationTransition extends NavigationUpdate {
88
- /**
89
- * Retries the navigation that was blocked.
90
- */
91
- retry(): void;
92
- }
93
-
94
- /**
95
- * Callback function for blocking navigation attempts.
96
- */
97
- export type NavigationBlocker = (transition: NavigationTransition) => void;
98
-
99
- /**
100
- * Options for controlling navigation behavior.
101
- *
102
- * @interface NavigateOptions
103
- *
104
- * @property {boolean} [replace] - Determines the navigation action:
105
- * - `false` (default): Pushes a new entry onto the history stack
106
- * - `true`: Replaces the current entry in the history stack
107
- *
108
- * @property {unknown} [state] - Optional state object to associate with the navigation entry.
109
- * This state can be accessed later when navigating back to this entry.
110
- */
111
- export interface NavigateOptions {
112
- replace?: boolean;
113
- state?: unknown;
114
- }
115
-
116
- /**
117
- * History interface for managing navigation state.
118
- *
119
- * Compatible with Remix / React Router and provides observable
120
- * state management via RxJS.
121
- *
122
- * @example
123
- * ```ts
124
- * const history = createHistory('browser');
125
- * history.push('/dashboard');
126
- * history.state$.subscribe(update => console.log(update.location.pathname));
127
- * ```
128
- */
129
- export interface History extends Disposable {
130
- /** Observable stream of navigation state updates. */
131
- readonly state$: Observable<NavigationUpdate>;
132
- /** Observable stream of navigation actions. */
133
- readonly action$: Observable<Actions>;
134
- /** Current navigation action. */
135
- readonly action: Action;
136
- /** Current location in the history stack. */
137
- readonly location: Location;
138
-
139
- /** Creates a valid href string for a given path.
140
- * @param to - Target path or partial path object
141
- * @returns Fully-qualified href string
142
- */
143
- createHref(to: To): string;
144
- /** Creates a {@link URL} object for a given path.
145
- * @param to - Target path or partial path object
146
- * @returns Resolved {@link URL} instance
147
- */
148
- createURL(to: To): URL;
149
- /** Encodes a location by properly URL-encoding the pathname.
150
- * @param to - Target path or partial path object
151
- * @returns A {@link Path} with URL-encoded components
152
- */
153
- encodeLocation(to: To): Path;
154
- /** Pushes a new navigation entry onto the history stack. */
155
- push(to: To, state?: unknown): void;
156
- /** Replaces the current history entry with a new one. */
157
- replace(to: To, state?: unknown): void;
158
- /** Navigate to a location with explicit options. */
159
- navigate(to: To, options?: NavigateOptions): void;
160
- /** Navigates backward or forward in the history stack. */
161
- go(delta: number): void;
162
- /** Sets up a listener for navigation changes.
163
- * @param listener - Callback invoked on POP actions (browser back/forward)
164
- * @returns A function that unsubscribes the listener when called
165
- */
166
- listen(listener: NavigationListener): () => void;
167
- /** Registers a blocker to intercept navigation attempts.
168
- * @param blocker - Callback invoked before each navigation to allow or prevent it
169
- * @returns A function that removes the blocker when called
170
- */
171
- block(blocker: NavigationBlocker): VoidFunction;
172
- }
173
-
174
- /**
175
- * History stack interface for managing navigation entries.
176
- */
177
- export interface HistoryStack {
178
- /** Origin URL for the history stack. */
179
- readonly origin: string;
180
- /** Current location in the stack. */
181
- readonly current: Location;
182
- /** Pushes a new entry onto the history stack. */
183
- push(location: Location): void;
184
- /** Replaces the current entry in the history stack. */
185
- replace(location: Location): void;
186
- /** Navigates backward or forward in the history stack. */
187
- go(delta: number, state: Readonly<LocationState>): void;
188
- /** Creates a URL object for a given path. */
189
- createURL(to: To): URL;
190
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Encodes trailing whitespace in a string by replacing it with '%20'.
3
- *
4
- * @param input - The string to encode
5
- * @returns The string with trailing whitespace encoded
6
- *
7
- * @example
8
- * ```ts
9
- * encodeTrailingWhitespace('hello ')
10
- * // 'hello%20'
11
- *
12
- * encodeTrailingWhitespace('hello')
13
- * // 'hello'
14
- * ```
15
- */
16
- export const encodeTrailingWhitespace = (input: string): string => {
17
- return input.replace(/ $/, '%20');
18
- };
@@ -1,21 +0,0 @@
1
- /**
2
- * Checks if a string has a protocol prefix.
3
- *
4
- * @param input - The string to check
5
- * @returns True if the string starts with a protocol (e.g., 'http:', 'https:', 'file:')
6
- *
7
- * @example
8
- * ```ts
9
- * hasProtocol('https://example.com')
10
- * // true
11
- *
12
- * hasProtocol('/users')
13
- * // false
14
- *
15
- * hasProtocol('file:///path/to/file')
16
- * // true
17
- * ```
18
- */
19
- export const hasProtocol = (input: string): boolean => {
20
- return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(input);
21
- };
@@ -1,5 +0,0 @@
1
- export { pathToString } from './path-to-string';
2
- export { pathToUrl } from './path-to-url';
3
- export { resolvePath } from './resolve-path';
4
- export { resolveWindowLocation } from './resolve-window-location';
5
- export { resolveHashLocation } from './resolve-hash-location';
@@ -1,24 +0,0 @@
1
- import type { Path } from '../types';
2
- import { encodeTrailingWhitespace } from './encode-trailing-whitespace';
3
-
4
- /**
5
- * Converts a Partial<Path> object to a path string.
6
- *
7
- * @param to - The path object with optional pathname, search, and hash
8
- * @returns A path string with properly formatted search and hash
9
- *
10
- * @example
11
- * ```ts
12
- * pathToString({ pathname: '/users', search: '?id=1', hash: '#section' })
13
- * // '/users?id=1#section'
14
- *
15
- * pathToString({ pathname: '/users', search: 'id=1', hash: 'section' })
16
- * // '/users?id=1#section'
17
- * ```
18
- */
19
- export const pathToString = (to: Partial<Path>): string => {
20
- const pathname = encodeTrailingWhitespace(to.pathname ?? '/');
21
- const search = encodeTrailingWhitespace(to.search?.replace(/^\?/, '') ?? '');
22
- const hash = encodeTrailingWhitespace(to.hash?.replace(/^#/, '') ?? '');
23
- return `${pathname}${search ? `?${search}` : ''}${hash ? `#${hash}` : ''}`;
24
- };