File size: 2,196 Bytes
ad48a43 | 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 60 61 62 63 64 65 | /**
* useBackToClose — the phone's Back gesture closes the open layer (drawer,
* full-screen settings) instead of leaving HomePilot.
*
* While `open`, one history entry is added for the layer. Back pops it and
* `onClose` runs; closing the layer any other way removes the entry again.
* Layers stack: each records its depth, and Back only closes layers above the
* entry it lands on — so Back from Settings opened over the drawer closes
* Settings and leaves the drawer open.
*
* If the owner declines to close (e.g. "Discard unsaved changes?" → Cancel),
* the entry is restored so the next Back is caught again.
*/
import { useEffect, useRef } from 'react'
type LayerState = { hpLayer?: number }
function depthOf(state: unknown): number {
const d = (state as LayerState | null)?.hpLayer
return typeof d === 'number' ? d : 0
}
export function useBackToClose(open: boolean, onClose: () => void): void {
const closeRef = useRef(onClose)
closeRef.current = onClose
useEffect(() => {
if (!open || typeof window === 'undefined' || !window.history?.pushState) return
let alive = true
let pushed = false
let depth = 0
const push = () => {
depth = depthOf(window.history.state) + 1
window.history.pushState({ ...(window.history.state || {}), hpLayer: depth }, '')
pushed = true
}
// Deferred one tick so React StrictMode's mount → unmount → mount in
// development doesn't leave a stray entry behind.
const startTimer = window.setTimeout(() => {
if (alive) push()
}, 0)
const onPop = (e: PopStateEvent) => {
if (!pushed || depthOf(e.state) >= depth) return
pushed = false
closeRef.current()
// Still open after asking to close → the owner kept it; catch the next Back too.
window.setTimeout(() => {
if (alive && !pushed) push()
}, 0)
}
window.addEventListener('popstate', onPop)
return () => {
alive = false
window.clearTimeout(startTimer)
window.removeEventListener('popstate', onPop)
if (pushed && depthOf(window.history.state) === depth) window.history.back()
}
}, [open])
}
export default useBackToClose
|