@tanstack/history 1.7.1 → 1.12.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,300 +0,0 @@
1
- /**
2
- * @tanstack/history/src/index.ts
3
- *
4
- * Copyright (c) TanStack
5
- *
6
- * This source code is licensed under the MIT license found in the
7
- * LICENSE.md file in the root directory of this source tree.
8
- *
9
- * @license MIT
10
- */
11
- // While the public API was clearly inspired by the "history" npm package,
12
- // This implementation attempts to be more lightweight by
13
- // making assumptions about the way TanStack Router works
14
-
15
- const pushStateEvent = 'pushstate';
16
- const popStateEvent = 'popstate';
17
- const beforeUnloadEvent = 'beforeunload';
18
- const beforeUnloadListener = event => {
19
- event.preventDefault();
20
- // @ts-ignore
21
- return event.returnValue = '';
22
- };
23
- const stopBlocking = () => {
24
- removeEventListener(beforeUnloadEvent, beforeUnloadListener, {
25
- capture: true
26
- });
27
- };
28
- function createHistory(opts) {
29
- let location = opts.getLocation();
30
- let subscribers = new Set();
31
- let blockers = [];
32
- const onUpdate = () => {
33
- location = opts.getLocation();
34
- subscribers.forEach(subscriber => subscriber());
35
- };
36
- const tryNavigation = async task => {
37
- if (typeof document !== 'undefined' && blockers.length) {
38
- for (let blocker of blockers) {
39
- const allowed = await blocker();
40
- if (!allowed) {
41
- opts.onBlocked?.(onUpdate);
42
- return;
43
- }
44
- }
45
- }
46
- task();
47
- };
48
- return {
49
- get location() {
50
- return location;
51
- },
52
- subscribe: cb => {
53
- subscribers.add(cb);
54
- return () => {
55
- subscribers.delete(cb);
56
- };
57
- },
58
- push: (path, state) => {
59
- state = assignKey(state);
60
- tryNavigation(() => {
61
- opts.pushState(path, state);
62
- onUpdate();
63
- });
64
- },
65
- replace: (path, state) => {
66
- state = assignKey(state);
67
- tryNavigation(() => {
68
- opts.replaceState(path, state);
69
- onUpdate();
70
- });
71
- },
72
- go: index => {
73
- tryNavigation(() => {
74
- opts.go(index);
75
- });
76
- },
77
- back: () => {
78
- tryNavigation(() => {
79
- opts.back();
80
- });
81
- },
82
- forward: () => {
83
- tryNavigation(() => {
84
- opts.forward();
85
- });
86
- },
87
- createHref: str => opts.createHref(str),
88
- block: blocker => {
89
- blockers.push(blocker);
90
- if (blockers.length === 1) {
91
- addEventListener(beforeUnloadEvent, beforeUnloadListener, {
92
- capture: true
93
- });
94
- }
95
- return () => {
96
- blockers = blockers.filter(b => b !== blocker);
97
- if (!blockers.length) {
98
- stopBlocking();
99
- }
100
- };
101
- },
102
- flush: () => opts.flush?.(),
103
- destroy: () => opts.destroy?.(),
104
- notify: onUpdate
105
- };
106
- }
107
- function assignKey(state) {
108
- if (!state) {
109
- state = {};
110
- }
111
- return {
112
- ...state,
113
- key: createRandomKey()
114
- };
115
- }
116
-
117
- /**
118
- * Creates a history object that can be used to interact with the browser's
119
- * navigation. This is a lightweight API wrapping the browser's native methods.
120
- * It is designed to work with TanStack Router, but could be used as a standalone API as well.
121
- * IMPORTANT: This API implements history throttling via a microtask to prevent
122
- * excessive calls to the history API. In some browsers, calling history.pushState or
123
- * history.replaceState in quick succession can cause the browser to ignore subsequent
124
- * calls. This API smooths out those differences and ensures that your application
125
- * state will *eventually* match the browser state. In most cases, this is not a problem,
126
- * but if you need to ensure that the browser state is up to date, you can use the
127
- * `history.flush` method to immediately flush all pending state changes to the browser URL.
128
- * @param opts
129
- * @param opts.getHref A function that returns the current href (path + search + hash)
130
- * @param opts.createHref A function that takes a path and returns a href (path + search + hash)
131
- * @returns A history instance
132
- */
133
- function createBrowserHistory(opts) {
134
- const win = opts?.window ?? (typeof document !== 'undefined' ? window : undefined);
135
- const createHref = opts?.createHref ?? (path => path);
136
- const parseLocation = opts?.parseLocation ?? (() => parseHref(`${win.location.pathname}${win.location.search}${win.location.hash}`, win.history.state));
137
- let currentLocation = parseLocation();
138
- let rollbackLocation;
139
- const getLocation = () => currentLocation;
140
- let next;
141
-
142
- // Because we are proactively updating the location
143
- // in memory before actually updating the browser history,
144
- // we need to track when we are doing this so we don't
145
- // notify subscribers twice on the last update.
146
- let tracking = true;
147
-
148
- // We need to track the current scheduled update to prevent
149
- // multiple updates from being scheduled at the same time.
150
- let scheduled;
151
-
152
- // This function is a wrapper to prevent any of the callback's
153
- // side effects from causing a subscriber notification
154
- const untrack = fn => {
155
- tracking = false;
156
- fn();
157
- tracking = true;
158
- };
159
-
160
- // This function flushes the next update to the browser history
161
- const flush = () => {
162
- // Do not notify subscribers about this push/replace call
163
- untrack(() => {
164
- if (!next) return;
165
- win.history[next.isPush ? 'pushState' : 'replaceState'](next.state, '', next.href);
166
- // Reset the nextIsPush flag and clear the scheduled update
167
- next = undefined;
168
- scheduled = undefined;
169
- rollbackLocation = undefined;
170
- });
171
- };
172
-
173
- // This function queues up a call to update the browser history
174
- const queueHistoryAction = (type, destHref, state) => {
175
- const href = createHref(destHref);
176
- if (!scheduled) {
177
- rollbackLocation = currentLocation;
178
- }
179
-
180
- // Update the location in memory
181
- currentLocation = parseHref(destHref, state);
182
-
183
- // Keep track of the next location we need to flush to the URL
184
- next = {
185
- href,
186
- state,
187
- isPush: next?.isPush || type === 'push'
188
- };
189
- if (!scheduled) {
190
- // Schedule an update to the browser history
191
- scheduled = Promise.resolve().then(() => flush());
192
- }
193
- };
194
- const onPushPop = () => {
195
- currentLocation = parseLocation();
196
- history.notify();
197
- };
198
- var originalPushState = win.history.pushState;
199
- var originalReplaceState = win.history.replaceState;
200
- const history = createHistory({
201
- getLocation,
202
- pushState: (href, state) => queueHistoryAction('push', href, state),
203
- replaceState: (href, state) => queueHistoryAction('replace', href, state),
204
- back: () => win.history.back(),
205
- forward: () => win.history.forward(),
206
- go: n => win.history.go(n),
207
- createHref: href => createHref(href),
208
- flush,
209
- destroy: () => {
210
- win.history.pushState = originalPushState;
211
- win.history.replaceState = originalReplaceState;
212
- win.removeEventListener(pushStateEvent, onPushPop);
213
- win.removeEventListener(popStateEvent, onPushPop);
214
- },
215
- onBlocked: onUpdate => {
216
- // If a navigation is blocked, we need to rollback the location
217
- // that we optimistically updated in memory.
218
- if (rollbackLocation && currentLocation !== rollbackLocation) {
219
- currentLocation = rollbackLocation;
220
- // Notify subscribers
221
- onUpdate();
222
- }
223
- }
224
- });
225
- win.addEventListener(pushStateEvent, onPushPop);
226
- win.addEventListener(popStateEvent, onPushPop);
227
- win.history.pushState = function () {
228
- let res = originalPushState.apply(win.history, arguments);
229
- if (tracking) history.notify();
230
- return res;
231
- };
232
- win.history.replaceState = function () {
233
- let res = originalReplaceState.apply(win.history, arguments);
234
- if (tracking) history.notify();
235
- return res;
236
- };
237
- return history;
238
- }
239
- function createHashHistory(opts) {
240
- const win = opts?.window ?? (typeof document !== 'undefined' ? window : undefined);
241
- return createBrowserHistory({
242
- window: win,
243
- parseLocation: () => {
244
- const hashHref = win.location.hash.split('#').slice(1).join('#') ?? '/';
245
- return parseHref(hashHref, win.history.state);
246
- },
247
- createHref: href => `${win.location.pathname}${win.location.search}#${href}`
248
- });
249
- }
250
- function createMemoryHistory(opts = {
251
- initialEntries: ['/']
252
- }) {
253
- const entries = opts.initialEntries;
254
- let index = opts.initialIndex ?? entries.length - 1;
255
- let currentState = {
256
- key: createRandomKey()
257
- };
258
- const getLocation = () => parseHref(entries[index], currentState);
259
- return createHistory({
260
- getLocation,
261
- pushState: (path, state) => {
262
- currentState = state;
263
- entries.push(path);
264
- index++;
265
- },
266
- replaceState: (path, state) => {
267
- currentState = state;
268
- entries[index] = path;
269
- },
270
- back: () => {
271
- index--;
272
- },
273
- forward: () => {
274
- index = Math.min(index + 1, entries.length - 1);
275
- },
276
- go: n => {
277
- index = Math.min(Math.max(index + n, 0), entries.length - 1);
278
- },
279
- createHref: path => path
280
- });
281
- }
282
- function parseHref(href, state) {
283
- let hashIndex = href.indexOf('#');
284
- let searchIndex = href.indexOf('?');
285
- return {
286
- href,
287
- pathname: href.substring(0, hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : href.length),
288
- hash: hashIndex > -1 ? href.substring(hashIndex) : '',
289
- search: searchIndex > -1 ? href.slice(searchIndex, hashIndex === -1 ? undefined : hashIndex) : '',
290
- state: state || {}
291
- };
292
- }
293
-
294
- // Thanks co-pilot!
295
- function createRandomKey() {
296
- return (Math.random() + 1).toString(36).substring(7);
297
- }
298
-
299
- export { createBrowserHistory, createHashHistory, createHistory, createMemoryHistory };
300
- //# sourceMappingURL=index.js.map