@tanstack/router-core 0.0.1-beta.193 → 0.0.1-beta.195

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.193",
4
+ "version": "0.0.1-beta.195",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
package/src/fileRoute.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  TrimPathLeft,
16
16
  RouteConstraints,
17
17
  } from './route'
18
+ import { DeepMergeAll, Expand, IsAny } from './utils'
18
19
 
19
20
  export interface FileRoutesByPath {
20
21
  // '/': {
@@ -99,12 +100,15 @@ export class FileRoute<
99
100
  TParentRoute['types']['allParams'],
100
101
  TParams
101
102
  >,
102
- TParentContext extends RouteConstraints['TParentContext'] = TParentRoute['types']['routeContext'],
103
- TAllParentContext extends RouteConstraints['TAllParentContext'] = TParentRoute['types']['context'],
104
103
  TRouteContext extends RouteConstraints['TRouteContext'] = RouteContext,
105
- TContext extends RouteConstraints['TAllContext'] = MergeFromFromParent<
106
- TParentRoute['types']['context'],
107
- TRouteContext
104
+ TContext extends RouteConstraints['TAllContext'] = Expand<
105
+ DeepMergeAll<
106
+ [
107
+ IsAny<TParentRoute['types']['context'], {}>,
108
+ TLoaderContext,
109
+ TRouteContext,
110
+ ]
111
+ >
108
112
  >,
109
113
  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
110
114
  TChildren extends RouteConstraints['TChildren'] = unknown,
@@ -121,21 +125,12 @@ export class FileRoute<
121
125
  TFullSearchSchema,
122
126
  TParams,
123
127
  TAllParams,
124
- TParentContext,
125
- TAllParentContext,
126
128
  TRouteContext,
127
129
  TContext
128
130
  >,
129
131
  'getParentRoute' | 'path' | 'id'
130
132
  > &
131
- UpdatableRouteOptions<
132
- TLoader,
133
- TSearchSchema,
134
- TFullSearchSchema,
135
- TAllParams,
136
- TRouteContext,
137
- TContext
138
- >,
133
+ UpdatableRouteOptions<TLoader, TFullSearchSchema, TAllParams, TContext>,
139
134
  ): Route<
140
135
  TParentRoute,
141
136
  TPath,
@@ -148,8 +143,6 @@ export class FileRoute<
148
143
  TFullSearchSchema,
149
144
  TParams,
150
145
  TAllParams,
151
- TParentContext,
152
- TAllParentContext,
153
146
  TRouteContext,
154
147
  TContext,
155
148
  TRouterContext,
package/src/history.ts CHANGED
@@ -12,6 +12,7 @@ export interface RouterHistory {
12
12
  forward: () => void
13
13
  createHref: (href: string) => string
14
14
  block: (blockerFn: BlockerFn) => () => void
15
+ flush: () => void
15
16
  }
16
17
 
17
18
  export interface HistoryLocation extends ParsedPath {
@@ -52,12 +53,13 @@ const stopBlocking = () => {
52
53
  function createHistory(opts: {
53
54
  getLocation: () => HistoryLocation
54
55
  subscriber: false | ((onUpdate: () => void) => () => void)
55
- pushState: (path: string, state: any) => void
56
- replaceState: (path: string, state: any) => void
56
+ pushState: (path: string, state: any, onUpdate: () => void) => void
57
+ replaceState: (path: string, state: any, onUpdate: () => void) => void
57
58
  go: (n: number) => void
58
59
  back: () => void
59
60
  forward: () => void
60
61
  createHref: (path: string) => string
62
+ flush?: () => void
61
63
  }): RouterHistory {
62
64
  let location = opts.getLocation()
63
65
  let unsub = () => {}
@@ -116,13 +118,13 @@ function createHistory(opts: {
116
118
  push: (path: string, state: any) => {
117
119
  assignKey(state)
118
120
  queueTask(() => {
119
- opts.pushState(path, state)
121
+ opts.pushState(path, state, onUpdate)
120
122
  })
121
123
  },
122
124
  replace: (path: string, state: any) => {
123
125
  assignKey(state)
124
126
  queueTask(() => {
125
- opts.replaceState(path, state)
127
+ opts.replaceState(path, state, onUpdate)
126
128
  })
127
129
  },
128
130
  go: (index) => {
@@ -158,6 +160,7 @@ function createHistory(opts: {
158
160
  }
159
161
  }
160
162
  },
163
+ flush: () => opts.flush?.(),
161
164
  }
162
165
  }
163
166
 
@@ -171,6 +174,22 @@ function assignKey(state: HistoryState) {
171
174
  // }
172
175
  }
173
176
 
177
+ /**
178
+ * Creates a history object that can be used to interact with the browser's
179
+ * navigation. This is a lightweight API wrapping the browser's native methods.
180
+ * It is designed to work with TanStack Router, but could be used as a standalone API as well.
181
+ * IMPORTANT: This API implements history throttling via a microtask to prevent
182
+ * excessive calls to the history API. In some browsers, calling history.pushState or
183
+ * history.replaceState in quick succession can cause the browser to ignore subsequent
184
+ * calls. This API smooths out those differences and ensures that your application
185
+ * state will *eventually* match the browser state. In most cases, this is not a problem,
186
+ * but if you need to ensure that the browser state is up to date, you can use the
187
+ * `history.flush` method to immediately flush all pending state changes to the browser URL.
188
+ * @param opts
189
+ * @param opts.getHref A function that returns the current href (path + search + hash)
190
+ * @param opts.createHref A function that takes a path and returns a href (path + search + hash)
191
+ * @returns A history instance
192
+ */
174
193
  export function createBrowserHistory(opts?: {
175
194
  getHref?: () => string
176
195
  createHref?: (path: string) => string
@@ -182,24 +201,104 @@ export function createBrowserHistory(opts?: {
182
201
 
183
202
  const createHref = opts?.createHref ?? ((path) => path)
184
203
 
185
- const getLocation = () => parseLocation(getHref(), window.history.state)
204
+ let currentLocation = parseLocation(getHref(), window.history.state)
205
+
206
+ const getLocation = () => currentLocation
207
+
208
+ let next:
209
+ | undefined
210
+ | {
211
+ // This is the latest location that we were attempting to push/replace
212
+ href: string
213
+ // This is the latest state that we were attempting to push/replace
214
+ state: any
215
+ // This is the latest type that we were attempting to push/replace
216
+ isPush: boolean
217
+ }
218
+
219
+ // Because we are proactively updating the location
220
+ // in memory before actually updating the browser history,
221
+ // we need to track when we are doing this so we don't
222
+ // notify subscribers twice on the last update.
223
+ let tracking = true
224
+
225
+ // We need to track the current scheduled update to prevent
226
+ // multiple updates from being scheduled at the same time.
227
+ let scheduled: Promise<void> | undefined
228
+
229
+ // This function is a wrapper to prevent any of the callback's
230
+ // side effects from causing a subscriber notification
231
+ const untrack = (fn: () => void) => {
232
+ tracking = false
233
+ fn()
234
+ tracking = true
235
+ }
236
+
237
+ // This function flushes the next update to the browser history
238
+ const flush = () => {
239
+ // Do not notify subscribers about this push/replace call
240
+ untrack(() => {
241
+ if (!next) return
242
+ window.history[next.isPush ? 'pushState' : 'replaceState'](
243
+ next.state,
244
+ '',
245
+ next.href,
246
+ )
247
+ // Reset the nextIsPush flag and clear the scheduled update
248
+ next = undefined
249
+ scheduled = undefined
250
+ })
251
+ }
252
+
253
+ // This function queues up a call to update the browser history
254
+ const queueHistoryAction = (
255
+ type: 'push' | 'replace',
256
+ path: string,
257
+ state: any,
258
+ onUpdate: () => void,
259
+ ) => {
260
+ const href = createHref(path)
261
+
262
+ // Update the location in memory
263
+ currentLocation = parseLocation(href, state)
264
+
265
+ // Keep track of the next location we need to flush to the URL
266
+ next = {
267
+ href,
268
+ state,
269
+ isPush: next?.isPush || type === 'push',
270
+ }
271
+ // Notify subscribers
272
+ onUpdate()
273
+
274
+ if (!scheduled) {
275
+ // Schedule an update to the browser history
276
+ scheduled = Promise.resolve().then(() => flush())
277
+ }
278
+ }
186
279
 
187
280
  return createHistory({
188
281
  getLocation,
189
282
  subscriber: (onUpdate) => {
190
- window.addEventListener(pushStateEvent, onUpdate)
191
- window.addEventListener(popStateEvent, onUpdate)
283
+ window.addEventListener(pushStateEvent, () => {
284
+ currentLocation = parseLocation(getHref(), window.history.state)
285
+ onUpdate()
286
+ })
287
+ window.addEventListener(popStateEvent, () => {
288
+ currentLocation = parseLocation(getHref(), window.history.state)
289
+ onUpdate()
290
+ })
192
291
 
193
292
  var pushState = window.history.pushState
194
293
  window.history.pushState = function () {
195
294
  let res = pushState.apply(history, arguments as any)
196
- onUpdate()
295
+ if (tracking) onUpdate()
197
296
  return res
198
297
  }
199
298
  var replaceState = window.history.replaceState
200
299
  window.history.replaceState = function () {
201
300
  let res = replaceState.apply(history, arguments as any)
202
- onUpdate()
301
+ if (tracking) onUpdate()
203
302
  return res
204
303
  }
205
304
 
@@ -210,16 +309,15 @@ export function createBrowserHistory(opts?: {
210
309
  window.removeEventListener(popStateEvent, onUpdate)
211
310
  }
212
311
  },
213
- pushState: (path, state) => {
214
- window.history.pushState(state, '', createHref(path))
215
- },
216
- replaceState: (path, state) => {
217
- window.history.replaceState(state, '', createHref(path))
218
- },
312
+ pushState: (path, state, onUpdate) =>
313
+ queueHistoryAction('push', path, state, onUpdate),
314
+ replaceState: (path, state, onUpdate) =>
315
+ queueHistoryAction('replace', path, state, onUpdate),
219
316
  back: () => window.history.back(),
220
317
  forward: () => window.history.forward(),
221
318
  go: (n) => window.history.go(n),
222
319
  createHref: (path) => createHref(path),
320
+ flush,
223
321
  })
224
322
  }
225
323