import { useSyncExternalStore } from './react-deps.js'; // array of callback subscribed to hash updates const listeners = { v: [], }; const onHashChange = () => listeners.v.forEach((cb) => cb()); // we subscribe to `hashchange` only once when needed to guarantee that // all listeners are called synchronously const subscribeToHashUpdates = (callback) => { if (listeners.v.push(callback) === 1) addEventListener("hashchange", onHashChange); return () => { listeners.v = listeners.v.filter((i) => i !== callback); if (!listeners.v.length) removeEventListener("hashchange", onHashChange); }; }; // leading '#' is ignored, leading '/' is optional const currentHashLocation = () => "/" + location.hash.replace(/^#?\/?/, ""); const navigate = (to, { state = null } = {}) => { // calling `replaceState` allows us to set the history // state without creating an extra entry const [hash, search] = to.replace(/^#?\/?/, "").split("?"); history.replaceState( state, "", // keep the current pathname, but replace query string and hash location.pathname + (search ? `?${search}` : location.search) + // update location hash, this will cause `hashchange` event to fire // normalise the value before updating, so it's always preceeded with "#/" (location.hash = `#/${hash}`) ); }; const useHashLocation = ({ ssrPath = "/" } = {}) => [ useSyncExternalStore( subscribeToHashUpdates, currentHashLocation, () => ssrPath ), navigate, ]; useHashLocation.hrefs = (href) => "#" + href; export { navigate, useHashLocation };