84 lines
2.6 KiB
JavaScript
84 lines
2.6 KiB
JavaScript
import { useSyncExternalStore } from './react-deps.js';
|
|
|
|
/**
|
|
* History API docs @see https://developer.mozilla.org/en-US/docs/Web/API/History
|
|
*/
|
|
const eventPopstate = "popstate";
|
|
const eventPushState = "pushState";
|
|
const eventReplaceState = "replaceState";
|
|
const eventHashchange = "hashchange";
|
|
const events = [
|
|
eventPopstate,
|
|
eventPushState,
|
|
eventReplaceState,
|
|
eventHashchange,
|
|
];
|
|
|
|
const subscribeToLocationUpdates = (callback) => {
|
|
for (const event of events) {
|
|
addEventListener(event, callback);
|
|
}
|
|
return () => {
|
|
for (const event of events) {
|
|
removeEventListener(event, callback);
|
|
}
|
|
};
|
|
};
|
|
|
|
const useLocationProperty = (fn, ssrFn) =>
|
|
useSyncExternalStore(subscribeToLocationUpdates, fn, ssrFn);
|
|
|
|
const currentSearch = () => location.search;
|
|
|
|
const useSearch = ({ ssrSearch = "" } = {}) =>
|
|
useLocationProperty(currentSearch, () => ssrSearch);
|
|
|
|
const currentPathname = () => location.pathname;
|
|
|
|
const usePathname = ({ ssrPath } = {}) =>
|
|
useLocationProperty(
|
|
currentPathname,
|
|
ssrPath ? () => ssrPath : currentPathname
|
|
);
|
|
|
|
const currentHistoryState = () => history.state;
|
|
const useHistoryState = () =>
|
|
useLocationProperty(currentHistoryState, () => null);
|
|
|
|
const navigate = (to, { replace = false, state = null } = {}) =>
|
|
history[replace ? eventReplaceState : eventPushState](state, "", to);
|
|
|
|
// the 2nd argument of the `useBrowserLocation` return value is a function
|
|
// that allows to perform a navigation.
|
|
const useBrowserLocation = (opts = {}) => [usePathname(opts), navigate];
|
|
|
|
const patchKey = Symbol.for("wouter_v3");
|
|
|
|
// While History API does have `popstate` event, the only
|
|
// proper way to listen to changes via `push/replaceState`
|
|
// is to monkey-patch these methods.
|
|
//
|
|
// See https://stackoverflow.com/a/4585031
|
|
if (typeof history !== "undefined" && typeof window[patchKey] === "undefined") {
|
|
for (const type of [eventPushState, eventReplaceState]) {
|
|
const original = history[type];
|
|
// TODO: we should be using unstable_batchedUpdates to avoid multiple re-renders,
|
|
// however that will require an additional peer dependency on react-dom.
|
|
// See: https://github.com/reactwg/react-18/discussions/86#discussioncomment-1567149
|
|
history[type] = function () {
|
|
const result = original.apply(this, arguments);
|
|
const event = new Event(type);
|
|
event.arguments = arguments;
|
|
|
|
dispatchEvent(event);
|
|
return result;
|
|
};
|
|
}
|
|
|
|
// patch history object only once
|
|
// See: https://github.com/molefrog/wouter/issues/167
|
|
Object.defineProperty(window, patchKey, { value: true });
|
|
}
|
|
|
|
export { navigate, useBrowserLocation, useHistoryState, useLocationProperty, usePathname, useSearch };
|