@tanstack/history 1.15.10 → 1.20.3-alpha.1

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.
@@ -1,19 +1,36 @@
1
+ export interface NavigateOptions {
2
+ ignoreBlocker?: boolean;
3
+ }
4
+ type SubscriberHistoryAction = {
5
+ type: Exclude<HistoryAction, 'GO'>;
6
+ } | {
7
+ type: 'GO';
8
+ index: number;
9
+ };
10
+ type SubscriberArgs = {
11
+ location: HistoryLocation;
12
+ action: SubscriberHistoryAction;
13
+ };
1
14
  export interface RouterHistory {
2
15
  location: HistoryLocation;
3
- subscribe: (cb: () => void) => () => void;
4
- push: (path: string, state?: any) => void;
5
- replace: (path: string, state?: any) => void;
6
- go: (index: number) => void;
7
- back: () => void;
8
- forward: () => void;
16
+ length: number;
17
+ subscribers: Set<(opts: SubscriberArgs) => void>;
18
+ subscribe: (cb: (opts: SubscriberArgs) => void) => () => void;
19
+ push: (path: string, state?: any, navigateOpts?: NavigateOptions) => void;
20
+ replace: (path: string, state?: any, navigateOpts?: NavigateOptions) => void;
21
+ go: (index: number, navigateOpts?: NavigateOptions) => void;
22
+ back: (navigateOpts?: NavigateOptions) => void;
23
+ forward: (navigateOpts?: NavigateOptions) => void;
24
+ canGoBack: () => boolean;
9
25
  createHref: (href: string) => string;
10
- block: (blocker: BlockerFn) => () => void;
26
+ block: (blocker: NavigationBlocker) => () => void;
11
27
  flush: () => void;
12
28
  destroy: () => void;
13
- notify: () => void;
29
+ notify: (action: SubscriberHistoryAction) => void;
30
+ _ignoreSubscribers?: boolean;
14
31
  }
15
32
  export interface HistoryLocation extends ParsedPath {
16
- state: HistoryState;
33
+ state: ParsedHistoryState;
17
34
  }
18
35
  export interface ParsedPath {
19
36
  href: string;
@@ -22,21 +39,38 @@ export interface ParsedPath {
22
39
  hash: string;
23
40
  }
24
41
  export interface HistoryState {
25
- key: string;
26
42
  }
43
+ export type ParsedHistoryState = HistoryState & {
44
+ key?: string;
45
+ __TSR_index: number;
46
+ };
27
47
  type ShouldAllowNavigation = any;
28
- export type BlockerFn = () => Promise<ShouldAllowNavigation> | ShouldAllowNavigation;
48
+ export type HistoryAction = 'PUSH' | 'REPLACE' | 'FORWARD' | 'BACK' | 'GO';
49
+ export type BlockerFnArgs = {
50
+ currentLocation: HistoryLocation;
51
+ nextLocation: HistoryLocation;
52
+ action: HistoryAction;
53
+ };
54
+ export type BlockerFn = (args: BlockerFnArgs) => Promise<ShouldAllowNavigation> | ShouldAllowNavigation;
55
+ export type NavigationBlocker = {
56
+ blockerFn: BlockerFn;
57
+ enableBeforeUnload?: (() => boolean) | boolean;
58
+ };
29
59
  export declare function createHistory(opts: {
30
60
  getLocation: () => HistoryLocation;
61
+ getLength: () => number;
31
62
  pushState: (path: string, state: any) => void;
32
63
  replaceState: (path: string, state: any) => void;
33
64
  go: (n: number) => void;
34
- back: () => void;
35
- forward: () => void;
65
+ back: (ignoreBlocker: boolean) => void;
66
+ forward: (ignoreBlocker: boolean) => void;
36
67
  createHref: (path: string) => string;
37
68
  flush?: () => void;
38
69
  destroy?: () => void;
39
- onBlocked?: (onUpdate: () => void) => void;
70
+ onBlocked?: () => void;
71
+ getBlockers?: () => Array<NavigationBlocker>;
72
+ setBlockers?: (blockers: Array<NavigationBlocker>) => void;
73
+ notifyOnIndexChange?: boolean;
40
74
  }): RouterHistory;
41
75
  /**
42
76
  * Creates a history object that can be used to interact with the browser's
@@ -63,7 +97,8 @@ export declare function createHashHistory(opts?: {
63
97
  window?: any;
64
98
  }): RouterHistory;
65
99
  export declare function createMemoryHistory(opts?: {
66
- initialEntries: string[];
100
+ initialEntries: Array<string>;
67
101
  initialIndex?: number;
68
102
  }): RouterHistory;
103
+ export declare function parseHref(href: string, state: ParsedHistoryState | undefined): HistoryLocation;
69
104
  export {};
@@ -1,19 +1,36 @@
1
+ export interface NavigateOptions {
2
+ ignoreBlocker?: boolean;
3
+ }
4
+ type SubscriberHistoryAction = {
5
+ type: Exclude<HistoryAction, 'GO'>;
6
+ } | {
7
+ type: 'GO';
8
+ index: number;
9
+ };
10
+ type SubscriberArgs = {
11
+ location: HistoryLocation;
12
+ action: SubscriberHistoryAction;
13
+ };
1
14
  export interface RouterHistory {
2
15
  location: HistoryLocation;
3
- subscribe: (cb: () => void) => () => void;
4
- push: (path: string, state?: any) => void;
5
- replace: (path: string, state?: any) => void;
6
- go: (index: number) => void;
7
- back: () => void;
8
- forward: () => void;
16
+ length: number;
17
+ subscribers: Set<(opts: SubscriberArgs) => void>;
18
+ subscribe: (cb: (opts: SubscriberArgs) => void) => () => void;
19
+ push: (path: string, state?: any, navigateOpts?: NavigateOptions) => void;
20
+ replace: (path: string, state?: any, navigateOpts?: NavigateOptions) => void;
21
+ go: (index: number, navigateOpts?: NavigateOptions) => void;
22
+ back: (navigateOpts?: NavigateOptions) => void;
23
+ forward: (navigateOpts?: NavigateOptions) => void;
24
+ canGoBack: () => boolean;
9
25
  createHref: (href: string) => string;
10
- block: (blocker: BlockerFn) => () => void;
26
+ block: (blocker: NavigationBlocker) => () => void;
11
27
  flush: () => void;
12
28
  destroy: () => void;
13
- notify: () => void;
29
+ notify: (action: SubscriberHistoryAction) => void;
30
+ _ignoreSubscribers?: boolean;
14
31
  }
15
32
  export interface HistoryLocation extends ParsedPath {
16
- state: HistoryState;
33
+ state: ParsedHistoryState;
17
34
  }
18
35
  export interface ParsedPath {
19
36
  href: string;
@@ -22,21 +39,38 @@ export interface ParsedPath {
22
39
  hash: string;
23
40
  }
24
41
  export interface HistoryState {
25
- key: string;
26
42
  }
43
+ export type ParsedHistoryState = HistoryState & {
44
+ key?: string;
45
+ __TSR_index: number;
46
+ };
27
47
  type ShouldAllowNavigation = any;
28
- export type BlockerFn = () => Promise<ShouldAllowNavigation> | ShouldAllowNavigation;
48
+ export type HistoryAction = 'PUSH' | 'REPLACE' | 'FORWARD' | 'BACK' | 'GO';
49
+ export type BlockerFnArgs = {
50
+ currentLocation: HistoryLocation;
51
+ nextLocation: HistoryLocation;
52
+ action: HistoryAction;
53
+ };
54
+ export type BlockerFn = (args: BlockerFnArgs) => Promise<ShouldAllowNavigation> | ShouldAllowNavigation;
55
+ export type NavigationBlocker = {
56
+ blockerFn: BlockerFn;
57
+ enableBeforeUnload?: (() => boolean) | boolean;
58
+ };
29
59
  export declare function createHistory(opts: {
30
60
  getLocation: () => HistoryLocation;
61
+ getLength: () => number;
31
62
  pushState: (path: string, state: any) => void;
32
63
  replaceState: (path: string, state: any) => void;
33
64
  go: (n: number) => void;
34
- back: () => void;
35
- forward: () => void;
65
+ back: (ignoreBlocker: boolean) => void;
66
+ forward: (ignoreBlocker: boolean) => void;
36
67
  createHref: (path: string) => string;
37
68
  flush?: () => void;
38
69
  destroy?: () => void;
39
- onBlocked?: (onUpdate: () => void) => void;
70
+ onBlocked?: () => void;
71
+ getBlockers?: () => Array<NavigationBlocker>;
72
+ setBlockers?: (blockers: Array<NavigationBlocker>) => void;
73
+ notifyOnIndexChange?: boolean;
40
74
  }): RouterHistory;
41
75
  /**
42
76
  * Creates a history object that can be used to interact with the browser's
@@ -63,7 +97,8 @@ export declare function createHashHistory(opts?: {
63
97
  window?: any;
64
98
  }): RouterHistory;
65
99
  export declare function createMemoryHistory(opts?: {
66
- initialEntries: string[];
100
+ initialEntries: Array<string>;
67
101
  initialIndex?: number;
68
102
  }): RouterHistory;
103
+ export declare function parseHref(href: string, state: ParsedHistoryState | undefined): HistoryLocation;
69
104
  export {};
package/dist/esm/index.js CHANGED
@@ -1,30 +1,40 @@
1
- const pushStateEvent = "pushstate";
1
+ const stateIndexKey = "__TSR_index";
2
2
  const popStateEvent = "popstate";
3
3
  const beforeUnloadEvent = "beforeunload";
4
- const beforeUnloadListener = (event) => {
5
- event.preventDefault();
6
- return event.returnValue = "";
7
- };
8
- const stopBlocking = () => {
9
- removeEventListener(beforeUnloadEvent, beforeUnloadListener, {
10
- capture: true
11
- });
12
- };
13
4
  function createHistory(opts) {
14
5
  let location = opts.getLocation();
15
- let subscribers = /* @__PURE__ */ new Set();
16
- let blockers = [];
17
- const onUpdate = () => {
6
+ const subscribers = /* @__PURE__ */ new Set();
7
+ const notify = (action) => {
18
8
  location = opts.getLocation();
19
- subscribers.forEach((subscriber) => subscriber());
9
+ subscribers.forEach((subscriber) => subscriber({ location, action }));
10
+ };
11
+ const handleIndexChange = (action) => {
12
+ if (opts.notifyOnIndexChange ?? true) notify(action);
13
+ else location = opts.getLocation();
20
14
  };
21
- const tryNavigation = async (task) => {
22
- var _a;
23
- if (typeof document !== "undefined" && blockers.length) {
24
- for (let blocker of blockers) {
25
- const allowed = await blocker();
26
- if (!allowed) {
27
- (_a = opts.onBlocked) == null ? void 0 : _a.call(opts, onUpdate);
15
+ const tryNavigation = async ({
16
+ task,
17
+ navigateOpts,
18
+ ...actionInfo
19
+ }) => {
20
+ var _a, _b;
21
+ const ignoreBlocker = (navigateOpts == null ? void 0 : navigateOpts.ignoreBlocker) ?? false;
22
+ if (ignoreBlocker) {
23
+ task();
24
+ return;
25
+ }
26
+ const blockers = ((_a = opts.getBlockers) == null ? void 0 : _a.call(opts)) ?? [];
27
+ const isPushOrReplace = actionInfo.type === "PUSH" || actionInfo.type === "REPLACE";
28
+ if (typeof document !== "undefined" && blockers.length && isPushOrReplace) {
29
+ for (const blocker of blockers) {
30
+ const nextLocation = parseHref(actionInfo.path, actionInfo.state);
31
+ const isBlocked = await blocker.blockerFn({
32
+ currentLocation: location,
33
+ nextLocation,
34
+ action: actionInfo.type
35
+ });
36
+ if (isBlocked) {
37
+ (_b = opts.onBlocked) == null ? void 0 : _b.call(opts);
28
38
  return;
29
39
  }
30
40
  }
@@ -35,54 +45,86 @@ function createHistory(opts) {
35
45
  get location() {
36
46
  return location;
37
47
  },
48
+ get length() {
49
+ return opts.getLength();
50
+ },
51
+ subscribers,
38
52
  subscribe: (cb) => {
39
53
  subscribers.add(cb);
40
54
  return () => {
41
55
  subscribers.delete(cb);
42
56
  };
43
57
  },
44
- push: (path, state) => {
45
- state = assignKey(state);
46
- tryNavigation(() => {
47
- opts.pushState(path, state);
48
- onUpdate();
58
+ push: (path, state, navigateOpts) => {
59
+ const currentIndex = location.state[stateIndexKey];
60
+ state = assignKeyAndIndex(currentIndex + 1, state);
61
+ tryNavigation({
62
+ task: () => {
63
+ opts.pushState(path, state);
64
+ notify({ type: "PUSH" });
65
+ },
66
+ navigateOpts,
67
+ type: "PUSH",
68
+ path,
69
+ state
49
70
  });
50
71
  },
51
- replace: (path, state) => {
52
- state = assignKey(state);
53
- tryNavigation(() => {
54
- opts.replaceState(path, state);
55
- onUpdate();
72
+ replace: (path, state, navigateOpts) => {
73
+ const currentIndex = location.state[stateIndexKey];
74
+ state = assignKeyAndIndex(currentIndex, state);
75
+ tryNavigation({
76
+ task: () => {
77
+ opts.replaceState(path, state);
78
+ notify({ type: "REPLACE" });
79
+ },
80
+ navigateOpts,
81
+ type: "REPLACE",
82
+ path,
83
+ state
56
84
  });
57
85
  },
58
- go: (index) => {
59
- tryNavigation(() => {
60
- opts.go(index);
86
+ go: (index, navigateOpts) => {
87
+ tryNavigation({
88
+ task: () => {
89
+ opts.go(index);
90
+ handleIndexChange({ type: "GO", index });
91
+ },
92
+ navigateOpts,
93
+ type: "GO"
61
94
  });
62
95
  },
63
- back: () => {
64
- tryNavigation(() => {
65
- opts.back();
96
+ back: (navigateOpts) => {
97
+ tryNavigation({
98
+ task: () => {
99
+ opts.back((navigateOpts == null ? void 0 : navigateOpts.ignoreBlocker) ?? false);
100
+ handleIndexChange({ type: "BACK" });
101
+ },
102
+ navigateOpts,
103
+ type: "BACK"
66
104
  });
67
105
  },
68
- forward: () => {
69
- tryNavigation(() => {
70
- opts.forward();
106
+ forward: (navigateOpts) => {
107
+ tryNavigation({
108
+ task: () => {
109
+ opts.forward((navigateOpts == null ? void 0 : navigateOpts.ignoreBlocker) ?? false);
110
+ handleIndexChange({ type: "FORWARD" });
111
+ },
112
+ navigateOpts,
113
+ type: "FORWARD"
71
114
  });
72
115
  },
116
+ canGoBack: () => location.state[stateIndexKey] !== 0,
73
117
  createHref: (str) => opts.createHref(str),
74
118
  block: (blocker) => {
75
- blockers.push(blocker);
76
- if (blockers.length === 1) {
77
- addEventListener(beforeUnloadEvent, beforeUnloadListener, {
78
- capture: true
79
- });
80
- }
119
+ var _a;
120
+ if (!opts.setBlockers) return () => {
121
+ };
122
+ const blockers = ((_a = opts.getBlockers) == null ? void 0 : _a.call(opts)) ?? [];
123
+ opts.setBlockers([...blockers, blocker]);
81
124
  return () => {
82
- blockers = blockers.filter((b) => b !== blocker);
83
- if (!blockers.length) {
84
- stopBlocking();
85
- }
125
+ var _a2, _b;
126
+ const blockers2 = ((_a2 = opts.getBlockers) == null ? void 0 : _a2.call(opts)) ?? [];
127
+ (_b = opts.setBlockers) == null ? void 0 : _b.call(opts, blockers2.filter((b) => b !== blocker));
86
128
  };
87
129
  },
88
130
  flush: () => {
@@ -93,49 +135,64 @@ function createHistory(opts) {
93
135
  var _a;
94
136
  return (_a = opts.destroy) == null ? void 0 : _a.call(opts);
95
137
  },
96
- notify: onUpdate
138
+ notify
97
139
  };
98
140
  }
99
- function assignKey(state) {
141
+ function assignKeyAndIndex(index, state) {
100
142
  if (!state) {
101
143
  state = {};
102
144
  }
103
145
  return {
104
146
  ...state,
105
- key: createRandomKey()
147
+ key: createRandomKey(),
148
+ [stateIndexKey]: index
106
149
  };
107
150
  }
108
151
  function createBrowserHistory(opts) {
152
+ var _a;
109
153
  const win = (opts == null ? void 0 : opts.window) ?? (typeof document !== "undefined" ? window : void 0);
154
+ const originalPushState = win.history.pushState;
155
+ const originalReplaceState = win.history.replaceState;
156
+ let blockers = [];
157
+ const _getBlockers = () => blockers;
158
+ const _setBlockers = (newBlockers) => blockers = newBlockers;
110
159
  const createHref = (opts == null ? void 0 : opts.createHref) ?? ((path) => path);
111
160
  const parseLocation = (opts == null ? void 0 : opts.parseLocation) ?? (() => parseHref(
112
161
  `${win.location.pathname}${win.location.search}${win.location.hash}`,
113
162
  win.history.state
114
163
  ));
164
+ if (!((_a = win.history.state) == null ? void 0 : _a.key)) {
165
+ win.history.replaceState(
166
+ {
167
+ [stateIndexKey]: 0,
168
+ key: createRandomKey()
169
+ },
170
+ ""
171
+ );
172
+ }
115
173
  let currentLocation = parseLocation();
116
174
  let rollbackLocation;
175
+ let nextPopIsGo = false;
176
+ let ignoreNextPop = false;
177
+ let skipBlockerNextPop = false;
178
+ let ignoreNextBeforeUnload = false;
117
179
  const getLocation = () => currentLocation;
118
180
  let next;
119
- let tracking = true;
120
181
  let scheduled;
121
- const untrack = (fn) => {
122
- tracking = false;
123
- fn();
124
- tracking = true;
125
- };
126
182
  const flush = () => {
127
- untrack(() => {
128
- if (!next)
129
- return;
130
- win.history[next.isPush ? "pushState" : "replaceState"](
131
- next.state,
132
- "",
133
- next.href
134
- );
135
- next = void 0;
136
- scheduled = void 0;
137
- rollbackLocation = void 0;
138
- });
183
+ if (!next) {
184
+ return;
185
+ }
186
+ history._ignoreSubscribers = true;
187
+ (next.isPush ? win.history.pushState : win.history.replaceState)(
188
+ next.state,
189
+ "",
190
+ next.href
191
+ );
192
+ history._ignoreSubscribers = false;
193
+ next = void 0;
194
+ scheduled = void 0;
195
+ rollbackLocation = void 0;
139
196
  };
140
197
  const queueHistoryAction = (type, destHref, state) => {
141
198
  const href = createHref(destHref);
@@ -152,46 +209,125 @@ function createBrowserHistory(opts) {
152
209
  scheduled = Promise.resolve().then(() => flush());
153
210
  }
154
211
  };
155
- const onPushPop = () => {
212
+ const onPushPop = (type) => {
213
+ currentLocation = parseLocation();
214
+ history.notify({ type });
215
+ };
216
+ const onPushPopEvent = async () => {
217
+ if (ignoreNextPop) {
218
+ ignoreNextPop = false;
219
+ return;
220
+ }
221
+ const nextLocation = parseLocation();
222
+ const delta = nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey];
223
+ const isForward = delta === 1;
224
+ const isBack = delta === -1;
225
+ const isGo = !isForward && !isBack || nextPopIsGo;
226
+ nextPopIsGo = false;
227
+ const action = isGo ? "GO" : isBack ? "BACK" : "FORWARD";
228
+ const notify = isGo ? {
229
+ type: "GO",
230
+ index: delta
231
+ } : {
232
+ type: isBack ? "BACK" : "FORWARD"
233
+ };
234
+ if (skipBlockerNextPop) {
235
+ skipBlockerNextPop = false;
236
+ } else {
237
+ const blockers2 = _getBlockers();
238
+ if (typeof document !== "undefined" && blockers2.length) {
239
+ for (const blocker of blockers2) {
240
+ const isBlocked = await blocker.blockerFn({
241
+ currentLocation,
242
+ nextLocation,
243
+ action
244
+ });
245
+ if (isBlocked) {
246
+ ignoreNextPop = true;
247
+ win.history.go(1);
248
+ history.notify(notify);
249
+ return;
250
+ }
251
+ }
252
+ }
253
+ }
156
254
  currentLocation = parseLocation();
157
- history.notify();
255
+ history.notify(notify);
256
+ };
257
+ const onBeforeUnload = (e) => {
258
+ if (ignoreNextBeforeUnload) {
259
+ ignoreNextBeforeUnload = false;
260
+ return;
261
+ }
262
+ let shouldBlock = false;
263
+ const blockers2 = _getBlockers();
264
+ if (typeof document !== "undefined" && blockers2.length) {
265
+ for (const blocker of blockers2) {
266
+ const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true;
267
+ if (shouldHaveBeforeUnload === true) {
268
+ shouldBlock = true;
269
+ break;
270
+ }
271
+ if (typeof shouldHaveBeforeUnload === "function" && shouldHaveBeforeUnload() === true) {
272
+ shouldBlock = true;
273
+ break;
274
+ }
275
+ }
276
+ }
277
+ if (shouldBlock) {
278
+ e.preventDefault();
279
+ return e.returnValue = "";
280
+ }
281
+ return;
158
282
  };
159
- var originalPushState = win.history.pushState;
160
- var originalReplaceState = win.history.replaceState;
161
283
  const history = createHistory({
162
284
  getLocation,
285
+ getLength: () => win.history.length,
163
286
  pushState: (href, state) => queueHistoryAction("push", href, state),
164
287
  replaceState: (href, state) => queueHistoryAction("replace", href, state),
165
- back: () => win.history.back(),
166
- forward: () => win.history.forward(),
167
- go: (n) => win.history.go(n),
288
+ back: (ignoreBlocker) => {
289
+ if (ignoreBlocker) skipBlockerNextPop = true;
290
+ ignoreNextBeforeUnload = true;
291
+ return win.history.back();
292
+ },
293
+ forward: (ignoreBlocker) => {
294
+ if (ignoreBlocker) skipBlockerNextPop = true;
295
+ ignoreNextBeforeUnload = true;
296
+ win.history.forward();
297
+ },
298
+ go: (n) => {
299
+ nextPopIsGo = true;
300
+ win.history.go(n);
301
+ },
168
302
  createHref: (href) => createHref(href),
169
303
  flush,
170
304
  destroy: () => {
171
305
  win.history.pushState = originalPushState;
172
306
  win.history.replaceState = originalReplaceState;
173
- win.removeEventListener(pushStateEvent, onPushPop);
174
- win.removeEventListener(popStateEvent, onPushPop);
307
+ win.removeEventListener(beforeUnloadEvent, onBeforeUnload, {
308
+ capture: true
309
+ });
310
+ win.removeEventListener(popStateEvent, onPushPopEvent);
175
311
  },
176
- onBlocked: (onUpdate) => {
312
+ onBlocked: () => {
177
313
  if (rollbackLocation && currentLocation !== rollbackLocation) {
178
314
  currentLocation = rollbackLocation;
179
- onUpdate();
180
315
  }
181
- }
316
+ },
317
+ getBlockers: _getBlockers,
318
+ setBlockers: _setBlockers,
319
+ notifyOnIndexChange: false
182
320
  });
183
- win.addEventListener(pushStateEvent, onPushPop);
184
- win.addEventListener(popStateEvent, onPushPop);
185
- win.history.pushState = function() {
186
- let res = originalPushState.apply(win.history, arguments);
187
- if (tracking)
188
- history.notify();
321
+ win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true });
322
+ win.addEventListener(popStateEvent, onPushPopEvent);
323
+ win.history.pushState = function(...args) {
324
+ const res = originalPushState.apply(win.history, args);
325
+ if (!history._ignoreSubscribers) onPushPop("PUSH");
189
326
  return res;
190
327
  };
191
- win.history.replaceState = function() {
192
- let res = originalReplaceState.apply(win.history, arguments);
193
- if (tracking)
194
- history.notify();
328
+ win.history.replaceState = function(...args) {
329
+ const res = originalReplaceState.apply(win.history, args);
330
+ if (!history._ignoreSubscribers) onPushPop("REPLACE");
195
331
  return res;
196
332
  };
197
333
  return history;
@@ -201,7 +337,12 @@ function createHashHistory(opts) {
201
337
  return createBrowserHistory({
202
338
  window: win,
203
339
  parseLocation: () => {
204
- const hashHref = win.location.hash.split("#").slice(1).join("#") ?? "/";
340
+ const hashSplit = win.location.hash.split("#").slice(1);
341
+ const pathPart = hashSplit[0] ?? "/";
342
+ const searchPart = win.location.search;
343
+ const hashEntries = hashSplit.slice(1);
344
+ const hashPart = hashEntries.length === 0 ? "" : `#${hashEntries.join("#")}`;
345
+ const hashHref = `${pathPart}${searchPart}${hashPart}`;
205
346
  return parseHref(hashHref, win.history.state);
206
347
  },
207
348
  createHref: (href) => `${win.location.pathname}${win.location.search}#${href}`
@@ -211,24 +352,29 @@ function createMemoryHistory(opts = {
211
352
  initialEntries: ["/"]
212
353
  }) {
213
354
  const entries = opts.initialEntries;
214
- let index = opts.initialIndex ?? entries.length - 1;
215
- let currentState = {
216
- key: createRandomKey()
217
- };
218
- const getLocation = () => parseHref(entries[index], currentState);
355
+ let index = opts.initialIndex ? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1) : entries.length - 1;
356
+ const states = entries.map(
357
+ (_entry, index2) => assignKeyAndIndex(index2, void 0)
358
+ );
359
+ const getLocation = () => parseHref(entries[index], states[index]);
219
360
  return createHistory({
220
361
  getLocation,
362
+ getLength: () => entries.length,
221
363
  pushState: (path, state) => {
222
- currentState = state;
364
+ if (index < entries.length - 1) {
365
+ entries.splice(index + 1);
366
+ states.splice(index + 1);
367
+ }
368
+ states.push(state);
223
369
  entries.push(path);
224
- index++;
370
+ index = Math.max(entries.length - 1, 0);
225
371
  },
226
372
  replaceState: (path, state) => {
227
- currentState = state;
373
+ states[index] = state;
228
374
  entries[index] = path;
229
375
  },
230
376
  back: () => {
231
- index--;
377
+ index = Math.max(index - 1, 0);
232
378
  },
233
379
  forward: () => {
234
380
  index = Math.min(index + 1, entries.length - 1);
@@ -240,8 +386,8 @@ function createMemoryHistory(opts = {
240
386
  });
241
387
  }
242
388
  function parseHref(href, state) {
243
- let hashIndex = href.indexOf("#");
244
- let searchIndex = href.indexOf("?");
389
+ const hashIndex = href.indexOf("#");
390
+ const searchIndex = href.indexOf("?");
245
391
  return {
246
392
  href,
247
393
  pathname: href.substring(
@@ -250,7 +396,7 @@ function parseHref(href, state) {
250
396
  ),
251
397
  hash: hashIndex > -1 ? href.substring(hashIndex) : "",
252
398
  search: searchIndex > -1 ? href.slice(searchIndex, hashIndex === -1 ? void 0 : hashIndex) : "",
253
- state: state || {}
399
+ state: state || { [stateIndexKey]: 0, key: createRandomKey() }
254
400
  };
255
401
  }
256
402
  function createRandomKey() {
@@ -260,6 +406,7 @@ export {
260
406
  createBrowserHistory,
261
407
  createHashHistory,
262
408
  createHistory,
263
- createMemoryHistory
409
+ createMemoryHistory,
410
+ parseHref
264
411
  };
265
412
  //# sourceMappingURL=index.js.map