-
-
Notifications
You must be signed in to change notification settings - Fork 785
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: backup Signed-off-by: Innei <[email protected]> * fix: update Signed-off-by: Innei <[email protected]> * chore: cleanup Signed-off-by: Innei <[email protected]> --------- Signed-off-by: Innei <[email protected]>
- Loading branch information
Showing
5 changed files
with
172 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/// useBeforeUnload.ts | ||
'use client' | ||
|
||
import { useEffect, useId } from 'react' | ||
import { useRouter } from 'next/navigation' | ||
|
||
let isForceRouting = false | ||
const activeIds: string[] = [] | ||
let lastKnownHref: string | ||
|
||
export const useBeforeUnload = (isActive = true) => { | ||
const id = useId() | ||
|
||
// Handle <Link> clicks & onbeforeunload(attemptimg to close/refresh browser) | ||
useEffect(() => { | ||
if (!isActive) return | ||
lastKnownHref = window.location.href | ||
|
||
activeIds.push(id) | ||
|
||
const handleAnchorClick = (e: Event) => { | ||
const targetUrl = (e.currentTarget as HTMLAnchorElement).href, | ||
currentUrl = window.location.href | ||
|
||
if (targetUrl !== currentUrl) { | ||
const res = beforeUnloadFn() | ||
if (!res) e.preventDefault() | ||
lastKnownHref = window.location.href | ||
} | ||
} | ||
|
||
let anchorElements: HTMLAnchorElement[] = [] | ||
|
||
const disconnectAnchors = () => { | ||
anchorElements.forEach((anchor) => { | ||
anchor.removeEventListener('click', handleAnchorClick) | ||
}) | ||
} | ||
|
||
const handleMutation = () => { | ||
disconnectAnchors() | ||
|
||
anchorElements = Array.from(document.querySelectorAll('a[href]')) | ||
anchorElements.forEach((anchor) => { | ||
anchor.addEventListener('click', handleAnchorClick) | ||
}) | ||
} | ||
|
||
const mutationObserver = new MutationObserver(handleMutation) | ||
mutationObserver.observe(document.body, { childList: true, subtree: true }) | ||
addEventListener('beforeunload', beforeUnloadFn) | ||
|
||
return () => { | ||
removeEventListener('beforeunload', beforeUnloadFn) | ||
disconnectAnchors() | ||
mutationObserver.disconnect() | ||
|
||
activeIds.splice(activeIds.indexOf(id), 1) | ||
} | ||
}, [isActive, id]) | ||
} | ||
|
||
const beforeUnloadFn = (event?: BeforeUnloadEvent) => { | ||
if (isForceRouting) return true | ||
|
||
const message = 'Discard unsaved changes?' | ||
|
||
if (event) { | ||
event.returnValue = message | ||
return message | ||
} else { | ||
return confirm(message) | ||
} | ||
} | ||
|
||
const BeforeUnloadProvider = ({ children }: React.PropsWithChildren) => { | ||
const router = useRouter() | ||
useEffect(() => { | ||
lastKnownHref = window.location.href | ||
}) | ||
|
||
// Hack nextjs13 popstate impl, so it will include route cancellation. | ||
// This Provider has to be rendered in the layout phase wrapping the page. | ||
useEffect(() => { | ||
let nextjsPopStateHandler: (...args: any[]) => void | ||
|
||
function popStateHandler(...args: any[]) { | ||
useBeforeUnload.ensureSafeNavigation( | ||
() => { | ||
nextjsPopStateHandler(...args) | ||
lastKnownHref = window.location.href | ||
}, | ||
() => { | ||
router.replace(lastKnownHref, { scroll: false }) | ||
}, | ||
) | ||
} | ||
|
||
addEventListener('popstate', popStateHandler) | ||
const originalAddEventListener = window.addEventListener | ||
window.addEventListener = (...args: any[]) => { | ||
if (args[0] === 'popstate') { | ||
nextjsPopStateHandler = args[1] | ||
window.addEventListener = originalAddEventListener | ||
} else { | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
originalAddEventListener(...args) | ||
} | ||
} | ||
|
||
originalAddEventListener('popstate', (e) => { | ||
e.preventDefault() | ||
}) | ||
|
||
// window.addEventListener('popstate', () => { | ||
// history.pushState(null, '', null) | ||
// }) | ||
return () => { | ||
window.addEventListener = originalAddEventListener | ||
removeEventListener('popstate', popStateHandler) | ||
} | ||
}, []) | ||
|
||
return children | ||
} | ||
|
||
useBeforeUnload.Provider = BeforeUnloadProvider | ||
|
||
useBeforeUnload.forceRoute = async (cb: () => void | Promise<void>) => { | ||
try { | ||
isForceRouting = true | ||
await cb() | ||
} finally { | ||
isForceRouting = false | ||
} | ||
} | ||
|
||
useBeforeUnload.ensureSafeNavigation = ( | ||
onPerformRoute: () => void, | ||
onRouteRejected?: () => void, | ||
) => { | ||
if (activeIds.length === 0 || beforeUnloadFn()) { | ||
onPerformRoute() | ||
} else { | ||
onRouteRejected?.() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
0826096
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
shiro – ./
springtide.vercel.app
shiro-git-main-innei.vercel.app
shiro-innei.vercel.app
innei.in