@tanstack/history 1.161.5 → 1.161.6

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,425 +1,411 @@
1
- "use strict";
2
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const stateIndexKey = "__TSR_index";
4
- const popStateEvent = "popstate";
5
- const beforeUnloadEvent = "beforeunload";
2
+ //#region src/index.ts
3
+ var stateIndexKey = "__TSR_index";
4
+ var popStateEvent = "popstate";
5
+ var beforeUnloadEvent = "beforeunload";
6
6
  function createHistory(opts) {
7
- let location = opts.getLocation();
8
- const subscribers = /* @__PURE__ */ new Set();
9
- const notify = (action) => {
10
- location = opts.getLocation();
11
- subscribers.forEach((subscriber) => subscriber({ location, action }));
12
- };
13
- const handleIndexChange = (action) => {
14
- if (opts.notifyOnIndexChange ?? true) notify(action);
15
- else location = opts.getLocation();
16
- };
17
- const tryNavigation = async ({
18
- task,
19
- navigateOpts,
20
- ...actionInfo
21
- }) => {
22
- const ignoreBlocker = navigateOpts?.ignoreBlocker ?? false;
23
- if (ignoreBlocker) {
24
- task();
25
- return;
26
- }
27
- const blockers = opts.getBlockers?.() ?? [];
28
- const isPushOrReplace = actionInfo.type === "PUSH" || actionInfo.type === "REPLACE";
29
- if (typeof document !== "undefined" && blockers.length && isPushOrReplace) {
30
- for (const blocker of blockers) {
31
- const nextLocation = parseHref(actionInfo.path, actionInfo.state);
32
- const isBlocked = await blocker.blockerFn({
33
- currentLocation: location,
34
- nextLocation,
35
- action: actionInfo.type
36
- });
37
- if (isBlocked) {
38
- opts.onBlocked?.();
39
- return;
40
- }
41
- }
42
- }
43
- task();
44
- };
45
- return {
46
- get location() {
47
- return location;
48
- },
49
- get length() {
50
- return opts.getLength();
51
- },
52
- subscribers,
53
- subscribe: (cb) => {
54
- subscribers.add(cb);
55
- return () => {
56
- subscribers.delete(cb);
57
- };
58
- },
59
- push: (path, state, navigateOpts) => {
60
- const currentIndex = location.state[stateIndexKey];
61
- state = assignKeyAndIndex(currentIndex + 1, state);
62
- tryNavigation({
63
- task: () => {
64
- opts.pushState(path, state);
65
- notify({ type: "PUSH" });
66
- },
67
- navigateOpts,
68
- type: "PUSH",
69
- path,
70
- state
71
- });
72
- },
73
- replace: (path, state, navigateOpts) => {
74
- const currentIndex = location.state[stateIndexKey];
75
- state = assignKeyAndIndex(currentIndex, state);
76
- tryNavigation({
77
- task: () => {
78
- opts.replaceState(path, state);
79
- notify({ type: "REPLACE" });
80
- },
81
- navigateOpts,
82
- type: "REPLACE",
83
- path,
84
- state
85
- });
86
- },
87
- go: (index, navigateOpts) => {
88
- tryNavigation({
89
- task: () => {
90
- opts.go(index);
91
- handleIndexChange({ type: "GO", index });
92
- },
93
- navigateOpts,
94
- type: "GO"
95
- });
96
- },
97
- back: (navigateOpts) => {
98
- tryNavigation({
99
- task: () => {
100
- opts.back(navigateOpts?.ignoreBlocker ?? false);
101
- handleIndexChange({ type: "BACK" });
102
- },
103
- navigateOpts,
104
- type: "BACK"
105
- });
106
- },
107
- forward: (navigateOpts) => {
108
- tryNavigation({
109
- task: () => {
110
- opts.forward(navigateOpts?.ignoreBlocker ?? false);
111
- handleIndexChange({ type: "FORWARD" });
112
- },
113
- navigateOpts,
114
- type: "FORWARD"
115
- });
116
- },
117
- canGoBack: () => location.state[stateIndexKey] !== 0,
118
- createHref: (str) => opts.createHref(str),
119
- block: (blocker) => {
120
- if (!opts.setBlockers) return () => {
121
- };
122
- const blockers = opts.getBlockers?.() ?? [];
123
- opts.setBlockers([...blockers, blocker]);
124
- return () => {
125
- const blockers2 = opts.getBlockers?.() ?? [];
126
- opts.setBlockers?.(blockers2.filter((b) => b !== blocker));
127
- };
128
- },
129
- flush: () => opts.flush?.(),
130
- destroy: () => opts.destroy?.(),
131
- notify
132
- };
7
+ let location = opts.getLocation();
8
+ const subscribers = /* @__PURE__ */ new Set();
9
+ const notify = (action) => {
10
+ location = opts.getLocation();
11
+ subscribers.forEach((subscriber) => subscriber({
12
+ location,
13
+ action
14
+ }));
15
+ };
16
+ const handleIndexChange = (action) => {
17
+ if (opts.notifyOnIndexChange ?? true) notify(action);
18
+ else location = opts.getLocation();
19
+ };
20
+ const tryNavigation = async ({ task, navigateOpts, ...actionInfo }) => {
21
+ if (navigateOpts?.ignoreBlocker ?? false) {
22
+ task();
23
+ return;
24
+ }
25
+ const blockers = opts.getBlockers?.() ?? [];
26
+ const isPushOrReplace = actionInfo.type === "PUSH" || actionInfo.type === "REPLACE";
27
+ if (typeof document !== "undefined" && blockers.length && isPushOrReplace) for (const blocker of blockers) {
28
+ const nextLocation = parseHref(actionInfo.path, actionInfo.state);
29
+ if (await blocker.blockerFn({
30
+ currentLocation: location,
31
+ nextLocation,
32
+ action: actionInfo.type
33
+ })) {
34
+ opts.onBlocked?.();
35
+ return;
36
+ }
37
+ }
38
+ task();
39
+ };
40
+ return {
41
+ get location() {
42
+ return location;
43
+ },
44
+ get length() {
45
+ return opts.getLength();
46
+ },
47
+ subscribers,
48
+ subscribe: (cb) => {
49
+ subscribers.add(cb);
50
+ return () => {
51
+ subscribers.delete(cb);
52
+ };
53
+ },
54
+ push: (path, state, navigateOpts) => {
55
+ const currentIndex = location.state[stateIndexKey];
56
+ state = assignKeyAndIndex(currentIndex + 1, state);
57
+ tryNavigation({
58
+ task: () => {
59
+ opts.pushState(path, state);
60
+ notify({ type: "PUSH" });
61
+ },
62
+ navigateOpts,
63
+ type: "PUSH",
64
+ path,
65
+ state
66
+ });
67
+ },
68
+ replace: (path, state, navigateOpts) => {
69
+ const currentIndex = location.state[stateIndexKey];
70
+ state = assignKeyAndIndex(currentIndex, state);
71
+ tryNavigation({
72
+ task: () => {
73
+ opts.replaceState(path, state);
74
+ notify({ type: "REPLACE" });
75
+ },
76
+ navigateOpts,
77
+ type: "REPLACE",
78
+ path,
79
+ state
80
+ });
81
+ },
82
+ go: (index, navigateOpts) => {
83
+ tryNavigation({
84
+ task: () => {
85
+ opts.go(index);
86
+ handleIndexChange({
87
+ type: "GO",
88
+ index
89
+ });
90
+ },
91
+ navigateOpts,
92
+ type: "GO"
93
+ });
94
+ },
95
+ back: (navigateOpts) => {
96
+ tryNavigation({
97
+ task: () => {
98
+ opts.back(navigateOpts?.ignoreBlocker ?? false);
99
+ handleIndexChange({ type: "BACK" });
100
+ },
101
+ navigateOpts,
102
+ type: "BACK"
103
+ });
104
+ },
105
+ forward: (navigateOpts) => {
106
+ tryNavigation({
107
+ task: () => {
108
+ opts.forward(navigateOpts?.ignoreBlocker ?? false);
109
+ handleIndexChange({ type: "FORWARD" });
110
+ },
111
+ navigateOpts,
112
+ type: "FORWARD"
113
+ });
114
+ },
115
+ canGoBack: () => location.state[stateIndexKey] !== 0,
116
+ createHref: (str) => opts.createHref(str),
117
+ block: (blocker) => {
118
+ if (!opts.setBlockers) return () => {};
119
+ const blockers = opts.getBlockers?.() ?? [];
120
+ opts.setBlockers([...blockers, blocker]);
121
+ return () => {
122
+ const blockers = opts.getBlockers?.() ?? [];
123
+ opts.setBlockers?.(blockers.filter((b) => b !== blocker));
124
+ };
125
+ },
126
+ flush: () => opts.flush?.(),
127
+ destroy: () => opts.destroy?.(),
128
+ notify
129
+ };
133
130
  }
134
131
  function assignKeyAndIndex(index, state) {
135
- if (!state) {
136
- state = {};
137
- }
138
- const key = createRandomKey();
139
- return {
140
- ...state,
141
- key,
142
- // TODO: Remove in v2 - use __TSR_key instead
143
- __TSR_key: key,
144
- [stateIndexKey]: index
145
- };
132
+ if (!state) state = {};
133
+ const key = createRandomKey();
134
+ return {
135
+ ...state,
136
+ key,
137
+ __TSR_key: key,
138
+ [stateIndexKey]: index
139
+ };
146
140
  }
141
+ /**
142
+ * Creates a history object that can be used to interact with the browser's
143
+ * navigation. This is a lightweight API wrapping the browser's native methods.
144
+ * It is designed to work with TanStack Router, but could be used as a standalone API as well.
145
+ * IMPORTANT: This API implements history throttling via a microtask to prevent
146
+ * excessive calls to the history API. In some browsers, calling history.pushState or
147
+ * history.replaceState in quick succession can cause the browser to ignore subsequent
148
+ * calls. This API smooths out those differences and ensures that your application
149
+ * state will *eventually* match the browser state. In most cases, this is not a problem,
150
+ * but if you need to ensure that the browser state is up to date, you can use the
151
+ * `history.flush` method to immediately flush all pending state changes to the browser URL.
152
+ * @param opts
153
+ * @param opts.getHref A function that returns the current href (path + search + hash)
154
+ * @param opts.createHref A function that takes a path and returns a href (path + search + hash)
155
+ * @returns A history instance
156
+ */
147
157
  function createBrowserHistory(opts) {
148
- const win = opts?.window ?? (typeof document !== "undefined" ? window : void 0);
149
- const originalPushState = win.history.pushState;
150
- const originalReplaceState = win.history.replaceState;
151
- let blockers = [];
152
- const _getBlockers = () => blockers;
153
- const _setBlockers = (newBlockers) => blockers = newBlockers;
154
- const createHref = opts?.createHref ?? ((path) => path);
155
- const parseLocation = opts?.parseLocation ?? (() => parseHref(
156
- `${win.location.pathname}${win.location.search}${win.location.hash}`,
157
- win.history.state
158
- ));
159
- if (!win.history.state?.__TSR_key && !win.history.state?.key) {
160
- const addedKey = createRandomKey();
161
- win.history.replaceState(
162
- {
163
- [stateIndexKey]: 0,
164
- key: addedKey,
165
- // TODO: Remove in v2 - use __TSR_key instead
166
- __TSR_key: addedKey
167
- },
168
- ""
169
- );
170
- }
171
- let currentLocation = parseLocation();
172
- let rollbackLocation;
173
- let nextPopIsGo = false;
174
- let ignoreNextPop = false;
175
- let skipBlockerNextPop = false;
176
- let ignoreNextBeforeUnload = false;
177
- const getLocation = () => currentLocation;
178
- let next;
179
- let scheduled;
180
- const flush = () => {
181
- if (!next) {
182
- return;
183
- }
184
- history._ignoreSubscribers = true;
185
- (next.isPush ? win.history.pushState : win.history.replaceState)(
186
- next.state,
187
- "",
188
- next.href
189
- );
190
- history._ignoreSubscribers = false;
191
- next = void 0;
192
- scheduled = void 0;
193
- rollbackLocation = void 0;
194
- };
195
- const queueHistoryAction = (type, destHref, state) => {
196
- const href = createHref(destHref);
197
- if (!scheduled) {
198
- rollbackLocation = currentLocation;
199
- }
200
- currentLocation = parseHref(destHref, state);
201
- next = {
202
- href,
203
- state,
204
- isPush: next?.isPush || type === "push"
205
- };
206
- if (!scheduled) {
207
- scheduled = Promise.resolve().then(() => flush());
208
- }
209
- };
210
- const onPushPop = (type) => {
211
- currentLocation = parseLocation();
212
- history.notify({ type });
213
- };
214
- const onPushPopEvent = async () => {
215
- if (ignoreNextPop) {
216
- ignoreNextPop = false;
217
- return;
218
- }
219
- const nextLocation = parseLocation();
220
- const delta = nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey];
221
- const isForward = delta === 1;
222
- const isBack = delta === -1;
223
- const isGo = !isForward && !isBack || nextPopIsGo;
224
- nextPopIsGo = false;
225
- const action = isGo ? "GO" : isBack ? "BACK" : "FORWARD";
226
- const notify = isGo ? {
227
- type: "GO",
228
- index: delta
229
- } : {
230
- type: isBack ? "BACK" : "FORWARD"
231
- };
232
- if (skipBlockerNextPop) {
233
- skipBlockerNextPop = false;
234
- } else {
235
- const blockers2 = _getBlockers();
236
- if (typeof document !== "undefined" && blockers2.length) {
237
- for (const blocker of blockers2) {
238
- const isBlocked = await blocker.blockerFn({
239
- currentLocation,
240
- nextLocation,
241
- action
242
- });
243
- if (isBlocked) {
244
- ignoreNextPop = true;
245
- win.history.go(1);
246
- history.notify(notify);
247
- return;
248
- }
249
- }
250
- }
251
- }
252
- currentLocation = parseLocation();
253
- history.notify(notify);
254
- };
255
- const onBeforeUnload = (e) => {
256
- if (ignoreNextBeforeUnload) {
257
- ignoreNextBeforeUnload = false;
258
- return;
259
- }
260
- let shouldBlock = false;
261
- const blockers2 = _getBlockers();
262
- if (typeof document !== "undefined" && blockers2.length) {
263
- for (const blocker of blockers2) {
264
- const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true;
265
- if (shouldHaveBeforeUnload === true) {
266
- shouldBlock = true;
267
- break;
268
- }
269
- if (typeof shouldHaveBeforeUnload === "function" && shouldHaveBeforeUnload() === true) {
270
- shouldBlock = true;
271
- break;
272
- }
273
- }
274
- }
275
- if (shouldBlock) {
276
- e.preventDefault();
277
- return e.returnValue = "";
278
- }
279
- return;
280
- };
281
- const history = createHistory({
282
- getLocation,
283
- getLength: () => win.history.length,
284
- pushState: (href, state) => queueHistoryAction("push", href, state),
285
- replaceState: (href, state) => queueHistoryAction("replace", href, state),
286
- back: (ignoreBlocker) => {
287
- if (ignoreBlocker) skipBlockerNextPop = true;
288
- ignoreNextBeforeUnload = true;
289
- return win.history.back();
290
- },
291
- forward: (ignoreBlocker) => {
292
- if (ignoreBlocker) skipBlockerNextPop = true;
293
- ignoreNextBeforeUnload = true;
294
- win.history.forward();
295
- },
296
- go: (n) => {
297
- nextPopIsGo = true;
298
- win.history.go(n);
299
- },
300
- createHref: (href) => createHref(href),
301
- flush,
302
- destroy: () => {
303
- win.history.pushState = originalPushState;
304
- win.history.replaceState = originalReplaceState;
305
- win.removeEventListener(beforeUnloadEvent, onBeforeUnload, {
306
- capture: true
307
- });
308
- win.removeEventListener(popStateEvent, onPushPopEvent);
309
- },
310
- onBlocked: () => {
311
- if (rollbackLocation && currentLocation !== rollbackLocation) {
312
- currentLocation = rollbackLocation;
313
- }
314
- },
315
- getBlockers: _getBlockers,
316
- setBlockers: _setBlockers,
317
- notifyOnIndexChange: false
318
- });
319
- win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true });
320
- win.addEventListener(popStateEvent, onPushPopEvent);
321
- win.history.pushState = function(...args) {
322
- const res = originalPushState.apply(win.history, args);
323
- if (!history._ignoreSubscribers) onPushPop("PUSH");
324
- return res;
325
- };
326
- win.history.replaceState = function(...args) {
327
- const res = originalReplaceState.apply(win.history, args);
328
- if (!history._ignoreSubscribers) onPushPop("REPLACE");
329
- return res;
330
- };
331
- return history;
158
+ const win = opts?.window ?? (typeof document !== "undefined" ? window : void 0);
159
+ const originalPushState = win.history.pushState;
160
+ const originalReplaceState = win.history.replaceState;
161
+ let blockers = [];
162
+ const _getBlockers = () => blockers;
163
+ const _setBlockers = (newBlockers) => blockers = newBlockers;
164
+ const createHref = opts?.createHref ?? ((path) => path);
165
+ const parseLocation = opts?.parseLocation ?? (() => parseHref(`${win.location.pathname}${win.location.search}${win.location.hash}`, win.history.state));
166
+ if (!win.history.state?.__TSR_key && !win.history.state?.key) {
167
+ const addedKey = createRandomKey();
168
+ win.history.replaceState({
169
+ [stateIndexKey]: 0,
170
+ key: addedKey,
171
+ __TSR_key: addedKey
172
+ }, "");
173
+ }
174
+ let currentLocation = parseLocation();
175
+ let rollbackLocation;
176
+ let nextPopIsGo = false;
177
+ let ignoreNextPop = false;
178
+ let skipBlockerNextPop = false;
179
+ let ignoreNextBeforeUnload = false;
180
+ const getLocation = () => currentLocation;
181
+ let next;
182
+ let scheduled;
183
+ const flush = () => {
184
+ if (!next) return;
185
+ history._ignoreSubscribers = true;
186
+ (next.isPush ? win.history.pushState : win.history.replaceState)(next.state, "", next.href);
187
+ history._ignoreSubscribers = false;
188
+ next = void 0;
189
+ scheduled = void 0;
190
+ rollbackLocation = void 0;
191
+ };
192
+ const queueHistoryAction = (type, destHref, state) => {
193
+ const href = createHref(destHref);
194
+ if (!scheduled) rollbackLocation = currentLocation;
195
+ currentLocation = parseHref(destHref, state);
196
+ next = {
197
+ href,
198
+ state,
199
+ isPush: next?.isPush || type === "push"
200
+ };
201
+ if (!scheduled) scheduled = Promise.resolve().then(() => flush());
202
+ };
203
+ const onPushPop = (type) => {
204
+ currentLocation = parseLocation();
205
+ history.notify({ type });
206
+ };
207
+ const onPushPopEvent = async () => {
208
+ if (ignoreNextPop) {
209
+ ignoreNextPop = false;
210
+ return;
211
+ }
212
+ const nextLocation = parseLocation();
213
+ const delta = nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey];
214
+ const isForward = delta === 1;
215
+ const isBack = delta === -1;
216
+ const isGo = !isForward && !isBack || nextPopIsGo;
217
+ nextPopIsGo = false;
218
+ const action = isGo ? "GO" : isBack ? "BACK" : "FORWARD";
219
+ const notify = isGo ? {
220
+ type: "GO",
221
+ index: delta
222
+ } : { type: isBack ? "BACK" : "FORWARD" };
223
+ if (skipBlockerNextPop) skipBlockerNextPop = false;
224
+ else {
225
+ const blockers = _getBlockers();
226
+ if (typeof document !== "undefined" && blockers.length) {
227
+ for (const blocker of blockers) if (await blocker.blockerFn({
228
+ currentLocation,
229
+ nextLocation,
230
+ action
231
+ })) {
232
+ ignoreNextPop = true;
233
+ win.history.go(1);
234
+ history.notify(notify);
235
+ return;
236
+ }
237
+ }
238
+ }
239
+ currentLocation = parseLocation();
240
+ history.notify(notify);
241
+ };
242
+ const onBeforeUnload = (e) => {
243
+ if (ignoreNextBeforeUnload) {
244
+ ignoreNextBeforeUnload = false;
245
+ return;
246
+ }
247
+ let shouldBlock = false;
248
+ const blockers = _getBlockers();
249
+ if (typeof document !== "undefined" && blockers.length) for (const blocker of blockers) {
250
+ const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true;
251
+ if (shouldHaveBeforeUnload === true) {
252
+ shouldBlock = true;
253
+ break;
254
+ }
255
+ if (typeof shouldHaveBeforeUnload === "function" && shouldHaveBeforeUnload() === true) {
256
+ shouldBlock = true;
257
+ break;
258
+ }
259
+ }
260
+ if (shouldBlock) {
261
+ e.preventDefault();
262
+ return e.returnValue = "";
263
+ }
264
+ };
265
+ const history = createHistory({
266
+ getLocation,
267
+ getLength: () => win.history.length,
268
+ pushState: (href, state) => queueHistoryAction("push", href, state),
269
+ replaceState: (href, state) => queueHistoryAction("replace", href, state),
270
+ back: (ignoreBlocker) => {
271
+ if (ignoreBlocker) skipBlockerNextPop = true;
272
+ ignoreNextBeforeUnload = true;
273
+ return win.history.back();
274
+ },
275
+ forward: (ignoreBlocker) => {
276
+ if (ignoreBlocker) skipBlockerNextPop = true;
277
+ ignoreNextBeforeUnload = true;
278
+ win.history.forward();
279
+ },
280
+ go: (n) => {
281
+ nextPopIsGo = true;
282
+ win.history.go(n);
283
+ },
284
+ createHref: (href) => createHref(href),
285
+ flush,
286
+ destroy: () => {
287
+ win.history.pushState = originalPushState;
288
+ win.history.replaceState = originalReplaceState;
289
+ win.removeEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true });
290
+ win.removeEventListener(popStateEvent, onPushPopEvent);
291
+ },
292
+ onBlocked: () => {
293
+ if (rollbackLocation && currentLocation !== rollbackLocation) currentLocation = rollbackLocation;
294
+ },
295
+ getBlockers: _getBlockers,
296
+ setBlockers: _setBlockers,
297
+ notifyOnIndexChange: false
298
+ });
299
+ win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true });
300
+ win.addEventListener(popStateEvent, onPushPopEvent);
301
+ win.history.pushState = function(...args) {
302
+ const res = originalPushState.apply(win.history, args);
303
+ if (!history._ignoreSubscribers) onPushPop("PUSH");
304
+ return res;
305
+ };
306
+ win.history.replaceState = function(...args) {
307
+ const res = originalReplaceState.apply(win.history, args);
308
+ if (!history._ignoreSubscribers) onPushPop("REPLACE");
309
+ return res;
310
+ };
311
+ return history;
332
312
  }
313
+ /**
314
+ * Create a hash-based history implementation.
315
+ * Useful for static hosts or environments without server URL rewriting.
316
+ * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types
317
+ */
333
318
  function createHashHistory(opts) {
334
- const win = opts?.window ?? (typeof document !== "undefined" ? window : void 0);
335
- return createBrowserHistory({
336
- window: win,
337
- parseLocation: () => {
338
- const hashSplit = win.location.hash.split("#").slice(1);
339
- const pathPart = hashSplit[0] ?? "/";
340
- const searchPart = win.location.search;
341
- const hashEntries = hashSplit.slice(1);
342
- const hashPart = hashEntries.length === 0 ? "" : `#${hashEntries.join("#")}`;
343
- const hashHref = `${pathPart}${searchPart}${hashPart}`;
344
- return parseHref(hashHref, win.history.state);
345
- },
346
- createHref: (href) => `${win.location.pathname}${win.location.search}#${href}`
347
- });
319
+ const win = opts?.window ?? (typeof document !== "undefined" ? window : void 0);
320
+ return createBrowserHistory({
321
+ window: win,
322
+ parseLocation: () => {
323
+ const hashSplit = win.location.hash.split("#").slice(1);
324
+ const pathPart = hashSplit[0] ?? "/";
325
+ const searchPart = win.location.search;
326
+ const hashEntries = hashSplit.slice(1);
327
+ return parseHref(`${pathPart}${searchPart}${hashEntries.length === 0 ? "" : `#${hashEntries.join("#")}`}`, win.history.state);
328
+ },
329
+ createHref: (href) => `${win.location.pathname}${win.location.search}#${href}`
330
+ });
348
331
  }
349
- function createMemoryHistory(opts = {
350
- initialEntries: ["/"]
351
- }) {
352
- const entries = opts.initialEntries;
353
- let index = opts.initialIndex ? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1) : entries.length - 1;
354
- const states = entries.map(
355
- (_entry, index2) => assignKeyAndIndex(index2, void 0)
356
- );
357
- const getLocation = () => parseHref(entries[index], states[index]);
358
- let blockers = [];
359
- const _getBlockers = () => blockers;
360
- const _setBlockers = (newBlockers) => blockers = newBlockers;
361
- return createHistory({
362
- getLocation,
363
- getLength: () => entries.length,
364
- pushState: (path, state) => {
365
- if (index < entries.length - 1) {
366
- entries.splice(index + 1);
367
- states.splice(index + 1);
368
- }
369
- states.push(state);
370
- entries.push(path);
371
- index = Math.max(entries.length - 1, 0);
372
- },
373
- replaceState: (path, state) => {
374
- states[index] = state;
375
- entries[index] = path;
376
- },
377
- back: () => {
378
- index = Math.max(index - 1, 0);
379
- },
380
- forward: () => {
381
- index = Math.min(index + 1, entries.length - 1);
382
- },
383
- go: (n) => {
384
- index = Math.min(Math.max(index + n, 0), entries.length - 1);
385
- },
386
- createHref: (path) => path,
387
- getBlockers: _getBlockers,
388
- setBlockers: _setBlockers
389
- });
332
+ /**
333
+ * Create an in-memory history implementation.
334
+ * Ideal for server rendering, tests, and non-DOM environments.
335
+ * @link https://tanstack.com/router/latest/docs/framework/react/guide/history-types
336
+ */
337
+ function createMemoryHistory(opts = { initialEntries: ["/"] }) {
338
+ const entries = opts.initialEntries;
339
+ let index = opts.initialIndex ? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1) : entries.length - 1;
340
+ const states = entries.map((_entry, index) => assignKeyAndIndex(index, void 0));
341
+ const getLocation = () => parseHref(entries[index], states[index]);
342
+ let blockers = [];
343
+ const _getBlockers = () => blockers;
344
+ const _setBlockers = (newBlockers) => blockers = newBlockers;
345
+ return createHistory({
346
+ getLocation,
347
+ getLength: () => entries.length,
348
+ pushState: (path, state) => {
349
+ if (index < entries.length - 1) {
350
+ entries.splice(index + 1);
351
+ states.splice(index + 1);
352
+ }
353
+ states.push(state);
354
+ entries.push(path);
355
+ index = Math.max(entries.length - 1, 0);
356
+ },
357
+ replaceState: (path, state) => {
358
+ states[index] = state;
359
+ entries[index] = path;
360
+ },
361
+ back: () => {
362
+ index = Math.max(index - 1, 0);
363
+ },
364
+ forward: () => {
365
+ index = Math.min(index + 1, entries.length - 1);
366
+ },
367
+ go: (n) => {
368
+ index = Math.min(Math.max(index + n, 0), entries.length - 1);
369
+ },
370
+ createHref: (path) => path,
371
+ getBlockers: _getBlockers,
372
+ setBlockers: _setBlockers
373
+ });
390
374
  }
375
+ /**
376
+ * Sanitize a path to prevent open redirect vulnerabilities.
377
+ * Removes control characters and collapses leading double slashes.
378
+ */
391
379
  function sanitizePath(path) {
392
- let sanitized = path.replace(/[\x00-\x1f\x7f]/g, "");
393
- if (sanitized.startsWith("//")) {
394
- sanitized = "/" + sanitized.replace(/^\/+/, "");
395
- }
396
- return sanitized;
380
+ let sanitized = path.replace(/[\x00-\x1f\x7f]/g, "");
381
+ if (sanitized.startsWith("//")) sanitized = "/" + sanitized.replace(/^\/+/, "");
382
+ return sanitized;
397
383
  }
398
384
  function parseHref(href, state) {
399
- const sanitizedHref = sanitizePath(href);
400
- const hashIndex = sanitizedHref.indexOf("#");
401
- const searchIndex = sanitizedHref.indexOf("?");
402
- const addedKey = createRandomKey();
403
- return {
404
- href: sanitizedHref,
405
- pathname: sanitizedHref.substring(
406
- 0,
407
- hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : sanitizedHref.length
408
- ),
409
- hash: hashIndex > -1 ? sanitizedHref.substring(hashIndex) : "",
410
- search: searchIndex > -1 ? sanitizedHref.slice(
411
- searchIndex,
412
- hashIndex === -1 ? void 0 : hashIndex
413
- ) : "",
414
- state: state || { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey }
415
- };
385
+ const sanitizedHref = sanitizePath(href);
386
+ const hashIndex = sanitizedHref.indexOf("#");
387
+ const searchIndex = sanitizedHref.indexOf("?");
388
+ const addedKey = createRandomKey();
389
+ return {
390
+ href: sanitizedHref,
391
+ pathname: sanitizedHref.substring(0, hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : sanitizedHref.length),
392
+ hash: hashIndex > -1 ? sanitizedHref.substring(hashIndex) : "",
393
+ search: searchIndex > -1 ? sanitizedHref.slice(searchIndex, hashIndex === -1 ? void 0 : hashIndex) : "",
394
+ state: state || {
395
+ [stateIndexKey]: 0,
396
+ key: addedKey,
397
+ __TSR_key: addedKey
398
+ }
399
+ };
416
400
  }
417
401
  function createRandomKey() {
418
- return (Math.random() + 1).toString(36).substring(7);
402
+ return (Math.random() + 1).toString(36).substring(7);
419
403
  }
404
+ //#endregion
420
405
  exports.createBrowserHistory = createBrowserHistory;
421
406
  exports.createHashHistory = createHashHistory;
422
407
  exports.createHistory = createHistory;
423
408
  exports.createMemoryHistory = createMemoryHistory;
424
409
  exports.parseHref = parseHref;
425
- //# sourceMappingURL=index.cjs.map
410
+
411
+ //# sourceMappingURL=index.cjs.map