@tanstack/router-core 0.0.1-beta.194 → 0.0.1-beta.196

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.
@@ -9,2251 +9,2334 @@
9
9
  * @license MIT
10
10
  */
11
11
  (function (global, factory) {
12
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
13
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
14
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.RouterCore = {}));
12
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
13
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
14
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.RouterCore = {}));
15
15
  })(this, (function (exports) { 'use strict';
16
16
 
17
- var prefix = 'Invariant failed';
18
- function invariant(condition, message) {
19
- if (condition) {
20
- return;
21
- }
22
- var provided = typeof message === 'function' ? message() : message;
23
- var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
24
- throw new Error(value);
25
- }
26
-
27
- function warning(condition, message) {
28
- {
29
- if (condition) {
30
- return;
31
- }
32
-
33
- var text = "Warning: " + message;
34
-
35
- if (typeof console !== 'undefined') {
36
- console.warn(text);
37
- }
38
-
39
- try {
40
- throw Error(text);
41
- } catch (x) {}
17
+ /**
18
+ * @tanstack/history/src/index.ts
19
+ *
20
+ * Copyright (c) TanStack
21
+ *
22
+ * This source code is licensed under the MIT license found in the
23
+ * LICENSE.md file in the root directory of this source tree.
24
+ *
25
+ * @license MIT
26
+ */
27
+ // While the public API was clearly inspired by the "history" npm package,
28
+ // This implementation attempts to be more lightweight by
29
+ // making assumptions about the way TanStack Router works
30
+
31
+ const pushStateEvent = 'pushstate';
32
+ const popStateEvent = 'popstate';
33
+ const beforeUnloadEvent = 'beforeunload';
34
+ const beforeUnloadListener = event => {
35
+ event.preventDefault();
36
+ // @ts-ignore
37
+ return event.returnValue = '';
38
+ };
39
+ const stopBlocking = () => {
40
+ removeEventListener(beforeUnloadEvent, beforeUnloadListener, {
41
+ capture: true
42
+ });
43
+ };
44
+ function createHistory(opts) {
45
+ let location = opts.getLocation();
46
+ let unsub = () => {};
47
+ let subscribers = new Set();
48
+ let blockers = [];
49
+ let queue = [];
50
+ const tryFlush = () => {
51
+ if (blockers.length) {
52
+ blockers[0]?.(tryFlush, () => {
53
+ blockers = [];
54
+ stopBlocking();
55
+ });
56
+ return;
57
+ }
58
+ while (queue.length) {
59
+ queue.shift()?.();
60
+ }
61
+ if (!opts.subscriber) {
62
+ onUpdate();
42
63
  }
43
- }
44
-
45
- // While the public API was clearly inspired by the "history" npm package,
46
- // This implementation attempts to be more lightweight by
47
- // making assumptions about the way TanStack Router works
48
-
49
- const pushStateEvent = 'pushstate';
50
- const popStateEvent = 'popstate';
51
- const beforeUnloadEvent = 'beforeunload';
52
- const beforeUnloadListener = event => {
53
- event.preventDefault();
54
- // @ts-ignore
55
- return event.returnValue = '';
56
64
  };
57
- const stopBlocking = () => {
58
- removeEventListener(beforeUnloadEvent, beforeUnloadListener, {
59
- capture: true
60
- });
65
+ const queueTask = task => {
66
+ queue.push(task);
67
+ tryFlush();
61
68
  };
62
- function createHistory(opts) {
63
- let location = opts.getLocation();
64
- let unsub = () => {};
65
- let subscribers = new Set();
66
- let blockers = [];
67
- let queue = [];
68
- const tryFlush = () => {
69
- if (blockers.length) {
70
- blockers[0]?.(tryFlush, () => {
71
- blockers = [];
72
- stopBlocking();
73
- });
74
- return;
75
- }
76
- while (queue.length) {
77
- queue.shift()?.();
78
- }
79
- if (!opts.subscriber) {
80
- onUpdate();
69
+ const onUpdate = () => {
70
+ location = opts.getLocation();
71
+ subscribers.forEach(subscriber => subscriber());
72
+ };
73
+ return {
74
+ get location() {
75
+ return location;
76
+ },
77
+ subscribe: cb => {
78
+ if (subscribers.size === 0) {
79
+ unsub = typeof opts.subscriber === 'function' ? opts.subscriber(onUpdate) : () => {};
81
80
  }
82
- };
83
- const queueTask = task => {
84
- queue.push(task);
85
- tryFlush();
86
- };
87
- const onUpdate = () => {
88
- location = opts.getLocation();
89
- subscribers.forEach(subscriber => subscriber());
90
- };
91
- return {
92
- get location() {
93
- return location;
94
- },
95
- subscribe: cb => {
81
+ subscribers.add(cb);
82
+ return () => {
83
+ subscribers.delete(cb);
96
84
  if (subscribers.size === 0) {
97
- unsub = typeof opts.subscriber === 'function' ? opts.subscriber(onUpdate) : () => {};
85
+ unsub();
98
86
  }
99
- subscribers.add(cb);
100
- return () => {
101
- subscribers.delete(cb);
102
- if (subscribers.size === 0) {
103
- unsub();
104
- }
105
- };
106
- },
107
- push: (path, state) => {
108
- assignKey(state);
109
- queueTask(() => {
110
- opts.pushState(path, state);
111
- });
112
- },
113
- replace: (path, state) => {
114
- assignKey(state);
115
- queueTask(() => {
116
- opts.replaceState(path, state);
117
- });
118
- },
119
- go: index => {
120
- queueTask(() => {
121
- opts.go(index);
122
- });
123
- },
124
- back: () => {
125
- queueTask(() => {
126
- opts.back();
127
- });
128
- },
129
- forward: () => {
130
- queueTask(() => {
131
- opts.forward();
87
+ };
88
+ },
89
+ push: (path, state) => {
90
+ state = assignKey(state);
91
+ queueTask(() => {
92
+ opts.pushState(path, state, onUpdate);
93
+ });
94
+ },
95
+ replace: (path, state) => {
96
+ state = assignKey(state);
97
+ queueTask(() => {
98
+ opts.replaceState(path, state, onUpdate);
99
+ });
100
+ },
101
+ go: index => {
102
+ queueTask(() => {
103
+ opts.go(index);
104
+ });
105
+ },
106
+ back: () => {
107
+ queueTask(() => {
108
+ opts.back();
109
+ });
110
+ },
111
+ forward: () => {
112
+ queueTask(() => {
113
+ opts.forward();
114
+ });
115
+ },
116
+ createHref: str => opts.createHref(str),
117
+ block: cb => {
118
+ blockers.push(cb);
119
+ if (blockers.length === 1) {
120
+ addEventListener(beforeUnloadEvent, beforeUnloadListener, {
121
+ capture: true
132
122
  });
133
- },
134
- createHref: str => opts.createHref(str),
135
- block: cb => {
136
- blockers.push(cb);
137
- if (blockers.length === 1) {
138
- addEventListener(beforeUnloadEvent, beforeUnloadListener, {
139
- capture: true
140
- });
141
- }
142
- return () => {
143
- blockers = blockers.filter(b => b !== cb);
144
- if (!blockers.length) {
145
- stopBlocking();
146
- }
147
- };
148
123
  }
149
- };
150
- }
151
- function assignKey(state) {
152
- state.key = createRandomKey();
153
- // if (state.__actualLocation) {
154
- // state.__actualLocation.state = {
155
- // ...state.__actualLocation.state,
156
- // key,
157
- // }
158
- // }
124
+ return () => {
125
+ blockers = blockers.filter(b => b !== cb);
126
+ if (!blockers.length) {
127
+ stopBlocking();
128
+ }
129
+ };
130
+ },
131
+ flush: () => opts.flush?.()
132
+ };
133
+ }
134
+ function assignKey(state) {
135
+ if (!state) {
136
+ state = {};
159
137
  }
138
+ state.key = createRandomKey();
139
+ return state;
140
+ }
141
+
142
+ /**
143
+ * Creates a history object that can be used to interact with the browser's
144
+ * navigation. This is a lightweight API wrapping the browser's native methods.
145
+ * It is designed to work with TanStack Router, but could be used as a standalone API as well.
146
+ * IMPORTANT: This API implements history throttling via a microtask to prevent
147
+ * excessive calls to the history API. In some browsers, calling history.pushState or
148
+ * history.replaceState in quick succession can cause the browser to ignore subsequent
149
+ * calls. This API smooths out those differences and ensures that your application
150
+ * state will *eventually* match the browser state. In most cases, this is not a problem,
151
+ * but if you need to ensure that the browser state is up to date, you can use the
152
+ * `history.flush` method to immediately flush all pending state changes to the browser URL.
153
+ * @param opts
154
+ * @param opts.getHref A function that returns the current href (path + search + hash)
155
+ * @param opts.createHref A function that takes a path and returns a href (path + search + hash)
156
+ * @returns A history instance
157
+ */
158
+ function createBrowserHistory(opts) {
159
+ const getHref = opts?.getHref ?? (() => `${window.location.pathname}${window.location.search}${window.location.hash}`);
160
+ const createHref = opts?.createHref ?? (path => path);
161
+ let currentLocation = parseLocation(getHref(), window.history.state);
162
+ const getLocation = () => currentLocation;
163
+ let next;
164
+
165
+ // Because we are proactively updating the location
166
+ // in memory before actually updating the browser history,
167
+ // we need to track when we are doing this so we don't
168
+ // notify subscribers twice on the last update.
169
+ let tracking = true;
170
+
171
+ // We need to track the current scheduled update to prevent
172
+ // multiple updates from being scheduled at the same time.
173
+ let scheduled;
174
+
175
+ // This function is a wrapper to prevent any of the callback's
176
+ // side effects from causing a subscriber notification
177
+ const untrack = fn => {
178
+ tracking = false;
179
+ fn();
180
+ tracking = true;
181
+ };
160
182
 
161
- function createBrowserHistory(opts) {
162
- const getHref = opts?.getHref ?? (() => `${window.location.pathname}${window.location.search}${window.location.hash}`);
163
- const createHref = opts?.createHref ?? (path => path);
164
- const getLocation = () => parseLocation(getHref(), window.history.state);
165
- return createHistory({
166
- getLocation,
167
- subscriber: onUpdate => {
168
- window.addEventListener(pushStateEvent, onUpdate);
169
- window.addEventListener(popStateEvent, onUpdate);
170
- var pushState = window.history.pushState;
171
- window.history.pushState = function () {
172
- let res = pushState.apply(history, arguments);
173
- onUpdate();
174
- return res;
175
- };
176
- var replaceState = window.history.replaceState;
177
- window.history.replaceState = function () {
178
- let res = replaceState.apply(history, arguments);
179
- onUpdate();
180
- return res;
181
- };
182
- return () => {
183
- window.history.pushState = pushState;
184
- window.history.replaceState = replaceState;
185
- window.removeEventListener(pushStateEvent, onUpdate);
186
- window.removeEventListener(popStateEvent, onUpdate);
187
- };
188
- },
189
- pushState: (path, state) => {
190
- window.history.pushState(state, '', createHref(path));
191
- },
192
- replaceState: (path, state) => {
193
- window.history.replaceState(state, '', createHref(path));
194
- },
195
- back: () => window.history.back(),
196
- forward: () => window.history.forward(),
197
- go: n => window.history.go(n),
198
- createHref: path => createHref(path)
199
- });
200
- }
201
- function createHashHistory() {
202
- return createBrowserHistory({
203
- getHref: () => window.location.hash.substring(1),
204
- createHref: path => `#${path}`
205
- });
206
- }
207
- function createMemoryHistory(opts = {
208
- initialEntries: ['/']
209
- }) {
210
- const entries = opts.initialEntries;
211
- let index = opts.initialIndex ?? entries.length - 1;
212
- let currentState = {
213
- key: createRandomKey()
214
- };
215
- const getLocation = () => parseLocation(entries[index], currentState);
216
- return createHistory({
217
- getLocation,
218
- subscriber: false,
219
- pushState: (path, state) => {
220
- currentState = state;
221
- entries.push(path);
222
- index++;
223
- },
224
- replaceState: (path, state) => {
225
- currentState = state;
226
- entries[index] = path;
227
- },
228
- back: () => {
229
- index--;
230
- },
231
- forward: () => {
232
- index = Math.min(index + 1, entries.length - 1);
233
- },
234
- go: n => window.history.go(n),
235
- createHref: path => path
183
+ // This function flushes the next update to the browser history
184
+ const flush = () => {
185
+ // Do not notify subscribers about this push/replace call
186
+ untrack(() => {
187
+ if (!next) return;
188
+ window.history[next.isPush ? 'pushState' : 'replaceState'](next.state, '', next.href);
189
+ // Reset the nextIsPush flag and clear the scheduled update
190
+ next = undefined;
191
+ scheduled = undefined;
236
192
  });
237
- }
238
- function parseLocation(href, state) {
239
- let hashIndex = href.indexOf('#');
240
- let searchIndex = href.indexOf('?');
241
- return {
242
- href,
243
- pathname: href.substring(0, hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : href.length),
244
- hash: hashIndex > -1 ? href.substring(hashIndex) : '',
245
- search: searchIndex > -1 ? href.slice(searchIndex, hashIndex === -1 ? undefined : hashIndex) : '',
246
- state: state || {}
247
- };
248
- }
193
+ };
249
194
 
250
- // Thanks co-pilot!
251
- function createRandomKey() {
252
- return (Math.random() + 1).toString(36).substring(7);
253
- }
195
+ // This function queues up a call to update the browser history
196
+ const queueHistoryAction = (type, path, state, onUpdate) => {
197
+ const href = createHref(path);
254
198
 
255
- // type Compute<T> = { [K in keyof T]: T[K] } | never
256
-
257
- // type AllKeys<T> = T extends any ? keyof T : never
258
-
259
- // export type MergeUnion<T, Keys extends keyof T = keyof T> = Compute<
260
- // {
261
- // [K in Keys]: T[Keys]
262
- // } & {
263
- // [K in AllKeys<T>]?: T extends any
264
- // ? K extends keyof T
265
- // ? T[K]
266
- // : never
267
- // : never
268
- // }
269
- // >
270
- function last(arr) {
271
- return arr[arr.length - 1];
272
- }
273
- function isFunction(d) {
274
- return typeof d === 'function';
275
- }
276
- function functionalUpdate(updater, previous) {
277
- if (isFunction(updater)) {
278
- return updater(previous);
199
+ // Update the location in memory
200
+ currentLocation = parseLocation(href, state);
201
+
202
+ // Keep track of the next location we need to flush to the URL
203
+ next = {
204
+ href,
205
+ state,
206
+ isPush: next?.isPush || type === 'push'
207
+ };
208
+ // Notify subscribers
209
+ onUpdate();
210
+ if (!scheduled) {
211
+ // Schedule an update to the browser history
212
+ scheduled = Promise.resolve().then(() => flush());
279
213
  }
280
- return updater;
281
- }
282
- function pick(parent, keys) {
283
- return keys.reduce((obj, key) => {
284
- obj[key] = parent[key];
285
- return obj;
286
- }, {});
287
- }
214
+ };
215
+ return createHistory({
216
+ getLocation,
217
+ subscriber: onUpdate => {
218
+ window.addEventListener(pushStateEvent, () => {
219
+ currentLocation = parseLocation(getHref(), window.history.state);
220
+ onUpdate();
221
+ });
222
+ window.addEventListener(popStateEvent, () => {
223
+ currentLocation = parseLocation(getHref(), window.history.state);
224
+ onUpdate();
225
+ });
226
+ var pushState = window.history.pushState;
227
+ window.history.pushState = function () {
228
+ let res = pushState.apply(history, arguments);
229
+ if (tracking) onUpdate();
230
+ return res;
231
+ };
232
+ var replaceState = window.history.replaceState;
233
+ window.history.replaceState = function () {
234
+ let res = replaceState.apply(history, arguments);
235
+ if (tracking) onUpdate();
236
+ return res;
237
+ };
238
+ return () => {
239
+ window.history.pushState = pushState;
240
+ window.history.replaceState = replaceState;
241
+ window.removeEventListener(pushStateEvent, onUpdate);
242
+ window.removeEventListener(popStateEvent, onUpdate);
243
+ };
244
+ },
245
+ pushState: (path, state, onUpdate) => queueHistoryAction('push', path, state, onUpdate),
246
+ replaceState: (path, state, onUpdate) => queueHistoryAction('replace', path, state, onUpdate),
247
+ back: () => window.history.back(),
248
+ forward: () => window.history.forward(),
249
+ go: n => window.history.go(n),
250
+ createHref: path => createHref(path),
251
+ flush
252
+ });
253
+ }
254
+ function createHashHistory() {
255
+ return createBrowserHistory({
256
+ getHref: () => window.location.hash.substring(1),
257
+ createHref: path => `#${path}`
258
+ });
259
+ }
260
+ function createMemoryHistory(opts = {
261
+ initialEntries: ['/']
262
+ }) {
263
+ const entries = opts.initialEntries;
264
+ let index = opts.initialIndex ?? entries.length - 1;
265
+ let currentState = {
266
+ key: createRandomKey()
267
+ };
268
+ const getLocation = () => parseLocation(entries[index], currentState);
269
+ return createHistory({
270
+ getLocation,
271
+ subscriber: false,
272
+ pushState: (path, state) => {
273
+ currentState = state;
274
+ entries.push(path);
275
+ index++;
276
+ },
277
+ replaceState: (path, state) => {
278
+ currentState = state;
279
+ entries[index] = path;
280
+ },
281
+ back: () => {
282
+ index--;
283
+ },
284
+ forward: () => {
285
+ index = Math.min(index + 1, entries.length - 1);
286
+ },
287
+ go: n => window.history.go(n),
288
+ createHref: path => path
289
+ });
290
+ }
291
+ function parseLocation(href, state) {
292
+ let hashIndex = href.indexOf('#');
293
+ let searchIndex = href.indexOf('?');
294
+ return {
295
+ href,
296
+ pathname: href.substring(0, hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : href.length),
297
+ hash: hashIndex > -1 ? href.substring(hashIndex) : '',
298
+ search: searchIndex > -1 ? href.slice(searchIndex, hashIndex === -1 ? undefined : hashIndex) : '',
299
+ state: state || {}
300
+ };
301
+ }
288
302
 
289
- /**
290
- * This function returns `a` if `b` is deeply equal.
291
- * If not, it will replace any deeply equal children of `b` with those of `a`.
292
- * This can be used for structural sharing between immutable JSON values for example.
293
- * Do not use this with signals
294
- */
295
- function replaceEqualDeep(prev, _next) {
296
- if (prev === _next) {
297
- return prev;
303
+ // Thanks co-pilot!
304
+ function createRandomKey() {
305
+ return (Math.random() + 1).toString(36).substring(7);
306
+ }
307
+
308
+ var prefix = 'Invariant failed';
309
+ function invariant(condition, message) {
310
+ if (condition) {
311
+ return;
298
312
  }
299
- const next = _next;
300
- const array = Array.isArray(prev) && Array.isArray(next);
301
- if (array || isPlainObject(prev) && isPlainObject(next)) {
302
- const prevSize = array ? prev.length : Object.keys(prev).length;
303
- const nextItems = array ? next : Object.keys(next);
304
- const nextSize = nextItems.length;
305
- const copy = array ? [] : {};
306
- let equalItems = 0;
307
- for (let i = 0; i < nextSize; i++) {
308
- const key = array ? i : nextItems[i];
309
- copy[key] = replaceEqualDeep(prev[key], next[key]);
310
- if (copy[key] === prev[key]) {
311
- equalItems++;
312
- }
313
- }
314
- return prevSize === nextSize && equalItems === prevSize ? prev : copy;
313
+ var provided = typeof message === 'function' ? message() : message;
314
+ var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
315
+ throw new Error(value);
316
+ }
317
+
318
+ function warning(condition, message) {
319
+ {
320
+ if (condition) {
321
+ return;
315
322
  }
316
- return next;
317
- }
318
323
 
319
- // Copied from: https://github.com/jonschlinkert/is-plain-object
320
- function isPlainObject(o) {
321
- if (!hasObjectPrototype(o)) {
322
- return false;
323
- }
324
+ var text = "Warning: " + message;
324
325
 
325
- // If has modified constructor
326
- const ctor = o.constructor;
327
- if (typeof ctor === 'undefined') {
328
- return true;
326
+ if (typeof console !== 'undefined') {
327
+ console.warn(text);
329
328
  }
330
329
 
331
- // If has modified prototype
332
- const prot = ctor.prototype;
333
- if (!hasObjectPrototype(prot)) {
334
- return false;
330
+ try {
331
+ throw Error(text);
332
+ } catch (x) {}
333
+ }
334
+ }
335
+
336
+ // export type Expand<T> = T
337
+
338
+ // type Compute<T> = { [K in keyof T]: T[K] } | never
339
+
340
+ // type AllKeys<T> = T extends any ? keyof T : never
341
+
342
+ // export type MergeUnion<T, Keys extends keyof T = keyof T> = Compute<
343
+ // {
344
+ // [K in Keys]: T[Keys]
345
+ // } & {
346
+ // [K in AllKeys<T>]?: T extends any
347
+ // ? K extends keyof T
348
+ // ? T[K]
349
+ // : never
350
+ // : never
351
+ // }
352
+ // >
353
+ function last(arr) {
354
+ return arr[arr.length - 1];
355
+ }
356
+ function isFunction(d) {
357
+ return typeof d === 'function';
358
+ }
359
+ function functionalUpdate(updater, previous) {
360
+ if (isFunction(updater)) {
361
+ return updater(previous);
362
+ }
363
+ return updater;
364
+ }
365
+ function pick(parent, keys) {
366
+ return keys.reduce((obj, key) => {
367
+ obj[key] = parent[key];
368
+ return obj;
369
+ }, {});
370
+ }
371
+
372
+ /**
373
+ * This function returns `a` if `b` is deeply equal.
374
+ * If not, it will replace any deeply equal children of `b` with those of `a`.
375
+ * This can be used for structural sharing between immutable JSON values for example.
376
+ * Do not use this with signals
377
+ */
378
+ function replaceEqualDeep(prev, _next) {
379
+ if (prev === _next) {
380
+ return prev;
381
+ }
382
+ const next = _next;
383
+ const array = Array.isArray(prev) && Array.isArray(next);
384
+ if (array || isPlainObject(prev) && isPlainObject(next)) {
385
+ const prevSize = array ? prev.length : Object.keys(prev).length;
386
+ const nextItems = array ? next : Object.keys(next);
387
+ const nextSize = nextItems.length;
388
+ const copy = array ? [] : {};
389
+ let equalItems = 0;
390
+ for (let i = 0; i < nextSize; i++) {
391
+ const key = array ? i : nextItems[i];
392
+ copy[key] = replaceEqualDeep(prev[key], next[key]);
393
+ if (copy[key] === prev[key]) {
394
+ equalItems++;
395
+ }
335
396
  }
397
+ return prevSize === nextSize && equalItems === prevSize ? prev : copy;
398
+ }
399
+ return next;
400
+ }
336
401
 
337
- // If constructor does not have an Object-specific method
338
- if (!prot.hasOwnProperty('isPrototypeOf')) {
339
- return false;
340
- }
402
+ // Copied from: https://github.com/jonschlinkert/is-plain-object
403
+ function isPlainObject(o) {
404
+ if (!hasObjectPrototype(o)) {
405
+ return false;
406
+ }
341
407
 
342
- // Most likely a plain Object
408
+ // If has modified constructor
409
+ const ctor = o.constructor;
410
+ if (typeof ctor === 'undefined') {
343
411
  return true;
344
412
  }
345
- function hasObjectPrototype(o) {
346
- return Object.prototype.toString.call(o) === '[object Object]';
347
- }
348
- function partialDeepEqual(a, b) {
349
- if (a === b) {
350
- return true;
351
- }
352
- if (typeof a !== typeof b) {
353
- return false;
354
- }
355
- if (isPlainObject(a) && isPlainObject(b)) {
356
- return !Object.keys(b).some(key => !partialDeepEqual(a[key], b[key]));
357
- }
358
- if (Array.isArray(a) && Array.isArray(b)) {
359
- return a.length === b.length && a.every((item, index) => partialDeepEqual(item, b[index]));
360
- }
413
+
414
+ // If has modified prototype
415
+ const prot = ctor.prototype;
416
+ if (!hasObjectPrototype(prot)) {
361
417
  return false;
362
418
  }
363
419
 
364
- function joinPaths(paths) {
365
- return cleanPath(paths.filter(Boolean).join('/'));
420
+ // If constructor does not have an Object-specific method
421
+ if (!prot.hasOwnProperty('isPrototypeOf')) {
422
+ return false;
366
423
  }
367
- function cleanPath(path) {
368
- // remove double slashes
369
- return path.replace(/\/{2,}/g, '/');
424
+
425
+ // Most likely a plain Object
426
+ return true;
427
+ }
428
+ function hasObjectPrototype(o) {
429
+ return Object.prototype.toString.call(o) === '[object Object]';
430
+ }
431
+ function partialDeepEqual(a, b) {
432
+ if (a === b) {
433
+ return true;
370
434
  }
371
- function trimPathLeft(path) {
372
- return path === '/' ? path : path.replace(/^\/{1,}/, '');
435
+ if (typeof a !== typeof b) {
436
+ return false;
373
437
  }
374
- function trimPathRight(path) {
375
- return path === '/' ? path : path.replace(/\/{1,}$/, '');
438
+ if (isPlainObject(a) && isPlainObject(b)) {
439
+ return !Object.keys(b).some(key => !partialDeepEqual(a[key], b[key]));
376
440
  }
377
- function trimPath(path) {
378
- return trimPathRight(trimPathLeft(path));
441
+ if (Array.isArray(a) && Array.isArray(b)) {
442
+ return a.length === b.length && a.every((item, index) => partialDeepEqual(item, b[index]));
379
443
  }
380
- function resolvePath(basepath, base, to) {
381
- base = base.replace(new RegExp(`^${basepath}`), '/');
382
- to = to.replace(new RegExp(`^${basepath}`), '/');
383
- let baseSegments = parsePathname(base);
384
- const toSegments = parsePathname(to);
385
- toSegments.forEach((toSegment, index) => {
386
- if (toSegment.value === '/') {
387
- if (!index) {
388
- // Leading slash
389
- baseSegments = [toSegment];
390
- } else if (index === toSegments.length - 1) {
391
- // Trailing Slash
392
- baseSegments.push(toSegment);
393
- } else ;
394
- } else if (toSegment.value === '..') {
395
- // Extra trailing slash? pop it off
396
- if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
397
- baseSegments.pop();
398
- }
399
- baseSegments.pop();
400
- } else if (toSegment.value === '.') {
401
- return;
402
- } else {
444
+ return false;
445
+ }
446
+
447
+ function joinPaths(paths) {
448
+ return cleanPath(paths.filter(Boolean).join('/'));
449
+ }
450
+ function cleanPath(path) {
451
+ // remove double slashes
452
+ return path.replace(/\/{2,}/g, '/');
453
+ }
454
+ function trimPathLeft(path) {
455
+ return path === '/' ? path : path.replace(/^\/{1,}/, '');
456
+ }
457
+ function trimPathRight(path) {
458
+ return path === '/' ? path : path.replace(/\/{1,}$/, '');
459
+ }
460
+ function trimPath(path) {
461
+ return trimPathRight(trimPathLeft(path));
462
+ }
463
+ function resolvePath(basepath, base, to) {
464
+ base = base.replace(new RegExp(`^${basepath}`), '/');
465
+ to = to.replace(new RegExp(`^${basepath}`), '/');
466
+ let baseSegments = parsePathname(base);
467
+ const toSegments = parsePathname(to);
468
+ toSegments.forEach((toSegment, index) => {
469
+ if (toSegment.value === '/') {
470
+ if (!index) {
471
+ // Leading slash
472
+ baseSegments = [toSegment];
473
+ } else if (index === toSegments.length - 1) {
474
+ // Trailing Slash
403
475
  baseSegments.push(toSegment);
476
+ } else ;
477
+ } else if (toSegment.value === '..') {
478
+ // Extra trailing slash? pop it off
479
+ if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
480
+ baseSegments.pop();
404
481
  }
482
+ baseSegments.pop();
483
+ } else if (toSegment.value === '.') {
484
+ return;
485
+ } else {
486
+ baseSegments.push(toSegment);
487
+ }
488
+ });
489
+ const joined = joinPaths([basepath, ...baseSegments.map(d => d.value)]);
490
+ return cleanPath(joined);
491
+ }
492
+ function parsePathname(pathname) {
493
+ if (!pathname) {
494
+ return [];
495
+ }
496
+ pathname = cleanPath(pathname);
497
+ const segments = [];
498
+ if (pathname.slice(0, 1) === '/') {
499
+ pathname = pathname.substring(1);
500
+ segments.push({
501
+ type: 'pathname',
502
+ value: '/'
405
503
  });
406
- const joined = joinPaths([basepath, ...baseSegments.map(d => d.value)]);
407
- return cleanPath(joined);
408
504
  }
409
- function parsePathname(pathname) {
410
- if (!pathname) {
411
- return [];
412
- }
413
- pathname = cleanPath(pathname);
414
- const segments = [];
415
- if (pathname.slice(0, 1) === '/') {
416
- pathname = pathname.substring(1);
417
- segments.push({
418
- type: 'pathname',
419
- value: '/'
420
- });
421
- }
422
- if (!pathname) {
423
- return segments;
424
- }
505
+ if (!pathname) {
506
+ return segments;
507
+ }
425
508
 
426
- // Remove empty segments and '.' segments
427
- const split = pathname.split('/').filter(Boolean);
428
- segments.push(...split.map(part => {
429
- if (part === '$' || part === '*') {
430
- return {
431
- type: 'wildcard',
432
- value: part
433
- };
434
- }
435
- if (part.charAt(0) === '$') {
436
- return {
437
- type: 'param',
438
- value: part
439
- };
440
- }
509
+ // Remove empty segments and '.' segments
510
+ const split = pathname.split('/').filter(Boolean);
511
+ segments.push(...split.map(part => {
512
+ if (part === '$' || part === '*') {
441
513
  return {
442
- type: 'pathname',
514
+ type: 'wildcard',
443
515
  value: part
444
516
  };
445
- }));
446
- if (pathname.slice(-1) === '/') {
447
- pathname = pathname.substring(1);
448
- segments.push({
449
- type: 'pathname',
450
- value: '/'
451
- });
452
517
  }
453
- return segments;
454
- }
455
- function interpolatePath(path, params, leaveWildcards = false) {
456
- const interpolatedPathSegments = parsePathname(path);
457
- return joinPaths(interpolatedPathSegments.map(segment => {
458
- if (segment.type === 'wildcard') {
459
- const value = params[segment.value];
460
- if (leaveWildcards) return `${segment.value}${value ?? ''}`;
461
- return value;
462
- }
463
- if (segment.type === 'param') {
464
- return params[segment.value.substring(1)] ?? '';
465
- }
466
- return segment.value;
467
- }));
468
- }
469
- function matchPathname(basepath, currentPathname, matchLocation) {
470
- const pathParams = matchByPath(basepath, currentPathname, matchLocation);
471
- // const searchMatched = matchBySearch(location.search, matchLocation)
472
-
473
- if (matchLocation.to && !pathParams) {
474
- return;
518
+ if (part.charAt(0) === '$') {
519
+ return {
520
+ type: 'param',
521
+ value: part
522
+ };
475
523
  }
476
- return pathParams ?? {};
524
+ return {
525
+ type: 'pathname',
526
+ value: part
527
+ };
528
+ }));
529
+ if (pathname.slice(-1) === '/') {
530
+ pathname = pathname.substring(1);
531
+ segments.push({
532
+ type: 'pathname',
533
+ value: '/'
534
+ });
477
535
  }
478
- function matchByPath(basepath, from, matchLocation) {
479
- // Remove the base path from the pathname
480
- from = basepath != '/' ? from.substring(basepath.length) : from;
481
- // Default to to $ (wildcard)
482
- const to = `${matchLocation.to ?? '$'}`;
483
- // Parse the from and to
484
- const baseSegments = parsePathname(from);
485
- const routeSegments = parsePathname(to);
486
- if (!from.startsWith('/')) {
487
- baseSegments.unshift({
488
- type: 'pathname',
489
- value: '/'
490
- });
536
+ return segments;
537
+ }
538
+ function interpolatePath(path, params, leaveWildcards = false) {
539
+ const interpolatedPathSegments = parsePathname(path);
540
+ return joinPaths(interpolatedPathSegments.map(segment => {
541
+ if (segment.type === 'wildcard') {
542
+ const value = params[segment.value];
543
+ if (leaveWildcards) return `${segment.value}${value ?? ''}`;
544
+ return value;
491
545
  }
492
- if (!to.startsWith('/')) {
493
- routeSegments.unshift({
494
- type: 'pathname',
495
- value: '/'
496
- });
546
+ if (segment.type === 'param') {
547
+ return params[segment.value.substring(1)] ?? '';
497
548
  }
498
- const params = {};
499
- let isMatch = (() => {
500
- for (let i = 0; i < Math.max(baseSegments.length, routeSegments.length); i++) {
501
- const baseSegment = baseSegments[i];
502
- const routeSegment = routeSegments[i];
503
- const isLastBaseSegment = i >= baseSegments.length - 1;
504
- const isLastRouteSegment = i >= routeSegments.length - 1;
505
- if (routeSegment) {
506
- if (routeSegment.type === 'wildcard') {
507
- if (baseSegment?.value) {
508
- params['*'] = joinPaths(baseSegments.slice(i).map(d => d.value));
509
- return true;
510
- }
511
- return false;
549
+ return segment.value;
550
+ }));
551
+ }
552
+ function matchPathname(basepath, currentPathname, matchLocation) {
553
+ const pathParams = matchByPath(basepath, currentPathname, matchLocation);
554
+ // const searchMatched = matchBySearch(location.search, matchLocation)
555
+
556
+ if (matchLocation.to && !pathParams) {
557
+ return;
558
+ }
559
+ return pathParams ?? {};
560
+ }
561
+ function matchByPath(basepath, from, matchLocation) {
562
+ // Remove the base path from the pathname
563
+ from = basepath != '/' ? from.substring(basepath.length) : from;
564
+ // Default to to $ (wildcard)
565
+ const to = `${matchLocation.to ?? '$'}`;
566
+ // Parse the from and to
567
+ const baseSegments = parsePathname(from);
568
+ const routeSegments = parsePathname(to);
569
+ if (!from.startsWith('/')) {
570
+ baseSegments.unshift({
571
+ type: 'pathname',
572
+ value: '/'
573
+ });
574
+ }
575
+ if (!to.startsWith('/')) {
576
+ routeSegments.unshift({
577
+ type: 'pathname',
578
+ value: '/'
579
+ });
580
+ }
581
+ const params = {};
582
+ let isMatch = (() => {
583
+ for (let i = 0; i < Math.max(baseSegments.length, routeSegments.length); i++) {
584
+ const baseSegment = baseSegments[i];
585
+ const routeSegment = routeSegments[i];
586
+ const isLastBaseSegment = i >= baseSegments.length - 1;
587
+ const isLastRouteSegment = i >= routeSegments.length - 1;
588
+ if (routeSegment) {
589
+ if (routeSegment.type === 'wildcard') {
590
+ if (baseSegment?.value) {
591
+ params['*'] = joinPaths(baseSegments.slice(i).map(d => d.value));
592
+ return true;
512
593
  }
513
- if (routeSegment.type === 'pathname') {
514
- if (routeSegment.value === '/' && !baseSegment?.value) {
515
- return true;
516
- }
517
- if (baseSegment) {
518
- if (matchLocation.caseSensitive) {
519
- if (routeSegment.value !== baseSegment.value) {
520
- return false;
521
- }
522
- } else if (routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()) {
594
+ return false;
595
+ }
596
+ if (routeSegment.type === 'pathname') {
597
+ if (routeSegment.value === '/' && !baseSegment?.value) {
598
+ return true;
599
+ }
600
+ if (baseSegment) {
601
+ if (matchLocation.caseSensitive) {
602
+ if (routeSegment.value !== baseSegment.value) {
523
603
  return false;
524
604
  }
605
+ } else if (routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()) {
606
+ return false;
525
607
  }
526
608
  }
527
- if (!baseSegment) {
609
+ }
610
+ if (!baseSegment) {
611
+ return false;
612
+ }
613
+ if (routeSegment.type === 'param') {
614
+ if (baseSegment?.value === '/') {
528
615
  return false;
529
616
  }
530
- if (routeSegment.type === 'param') {
531
- if (baseSegment?.value === '/') {
532
- return false;
533
- }
534
- if (baseSegment.value.charAt(0) !== '$') {
535
- params[routeSegment.value.substring(1)] = baseSegment.value;
536
- }
617
+ if (baseSegment.value.charAt(0) !== '$') {
618
+ params[routeSegment.value.substring(1)] = baseSegment.value;
537
619
  }
538
620
  }
539
- if (!isLastBaseSegment && isLastRouteSegment) {
540
- return !!matchLocation.fuzzy;
541
- }
542
621
  }
543
- return true;
544
- })();
545
- return isMatch ? params : undefined;
546
- }
547
-
548
- // @ts-nocheck
549
-
550
- // qss has been slightly modified and inlined here for our use cases (and compression's sake). We've included it as a hard dependency for MIT license attribution.
551
-
552
- function encode(obj, pfx) {
553
- var k,
554
- i,
555
- tmp,
556
- str = '';
557
- for (k in obj) {
558
- if ((tmp = obj[k]) !== void 0) {
559
- if (Array.isArray(tmp)) {
560
- for (i = 0; i < tmp.length; i++) {
561
- str && (str += '&');
562
- str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp[i]);
563
- }
564
- } else {
622
+ if (!isLastBaseSegment && isLastRouteSegment) {
623
+ return !!matchLocation.fuzzy;
624
+ }
625
+ }
626
+ return true;
627
+ })();
628
+ return isMatch ? params : undefined;
629
+ }
630
+
631
+ // @ts-nocheck
632
+
633
+ // qss has been slightly modified and inlined here for our use cases (and compression's sake). We've included it as a hard dependency for MIT license attribution.
634
+
635
+ function encode(obj, pfx) {
636
+ var k,
637
+ i,
638
+ tmp,
639
+ str = '';
640
+ for (k in obj) {
641
+ if ((tmp = obj[k]) !== void 0) {
642
+ if (Array.isArray(tmp)) {
643
+ for (i = 0; i < tmp.length; i++) {
565
644
  str && (str += '&');
566
- str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp);
645
+ str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp[i]);
567
646
  }
647
+ } else {
648
+ str && (str += '&');
649
+ str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp);
568
650
  }
569
651
  }
570
- return (pfx || '') + str;
571
- }
572
- function toValue(mix) {
573
- if (!mix) return '';
574
- var str = decodeURIComponent(mix);
575
- if (str === 'false') return false;
576
- if (str === 'true') return true;
577
- return +str * 0 === 0 && +str + '' === str ? +str : str;
578
652
  }
579
- function decode(str) {
580
- var tmp,
581
- k,
582
- out = {},
583
- arr = str.split('&');
584
- while (tmp = arr.shift()) {
585
- tmp = tmp.split('=');
586
- k = tmp.shift();
587
- if (out[k] !== void 0) {
588
- out[k] = [].concat(out[k], toValue(tmp.shift()));
589
- } else {
590
- out[k] = toValue(tmp.shift());
591
- }
653
+ return (pfx || '') + str;
654
+ }
655
+ function toValue(mix) {
656
+ if (!mix) return '';
657
+ var str = decodeURIComponent(mix);
658
+ if (str === 'false') return false;
659
+ if (str === 'true') return true;
660
+ return +str * 0 === 0 && +str + '' === str ? +str : str;
661
+ }
662
+ function decode(str) {
663
+ var tmp,
664
+ k,
665
+ out = {},
666
+ arr = str.split('&');
667
+ while (tmp = arr.shift()) {
668
+ tmp = tmp.split('=');
669
+ k = tmp.shift();
670
+ if (out[k] !== void 0) {
671
+ out[k] = [].concat(out[k], toValue(tmp.shift()));
672
+ } else {
673
+ out[k] = toValue(tmp.shift());
592
674
  }
593
- return out;
594
675
  }
676
+ return out;
677
+ }
595
678
 
596
- const rootRouteId = '__root__';
679
+ const rootRouteId = '__root__';
597
680
 
598
- // The parse type here allows a zod schema to be passed directly to the validator
681
+ // The parse type here allows a zod schema to be passed directly to the validator
599
682
 
600
- // T extends Record<PropertyKey, infer U>
601
- // ? {
602
- // [K in keyof T]: UseLoaderResultPromise<T[K]>
603
- // }
604
- // : UseLoaderResultPromise<T>
683
+ // T extends Record<PropertyKey, infer U>
684
+ // ? {
685
+ // [K in keyof T]: UseLoaderResultPromise<T[K]>
686
+ // }
687
+ // : UseLoaderResultPromise<T>
605
688
 
606
- // export type UseLoaderResultPromise<T> = T extends Promise<infer U>
607
- // ? StreamedPromise<U>
608
- // : T
609
- class Route {
610
- // Set up in this.init()
689
+ // export type UseLoaderResultPromise<T> = T extends Promise<infer U>
690
+ // ? StreamedPromise<U>
691
+ // : T
692
+ class Route {
693
+ // Set up in this.init()
611
694
 
612
- // customId!: TCustomId
695
+ // customId!: TCustomId
613
696
 
614
- // Optional
697
+ // Optional
615
698
 
616
- constructor(options) {
617
- this.options = options || {};
618
- this.isRoot = !options?.getParentRoute;
619
- Route.__onInit(this);
699
+ constructor(options) {
700
+ this.options = options || {};
701
+ this.isRoot = !options?.getParentRoute;
702
+ Route.__onInit(this);
703
+ }
704
+ init = opts => {
705
+ this.originalIndex = opts.originalIndex;
706
+ this.router = opts.router;
707
+ const options = this.options;
708
+ const isRoot = !options?.path && !options?.id;
709
+ this.parentRoute = this.options?.getParentRoute?.();
710
+ if (isRoot) {
711
+ this.path = rootRouteId;
712
+ } else {
713
+ invariant(this.parentRoute, `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`);
620
714
  }
621
- init = opts => {
622
- this.originalIndex = opts.originalIndex;
623
- this.router = opts.router;
624
- const options = this.options;
625
- const isRoot = !options?.path && !options?.id;
626
- this.parentRoute = this.options?.getParentRoute?.();
627
- if (isRoot) {
628
- this.path = rootRouteId;
629
- } else {
630
- invariant(this.parentRoute, `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`);
631
- }
632
- let path = isRoot ? rootRouteId : options.path;
715
+ let path = isRoot ? rootRouteId : options.path;
633
716
 
634
- // If the path is anything other than an index path, trim it up
635
- if (path && path !== '/') {
636
- path = trimPath(path);
637
- }
638
- const customId = options?.id || path;
717
+ // If the path is anything other than an index path, trim it up
718
+ if (path && path !== '/') {
719
+ path = trimPath(path);
720
+ }
721
+ const customId = options?.id || path;
639
722
 
640
- // Strip the parentId prefix from the first level of children
641
- let id = isRoot ? rootRouteId : joinPaths([this.parentRoute.id === rootRouteId ? '' : this.parentRoute.id, customId]);
642
- if (path === rootRouteId) {
643
- path = '/';
644
- }
645
- if (id !== rootRouteId) {
646
- id = joinPaths(['/', id]);
647
- }
648
- const fullPath = id === rootRouteId ? '/' : joinPaths([this.parentRoute.fullPath, path]);
649
- this.path = path;
650
- this.id = id;
651
- // this.customId = customId as TCustomId
652
- this.fullPath = fullPath;
653
- this.to = fullPath;
654
- };
655
- addChildren = children => {
656
- this.children = children;
657
- return this;
658
- };
659
- update = options => {
660
- Object.assign(this.options, options);
661
- return this;
662
- };
663
- static __onInit = route => {
664
- // This is a dummy static method that should get
665
- // replaced by a framework specific implementation if necessary
666
- };
667
- }
668
- class RouterContext {
669
- constructor() {}
670
- createRootRoute = options => {
671
- return new RootRoute(options);
672
- };
673
- }
674
- class RootRoute extends Route {
675
- constructor(options) {
676
- super(options);
723
+ // Strip the parentId prefix from the first level of children
724
+ let id = isRoot ? rootRouteId : joinPaths([this.parentRoute.id === rootRouteId ? '' : this.parentRoute.id, customId]);
725
+ if (path === rootRouteId) {
726
+ path = '/';
677
727
  }
728
+ if (id !== rootRouteId) {
729
+ id = joinPaths(['/', id]);
730
+ }
731
+ const fullPath = id === rootRouteId ? '/' : joinPaths([this.parentRoute.fullPath, path]);
732
+ this.path = path;
733
+ this.id = id;
734
+ // this.customId = customId as TCustomId
735
+ this.fullPath = fullPath;
736
+ this.to = fullPath;
737
+ };
738
+ addChildren = children => {
739
+ this.children = children;
740
+ return this;
741
+ };
742
+ update = options => {
743
+ Object.assign(this.options, options);
744
+ return this;
745
+ };
746
+ static __onInit = route => {
747
+ // This is a dummy static method that should get
748
+ // replaced by a framework specific implementation if necessary
749
+ };
750
+ }
751
+ class RouterContext {
752
+ constructor() {}
753
+ createRootRoute = options => {
754
+ return new RootRoute(options);
755
+ };
756
+ }
757
+ class RootRoute extends Route {
758
+ constructor(options) {
759
+ super(options);
678
760
  }
679
- function createRouteMask(opts) {
680
- return opts;
761
+ }
762
+ function createRouteMask(opts) {
763
+ return opts;
764
+ }
765
+
766
+ class FileRoute {
767
+ constructor(path) {
768
+ this.path = path;
681
769
  }
682
-
683
- class FileRoute {
684
- constructor(path) {
685
- this.path = path;
686
- }
687
- createRoute = options => {
688
- const route = new Route(options);
689
- route.isRoot = false;
690
- return route;
691
- };
770
+ createRoute = options => {
771
+ const route = new Route(options);
772
+ route.isRoot = false;
773
+ return route;
774
+ };
775
+ }
776
+
777
+ /**
778
+ * @tanstack/store/src/index.ts
779
+ *
780
+ * Copyright (c) TanStack
781
+ *
782
+ * This source code is licensed under the MIT license found in the
783
+ * LICENSE.md file in the root directory of this source tree.
784
+ *
785
+ * @license MIT
786
+ */
787
+ class Store {
788
+ listeners = new Set();
789
+ _batching = false;
790
+ _flushing = 0;
791
+ _nextPriority = null;
792
+ constructor(initialState, options) {
793
+ this.state = initialState;
794
+ this.options = options;
692
795
  }
693
-
694
- /**
695
- * @tanstack/store/src/index.ts
696
- *
697
- * Copyright (c) TanStack
698
- *
699
- * This source code is licensed under the MIT license found in the
700
- * LICENSE.md file in the root directory of this source tree.
701
- *
702
- * @license MIT
703
- */
704
- class Store {
705
- listeners = new Set();
706
- _batching = false;
707
- _flushing = 0;
708
- _nextPriority = null;
709
- constructor(initialState, options) {
710
- this.state = initialState;
711
- this.options = options;
712
- }
713
- subscribe = listener => {
714
- this.listeners.add(listener);
715
- const unsub = this.options?.onSubscribe?.(listener, this);
716
- return () => {
717
- this.listeners.delete(listener);
718
- unsub?.();
719
- };
796
+ subscribe = listener => {
797
+ this.listeners.add(listener);
798
+ const unsub = this.options?.onSubscribe?.(listener, this);
799
+ return () => {
800
+ this.listeners.delete(listener);
801
+ unsub?.();
720
802
  };
721
- setState = (updater, opts) => {
722
- const previous = this.state;
723
- this.state = this.options?.updateFn ? this.options.updateFn(previous)(updater) : updater(previous);
724
- const priority = opts?.priority ?? this.options?.defaultPriority ?? 'high';
725
- if (this._nextPriority === null) {
726
- this._nextPriority = priority;
727
- } else if (this._nextPriority === 'high') {
728
- this._nextPriority = priority;
729
- } else {
730
- this._nextPriority = this.options?.defaultPriority ?? 'high';
731
- }
803
+ };
804
+ setState = (updater, opts) => {
805
+ const previous = this.state;
806
+ this.state = this.options?.updateFn ? this.options.updateFn(previous)(updater) : updater(previous);
807
+ const priority = opts?.priority ?? this.options?.defaultPriority ?? 'high';
808
+ if (this._nextPriority === null) {
809
+ this._nextPriority = priority;
810
+ } else if (this._nextPriority === 'high') {
811
+ this._nextPriority = priority;
812
+ } else {
813
+ this._nextPriority = this.options?.defaultPriority ?? 'high';
814
+ }
732
815
 
733
- // Always run onUpdate, regardless of batching
734
- this.options?.onUpdate?.({
735
- priority: this._nextPriority
736
- });
816
+ // Always run onUpdate, regardless of batching
817
+ this.options?.onUpdate?.({
818
+ priority: this._nextPriority
819
+ });
737
820
 
738
- // Attempt to flush
739
- this._flush();
740
- };
741
- _flush = () => {
742
- if (this._batching) return;
743
- const flushId = ++this._flushing;
744
- this.listeners.forEach(listener => {
745
- if (this._flushing !== flushId) return;
746
- listener({
747
- priority: this._nextPriority ?? 'high'
748
- });
821
+ // Attempt to flush
822
+ this._flush();
823
+ };
824
+ _flush = () => {
825
+ if (this._batching) return;
826
+ const flushId = ++this._flushing;
827
+ this.listeners.forEach(listener => {
828
+ if (this._flushing !== flushId) return;
829
+ listener({
830
+ priority: this._nextPriority ?? 'high'
749
831
  });
750
- };
751
- batch = cb => {
752
- if (this._batching) return cb();
753
- this._batching = true;
754
- cb();
755
- this._batching = false;
756
- this._flush();
757
- };
758
- }
759
-
760
- const defaultParseSearch = parseSearchWith(JSON.parse);
761
- const defaultStringifySearch = stringifySearchWith(JSON.stringify, JSON.parse);
762
- function parseSearchWith(parser) {
763
- return searchStr => {
764
- if (searchStr.substring(0, 1) === '?') {
765
- searchStr = searchStr.substring(1);
766
- }
767
- let query = decode(searchStr);
832
+ });
833
+ };
834
+ batch = cb => {
835
+ if (this._batching) return cb();
836
+ this._batching = true;
837
+ cb();
838
+ this._batching = false;
839
+ this._flush();
840
+ };
841
+ }
842
+
843
+ const defaultParseSearch = parseSearchWith(JSON.parse);
844
+ const defaultStringifySearch = stringifySearchWith(JSON.stringify, JSON.parse);
845
+ function parseSearchWith(parser) {
846
+ return searchStr => {
847
+ if (searchStr.substring(0, 1) === '?') {
848
+ searchStr = searchStr.substring(1);
849
+ }
850
+ let query = decode(searchStr);
768
851
 
769
- // Try to parse any query params that might be json
770
- for (let key in query) {
771
- const value = query[key];
772
- if (typeof value === 'string') {
773
- try {
774
- query[key] = parser(value);
775
- } catch (err) {
776
- //
777
- }
778
- }
779
- }
780
- return query;
781
- };
782
- }
783
- function stringifySearchWith(stringify, parser) {
784
- function stringifyValue(val) {
785
- if (typeof val === 'object' && val !== null) {
786
- try {
787
- return stringify(val);
788
- } catch (err) {
789
- // silent
790
- }
791
- } else if (typeof val === 'string' && typeof parser === 'function') {
852
+ // Try to parse any query params that might be json
853
+ for (let key in query) {
854
+ const value = query[key];
855
+ if (typeof value === 'string') {
792
856
  try {
793
- // Check if it's a valid parseable string.
794
- // If it is, then stringify it again.
795
- parser(val);
796
- return stringify(val);
857
+ query[key] = parser(value);
797
858
  } catch (err) {
798
- // silent
859
+ //
799
860
  }
800
861
  }
801
- return val;
802
862
  }
803
- return search => {
804
- search = {
805
- ...search
806
- };
807
- if (search) {
808
- Object.keys(search).forEach(key => {
809
- const val = search[key];
810
- if (typeof val === 'undefined' || val === undefined) {
811
- delete search[key];
812
- } else {
813
- search[key] = stringifyValue(val);
814
- }
815
- });
863
+ return query;
864
+ };
865
+ }
866
+ function stringifySearchWith(stringify, parser) {
867
+ function stringifyValue(val) {
868
+ if (typeof val === 'object' && val !== null) {
869
+ try {
870
+ return stringify(val);
871
+ } catch (err) {
872
+ // silent
816
873
  }
817
- const searchStr = encode(search).toString();
818
- return searchStr ? `?${searchStr}` : '';
819
- };
820
- }
821
-
822
- //
823
-
824
- const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
825
- const visibilityChangeEvent = 'visibilitychange';
826
- const focusEvent = 'focus';
827
- const preloadWarning = 'Error preloading route! ☝️';
828
- class Router {
829
- #unsubHistory;
830
- resetNextScroll = false;
831
- tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
832
- // nextTemporaryLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
833
-
834
- constructor(options) {
835
- this.options = {
836
- defaultPreloadDelay: 50,
837
- context: undefined,
838
- ...options,
839
- stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
840
- parseSearch: options?.parseSearch ?? defaultParseSearch
841
- // fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
842
- };
843
-
844
- this.__store = new Store(getInitialRouterState(), {
845
- onUpdate: () => {
846
- const prev = this.state;
847
- const next = this.__store.state;
848
- const matchesByIdChanged = prev.matchesById !== next.matchesById;
849
- let matchesChanged;
850
- let pendingMatchesChanged;
851
- if (!matchesByIdChanged) {
852
- matchesChanged = prev.matchIds.length !== next.matchIds.length || prev.matchIds.some((d, i) => d !== next.matchIds[i]);
853
- pendingMatchesChanged = prev.pendingMatchIds.length !== next.pendingMatchIds.length || prev.pendingMatchIds.some((d, i) => d !== next.pendingMatchIds[i]);
854
- }
855
- if (matchesByIdChanged || matchesChanged) {
856
- next.matches = next.matchIds.map(id => {
857
- return next.matchesById[id];
858
- });
859
- }
860
- if (matchesByIdChanged || pendingMatchesChanged) {
861
- next.pendingMatches = next.pendingMatchIds.map(id => {
862
- return next.matchesById[id];
863
- });
864
- }
865
- if (matchesByIdChanged || matchesChanged || pendingMatchesChanged) {
866
- const hasPendingComponent = next.pendingMatches.some(d => {
867
- const route = this.getRoute(d.routeId);
868
- return !!route?.options.pendingComponent;
869
- });
870
- next.renderedMatchIds = hasPendingComponent ? next.pendingMatchIds : next.matchIds;
871
- next.renderedMatches = next.renderedMatchIds.map(id => {
872
- return next.matchesById[id];
873
- });
874
- }
875
- next.isFetching = [...next.matches, ...next.pendingMatches].some(d => d.isFetching);
876
- this.state = next;
877
- },
878
- defaultPriority: 'low'
879
- });
880
- this.state = this.__store.state;
881
- this.update(options);
882
- const nextLocation = this.buildLocation({
883
- search: true,
884
- params: true,
885
- hash: true,
886
- state: true
887
- });
888
- if (this.state.location.href !== nextLocation.href) {
889
- this.#commitLocation({
890
- ...nextLocation,
891
- replace: true
892
- });
874
+ } else if (typeof val === 'string' && typeof parser === 'function') {
875
+ try {
876
+ // Check if it's a valid parseable string.
877
+ // If it is, then stringify it again.
878
+ parser(val);
879
+ return stringify(val);
880
+ } catch (err) {
881
+ // silent
893
882
  }
894
883
  }
895
- subscribers = new Set();
896
- subscribe = (eventType, fn) => {
897
- const listener = {
898
- eventType,
899
- fn
900
- };
901
- this.subscribers.add(listener);
902
- return () => {
903
- this.subscribers.delete(listener);
904
- };
884
+ return val;
885
+ }
886
+ return search => {
887
+ search = {
888
+ ...search
905
889
  };
906
- #emit = routerEvent => {
907
- this.subscribers.forEach(listener => {
908
- if (listener.eventType === routerEvent.type) {
909
- listener.fn(routerEvent);
890
+ if (search) {
891
+ Object.keys(search).forEach(key => {
892
+ const val = search[key];
893
+ if (typeof val === 'undefined' || val === undefined) {
894
+ delete search[key];
895
+ } else {
896
+ search[key] = stringifyValue(val);
910
897
  }
911
898
  });
899
+ }
900
+ const searchStr = encode(search).toString();
901
+ return searchStr ? `?${searchStr}` : '';
902
+ };
903
+ }
904
+
905
+ //
906
+
907
+ const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
908
+ const visibilityChangeEvent = 'visibilitychange';
909
+ const focusEvent = 'focus';
910
+ const preloadWarning = 'Error preloading route! ☝️';
911
+ class Router {
912
+ #unsubHistory;
913
+ resetNextScroll = false;
914
+ tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
915
+ // nextTemporaryLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
916
+
917
+ constructor(options) {
918
+ this.options = {
919
+ defaultPreloadDelay: 50,
920
+ context: undefined,
921
+ ...options,
922
+ stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
923
+ parseSearch: options?.parseSearch ?? defaultParseSearch
924
+ // fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
912
925
  };
913
- reset = () => {
914
- this.__store.setState(s => Object.assign(s, getInitialRouterState()));
915
- };
916
- mount = () => {
917
- // addEventListener does not exist in React Native, but window does
918
- // In the future, we might need to invert control here for more adapters
919
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
920
- if (typeof window !== 'undefined' && window.addEventListener) {
921
- window.addEventListener(visibilityChangeEvent, this.#onFocus, false);
922
- window.addEventListener(focusEvent, this.#onFocus, false);
923
- }
924
- this.safeLoad();
925
- return () => {
926
- if (typeof window !== 'undefined' && window.removeEventListener) {
927
- window.removeEventListener(visibilityChangeEvent, this.#onFocus);
928
- window.removeEventListener(focusEvent, this.#onFocus);
926
+
927
+ this.__store = new Store(getInitialRouterState(), {
928
+ onUpdate: () => {
929
+ const prev = this.state;
930
+ const next = this.__store.state;
931
+ const matchesByIdChanged = prev.matchesById !== next.matchesById;
932
+ let matchesChanged;
933
+ let pendingMatchesChanged;
934
+ if (!matchesByIdChanged) {
935
+ matchesChanged = prev.matchIds.length !== next.matchIds.length || prev.matchIds.some((d, i) => d !== next.matchIds[i]);
936
+ pendingMatchesChanged = prev.pendingMatchIds.length !== next.pendingMatchIds.length || prev.pendingMatchIds.some((d, i) => d !== next.pendingMatchIds[i]);
929
937
  }
930
- };
931
- };
932
- #onFocus = () => {
933
- if (this.options.reloadOnWindowFocus ?? true) {
934
- this.invalidate({
935
- __fromFocus: true
936
- });
937
- }
938
- };
939
- update = opts => {
940
- this.options = {
941
- ...this.options,
942
- ...opts,
943
- context: {
944
- ...this.options.context,
945
- ...opts?.context
938
+ if (matchesByIdChanged || matchesChanged) {
939
+ next.matches = next.matchIds.map(id => {
940
+ return next.matchesById[id];
941
+ });
946
942
  }
947
- };
948
- if (!this.history || this.options.history && this.options.history !== this.history) {
949
- if (this.#unsubHistory) {
950
- this.#unsubHistory();
943
+ if (matchesByIdChanged || pendingMatchesChanged) {
944
+ next.pendingMatches = next.pendingMatchIds.map(id => {
945
+ return next.matchesById[id];
946
+ });
951
947
  }
952
- this.history = this.options.history ?? (isServer ? createMemoryHistory() : createBrowserHistory());
953
- const parsedLocation = this.#parseLocation();
954
- this.__store.setState(s => ({
955
- ...s,
956
- resolvedLocation: parsedLocation,
957
- location: parsedLocation
958
- }));
959
- this.#unsubHistory = this.history.subscribe(() => {
960
- this.safeLoad({
961
- next: this.#parseLocation(this.state.location)
948
+ if (matchesByIdChanged || matchesChanged || pendingMatchesChanged) {
949
+ const hasPendingComponent = next.pendingMatches.some(d => {
950
+ const route = this.getRoute(d.routeId);
951
+ return !!route?.options.pendingComponent;
962
952
  });
963
- });
953
+ next.renderedMatchIds = hasPendingComponent ? next.pendingMatchIds : next.matchIds;
954
+ next.renderedMatches = next.renderedMatchIds.map(id => {
955
+ return next.matchesById[id];
956
+ });
957
+ }
958
+ next.isFetching = [...next.matches, ...next.pendingMatches].some(d => d.isFetching);
959
+ this.state = next;
960
+ },
961
+ defaultPriority: 'low'
962
+ });
963
+ this.state = this.__store.state;
964
+ this.update(options);
965
+ const nextLocation = this.buildLocation({
966
+ search: true,
967
+ params: true,
968
+ hash: true,
969
+ state: true
970
+ });
971
+ if (this.state.location.href !== nextLocation.href) {
972
+ this.#commitLocation({
973
+ ...nextLocation,
974
+ replace: true
975
+ });
976
+ }
977
+ }
978
+ subscribers = new Set();
979
+ subscribe = (eventType, fn) => {
980
+ const listener = {
981
+ eventType,
982
+ fn
983
+ };
984
+ this.subscribers.add(listener);
985
+ return () => {
986
+ this.subscribers.delete(listener);
987
+ };
988
+ };
989
+ #emit = routerEvent => {
990
+ this.subscribers.forEach(listener => {
991
+ if (listener.eventType === routerEvent.type) {
992
+ listener.fn(routerEvent);
964
993
  }
965
- const {
966
- basepath,
967
- routeTree
968
- } = this.options;
969
- this.basepath = `/${trimPath(basepath ?? '') ?? ''}`;
970
- if (routeTree && routeTree !== this.routeTree) {
971
- this.#processRoutes(routeTree);
994
+ });
995
+ };
996
+ reset = () => {
997
+ this.__store.setState(s => Object.assign(s, getInitialRouterState()));
998
+ };
999
+ mount = () => {
1000
+ // addEventListener does not exist in React Native, but window does
1001
+ // In the future, we might need to invert control here for more adapters
1002
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1003
+ if (typeof window !== 'undefined' && window.addEventListener) {
1004
+ window.addEventListener(visibilityChangeEvent, this.#onFocus, false);
1005
+ window.addEventListener(focusEvent, this.#onFocus, false);
1006
+ }
1007
+ this.safeLoad();
1008
+ return () => {
1009
+ if (typeof window !== 'undefined' && window.removeEventListener) {
1010
+ window.removeEventListener(visibilityChangeEvent, this.#onFocus);
1011
+ window.removeEventListener(focusEvent, this.#onFocus);
972
1012
  }
973
- return this;
974
1013
  };
975
- cancelMatches = () => {
976
- this.state.matches.forEach(match => {
977
- this.cancelMatch(match.id);
1014
+ };
1015
+ #onFocus = () => {
1016
+ if (this.options.reloadOnWindowFocus ?? true) {
1017
+ this.invalidate({
1018
+ __fromFocus: true
978
1019
  });
979
- };
980
- cancelMatch = id => {
981
- this.getRouteMatch(id)?.abortController?.abort();
982
- };
983
- safeLoad = async opts => {
984
- try {
985
- return this.load(opts);
986
- } catch (err) {
987
- // Don't do anything
1020
+ }
1021
+ };
1022
+ update = opts => {
1023
+ this.options = {
1024
+ ...this.options,
1025
+ ...opts,
1026
+ context: {
1027
+ ...this.options.context,
1028
+ ...opts?.context
988
1029
  }
989
1030
  };
990
- latestLoadPromise = Promise.resolve();
991
- load = async opts => {
992
- const promise = new Promise(async (resolve, reject) => {
993
- const prevLocation = this.state.resolvedLocation;
994
- const pathDidChange = !!(opts?.next && prevLocation.href !== opts.next.href);
995
- let latestPromise;
996
- const checkLatest = () => {
997
- return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
998
- };
999
-
1000
- // Cancel any pending matches
1001
-
1002
- let pendingMatches;
1003
- this.#emit({
1004
- type: 'onBeforeLoad',
1005
- from: prevLocation,
1006
- to: opts?.next ?? this.state.location,
1007
- pathChanged: pathDidChange
1031
+ if (!this.history || this.options.history && this.options.history !== this.history) {
1032
+ if (this.#unsubHistory) {
1033
+ this.#unsubHistory();
1034
+ }
1035
+ this.history = this.options.history ?? (isServer ? createMemoryHistory() : createBrowserHistory());
1036
+ const parsedLocation = this.#parseLocation();
1037
+ this.__store.setState(s => ({
1038
+ ...s,
1039
+ resolvedLocation: parsedLocation,
1040
+ location: parsedLocation
1041
+ }));
1042
+ this.#unsubHistory = this.history.subscribe(() => {
1043
+ this.safeLoad({
1044
+ next: this.#parseLocation(this.state.location)
1008
1045
  });
1009
- this.__store.batch(() => {
1010
- if (opts?.next) {
1011
- // Ingest the new location
1012
- this.__store.setState(s => ({
1013
- ...s,
1014
- location: opts.next
1015
- }));
1016
- }
1046
+ });
1047
+ }
1048
+ const {
1049
+ basepath,
1050
+ routeTree
1051
+ } = this.options;
1052
+ this.basepath = `/${trimPath(basepath ?? '') ?? ''}`;
1053
+ if (routeTree && routeTree !== this.routeTree) {
1054
+ this.#processRoutes(routeTree);
1055
+ }
1056
+ return this;
1057
+ };
1058
+ cancelMatches = () => {
1059
+ this.state.matches.forEach(match => {
1060
+ this.cancelMatch(match.id);
1061
+ });
1062
+ };
1063
+ cancelMatch = id => {
1064
+ this.getRouteMatch(id)?.abortController?.abort();
1065
+ };
1066
+ safeLoad = async opts => {
1067
+ try {
1068
+ return this.load(opts);
1069
+ } catch (err) {
1070
+ // Don't do anything
1071
+ }
1072
+ };
1073
+ latestLoadPromise = Promise.resolve();
1074
+ load = async opts => {
1075
+ const promise = new Promise(async (resolve, reject) => {
1076
+ const prevLocation = this.state.resolvedLocation;
1077
+ const pathDidChange = !!(opts?.next && prevLocation.href !== opts.next.href);
1078
+ let latestPromise;
1079
+ const checkLatest = () => {
1080
+ return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
1081
+ };
1017
1082
 
1018
- // Match the routes
1019
- pendingMatches = this.matchRoutes(this.state.location.pathname, this.state.location.search, {
1020
- throwOnError: opts?.throwOnError,
1021
- debug: true
1022
- });
1083
+ // Cancel any pending matches
1084
+
1085
+ let pendingMatches;
1086
+ this.#emit({
1087
+ type: 'onBeforeLoad',
1088
+ from: prevLocation,
1089
+ to: opts?.next ?? this.state.location,
1090
+ pathChanged: pathDidChange
1091
+ });
1092
+ this.__store.batch(() => {
1093
+ if (opts?.next) {
1094
+ // Ingest the new location
1023
1095
  this.__store.setState(s => ({
1024
1096
  ...s,
1025
- status: 'pending',
1026
- pendingMatchIds: pendingMatches.map(d => d.id),
1027
- matchesById: this.#mergeMatches(s.matchesById, pendingMatches)
1097
+ location: opts.next
1028
1098
  }));
1099
+ }
1100
+
1101
+ // Match the routes
1102
+ pendingMatches = this.matchRoutes(this.state.location.pathname, this.state.location.search, {
1103
+ throwOnError: opts?.throwOnError,
1104
+ debug: true
1029
1105
  });
1106
+ this.__store.setState(s => ({
1107
+ ...s,
1108
+ status: 'pending',
1109
+ pendingMatchIds: pendingMatches.map(d => d.id),
1110
+ matchesById: this.#mergeMatches(s.matchesById, pendingMatches)
1111
+ }));
1112
+ });
1113
+ try {
1114
+ // Load the matches
1030
1115
  try {
1031
- // Load the matches
1032
- try {
1033
- await this.loadMatches(pendingMatches.map(d => d.id));
1034
- } catch (err) {
1035
- // swallow this error, since we'll display the
1036
- // errors on the route components
1037
- }
1116
+ await this.loadMatches(pendingMatches.map(d => d.id));
1117
+ } catch (err) {
1118
+ // swallow this error, since we'll display the
1119
+ // errors on the route components
1120
+ }
1038
1121
 
1039
- // Only apply the latest transition
1040
- if (latestPromise = checkLatest()) {
1041
- return latestPromise;
1042
- }
1043
- const exitingMatchIds = this.state.matchIds.filter(id => !this.state.pendingMatchIds.includes(id));
1044
- const enteringMatchIds = this.state.pendingMatchIds.filter(id => !this.state.matchIds.includes(id));
1045
- const stayingMatchIds = this.state.matchIds.filter(id => this.state.pendingMatchIds.includes(id));
1046
- this.__store.setState(s => ({
1047
- ...s,
1048
- status: 'idle',
1049
- resolvedLocation: s.location,
1050
- matchIds: s.pendingMatchIds,
1051
- pendingMatchIds: []
1052
- }));
1053
- [[exitingMatchIds, 'onLeave'], [enteringMatchIds, 'onEnter'], [stayingMatchIds, 'onTransition']].forEach(([matchIds, hook]) => {
1054
- matchIds.forEach(id => {
1055
- const match = this.getRouteMatch(id);
1056
- const route = this.getRoute(match.routeId);
1057
- route.options[hook]?.(match);
1058
- });
1059
- });
1060
- this.#emit({
1061
- type: 'onLoad',
1062
- from: prevLocation,
1063
- to: this.state.location,
1064
- pathChanged: pathDidChange
1122
+ // Only apply the latest transition
1123
+ if (latestPromise = checkLatest()) {
1124
+ return latestPromise;
1125
+ }
1126
+ const exitingMatchIds = this.state.matchIds.filter(id => !this.state.pendingMatchIds.includes(id));
1127
+ const enteringMatchIds = this.state.pendingMatchIds.filter(id => !this.state.matchIds.includes(id));
1128
+ const stayingMatchIds = this.state.matchIds.filter(id => this.state.pendingMatchIds.includes(id));
1129
+ this.__store.setState(s => ({
1130
+ ...s,
1131
+ status: 'idle',
1132
+ resolvedLocation: s.location,
1133
+ matchIds: s.pendingMatchIds,
1134
+ pendingMatchIds: []
1135
+ }));
1136
+ [[exitingMatchIds, 'onLeave'], [enteringMatchIds, 'onEnter'], [stayingMatchIds, 'onTransition']].forEach(([matchIds, hook]) => {
1137
+ matchIds.forEach(id => {
1138
+ const match = this.getRouteMatch(id);
1139
+ const route = this.getRoute(match.routeId);
1140
+ route.options[hook]?.(match);
1065
1141
  });
1066
- resolve();
1067
- } catch (err) {
1068
- // Only apply the latest transition
1069
- if (latestPromise = checkLatest()) {
1070
- return latestPromise;
1071
- }
1072
- reject(err);
1142
+ });
1143
+ this.#emit({
1144
+ type: 'onLoad',
1145
+ from: prevLocation,
1146
+ to: this.state.location,
1147
+ pathChanged: pathDidChange
1148
+ });
1149
+ resolve();
1150
+ } catch (err) {
1151
+ // Only apply the latest transition
1152
+ if (latestPromise = checkLatest()) {
1153
+ return latestPromise;
1073
1154
  }
1074
- });
1075
- this.latestLoadPromise = promise;
1076
- this.latestLoadPromise.then(() => {
1077
- this.cleanMatches();
1078
- });
1079
- return this.latestLoadPromise;
1155
+ reject(err);
1156
+ }
1157
+ });
1158
+ this.latestLoadPromise = promise;
1159
+ this.latestLoadPromise.then(() => {
1160
+ this.cleanMatches();
1161
+ });
1162
+ return this.latestLoadPromise;
1163
+ };
1164
+ #mergeMatches = (prevMatchesById, nextMatches) => {
1165
+ let matchesById = {
1166
+ ...prevMatchesById
1080
1167
  };
1081
- #mergeMatches = (prevMatchesById, nextMatches) => {
1082
- let matchesById = {
1083
- ...prevMatchesById
1168
+ nextMatches.forEach(match => {
1169
+ if (!matchesById[match.id]) {
1170
+ matchesById[match.id] = match;
1171
+ }
1172
+ matchesById[match.id] = {
1173
+ ...matchesById[match.id],
1174
+ ...match
1084
1175
  };
1085
- nextMatches.forEach(match => {
1086
- if (!matchesById[match.id]) {
1087
- matchesById[match.id] = match;
1088
- }
1089
- matchesById[match.id] = {
1090
- ...matchesById[match.id],
1091
- ...match
1092
- };
1093
- });
1094
- return matchesById;
1095
- };
1096
- getRoute = id => {
1097
- const route = this.routesById[id];
1098
- invariant(route, `Route with id "${id}" not found`);
1099
- return route;
1100
- };
1101
- preloadRoute = async (navigateOpts = this.state.location) => {
1102
- let next = this.buildLocation(navigateOpts);
1103
- const matches = this.matchRoutes(next.pathname, next.search, {
1104
- throwOnError: true
1105
- });
1176
+ });
1177
+ return matchesById;
1178
+ };
1179
+ getRoute = id => {
1180
+ const route = this.routesById[id];
1181
+ invariant(route, `Route with id "${id}" not found`);
1182
+ return route;
1183
+ };
1184
+ preloadRoute = async (navigateOpts = this.state.location) => {
1185
+ let next = this.buildLocation(navigateOpts);
1186
+ const matches = this.matchRoutes(next.pathname, next.search, {
1187
+ throwOnError: true
1188
+ });
1189
+ this.__store.setState(s => {
1190
+ return {
1191
+ ...s,
1192
+ matchesById: this.#mergeMatches(s.matchesById, matches)
1193
+ };
1194
+ });
1195
+ await this.loadMatches(matches.map(d => d.id), {
1196
+ preload: true,
1197
+ maxAge: navigateOpts.maxAge
1198
+ });
1199
+ return [last(matches), matches];
1200
+ };
1201
+ cleanMatches = () => {
1202
+ const now = Date.now();
1203
+ const outdatedMatchIds = Object.values(this.state.matchesById).filter(match => {
1204
+ const route = this.getRoute(match.routeId);
1205
+ return !this.state.matchIds.includes(match.id) && !this.state.pendingMatchIds.includes(match.id) && (match.preloadMaxAge > -1 ? match.updatedAt + match.preloadMaxAge < now : true) && (route.options.gcMaxAge ? match.updatedAt + route.options.gcMaxAge < now : true);
1206
+ }).map(d => d.id);
1207
+ if (outdatedMatchIds.length) {
1106
1208
  this.__store.setState(s => {
1209
+ const matchesById = {
1210
+ ...s.matchesById
1211
+ };
1212
+ outdatedMatchIds.forEach(id => {
1213
+ delete matchesById[id];
1214
+ });
1107
1215
  return {
1108
1216
  ...s,
1109
- matchesById: this.#mergeMatches(s.matchesById, matches)
1217
+ matchesById
1110
1218
  };
1111
1219
  });
1112
- await this.loadMatches(matches.map(d => d.id), {
1113
- preload: true,
1114
- maxAge: navigateOpts.maxAge
1115
- });
1116
- return [last(matches), matches];
1117
- };
1118
- cleanMatches = () => {
1119
- const now = Date.now();
1120
- const outdatedMatchIds = Object.values(this.state.matchesById).filter(match => {
1121
- const route = this.getRoute(match.routeId);
1122
- return !this.state.matchIds.includes(match.id) && !this.state.pendingMatchIds.includes(match.id) && (match.preloadMaxAge > -1 ? match.updatedAt + match.preloadMaxAge < now : true) && (route.options.gcMaxAge ? match.updatedAt + route.options.gcMaxAge < now : true);
1123
- }).map(d => d.id);
1124
- if (outdatedMatchIds.length) {
1125
- this.__store.setState(s => {
1126
- const matchesById = {
1127
- ...s.matchesById
1128
- };
1129
- outdatedMatchIds.forEach(id => {
1130
- delete matchesById[id];
1131
- });
1132
- return {
1133
- ...s,
1134
- matchesById
1135
- };
1136
- });
1137
- }
1138
- };
1139
- matchRoutes = (pathname, locationSearch, opts) => {
1140
- let routeParams = {};
1141
- let foundRoute = this.flatRoutes.find(route => {
1142
- const matchedParams = matchPathname(this.basepath, trimPathRight(pathname), {
1143
- to: route.fullPath,
1144
- caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive
1145
- });
1146
- if (matchedParams) {
1147
- routeParams = matchedParams;
1148
- return true;
1149
- }
1150
- return false;
1220
+ }
1221
+ };
1222
+ matchRoutes = (pathname, locationSearch, opts) => {
1223
+ let routeParams = {};
1224
+ let foundRoute = this.flatRoutes.find(route => {
1225
+ const matchedParams = matchPathname(this.basepath, trimPathRight(pathname), {
1226
+ to: route.fullPath,
1227
+ caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive
1151
1228
  });
1152
- let routeCursor = foundRoute || this.routesById['__root__'];
1153
- let matchedRoutes = [routeCursor];
1154
- // let includingLayouts = true
1155
- while (routeCursor?.parentRoute) {
1156
- routeCursor = routeCursor.parentRoute;
1157
- if (routeCursor) matchedRoutes.unshift(routeCursor);
1229
+ if (matchedParams) {
1230
+ routeParams = matchedParams;
1231
+ return true;
1158
1232
  }
1233
+ return false;
1234
+ });
1235
+ let routeCursor = foundRoute || this.routesById['__root__'];
1236
+ let matchedRoutes = [routeCursor];
1237
+ // let includingLayouts = true
1238
+ while (routeCursor?.parentRoute) {
1239
+ routeCursor = routeCursor.parentRoute;
1240
+ if (routeCursor) matchedRoutes.unshift(routeCursor);
1241
+ }
1159
1242
 
1160
- // Existing matches are matches that are already loaded along with
1161
- // pending matches that are still loading
1243
+ // Existing matches are matches that are already loaded along with
1244
+ // pending matches that are still loading
1162
1245
 
1163
- const parseErrors = matchedRoutes.map(route => {
1164
- let parsedParamsError;
1165
- if (route.options.parseParams) {
1166
- try {
1167
- const parsedParams = route.options.parseParams(routeParams);
1168
- // Add the parsed params to the accumulated params bag
1169
- Object.assign(routeParams, parsedParams);
1170
- } catch (err) {
1171
- parsedParamsError = new PathParamError(err.message, {
1172
- cause: err
1173
- });
1174
- if (opts?.throwOnError) {
1175
- throw parsedParamsError;
1176
- }
1177
- return parsedParamsError;
1246
+ const parseErrors = matchedRoutes.map(route => {
1247
+ let parsedParamsError;
1248
+ if (route.options.parseParams) {
1249
+ try {
1250
+ const parsedParams = route.options.parseParams(routeParams);
1251
+ // Add the parsed params to the accumulated params bag
1252
+ Object.assign(routeParams, parsedParams);
1253
+ } catch (err) {
1254
+ parsedParamsError = new PathParamError(err.message, {
1255
+ cause: err
1256
+ });
1257
+ if (opts?.throwOnError) {
1258
+ throw parsedParamsError;
1178
1259
  }
1260
+ return parsedParamsError;
1179
1261
  }
1180
- return;
1262
+ }
1263
+ return;
1264
+ });
1265
+ const matches = matchedRoutes.map((route, index) => {
1266
+ const interpolatedPath = interpolatePath(route.path, routeParams);
1267
+ const loaderContext = route.options.loaderContext ? route.options.loaderContext({
1268
+ search: locationSearch
1269
+ }) : undefined;
1270
+ const matchId = JSON.stringify([interpolatePath(route.id, routeParams, true), loaderContext].filter(d => d !== undefined), (key, value) => {
1271
+ if (typeof value === 'function') {
1272
+ console.info(route);
1273
+ invariant(false, `Cannot return functions and other non-serializable values from routeOptions.loaderContext! Please use routeOptions.beforeLoad to do this. Route info is logged above 👆`);
1274
+ }
1275
+ if (typeof value === 'object' && value !== null) {
1276
+ return Object.fromEntries(Object.keys(value).sort().map(key => [key, value[key]]));
1277
+ }
1278
+ return value;
1181
1279
  });
1182
- const matches = matchedRoutes.map((route, index) => {
1183
- const interpolatedPath = interpolatePath(route.path, routeParams);
1184
- const loaderContext = route.options.loaderContext ? route.options.loaderContext({
1185
- search: locationSearch
1186
- }) : undefined;
1187
- const matchId = JSON.stringify([interpolatePath(route.id, routeParams, true), loaderContext].filter(d => d !== undefined), (key, value) => {
1188
- if (typeof value === 'function') {
1189
- console.info(route);
1190
- invariant(false, `Cannot return functions and other non-serializable values from routeOptions.loaderContext! Please use routeOptions.beforeLoad to do this. Route info is logged above 👆`);
1191
- }
1192
- if (typeof value === 'object' && value !== null) {
1193
- return Object.fromEntries(Object.keys(value).sort().map(key => [key, value[key]]));
1194
- }
1195
- return value;
1196
- });
1197
1280
 
1198
- // Waste not, want not. If we already have a match for this route,
1199
- // reuse it. This is important for layout routes, which might stick
1200
- // around between navigation actions that only change leaf routes.
1201
- const existingMatch = this.getRouteMatch(matchId);
1202
- if (existingMatch) {
1281
+ // Waste not, want not. If we already have a match for this route,
1282
+ // reuse it. This is important for layout routes, which might stick
1283
+ // around between navigation actions that only change leaf routes.
1284
+ const existingMatch = this.getRouteMatch(matchId);
1285
+ if (existingMatch) {
1286
+ return {
1287
+ ...existingMatch
1288
+ };
1289
+ }
1290
+
1291
+ // Create a fresh route match
1292
+ const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
1293
+ const routeMatch = {
1294
+ id: matchId,
1295
+ loaderContext,
1296
+ routeId: route.id,
1297
+ params: routeParams,
1298
+ pathname: joinPaths([this.basepath, interpolatedPath]),
1299
+ updatedAt: Date.now(),
1300
+ maxAge: -1,
1301
+ preloadMaxAge: -1,
1302
+ routeSearch: {},
1303
+ search: {},
1304
+ status: hasLoaders ? 'pending' : 'success',
1305
+ isFetching: false,
1306
+ invalid: false,
1307
+ error: undefined,
1308
+ paramsError: parseErrors[index],
1309
+ searchError: undefined,
1310
+ loaderData: undefined,
1311
+ loadPromise: Promise.resolve(),
1312
+ context: undefined,
1313
+ abortController: new AbortController(),
1314
+ fetchedAt: 0
1315
+ };
1316
+ return routeMatch;
1317
+ });
1318
+
1319
+ // Take each match and resolve its search params and context
1320
+ // This has to happen after the matches are created or found
1321
+ // so that we can use the parent match's search params and context
1322
+ matches.forEach((match, i) => {
1323
+ const parentMatch = matches[i - 1];
1324
+ const route = this.getRoute(match.routeId);
1325
+ const searchInfo = (() => {
1326
+ // Validate the search params and stabilize them
1327
+ const parentSearchInfo = {
1328
+ search: parentMatch?.search ?? locationSearch,
1329
+ routeSearch: parentMatch?.routeSearch ?? locationSearch
1330
+ };
1331
+ try {
1332
+ const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
1333
+ let routeSearch = validator?.(parentSearchInfo.search) ?? {};
1334
+ let search = {
1335
+ ...parentSearchInfo.search,
1336
+ ...routeSearch
1337
+ };
1338
+ routeSearch = replaceEqualDeep(match.routeSearch, routeSearch);
1339
+ search = replaceEqualDeep(match.search, search);
1203
1340
  return {
1204
- ...existingMatch
1341
+ routeSearch,
1342
+ search,
1343
+ searchDidChange: match.routeSearch !== routeSearch
1205
1344
  };
1345
+ } catch (err) {
1346
+ match.searchError = new SearchParamError(err.message, {
1347
+ cause: err
1348
+ });
1349
+ if (opts?.throwOnError) {
1350
+ throw match.searchError;
1351
+ }
1352
+ return parentSearchInfo;
1206
1353
  }
1207
-
1208
- // Create a fresh route match
1209
- const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
1210
- const routeMatch = {
1211
- id: matchId,
1212
- loaderContext,
1213
- routeId: route.id,
1214
- params: routeParams,
1215
- pathname: joinPaths([this.basepath, interpolatedPath]),
1216
- updatedAt: Date.now(),
1217
- maxAge: -1,
1218
- preloadMaxAge: -1,
1219
- routeSearch: {},
1220
- search: {},
1221
- status: hasLoaders ? 'pending' : 'success',
1222
- isFetching: false,
1223
- invalid: false,
1224
- error: undefined,
1225
- paramsError: parseErrors[index],
1226
- searchError: undefined,
1227
- loaderData: undefined,
1228
- loadPromise: Promise.resolve(),
1229
- context: undefined,
1230
- abortController: new AbortController(),
1231
- fetchedAt: 0
1232
- };
1233
- return routeMatch;
1354
+ })();
1355
+ Object.assign(match, searchInfo);
1356
+ });
1357
+ return matches;
1358
+ };
1359
+ loadMatches = async (matchIds, opts) => {
1360
+ const getFreshMatches = () => matchIds.map(d => this.getRouteMatch(d));
1361
+ if (!opts?.preload) {
1362
+ getFreshMatches().forEach(match => {
1363
+ // Update each match with its latest route data
1364
+ this.setRouteMatch(match.id, s => ({
1365
+ ...s,
1366
+ routeSearch: match.routeSearch,
1367
+ search: match.search,
1368
+ context: match.context,
1369
+ error: match.error,
1370
+ paramsError: match.paramsError,
1371
+ searchError: match.searchError,
1372
+ params: match.params,
1373
+ preloadMaxAge: 0
1374
+ }));
1234
1375
  });
1376
+ }
1377
+ let firstBadMatchIndex;
1235
1378
 
1236
- // Take each match and resolve its search params and context
1237
- // This has to happen after the matches are created or found
1238
- // so that we can use the parent match's search params and context
1239
- matches.forEach((match, i) => {
1240
- const parentMatch = matches[i - 1];
1379
+ // Check each match middleware to see if the route can be accessed
1380
+ try {
1381
+ for (const [index, match] of getFreshMatches().entries()) {
1382
+ const parentMatch = getFreshMatches()[index - 1];
1241
1383
  const route = this.getRoute(match.routeId);
1242
- const searchInfo = (() => {
1243
- // Validate the search params and stabilize them
1244
- const parentSearchInfo = {
1245
- search: parentMatch?.search ?? locationSearch,
1246
- routeSearch: parentMatch?.routeSearch ?? locationSearch
1247
- };
1384
+ const handleError = (err, code) => {
1385
+ err.routerCode = code;
1386
+ firstBadMatchIndex = firstBadMatchIndex ?? index;
1387
+ if (isRedirect(err)) {
1388
+ throw err;
1389
+ }
1248
1390
  try {
1249
- const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
1250
- let routeSearch = validator?.(parentSearchInfo.search) ?? {};
1251
- let search = {
1252
- ...parentSearchInfo.search,
1253
- ...routeSearch
1254
- };
1255
- routeSearch = replaceEqualDeep(match.routeSearch, routeSearch);
1256
- search = replaceEqualDeep(match.search, search);
1257
- return {
1258
- routeSearch,
1259
- search,
1260
- searchDidChange: match.routeSearch !== routeSearch
1261
- };
1262
- } catch (err) {
1263
- match.searchError = new SearchParamError(err.message, {
1264
- cause: err
1265
- });
1266
- if (opts?.throwOnError) {
1267
- throw match.searchError;
1391
+ route.options.onError?.(err);
1392
+ } catch (errorHandlerErr) {
1393
+ err = errorHandlerErr;
1394
+ if (isRedirect(errorHandlerErr)) {
1395
+ throw errorHandlerErr;
1268
1396
  }
1269
- return parentSearchInfo;
1270
1397
  }
1271
- })();
1272
- Object.assign(match, searchInfo);
1273
- });
1274
- return matches;
1275
- };
1276
- loadMatches = async (matchIds, opts) => {
1277
- const getFreshMatches = () => matchIds.map(d => this.getRouteMatch(d));
1278
- if (!opts?.preload) {
1279
- getFreshMatches().forEach(match => {
1280
- // Update each match with its latest route data
1281
1398
  this.setRouteMatch(match.id, s => ({
1282
1399
  ...s,
1283
- routeSearch: match.routeSearch,
1284
- search: match.search,
1285
- context: match.context,
1286
- error: match.error,
1287
- paramsError: match.paramsError,
1288
- searchError: match.searchError,
1289
- params: match.params,
1290
- preloadMaxAge: 0
1400
+ error: err,
1401
+ status: 'error',
1402
+ updatedAt: Date.now()
1291
1403
  }));
1292
- });
1293
- }
1294
- let firstBadMatchIndex;
1295
-
1296
- // Check each match middleware to see if the route can be accessed
1297
- try {
1298
- for (const [index, match] of getFreshMatches().entries()) {
1299
- const parentMatch = getFreshMatches()[index - 1];
1300
- const route = this.getRoute(match.routeId);
1301
- const handleError = (err, code) => {
1302
- err.routerCode = code;
1303
- firstBadMatchIndex = firstBadMatchIndex ?? index;
1304
- if (isRedirect(err)) {
1305
- throw err;
1306
- }
1307
- try {
1308
- route.options.onError?.(err);
1309
- } catch (errorHandlerErr) {
1310
- err = errorHandlerErr;
1311
- if (isRedirect(errorHandlerErr)) {
1312
- throw errorHandlerErr;
1313
- }
1404
+ };
1405
+ if (match.paramsError) {
1406
+ handleError(match.paramsError, 'PARSE_PARAMS');
1407
+ }
1408
+ if (match.searchError) {
1409
+ handleError(match.searchError, 'VALIDATE_SEARCH');
1410
+ }
1411
+ let didError = false;
1412
+ const parentContext = parentMatch?.context ?? this?.options.context ?? {};
1413
+ try {
1414
+ const beforeLoadContext = (await route.options.beforeLoad?.({
1415
+ abortController: match.abortController,
1416
+ params: match.params,
1417
+ preload: !!opts?.preload,
1418
+ context: {
1419
+ ...parentContext,
1420
+ ...match.loaderContext
1314
1421
  }
1315
- this.setRouteMatch(match.id, s => ({
1316
- ...s,
1317
- error: err,
1318
- status: 'error',
1319
- updatedAt: Date.now()
1320
- }));
1422
+ })) ?? {};
1423
+ const context = {
1424
+ ...parentContext,
1425
+ ...match.loaderContext,
1426
+ ...beforeLoadContext
1321
1427
  };
1322
- if (match.paramsError) {
1323
- handleError(match.paramsError, 'PARSE_PARAMS');
1324
- }
1325
- if (match.searchError) {
1326
- handleError(match.searchError, 'VALIDATE_SEARCH');
1327
- }
1328
- let didError = false;
1329
- const parentContext = parentMatch?.context ?? this?.options.context ?? {};
1330
- try {
1331
- const beforeLoadContext = (await route.options.beforeLoad?.({
1332
- abortController: match.abortController,
1333
- params: match.params,
1334
- preload: !!opts?.preload,
1335
- context: {
1336
- ...parentContext,
1337
- ...match.loaderContext
1338
- }
1339
- })) ?? {};
1340
- const context = {
1341
- ...parentContext,
1342
- ...match.loaderContext,
1343
- ...beforeLoadContext
1344
- };
1345
- this.setRouteMatch(match.id, s => ({
1346
- ...s,
1347
- context: replaceEqualDeep(s.context, context)
1348
- }));
1349
- } catch (err) {
1350
- handleError(err, 'BEFORE_LOAD');
1351
- didError = true;
1352
- }
1353
-
1354
- // If we errored, do not run the next matches' middleware
1355
- if (didError) {
1356
- break;
1357
- }
1428
+ this.setRouteMatch(match.id, s => ({
1429
+ ...s,
1430
+ context: replaceEqualDeep(s.context, context)
1431
+ }));
1432
+ } catch (err) {
1433
+ handleError(err, 'BEFORE_LOAD');
1434
+ didError = true;
1358
1435
  }
1359
- } catch (err) {
1360
- if (isRedirect(err)) {
1361
- if (!opts?.preload) this.navigate(err);
1362
- return;
1436
+
1437
+ // If we errored, do not run the next matches' middleware
1438
+ if (didError) {
1439
+ break;
1363
1440
  }
1364
- throw err;
1365
1441
  }
1366
- const validResolvedMatches = getFreshMatches().slice(0, firstBadMatchIndex);
1367
- const matchPromises = [];
1368
- validResolvedMatches.forEach((match, index) => {
1369
- matchPromises.push((async () => {
1370
- const parentMatchPromise = matchPromises[index - 1];
1371
- const route = this.getRoute(match.routeId);
1372
- if (match.isFetching || match.status === 'success' && !isMatchInvalid(match, {
1373
- preload: opts?.preload
1374
- })) {
1375
- return this.getRouteMatch(match.id)?.loadPromise;
1442
+ } catch (err) {
1443
+ if (isRedirect(err)) {
1444
+ if (!opts?.preload) this.navigate(err);
1445
+ return;
1446
+ }
1447
+ throw err;
1448
+ }
1449
+ const validResolvedMatches = getFreshMatches().slice(0, firstBadMatchIndex);
1450
+ const matchPromises = [];
1451
+ validResolvedMatches.forEach((match, index) => {
1452
+ matchPromises.push((async () => {
1453
+ const parentMatchPromise = matchPromises[index - 1];
1454
+ const route = this.getRoute(match.routeId);
1455
+ if (match.isFetching || match.status === 'success' && !isMatchInvalid(match, {
1456
+ preload: opts?.preload
1457
+ })) {
1458
+ return this.getRouteMatch(match.id)?.loadPromise;
1459
+ }
1460
+ const fetchedAt = Date.now();
1461
+ const checkLatest = () => {
1462
+ const latest = this.getRouteMatch(match.id);
1463
+ return latest && latest.fetchedAt !== fetchedAt ? latest.loadPromise : undefined;
1464
+ };
1465
+ const handleIfRedirect = err => {
1466
+ if (isRedirect(err)) {
1467
+ if (!opts?.preload) {
1468
+ this.navigate(err);
1469
+ }
1470
+ return true;
1376
1471
  }
1377
- const fetchedAt = Date.now();
1378
- const checkLatest = () => {
1379
- const latest = this.getRouteMatch(match.id);
1380
- return latest && latest.fetchedAt !== fetchedAt ? latest.loadPromise : undefined;
1381
- };
1382
- const handleIfRedirect = err => {
1383
- if (isRedirect(err)) {
1384
- if (!opts?.preload) {
1385
- this.navigate(err);
1472
+ return false;
1473
+ };
1474
+ const load = async () => {
1475
+ let latestPromise;
1476
+ try {
1477
+ const componentsPromise = Promise.all(componentTypes.map(async type => {
1478
+ const component = route.options[type];
1479
+ if (component?.preload) {
1480
+ await component.preload();
1386
1481
  }
1387
- return true;
1388
- }
1389
- return false;
1390
- };
1391
- const load = async () => {
1392
- let latestPromise;
1482
+ }));
1483
+ const loaderPromise = route.options.loader?.({
1484
+ params: match.params,
1485
+ preload: !!opts?.preload,
1486
+ parentMatchPromise,
1487
+ abortController: match.abortController,
1488
+ context: match.context
1489
+ });
1490
+ const [_, loader] = await Promise.all([componentsPromise, loaderPromise]);
1491
+ if (latestPromise = checkLatest()) return await latestPromise;
1492
+ this.setRouteMatchData(match.id, () => loader, opts);
1493
+ } catch (error) {
1494
+ if (latestPromise = checkLatest()) return await latestPromise;
1495
+ if (handleIfRedirect(error)) return;
1393
1496
  try {
1394
- const componentsPromise = Promise.all(componentTypes.map(async type => {
1395
- const component = route.options[type];
1396
- if (component?.preload) {
1397
- await component.preload();
1398
- }
1399
- }));
1400
- const loaderPromise = route.options.loader?.({
1401
- params: match.params,
1402
- preload: !!opts?.preload,
1403
- parentMatchPromise,
1404
- abortController: match.abortController,
1405
- context: match.context
1406
- });
1407
- const [_, loader] = await Promise.all([componentsPromise, loaderPromise]);
1408
- if (latestPromise = checkLatest()) return await latestPromise;
1409
- this.setRouteMatchData(match.id, () => loader, opts);
1410
- } catch (error) {
1411
- if (latestPromise = checkLatest()) return await latestPromise;
1412
- if (handleIfRedirect(error)) return;
1413
- try {
1414
- route.options.onError?.(error);
1415
- } catch (onErrorError) {
1416
- error = onErrorError;
1417
- if (handleIfRedirect(onErrorError)) return;
1418
- }
1419
- this.setRouteMatch(match.id, s => ({
1420
- ...s,
1421
- error,
1422
- status: 'error',
1423
- isFetching: false,
1424
- updatedAt: Date.now()
1425
- }));
1497
+ route.options.onError?.(error);
1498
+ } catch (onErrorError) {
1499
+ error = onErrorError;
1500
+ if (handleIfRedirect(onErrorError)) return;
1426
1501
  }
1427
- };
1428
- let loadPromise;
1429
- this.__store.batch(() => {
1430
1502
  this.setRouteMatch(match.id, s => ({
1431
1503
  ...s,
1432
- // status: s.status !== 'success' ? 'pending' : s.status,
1433
- isFetching: true,
1434
- fetchedAt,
1435
- invalid: false
1436
- }));
1437
- loadPromise = load();
1438
- this.setRouteMatch(match.id, s => ({
1439
- ...s,
1440
- loadPromise
1504
+ error,
1505
+ status: 'error',
1506
+ isFetching: false,
1507
+ updatedAt: Date.now()
1441
1508
  }));
1442
- });
1443
- await loadPromise;
1444
- })());
1445
- });
1446
- await Promise.all(matchPromises);
1447
- };
1448
- resolvePath = (from, path) => {
1449
- return resolvePath(this.basepath, from, cleanPath(path));
1450
- };
1451
- navigate = async ({
1452
- from,
1453
- to = '',
1454
- ...rest
1455
- }) => {
1456
- // If this link simply reloads the current route,
1457
- // make sure it has a new key so it will trigger a data refresh
1458
-
1459
- // If this `to` is a valid external URL, return
1460
- // null for LinkUtils
1461
- const toString = String(to);
1462
- const fromString = typeof from === 'undefined' ? from : String(from);
1463
- let isExternal;
1464
- try {
1465
- new URL(`${toString}`);
1466
- isExternal = true;
1467
- } catch (e) {}
1468
- invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
1469
- return this.#buildAndCommitLocation({
1470
- ...rest,
1471
- from: fromString,
1472
- to: toString
1473
- });
1509
+ }
1510
+ };
1511
+ let loadPromise;
1512
+ this.__store.batch(() => {
1513
+ this.setRouteMatch(match.id, s => ({
1514
+ ...s,
1515
+ // status: s.status !== 'success' ? 'pending' : s.status,
1516
+ isFetching: true,
1517
+ fetchedAt,
1518
+ invalid: false
1519
+ }));
1520
+ loadPromise = load();
1521
+ this.setRouteMatch(match.id, s => ({
1522
+ ...s,
1523
+ loadPromise
1524
+ }));
1525
+ });
1526
+ await loadPromise;
1527
+ })());
1528
+ });
1529
+ await Promise.all(matchPromises);
1530
+ };
1531
+ resolvePath = (from, path) => {
1532
+ return resolvePath(this.basepath, from, cleanPath(path));
1533
+ };
1534
+ navigate = async ({
1535
+ from,
1536
+ to = '',
1537
+ ...rest
1538
+ }) => {
1539
+ // If this link simply reloads the current route,
1540
+ // make sure it has a new key so it will trigger a data refresh
1541
+
1542
+ // If this `to` is a valid external URL, return
1543
+ // null for LinkUtils
1544
+ const toString = String(to);
1545
+ const fromString = typeof from === 'undefined' ? from : String(from);
1546
+ let isExternal;
1547
+ try {
1548
+ new URL(`${toString}`);
1549
+ isExternal = true;
1550
+ } catch (e) {}
1551
+ invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
1552
+ return this.#buildAndCommitLocation({
1553
+ ...rest,
1554
+ from: fromString,
1555
+ to: toString
1556
+ });
1557
+ };
1558
+ matchRoute = (location, opts) => {
1559
+ location = {
1560
+ ...location,
1561
+ to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
1474
1562
  };
1475
- matchRoute = (location, opts) => {
1476
- location = {
1477
- ...location,
1478
- to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
1479
- };
1480
- const next = this.buildLocation(location);
1481
- if (opts?.pending && this.state.status !== 'pending') {
1482
- return false;
1483
- }
1484
- const baseLocation = opts?.pending ? this.state.location : this.state.resolvedLocation;
1485
- if (!baseLocation) {
1486
- return false;
1487
- }
1488
- const match = matchPathname(this.basepath, baseLocation.pathname, {
1489
- ...opts,
1490
- to: next.pathname
1491
- });
1492
- if (!match) {
1493
- return false;
1494
- }
1495
- if (opts?.includeSearch ?? true) {
1496
- return partialDeepEqual(baseLocation.search, next.search) ? match : false;
1563
+ const next = this.buildLocation(location);
1564
+ if (opts?.pending && this.state.status !== 'pending') {
1565
+ return false;
1566
+ }
1567
+ const baseLocation = opts?.pending ? this.state.location : this.state.resolvedLocation;
1568
+ if (!baseLocation) {
1569
+ return false;
1570
+ }
1571
+ const match = matchPathname(this.basepath, baseLocation.pathname, {
1572
+ ...opts,
1573
+ to: next.pathname
1574
+ });
1575
+ if (!match) {
1576
+ return false;
1577
+ }
1578
+ if (opts?.includeSearch ?? true) {
1579
+ return partialDeepEqual(baseLocation.search, next.search) ? match : false;
1580
+ }
1581
+ return match;
1582
+ };
1583
+ buildLink = dest => {
1584
+ // If this link simply reloads the current route,
1585
+ // make sure it has a new key so it will trigger a data refresh
1586
+
1587
+ // If this `to` is a valid external URL, return
1588
+ // null for LinkUtils
1589
+
1590
+ const {
1591
+ to,
1592
+ preload: userPreload,
1593
+ preloadDelay: userPreloadDelay,
1594
+ activeOptions,
1595
+ disabled,
1596
+ target,
1597
+ replace,
1598
+ resetScroll
1599
+ } = dest;
1600
+ try {
1601
+ new URL(`${to}`);
1602
+ return {
1603
+ type: 'external',
1604
+ href: to
1605
+ };
1606
+ } catch (e) {}
1607
+ const nextOpts = dest;
1608
+ const next = this.buildLocation(nextOpts);
1609
+ const preload = userPreload ?? this.options.defaultPreload;
1610
+ const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
1611
+
1612
+ // Compare path/hash for matches
1613
+ const currentPathSplit = this.state.location.pathname.split('/');
1614
+ const nextPathSplit = next.pathname.split('/');
1615
+ const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
1616
+ // Combine the matches based on user options
1617
+ const pathTest = activeOptions?.exact ? this.state.location.pathname === next.pathname : pathIsFuzzyEqual;
1618
+ const hashTest = activeOptions?.includeHash ? this.state.location.hash === next.hash : true;
1619
+ const searchTest = activeOptions?.includeSearch ?? true ? partialDeepEqual(this.state.location.search, next.search) : true;
1620
+
1621
+ // The final "active" test
1622
+ const isActive = pathTest && hashTest && searchTest;
1623
+
1624
+ // The click handler
1625
+ const handleClick = e => {
1626
+ if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
1627
+ e.preventDefault();
1628
+
1629
+ // All is well? Navigate!
1630
+ this.#commitLocation({
1631
+ ...next,
1632
+ replace,
1633
+ resetScroll
1634
+ });
1497
1635
  }
1498
- return match;
1499
1636
  };
1500
- buildLink = dest => {
1501
- // If this link simply reloads the current route,
1502
- // make sure it has a new key so it will trigger a data refresh
1503
-
1504
- // If this `to` is a valid external URL, return
1505
- // null for LinkUtils
1506
-
1507
- const {
1508
- to,
1509
- preload: userPreload,
1510
- preloadDelay: userPreloadDelay,
1511
- activeOptions,
1512
- disabled,
1513
- target,
1514
- replace,
1515
- resetScroll
1516
- } = dest;
1517
- try {
1518
- new URL(`${to}`);
1519
- return {
1520
- type: 'external',
1521
- href: to
1522
- };
1523
- } catch (e) {}
1524
- const nextOpts = dest;
1525
- const next = this.buildLocation(nextOpts);
1526
- const preload = userPreload ?? this.options.defaultPreload;
1527
- const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
1528
-
1529
- // Compare path/hash for matches
1530
- const currentPathSplit = this.state.location.pathname.split('/');
1531
- const nextPathSplit = next.pathname.split('/');
1532
- const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
1533
- // Combine the matches based on user options
1534
- const pathTest = activeOptions?.exact ? this.state.location.pathname === next.pathname : pathIsFuzzyEqual;
1535
- const hashTest = activeOptions?.includeHash ? this.state.location.hash === next.hash : true;
1536
- const searchTest = activeOptions?.includeSearch ?? true ? partialDeepEqual(this.state.location.search, next.search) : true;
1537
-
1538
- // The final "active" test
1539
- const isActive = pathTest && hashTest && searchTest;
1540
-
1541
- // The click handler
1542
- const handleClick = e => {
1543
- if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
1544
- e.preventDefault();
1545
-
1546
- // All is well? Navigate!
1547
- this.#commitLocation({
1548
- ...next,
1549
- replace,
1550
- resetScroll
1551
- });
1552
- }
1553
- };
1554
1637
 
1555
- // The click handler
1556
- const handleFocus = e => {
1557
- if (preload) {
1558
- this.preloadRoute(nextOpts).catch(err => {
1559
- console.warn(err);
1560
- console.warn(preloadWarning);
1561
- });
1562
- }
1563
- };
1564
- const handleTouchStart = e => {
1638
+ // The click handler
1639
+ const handleFocus = e => {
1640
+ if (preload) {
1565
1641
  this.preloadRoute(nextOpts).catch(err => {
1566
1642
  console.warn(err);
1567
1643
  console.warn(preloadWarning);
1568
1644
  });
1569
- };
1570
- const handleEnter = e => {
1571
- const target = e.target || {};
1572
- if (preload) {
1573
- if (target.preloadTimeout) {
1574
- return;
1575
- }
1576
- target.preloadTimeout = setTimeout(() => {
1577
- target.preloadTimeout = null;
1578
- this.preloadRoute(nextOpts).catch(err => {
1579
- console.warn(err);
1580
- console.warn(preloadWarning);
1581
- });
1582
- }, preloadDelay);
1583
- }
1584
- };
1585
- const handleLeave = e => {
1586
- const target = e.target || {};
1645
+ }
1646
+ };
1647
+ const handleTouchStart = e => {
1648
+ this.preloadRoute(nextOpts).catch(err => {
1649
+ console.warn(err);
1650
+ console.warn(preloadWarning);
1651
+ });
1652
+ };
1653
+ const handleEnter = e => {
1654
+ const target = e.target || {};
1655
+ if (preload) {
1587
1656
  if (target.preloadTimeout) {
1588
- clearTimeout(target.preloadTimeout);
1589
- target.preloadTimeout = null;
1657
+ return;
1590
1658
  }
1591
- };
1592
- return {
1593
- type: 'internal',
1594
- next,
1595
- handleFocus,
1596
- handleClick,
1597
- handleEnter,
1598
- handleLeave,
1599
- handleTouchStart,
1600
- isActive,
1601
- disabled
1602
- };
1659
+ target.preloadTimeout = setTimeout(() => {
1660
+ target.preloadTimeout = null;
1661
+ this.preloadRoute(nextOpts).catch(err => {
1662
+ console.warn(err);
1663
+ console.warn(preloadWarning);
1664
+ });
1665
+ }, preloadDelay);
1666
+ }
1603
1667
  };
1604
- dehydrate = () => {
1605
- return {
1606
- state: {
1607
- matchIds: this.state.matchIds,
1608
- dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'invalid', 'preloadMaxAge', 'maxAge', 'id', 'loaderData', 'status', 'updatedAt']))
1609
- }
1610
- };
1668
+ const handleLeave = e => {
1669
+ const target = e.target || {};
1670
+ if (target.preloadTimeout) {
1671
+ clearTimeout(target.preloadTimeout);
1672
+ target.preloadTimeout = null;
1673
+ }
1674
+ };
1675
+ return {
1676
+ type: 'internal',
1677
+ next,
1678
+ handleFocus,
1679
+ handleClick,
1680
+ handleEnter,
1681
+ handleLeave,
1682
+ handleTouchStart,
1683
+ isActive,
1684
+ disabled
1611
1685
  };
1612
- hydrate = async __do_not_use_server_ctx => {
1613
- let _ctx = __do_not_use_server_ctx;
1614
- // Client hydrates from window
1615
- if (typeof document !== 'undefined') {
1616
- _ctx = window.__TSR_DEHYDRATED__;
1686
+ };
1687
+ dehydrate = () => {
1688
+ return {
1689
+ state: {
1690
+ matchIds: this.state.matchIds,
1691
+ dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'invalid', 'preloadMaxAge', 'maxAge', 'id', 'loaderData', 'status', 'updatedAt']))
1617
1692
  }
1618
- invariant(_ctx, 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?');
1619
- const ctx = _ctx;
1620
- this.dehydratedData = ctx.payload;
1621
- this.options.hydrate?.(ctx.payload);
1622
- const dehydratedState = ctx.router.state;
1623
- let matches = this.matchRoutes(this.state.location.pathname, this.state.location.search).map(match => {
1624
- const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
1625
- invariant(dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
1626
- if (dehydratedMatch) {
1627
- return {
1628
- ...match,
1629
- ...dehydratedMatch
1630
- };
1631
- }
1632
- return match;
1633
- });
1634
- this.__store.setState(s => {
1693
+ };
1694
+ };
1695
+ hydrate = async __do_not_use_server_ctx => {
1696
+ let _ctx = __do_not_use_server_ctx;
1697
+ // Client hydrates from window
1698
+ if (typeof document !== 'undefined') {
1699
+ _ctx = window.__TSR_DEHYDRATED__;
1700
+ }
1701
+ invariant(_ctx, 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?');
1702
+ const ctx = _ctx;
1703
+ this.dehydratedData = ctx.payload;
1704
+ this.options.hydrate?.(ctx.payload);
1705
+ const dehydratedState = ctx.router.state;
1706
+ let matches = this.matchRoutes(this.state.location.pathname, this.state.location.search).map(match => {
1707
+ const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
1708
+ invariant(dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
1709
+ if (dehydratedMatch) {
1635
1710
  return {
1636
- ...s,
1637
- matchIds: dehydratedState.matchIds,
1638
- matches,
1639
- matchesById: this.#mergeMatches(s.matchesById, matches)
1711
+ ...match,
1712
+ ...dehydratedMatch
1640
1713
  };
1641
- });
1642
- };
1643
- injectedHtml = [];
1644
- injectHtml = async html => {
1645
- this.injectedHtml.push(html);
1646
- };
1647
- dehydrateData = (key, getData) => {
1648
- if (typeof document === 'undefined') {
1649
- const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1650
- this.injectHtml(async () => {
1651
- const id = `__TSR_DEHYDRATED__${strKey}`;
1652
- const data = typeof getData === 'function' ? await getData() : getData;
1653
- return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(strKey)}"] = ${JSON.stringify(data)}
1714
+ }
1715
+ return match;
1716
+ });
1717
+ this.__store.setState(s => {
1718
+ return {
1719
+ ...s,
1720
+ matchIds: dehydratedState.matchIds,
1721
+ matches,
1722
+ matchesById: this.#mergeMatches(s.matchesById, matches)
1723
+ };
1724
+ });
1725
+ };
1726
+ injectedHtml = [];
1727
+ injectHtml = async html => {
1728
+ this.injectedHtml.push(html);
1729
+ };
1730
+ dehydrateData = (key, getData) => {
1731
+ if (typeof document === 'undefined') {
1732
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1733
+ this.injectHtml(async () => {
1734
+ const id = `__TSR_DEHYDRATED__${strKey}`;
1735
+ const data = typeof getData === 'function' ? await getData() : getData;
1736
+ return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(strKey)}"] = ${JSON.stringify(data)}
1654
1737
  ;(() => {
1655
1738
  var el = document.getElementById('${id}')
1656
1739
  el.parentElement.removeChild(el)
1657
1740
  })()
1658
1741
  </script>`;
1742
+ });
1743
+ return () => this.hydrateData(key);
1744
+ }
1745
+ return () => undefined;
1746
+ };
1747
+ hydrateData = key => {
1748
+ if (typeof document !== 'undefined') {
1749
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1750
+ return window[`__TSR_DEHYDRATED__${strKey}`];
1751
+ }
1752
+ return undefined;
1753
+ };
1754
+
1755
+ // resolveMatchPromise = (matchId: string, key: string, value: any) => {
1756
+ // this.state.matches
1757
+ // .find((d) => d.id === matchId)
1758
+ // ?.__promisesByKey[key]?.resolve(value)
1759
+ // }
1760
+
1761
+ #processRoutes = routeTree => {
1762
+ this.routeTree = routeTree;
1763
+ this.routesById = {};
1764
+ this.routesByPath = {};
1765
+ this.flatRoutes = [];
1766
+ const recurseRoutes = routes => {
1767
+ routes.forEach((route, i) => {
1768
+ route.init({
1769
+ originalIndex: i,
1770
+ router: this
1659
1771
  });
1660
- return () => this.hydrateData(key);
1661
- }
1662
- return () => undefined;
1772
+ const existingRoute = this.routesById[route.id];
1773
+ invariant(!existingRoute, `Duplicate routes found with id: ${String(route.id)}`);
1774
+ this.routesById[route.id] = route;
1775
+ if (!route.isRoot && route.path) {
1776
+ const trimmedFullPath = trimPathRight(route.fullPath);
1777
+ if (!this.routesByPath[trimmedFullPath] || route.fullPath.endsWith('/')) {
1778
+ this.routesByPath[trimmedFullPath] = route;
1779
+ }
1780
+ }
1781
+ const children = route.children;
1782
+ if (children?.length) {
1783
+ recurseRoutes(children);
1784
+ }
1785
+ });
1663
1786
  };
1664
- hydrateData = key => {
1665
- if (typeof document !== 'undefined') {
1666
- const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1667
- return window[`__TSR_DEHYDRATED__${strKey}`];
1787
+ recurseRoutes([routeTree]);
1788
+ this.flatRoutes = Object.values(this.routesByPath).map((d, i) => {
1789
+ const trimmed = trimPath(d.fullPath);
1790
+ const parsed = parsePathname(trimmed);
1791
+ while (parsed.length > 1 && parsed[0]?.value === '/') {
1792
+ parsed.shift();
1668
1793
  }
1669
- return undefined;
1670
- };
1671
-
1672
- // resolveMatchPromise = (matchId: string, key: string, value: any) => {
1673
- // this.state.matches
1674
- // .find((d) => d.id === matchId)
1675
- // ?.__promisesByKey[key]?.resolve(value)
1676
- // }
1677
-
1678
- #processRoutes = routeTree => {
1679
- this.routeTree = routeTree;
1680
- this.routesById = {};
1681
- this.routesByPath = {};
1682
- this.flatRoutes = [];
1683
- const recurseRoutes = routes => {
1684
- routes.forEach((route, i) => {
1685
- route.init({
1686
- originalIndex: i,
1687
- router: this
1688
- });
1689
- const existingRoute = this.routesById[route.id];
1690
- invariant(!existingRoute, `Duplicate routes found with id: ${String(route.id)}`);
1691
- this.routesById[route.id] = route;
1692
- if (!route.isRoot && route.path) {
1693
- const trimmedFullPath = trimPathRight(route.fullPath);
1694
- if (!this.routesByPath[trimmedFullPath] || route.fullPath.endsWith('/')) {
1695
- this.routesByPath[trimmedFullPath] = route;
1696
- }
1697
- }
1698
- const children = route.children;
1699
- if (children?.length) {
1700
- recurseRoutes(children);
1701
- }
1702
- });
1703
- };
1704
- recurseRoutes([routeTree]);
1705
- this.flatRoutes = Object.values(this.routesByPath).map((d, i) => {
1706
- const trimmed = trimPath(d.fullPath);
1707
- const parsed = parsePathname(trimmed);
1708
- while (parsed.length > 1 && parsed[0]?.value === '/') {
1709
- parsed.shift();
1794
+ const score = parsed.map(d => {
1795
+ if (d.type === 'param') {
1796
+ return 0.5;
1710
1797
  }
1711
- const score = parsed.map(d => {
1712
- if (d.type === 'param') {
1713
- return 0.5;
1714
- }
1715
- if (d.type === 'wildcard') {
1716
- return 0.25;
1717
- }
1718
- return 1;
1719
- });
1720
- return {
1721
- child: d,
1722
- trimmed,
1723
- parsed,
1724
- index: i,
1725
- score
1726
- };
1727
- }).sort((a, b) => {
1728
- let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0;
1729
- if (isIndex !== 0) return isIndex;
1730
- const length = Math.min(a.score.length, b.score.length);
1731
-
1732
- // Sort by length of score
1733
- if (a.score.length !== b.score.length) {
1734
- return b.score.length - a.score.length;
1798
+ if (d.type === 'wildcard') {
1799
+ return 0.25;
1735
1800
  }
1801
+ return 1;
1802
+ });
1803
+ return {
1804
+ child: d,
1805
+ trimmed,
1806
+ parsed,
1807
+ index: i,
1808
+ score
1809
+ };
1810
+ }).sort((a, b) => {
1811
+ let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0;
1812
+ if (isIndex !== 0) return isIndex;
1813
+ const length = Math.min(a.score.length, b.score.length);
1814
+
1815
+ // Sort by length of score
1816
+ if (a.score.length !== b.score.length) {
1817
+ return b.score.length - a.score.length;
1818
+ }
1736
1819
 
1737
- // Sort by min available score
1738
- for (let i = 0; i < length; i++) {
1739
- if (a.score[i] !== b.score[i]) {
1740
- return b.score[i] - a.score[i];
1741
- }
1820
+ // Sort by min available score
1821
+ for (let i = 0; i < length; i++) {
1822
+ if (a.score[i] !== b.score[i]) {
1823
+ return b.score[i] - a.score[i];
1742
1824
  }
1825
+ }
1743
1826
 
1744
- // Sort by min available parsed value
1745
- for (let i = 0; i < length; i++) {
1746
- if (a.parsed[i].value !== b.parsed[i].value) {
1747
- return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
1748
- }
1827
+ // Sort by min available parsed value
1828
+ for (let i = 0; i < length; i++) {
1829
+ if (a.parsed[i].value !== b.parsed[i].value) {
1830
+ return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
1749
1831
  }
1832
+ }
1750
1833
 
1751
- // Sort by length of trimmed full path
1752
- if (a.trimmed !== b.trimmed) {
1753
- return a.trimmed > b.trimmed ? 1 : -1;
1754
- }
1834
+ // Sort by length of trimmed full path
1835
+ if (a.trimmed !== b.trimmed) {
1836
+ return a.trimmed > b.trimmed ? 1 : -1;
1837
+ }
1755
1838
 
1756
- // Sort by original index
1757
- return a.index - b.index;
1758
- }).map((d, i) => {
1759
- d.child.rank = i;
1760
- return d.child;
1761
- });
1839
+ // Sort by original index
1840
+ return a.index - b.index;
1841
+ }).map((d, i) => {
1842
+ d.child.rank = i;
1843
+ return d.child;
1844
+ });
1845
+ };
1846
+ #parseLocation = previousLocation => {
1847
+ const parse = ({
1848
+ pathname,
1849
+ search,
1850
+ hash,
1851
+ state
1852
+ }) => {
1853
+ const parsedSearch = this.options.parseSearch(search);
1854
+ return {
1855
+ pathname: pathname,
1856
+ searchStr: search,
1857
+ search: replaceEqualDeep(previousLocation?.search, parsedSearch),
1858
+ hash: hash.split('#').reverse()[0] ?? '',
1859
+ href: `${pathname}${search}${hash}`,
1860
+ state: replaceEqualDeep(previousLocation?.state, state)
1861
+ };
1762
1862
  };
1763
- #parseLocation = previousLocation => {
1764
- const parse = ({
1863
+ const location = parse(this.history.location);
1864
+ let {
1865
+ __tempLocation,
1866
+ __tempKey
1867
+ } = location.state;
1868
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
1869
+ // Sync up the location keys
1870
+ const parsedTempLocation = parse(__tempLocation);
1871
+ parsedTempLocation.state.key = location.state.key;
1872
+ delete parsedTempLocation.state.__tempLocation;
1873
+ return {
1874
+ ...parsedTempLocation,
1875
+ maskedLocation: location
1876
+ };
1877
+ }
1878
+ return location;
1879
+ };
1880
+ buildLocation = (opts = {}) => {
1881
+ const build = (dest = {}, matches) => {
1882
+ const from = this.state.location;
1883
+ const fromPathname = dest.from ?? from.pathname;
1884
+ let pathname = resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
1885
+ const fromMatches = this.matchRoutes(fromPathname, from.search);
1886
+ const prevParams = {
1887
+ ...last(fromMatches)?.params
1888
+ };
1889
+ let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
1890
+ if (nextParams) {
1891
+ matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
1892
+ nextParams = {
1893
+ ...nextParams,
1894
+ ...fn(nextParams)
1895
+ };
1896
+ });
1897
+ }
1898
+ pathname = interpolatePath(pathname, nextParams ?? {});
1899
+ const preSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
1900
+ const postSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
1901
+
1902
+ // Pre filters first
1903
+ const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), from.search) : from.search;
1904
+
1905
+ // Then the link/navigate function
1906
+ const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
1907
+ : dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1908
+ : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
1909
+ : {};
1910
+
1911
+ // Then post filters
1912
+ const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1913
+ const search = replaceEqualDeep(from.search, postFilteredSearch);
1914
+ const searchStr = this.options.stringifySearch(search);
1915
+ const hash = dest.hash === true ? from.hash : dest.hash ? functionalUpdate(dest.hash, from.hash) : from.hash;
1916
+ const hashStr = hash ? `#${hash}` : '';
1917
+ let nextState = dest.state === true ? from.state : dest.state ? functionalUpdate(dest.state, from.state) : from.state;
1918
+ nextState = replaceEqualDeep(from.state, nextState);
1919
+ return {
1765
1920
  pathname,
1766
1921
  search,
1922
+ searchStr,
1923
+ state: nextState,
1767
1924
  hash,
1768
- state
1769
- }) => {
1770
- const parsedSearch = this.options.parseSearch(search);
1771
- return {
1772
- pathname: pathname,
1773
- searchStr: search,
1774
- search: replaceEqualDeep(previousLocation?.search, parsedSearch),
1775
- hash: hash.split('#').reverse()[0] ?? '',
1776
- href: `${pathname}${search}${hash}`,
1777
- state: replaceEqualDeep(previousLocation?.state, state)
1778
- };
1925
+ href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1926
+ unmaskOnReload: dest.unmaskOnReload
1779
1927
  };
1780
- const location = parse(this.history.location);
1781
- let {
1782
- __tempLocation,
1783
- __tempKey
1784
- } = location.state;
1785
- if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
1786
- // Sync up the location keys
1787
- const parsedTempLocation = parse(__tempLocation);
1788
- parsedTempLocation.state.key = location.state.key;
1789
- delete parsedTempLocation.state.__tempLocation;
1790
- return {
1791
- ...parsedTempLocation,
1792
- maskedLocation: location
1793
- };
1794
- }
1795
- return location;
1796
1928
  };
1797
- buildLocation = (opts = {}) => {
1798
- const build = (dest = {}, matches) => {
1799
- const from = this.state.location;
1800
- const fromPathname = dest.from ?? from.pathname;
1801
- let pathname = resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
1802
- const fromMatches = this.matchRoutes(fromPathname, from.search);
1803
- const prevParams = {
1804
- ...last(fromMatches)?.params
1805
- };
1806
- let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
1807
- if (nextParams) {
1808
- matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
1809
- nextParams = {
1810
- ...nextParams,
1811
- ...fn(nextParams)
1812
- };
1813
- });
1814
- }
1815
- pathname = interpolatePath(pathname, nextParams ?? {});
1816
- const preSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
1817
- const postSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
1818
-
1819
- // Pre filters first
1820
- const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), from.search) : from.search;
1821
-
1822
- // Then the link/navigate function
1823
- const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
1824
- : dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1825
- : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
1826
- : {};
1827
-
1828
- // Then post filters
1829
- const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1830
- const search = replaceEqualDeep(from.search, postFilteredSearch);
1831
- const searchStr = this.options.stringifySearch(search);
1832
- const hash = dest.hash === true ? from.hash : dest.hash ? functionalUpdate(dest.hash, from.hash) : from.hash;
1833
- const hashStr = hash ? `#${hash}` : '';
1834
- let nextState = dest.state === true ? from.state : dest.state ? functionalUpdate(dest.state, from.state) : from.state;
1835
- nextState = replaceEqualDeep(from.state, nextState);
1836
- return {
1837
- pathname,
1838
- search,
1839
- searchStr,
1840
- state: nextState,
1841
- hash,
1842
- href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1843
- unmaskOnReload: dest.unmaskOnReload
1844
- };
1845
- };
1846
- const buildWithMatches = (dest = {}, maskedDest) => {
1847
- let next = build(dest);
1848
- let maskedNext = maskedDest ? build(maskedDest) : undefined;
1849
- if (!maskedNext) {
1850
- let params = {};
1851
- let foundMask = this.options.routeMasks?.find(d => {
1852
- const match = matchPathname(this.basepath, next.pathname, {
1853
- to: d.from,
1854
- fuzzy: false
1855
- });
1856
- if (match) {
1857
- params = match;
1858
- return true;
1859
- }
1860
- return false;
1929
+ const buildWithMatches = (dest = {}, maskedDest) => {
1930
+ let next = build(dest);
1931
+ let maskedNext = maskedDest ? build(maskedDest) : undefined;
1932
+ if (!maskedNext) {
1933
+ let params = {};
1934
+ let foundMask = this.options.routeMasks?.find(d => {
1935
+ const match = matchPathname(this.basepath, next.pathname, {
1936
+ to: d.from,
1937
+ fuzzy: false
1861
1938
  });
1862
- if (foundMask) {
1863
- foundMask = {
1864
- ...foundMask,
1865
- from: interpolatePath(foundMask.from, params)
1866
- };
1867
- maskedDest = foundMask;
1868
- maskedNext = build(maskedDest);
1939
+ if (match) {
1940
+ params = match;
1941
+ return true;
1869
1942
  }
1870
- }
1871
- const nextMatches = this.matchRoutes(next.pathname, next.search);
1872
- const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
1873
- const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
1874
- const final = build(dest, nextMatches);
1875
- if (maskedFinal) {
1876
- final.maskedLocation = maskedFinal;
1877
- }
1878
- return final;
1879
- };
1880
- if (opts.mask) {
1881
- return buildWithMatches(opts, {
1882
- ...pick(opts, ['from']),
1883
- ...opts.mask
1943
+ return false;
1884
1944
  });
1945
+ if (foundMask) {
1946
+ foundMask = {
1947
+ ...foundMask,
1948
+ from: interpolatePath(foundMask.from, params)
1949
+ };
1950
+ maskedDest = foundMask;
1951
+ maskedNext = build(maskedDest);
1952
+ }
1953
+ }
1954
+ const nextMatches = this.matchRoutes(next.pathname, next.search);
1955
+ const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
1956
+ const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
1957
+ const final = build(dest, nextMatches);
1958
+ if (maskedFinal) {
1959
+ final.maskedLocation = maskedFinal;
1885
1960
  }
1886
- return buildWithMatches(opts);
1961
+ return final;
1887
1962
  };
1888
- #buildAndCommitLocation = ({
1889
- replace,
1890
- resetScroll,
1891
- ...rest
1892
- } = {}) => {
1893
- const location = this.buildLocation(rest);
1894
- return this.#commitLocation({
1895
- ...location,
1896
- replace,
1897
- resetScroll
1963
+ if (opts.mask) {
1964
+ return buildWithMatches(opts, {
1965
+ ...pick(opts, ['from']),
1966
+ ...opts.mask
1898
1967
  });
1899
- };
1900
- #commitLocation = async next => {
1901
- if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
1902
- let nextAction = 'replace';
1903
- if (!next.replace) {
1904
- nextAction = 'push';
1905
- }
1906
- const isSameUrl = this.state.location.href === next.href;
1907
- if (isSameUrl) {
1908
- nextAction = 'replace';
1909
- }
1910
- let {
1911
- maskedLocation,
1912
- ...nextHistory
1913
- } = next;
1914
- if (maskedLocation) {
1915
- nextHistory = {
1916
- ...maskedLocation,
1917
- state: {
1918
- ...maskedLocation.state,
1919
- __tempKey: undefined,
1920
- __tempLocation: {
1921
- ...nextHistory,
1922
- search: nextHistory.searchStr,
1923
- state: {
1924
- ...nextHistory.state,
1925
- __tempKey: undefined,
1926
- __tempLocation: undefined,
1927
- key: undefined
1928
- }
1968
+ }
1969
+ return buildWithMatches(opts);
1970
+ };
1971
+ #buildAndCommitLocation = ({
1972
+ replace,
1973
+ resetScroll,
1974
+ ...rest
1975
+ } = {}) => {
1976
+ const location = this.buildLocation(rest);
1977
+ return this.#commitLocation({
1978
+ ...location,
1979
+ replace,
1980
+ resetScroll
1981
+ });
1982
+ };
1983
+ #commitLocation = async next => {
1984
+ if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
1985
+ let nextAction = 'replace';
1986
+ if (!next.replace) {
1987
+ nextAction = 'push';
1988
+ }
1989
+ const isSameUrl = this.state.location.href === next.href;
1990
+ if (isSameUrl) {
1991
+ nextAction = 'replace';
1992
+ }
1993
+ let {
1994
+ maskedLocation,
1995
+ ...nextHistory
1996
+ } = next;
1997
+ if (maskedLocation) {
1998
+ nextHistory = {
1999
+ ...maskedLocation,
2000
+ state: {
2001
+ ...maskedLocation.state,
2002
+ __tempKey: undefined,
2003
+ __tempLocation: {
2004
+ ...nextHistory,
2005
+ search: nextHistory.searchStr,
2006
+ state: {
2007
+ ...nextHistory.state,
2008
+ __tempKey: undefined,
2009
+ __tempLocation: undefined,
2010
+ key: undefined
1929
2011
  }
1930
2012
  }
1931
- };
1932
- if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
1933
- nextHistory.state.__tempKey = this.tempLocationKey;
1934
2013
  }
2014
+ };
2015
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
2016
+ nextHistory.state.__tempKey = this.tempLocationKey;
1935
2017
  }
1936
- this.history[nextAction === 'push' ? 'push' : 'replace'](nextHistory.href, nextHistory.state);
1937
- this.resetNextScroll = next.resetScroll ?? true;
1938
- return this.latestLoadPromise;
1939
- };
1940
- getRouteMatch = id => {
1941
- return this.state.matchesById[id];
1942
- };
1943
- setRouteMatch = (id, updater) => {
1944
- this.__store.setState(prev => {
1945
- if (!prev.matchesById[id]) {
1946
- return prev;
2018
+ }
2019
+ this.history[nextAction === 'push' ? 'push' : 'replace'](nextHistory.href, nextHistory.state);
2020
+ this.resetNextScroll = next.resetScroll ?? true;
2021
+ return this.latestLoadPromise;
2022
+ };
2023
+ getRouteMatch = id => {
2024
+ return this.state.matchesById[id];
2025
+ };
2026
+ setRouteMatch = (id, updater) => {
2027
+ this.__store.setState(prev => {
2028
+ if (!prev.matchesById[id]) {
2029
+ return prev;
2030
+ }
2031
+ return {
2032
+ ...prev,
2033
+ matchesById: {
2034
+ ...prev.matchesById,
2035
+ [id]: updater(prev.matchesById[id])
1947
2036
  }
1948
- return {
1949
- ...prev,
1950
- matchesById: {
1951
- ...prev.matchesById,
1952
- [id]: updater(prev.matchesById[id])
1953
- }
1954
- };
1955
- });
1956
- };
1957
- setRouteMatchData = (id, updater, opts) => {
1958
- const match = this.getRouteMatch(id);
1959
- if (!match) return;
1960
- const route = this.getRoute(match.routeId);
1961
- const updatedAt = opts?.updatedAt ?? Date.now();
1962
- const preloadMaxAge = opts?.maxAge ?? route.options.preloadMaxAge ?? this.options.defaultPreloadMaxAge ?? 5000;
1963
- const maxAge = opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? -1;
1964
- this.setRouteMatch(id, s => ({
2037
+ };
2038
+ });
2039
+ };
2040
+ setRouteMatchData = (id, updater, opts) => {
2041
+ const match = this.getRouteMatch(id);
2042
+ if (!match) return;
2043
+ const route = this.getRoute(match.routeId);
2044
+ const updatedAt = opts?.updatedAt ?? Date.now();
2045
+ const preloadMaxAge = opts?.maxAge ?? route.options.preloadMaxAge ?? this.options.defaultPreloadMaxAge ?? 5000;
2046
+ const maxAge = opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? -1;
2047
+ this.setRouteMatch(id, s => ({
2048
+ ...s,
2049
+ error: undefined,
2050
+ status: 'success',
2051
+ isFetching: false,
2052
+ updatedAt: updatedAt,
2053
+ loaderData: functionalUpdate(updater, s.loaderData),
2054
+ preloadMaxAge,
2055
+ maxAge
2056
+ }));
2057
+ };
2058
+ invalidate = async opts => {
2059
+ if (opts?.matchId) {
2060
+ this.setRouteMatch(opts.matchId, s => ({
1965
2061
  ...s,
1966
- error: undefined,
1967
- status: 'success',
1968
- isFetching: false,
1969
- updatedAt: updatedAt,
1970
- loaderData: functionalUpdate(updater, s.loaderData),
1971
- preloadMaxAge,
1972
- maxAge
2062
+ invalid: true
1973
2063
  }));
1974
- };
1975
- invalidate = async opts => {
1976
- if (opts?.matchId) {
1977
- this.setRouteMatch(opts.matchId, s => ({
1978
- ...s,
1979
- invalid: true
1980
- }));
1981
- const matchIndex = this.state.matches.findIndex(d => d.id === opts.matchId);
1982
- const childMatch = this.state.matches[matchIndex + 1];
1983
- if (childMatch) {
1984
- return this.invalidate({
1985
- matchId: childMatch.id,
1986
- reload: false,
1987
- __fromFocus: opts.__fromFocus
1988
- });
1989
- }
1990
- } else {
1991
- this.__store.batch(() => {
1992
- Object.values(this.state.matchesById).forEach(match => {
1993
- const route = this.getRoute(match.routeId);
1994
- const shouldInvalidate = opts?.__fromFocus ? route.options.reloadOnWindowFocus ?? true : true;
1995
- if (shouldInvalidate) {
1996
- this.setRouteMatch(match.id, s => ({
1997
- ...s,
1998
- invalid: true
1999
- }));
2000
- }
2001
- });
2064
+ const matchIndex = this.state.matches.findIndex(d => d.id === opts.matchId);
2065
+ const childMatch = this.state.matches[matchIndex + 1];
2066
+ if (childMatch) {
2067
+ return this.invalidate({
2068
+ matchId: childMatch.id,
2069
+ reload: false,
2070
+ __fromFocus: opts.__fromFocus
2002
2071
  });
2003
2072
  }
2004
- if (opts?.reload ?? true) {
2005
- return this.load();
2006
- }
2007
- };
2008
- }
2009
-
2010
- // Detect if we're in the DOM
2011
- const isServer = typeof window === 'undefined' || !window.document.createElement;
2012
- function getInitialRouterState() {
2013
- return {
2014
- status: 'idle',
2015
- isFetching: false,
2016
- resolvedLocation: null,
2017
- location: null,
2018
- matchesById: {},
2019
- matchIds: [],
2020
- pendingMatchIds: [],
2021
- matches: [],
2022
- pendingMatches: [],
2023
- renderedMatchIds: [],
2024
- renderedMatches: [],
2025
- lastUpdated: Date.now()
2026
- };
2027
- }
2028
- function isCtrlEvent(e) {
2029
- return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
2030
- }
2031
- function redirect(opts) {
2032
- opts.isRedirect = true;
2033
- return opts;
2073
+ } else {
2074
+ this.__store.batch(() => {
2075
+ Object.values(this.state.matchesById).forEach(match => {
2076
+ const route = this.getRoute(match.routeId);
2077
+ const shouldInvalidate = opts?.__fromFocus ? route.options.reloadOnWindowFocus ?? true : true;
2078
+ if (shouldInvalidate) {
2079
+ this.setRouteMatch(match.id, s => ({
2080
+ ...s,
2081
+ invalid: true
2082
+ }));
2083
+ }
2084
+ });
2085
+ });
2086
+ }
2087
+ if (opts?.reload ?? true) {
2088
+ return this.load();
2089
+ }
2090
+ };
2091
+ }
2092
+
2093
+ // Detect if we're in the DOM
2094
+ const isServer = typeof window === 'undefined' || !window.document.createElement;
2095
+ function getInitialRouterState() {
2096
+ return {
2097
+ status: 'idle',
2098
+ isFetching: false,
2099
+ resolvedLocation: null,
2100
+ location: null,
2101
+ matchesById: {},
2102
+ matchIds: [],
2103
+ pendingMatchIds: [],
2104
+ matches: [],
2105
+ pendingMatches: [],
2106
+ renderedMatchIds: [],
2107
+ renderedMatches: [],
2108
+ lastUpdated: Date.now()
2109
+ };
2110
+ }
2111
+ function isCtrlEvent(e) {
2112
+ return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
2113
+ }
2114
+ function redirect(opts) {
2115
+ opts.isRedirect = true;
2116
+ return opts;
2117
+ }
2118
+ function isRedirect(obj) {
2119
+ return !!obj?.isRedirect;
2120
+ }
2121
+ class SearchParamError extends Error {}
2122
+ class PathParamError extends Error {}
2123
+ function escapeJSON(jsonString) {
2124
+ return jsonString.replace(/\\/g, '\\\\') // Escape backslashes
2125
+ .replace(/'/g, "\\'") // Escape single quotes
2126
+ .replace(/"/g, '\\"'); // Escape double quotes
2127
+ }
2128
+
2129
+ // A function that takes an import() argument which is a function and returns a new function that will
2130
+ // proxy arguments from the caller to the imported function, retaining all type
2131
+ // information along the way
2132
+ function lazyFn(fn, key) {
2133
+ return async (...args) => {
2134
+ const imported = await fn();
2135
+ return imported[key || 'default'](...args);
2136
+ };
2137
+ }
2138
+ function isMatchInvalid(match, opts) {
2139
+ const now = Date.now();
2140
+ if (match.invalid) {
2141
+ return true;
2034
2142
  }
2035
- function isRedirect(obj) {
2036
- return !!obj?.isRedirect;
2143
+ if (opts?.preload) {
2144
+ return match.preloadMaxAge < 0 ? false : match.updatedAt + match.preloadMaxAge < now;
2037
2145
  }
2038
- class SearchParamError extends Error {}
2039
- class PathParamError extends Error {}
2040
- function escapeJSON(jsonString) {
2041
- return jsonString.replace(/\\/g, '\\\\') // Escape backslashes
2042
- .replace(/'/g, "\\'") // Escape single quotes
2043
- .replace(/"/g, '\\"'); // Escape double quotes
2146
+ return match.maxAge < 0 ? false : match.updatedAt + match.maxAge < now;
2147
+ }
2148
+
2149
+ const windowKey = 'window';
2150
+ const delimiter = '___';
2151
+ let weakScrolledElementsByRestoreKey = {};
2152
+ let cache;
2153
+ let pathDidChange = false;
2154
+ const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
2155
+ const defaultGetKey = location => location.state.key;
2156
+ function watchScrollPositions(router, opts) {
2157
+ const getKey = opts?.getKey || defaultGetKey;
2158
+ if (sessionsStorage) {
2159
+ if (!cache) {
2160
+ cache = (() => {
2161
+ const storageKey = 'tsr-scroll-restoration-v1';
2162
+ const current = JSON.parse(window.sessionStorage.getItem(storageKey) || '{}');
2163
+ return {
2164
+ current,
2165
+ set: (key, value) => {
2166
+ current[key] = value;
2167
+ window.sessionStorage.setItem(storageKey, JSON.stringify(cache));
2168
+ }
2169
+ };
2170
+ })();
2171
+ }
2044
2172
  }
2045
-
2046
- // A function that takes an import() argument which is a function and returns a new function that will
2047
- // proxy arguments from the caller to the imported function, retaining all type
2048
- // information along the way
2049
- function lazyFn(fn, key) {
2050
- return async (...args) => {
2051
- const imported = await fn();
2052
- return imported[key || 'default'](...args);
2053
- };
2173
+ const {
2174
+ history
2175
+ } = window;
2176
+ if (history.scrollRestoration) {
2177
+ history.scrollRestoration = 'manual';
2054
2178
  }
2055
- function isMatchInvalid(match, opts) {
2056
- const now = Date.now();
2057
- if (match.invalid) {
2058
- return true;
2059
- }
2060
- if (opts?.preload) {
2061
- return match.preloadMaxAge < 0 ? false : match.updatedAt + match.preloadMaxAge < now;
2179
+ const onScroll = event => {
2180
+ const restoreKey = getKey(router.state.resolvedLocation);
2181
+ if (!weakScrolledElementsByRestoreKey[restoreKey]) {
2182
+ weakScrolledElementsByRestoreKey[restoreKey] = new WeakSet();
2062
2183
  }
2063
- return match.maxAge < 0 ? false : match.updatedAt + match.maxAge < now;
2064
- }
2065
-
2066
- const windowKey = 'window';
2067
- const delimiter = '___';
2068
- let weakScrolledElementsByRestoreKey = {};
2069
- let cache;
2070
- let pathDidChange = false;
2071
- const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
2072
- const defaultGetKey = location => location.state.key;
2073
- function watchScrollPositions(router, opts) {
2074
- const getKey = opts?.getKey || defaultGetKey;
2075
- if (sessionsStorage) {
2076
- if (!cache) {
2077
- cache = (() => {
2078
- const storageKey = 'tsr-scroll-restoration-v1';
2079
- const current = JSON.parse(window.sessionStorage.getItem(storageKey) || '{}');
2080
- return {
2081
- current,
2082
- set: (key, value) => {
2083
- current[key] = value;
2084
- window.sessionStorage.setItem(storageKey, JSON.stringify(cache));
2085
- }
2086
- };
2087
- })();
2088
- }
2184
+ const set = weakScrolledElementsByRestoreKey[restoreKey];
2185
+ if (set.has(event.target)) return;
2186
+ set.add(event.target);
2187
+ const cacheKey = [restoreKey, event.target === document || event.target === window ? windowKey : getCssSelector(event.target)].join(delimiter);
2188
+ if (!cache.current[cacheKey]) {
2189
+ cache.set(cacheKey, {
2190
+ scrollX: NaN,
2191
+ scrollY: NaN
2192
+ });
2089
2193
  }
2090
- const {
2091
- history
2092
- } = window;
2093
- if (history.scrollRestoration) {
2094
- history.scrollRestoration = 'manual';
2194
+ };
2195
+ const getCssSelector = el => {
2196
+ let path = [],
2197
+ parent;
2198
+ while (parent = el.parentNode) {
2199
+ path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
2200
+ el = parent;
2095
2201
  }
2096
- const onScroll = event => {
2097
- const restoreKey = getKey(router.state.resolvedLocation);
2098
- if (!weakScrolledElementsByRestoreKey[restoreKey]) {
2099
- weakScrolledElementsByRestoreKey[restoreKey] = new WeakSet();
2100
- }
2101
- const set = weakScrolledElementsByRestoreKey[restoreKey];
2102
- if (set.has(event.target)) return;
2103
- set.add(event.target);
2104
- const cacheKey = [restoreKey, event.target === document || event.target === window ? windowKey : getCssSelector(event.target)].join(delimiter);
2105
- if (!cache.current[cacheKey]) {
2106
- cache.set(cacheKey, {
2107
- scrollX: NaN,
2108
- scrollY: NaN
2109
- });
2110
- }
2111
- };
2112
- const getCssSelector = el => {
2113
- let path = [],
2114
- parent;
2115
- while (parent = el.parentNode) {
2116
- path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
2117
- el = parent;
2118
- }
2119
- return `${path.join(' > ')}`.toLowerCase();
2120
- };
2121
- const onPathWillChange = from => {
2122
- const restoreKey = getKey(from);
2123
- for (const cacheKey in cache.current) {
2124
- const entry = cache.current[cacheKey];
2125
- const [key, elementSelector] = cacheKey.split(delimiter);
2126
- if (restoreKey === key) {
2127
- if (elementSelector === windowKey) {
2128
- entry.scrollX = window.scrollX || 0;
2129
- entry.scrollY = window.scrollY || 0;
2130
- } else if (elementSelector) {
2131
- const element = document.querySelector(elementSelector);
2132
- entry.scrollX = element?.scrollLeft || 0;
2133
- entry.scrollY = element?.scrollTop || 0;
2134
- }
2135
- cache.set(cacheKey, entry);
2202
+ return `${path.join(' > ')}`.toLowerCase();
2203
+ };
2204
+ const onPathWillChange = from => {
2205
+ const restoreKey = getKey(from);
2206
+ for (const cacheKey in cache.current) {
2207
+ const entry = cache.current[cacheKey];
2208
+ const [key, elementSelector] = cacheKey.split(delimiter);
2209
+ if (restoreKey === key) {
2210
+ if (elementSelector === windowKey) {
2211
+ entry.scrollX = window.scrollX || 0;
2212
+ entry.scrollY = window.scrollY || 0;
2213
+ } else if (elementSelector) {
2214
+ const element = document.querySelector(elementSelector);
2215
+ entry.scrollX = element?.scrollLeft || 0;
2216
+ entry.scrollY = element?.scrollTop || 0;
2136
2217
  }
2218
+ cache.set(cacheKey, entry);
2137
2219
  }
2138
- };
2139
- const onPathChange = () => {
2140
- pathDidChange = true;
2141
- };
2142
- if (typeof document !== 'undefined') {
2143
- document.addEventListener('scroll', onScroll, true);
2144
2220
  }
2145
- const unsubOnBeforeLoad = router.subscribe('onBeforeLoad', event => {
2146
- if (event.pathChanged) onPathWillChange(event.from);
2147
- });
2148
- const unsubOnLoad = router.subscribe('onLoad', event => {
2149
- if (event.pathChanged) onPathChange();
2150
- });
2151
- return () => {
2152
- document.removeEventListener('scroll', onScroll);
2153
- unsubOnBeforeLoad();
2154
- unsubOnLoad();
2155
- };
2221
+ };
2222
+ const onPathChange = () => {
2223
+ pathDidChange = true;
2224
+ };
2225
+ if (typeof document !== 'undefined') {
2226
+ document.addEventListener('scroll', onScroll, true);
2156
2227
  }
2157
- function restoreScrollPositions(router, opts) {
2158
- if (pathDidChange) {
2159
- if (!router.resetNextScroll) {
2160
- return;
2161
- }
2162
- const getKey = opts?.getKey || defaultGetKey;
2163
- pathDidChange = false;
2164
- const restoreKey = getKey(router.state.location);
2165
- let windowRestored = false;
2166
- for (const cacheKey in cache.current) {
2167
- const entry = cache.current[cacheKey];
2168
- const [key, elementSelector] = cacheKey.split(delimiter);
2169
- if (key === restoreKey) {
2170
- if (elementSelector === windowKey) {
2171
- windowRestored = true;
2172
- window.scrollTo(entry.scrollX, entry.scrollY);
2173
- } else if (elementSelector) {
2174
- const element = document.querySelector(elementSelector);
2175
- if (element) {
2176
- element.scrollLeft = entry.scrollX;
2177
- element.scrollTop = entry.scrollY;
2178
- }
2228
+ const unsubOnBeforeLoad = router.subscribe('onBeforeLoad', event => {
2229
+ if (event.pathChanged) onPathWillChange(event.from);
2230
+ });
2231
+ const unsubOnLoad = router.subscribe('onLoad', event => {
2232
+ if (event.pathChanged) onPathChange();
2233
+ });
2234
+ return () => {
2235
+ document.removeEventListener('scroll', onScroll);
2236
+ unsubOnBeforeLoad();
2237
+ unsubOnLoad();
2238
+ };
2239
+ }
2240
+ function restoreScrollPositions(router, opts) {
2241
+ if (pathDidChange) {
2242
+ if (!router.resetNextScroll) {
2243
+ return;
2244
+ }
2245
+ const getKey = opts?.getKey || defaultGetKey;
2246
+ pathDidChange = false;
2247
+ const restoreKey = getKey(router.state.location);
2248
+ let windowRestored = false;
2249
+ for (const cacheKey in cache.current) {
2250
+ const entry = cache.current[cacheKey];
2251
+ const [key, elementSelector] = cacheKey.split(delimiter);
2252
+ if (key === restoreKey) {
2253
+ if (elementSelector === windowKey) {
2254
+ windowRestored = true;
2255
+ window.scrollTo(entry.scrollX, entry.scrollY);
2256
+ } else if (elementSelector) {
2257
+ const element = document.querySelector(elementSelector);
2258
+ if (element) {
2259
+ element.scrollLeft = entry.scrollX;
2260
+ element.scrollTop = entry.scrollY;
2179
2261
  }
2180
2262
  }
2181
2263
  }
2182
- if (!windowRestored) {
2183
- window.scrollTo(0, 0);
2184
- }
2185
2264
  }
2186
- }
2187
-
2188
- function defer(_promise) {
2189
- const promise = _promise;
2190
- if (!promise.__deferredState) {
2191
- promise.__deferredState = {
2192
- uid: Math.random().toString(36).slice(2),
2193
- status: 'pending'
2194
- };
2195
- const state = promise.__deferredState;
2196
- promise.then(data => {
2197
- state.status = 'success';
2198
- state.data = data;
2199
- }).catch(error => {
2200
- state.status = 'error';
2201
- state.error = error;
2202
- });
2265
+ if (!windowRestored) {
2266
+ window.scrollTo(0, 0);
2203
2267
  }
2204
- return promise;
2205
2268
  }
2206
- function isDehydratedDeferred(obj) {
2207
- return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
2269
+ }
2270
+
2271
+ function defer(_promise) {
2272
+ const promise = _promise;
2273
+ if (!promise.__deferredState) {
2274
+ promise.__deferredState = {
2275
+ uid: Math.random().toString(36).slice(2),
2276
+ status: 'pending'
2277
+ };
2278
+ const state = promise.__deferredState;
2279
+ promise.then(data => {
2280
+ state.status = 'success';
2281
+ state.data = data;
2282
+ }).catch(error => {
2283
+ state.status = 'error';
2284
+ state.error = error;
2285
+ });
2208
2286
  }
2209
-
2210
- exports.FileRoute = FileRoute;
2211
- exports.PathParamError = PathParamError;
2212
- exports.RootRoute = RootRoute;
2213
- exports.Route = Route;
2214
- exports.Router = Router;
2215
- exports.RouterContext = RouterContext;
2216
- exports.SearchParamError = SearchParamError;
2217
- exports.cleanPath = cleanPath;
2218
- exports.componentTypes = componentTypes;
2219
- exports.createBrowserHistory = createBrowserHistory;
2220
- exports.createHashHistory = createHashHistory;
2221
- exports.createMemoryHistory = createMemoryHistory;
2222
- exports.createRouteMask = createRouteMask;
2223
- exports.decode = decode;
2224
- exports.defaultParseSearch = defaultParseSearch;
2225
- exports.defaultStringifySearch = defaultStringifySearch;
2226
- exports.defer = defer;
2227
- exports.encode = encode;
2228
- exports.functionalUpdate = functionalUpdate;
2229
- exports.interpolatePath = interpolatePath;
2230
- exports.invariant = invariant;
2231
- exports.isDehydratedDeferred = isDehydratedDeferred;
2232
- exports.isMatchInvalid = isMatchInvalid;
2233
- exports.isPlainObject = isPlainObject;
2234
- exports.isRedirect = isRedirect;
2235
- exports.joinPaths = joinPaths;
2236
- exports.last = last;
2237
- exports.lazyFn = lazyFn;
2238
- exports.matchByPath = matchByPath;
2239
- exports.matchPathname = matchPathname;
2240
- exports.parsePathname = parsePathname;
2241
- exports.parseSearchWith = parseSearchWith;
2242
- exports.partialDeepEqual = partialDeepEqual;
2243
- exports.pick = pick;
2244
- exports.redirect = redirect;
2245
- exports.replaceEqualDeep = replaceEqualDeep;
2246
- exports.resolvePath = resolvePath;
2247
- exports.restoreScrollPositions = restoreScrollPositions;
2248
- exports.rootRouteId = rootRouteId;
2249
- exports.stringifySearchWith = stringifySearchWith;
2250
- exports.trimPath = trimPath;
2251
- exports.trimPathLeft = trimPathLeft;
2252
- exports.trimPathRight = trimPathRight;
2253
- exports.warning = warning;
2254
- exports.watchScrollPositions = watchScrollPositions;
2255
-
2256
- Object.defineProperty(exports, '__esModule', { value: true });
2287
+ return promise;
2288
+ }
2289
+ function isDehydratedDeferred(obj) {
2290
+ return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
2291
+ }
2292
+
2293
+ exports.FileRoute = FileRoute;
2294
+ exports.PathParamError = PathParamError;
2295
+ exports.RootRoute = RootRoute;
2296
+ exports.Route = Route;
2297
+ exports.Router = Router;
2298
+ exports.RouterContext = RouterContext;
2299
+ exports.SearchParamError = SearchParamError;
2300
+ exports.cleanPath = cleanPath;
2301
+ exports.componentTypes = componentTypes;
2302
+ exports.createBrowserHistory = createBrowserHistory;
2303
+ exports.createHashHistory = createHashHistory;
2304
+ exports.createMemoryHistory = createMemoryHistory;
2305
+ exports.createRouteMask = createRouteMask;
2306
+ exports.decode = decode;
2307
+ exports.defaultParseSearch = defaultParseSearch;
2308
+ exports.defaultStringifySearch = defaultStringifySearch;
2309
+ exports.defer = defer;
2310
+ exports.encode = encode;
2311
+ exports.functionalUpdate = functionalUpdate;
2312
+ exports.interpolatePath = interpolatePath;
2313
+ exports.invariant = invariant;
2314
+ exports.isDehydratedDeferred = isDehydratedDeferred;
2315
+ exports.isMatchInvalid = isMatchInvalid;
2316
+ exports.isPlainObject = isPlainObject;
2317
+ exports.isRedirect = isRedirect;
2318
+ exports.joinPaths = joinPaths;
2319
+ exports.last = last;
2320
+ exports.lazyFn = lazyFn;
2321
+ exports.matchByPath = matchByPath;
2322
+ exports.matchPathname = matchPathname;
2323
+ exports.parsePathname = parsePathname;
2324
+ exports.parseSearchWith = parseSearchWith;
2325
+ exports.partialDeepEqual = partialDeepEqual;
2326
+ exports.pick = pick;
2327
+ exports.redirect = redirect;
2328
+ exports.replaceEqualDeep = replaceEqualDeep;
2329
+ exports.resolvePath = resolvePath;
2330
+ exports.restoreScrollPositions = restoreScrollPositions;
2331
+ exports.rootRouteId = rootRouteId;
2332
+ exports.stringifySearchWith = stringifySearchWith;
2333
+ exports.trimPath = trimPath;
2334
+ exports.trimPathLeft = trimPathLeft;
2335
+ exports.trimPathRight = trimPathRight;
2336
+ exports.warning = warning;
2337
+ exports.watchScrollPositions = watchScrollPositions;
2338
+
2339
+ Object.defineProperty(exports, '__esModule', { value: true });
2257
2340
 
2258
2341
  }));
2259
2342
  //# sourceMappingURL=index.development.js.map