Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 5x 5x 5x 5x 5x 46x 29x 12x 12x 12x 12x 1x 1x 12x 7x 7x 7x 2x 1x 1x 12x 13x 13x 13x 4x 1x 1x 12x 12x 12x 12x 12x 12x 12x 12x | "use client";
import { useEffect } from "react";
export const LEAVE_PROMPT = "You have changes that aren't saved yet. Leave anyway?";
const GUARD = "__adoredLeaveGuard";
let activeGuards = 0;
/** For in-app exits the guard can't see (a router.push after an action): true when
* nothing is unsaved, or the curator chooses to leave anyway. */
export const confirmLeave = () => activeGuards === 0 || window.confirm(LEAVE_PROMPT);
// App Router has no route-change event, so guard the ways out directly: a full unload,
// a same-origin link click (capture phase, before Next's <Link> handler), and — best
// effort — Back, by parking a duplicate of this history entry for Back to land on first.
export function useLeaveGuard(active: boolean): void {
useEffect(() => {
if (!active) return;
activeGuards++;
// Spread the existing state so Next's router keys survive on the duplicate entry.
const arm = () =>
window.history.pushState({ ...window.history.state, [GUARD]: true }, "", window.location.href);
const onUnload = (e: BeforeUnloadEvent) => {
e.preventDefault();
e.returnValue = "";
};
const onClick = (e: MouseEvent) => {
Iif (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
const a = (e.target as Element | null)?.closest?.("a[href]");
if (!(a instanceof HTMLAnchorElement) || a.target === "_blank" || a.origin !== window.location.origin) return;
if (!window.confirm(LEAVE_PROMPT)) {
e.preventDefault();
e.stopPropagation();
}
};
const stop = () => {
window.removeEventListener("beforeunload", onUnload);
window.removeEventListener("popstate", onPop);
document.removeEventListener("click", onClick, true);
};
function onPop() {
if (!window.confirm(LEAVE_PROMPT)) return arm();
stop(); // our own back() must not ask twice
window.history.back();
}
if (!window.history.state?.[GUARD]) arm();
window.addEventListener("beforeunload", onUnload);
window.addEventListener("popstate", onPop);
document.addEventListener("click", onClick, true);
return () => {
activeGuards--;
stop();
// Saved (or unmounted) while still on the guard entry: drop it, so the next Back
// isn't spent on a duplicate of this page. After a link navigation the current
// entry is the new page's, so this never fires there.
if (window.history.state?.[GUARD]) window.history.back();
};
}, [active]);
}
|