import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
const NavigationContext = createContext(() => {});
/**
* Path routing over the History API. The site record serves index.html for
* every unmatched path, so a reload or a shared link lands on the same screen a
* click does.
*
* The query string is tracked alongside the path, because a space is addressed
* by its AT-URI and that URI holds slashes. Carrying it as a parameter keeps
* every screen a real, shareable page without encoding a path inside a path.
*/
export function useLocation() {
const [href, setHref] = useState(
() => window.location.pathname + window.location.search,
);
useEffect(() => {
const onPop = () =>
setHref(window.location.pathname + window.location.search);
window.addEventListener('popstate', onPop);
return () => window.removeEventListener('popstate', onPop);
}, []);
const navigate = useCallback((next) => {
window.history.pushState({}, '', next);
setHref(next);
window.scrollTo(0, 0);
}, []);
const url = new URL(href, window.location.origin);
return { path: url.pathname, params: url.searchParams, navigate };
}
export function NavigationProvider({ navigate, children }) {
return (
{children}
);
}
export function useNavigate() {
return useContext(NavigationContext);
}
/**
* The href for a screen: a path and the parameters that address what it shows.
* @param {string} path
* @param {Record} params
* @returns {string}
*/
export function href(path, params = {}) {
const search = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
if (value) search.set(key, value);
}
const query = search.toString();
return query ? `${path}?${query}` : path;
}