()
| 92 | } |
| 93 | |
| 94 | function HomeIndexContent() { |
| 95 | const [pageIndex, setPageIndex] = useState(0); |
| 96 | const contentRef = useRef(null); |
| 97 | const pageIndexRef = useRef(0); |
| 98 | |
| 99 | // the landing is a fixed full-screen snap experience — lock document scroll |
| 100 | // while it's mounted so the sticky top bar can never be dragged by a gesture |
| 101 | useEffect(() => { |
| 102 | const html = document.documentElement; |
| 103 | const prevHtml = html.style.overflow; |
| 104 | const prevBody = document.body.style.overflow; |
| 105 | // reset any scroll carried over from the previous page before locking, |
| 106 | // otherwise the frozen offset would clip the sticky top bar |
| 107 | window.scrollTo(0, 0); |
| 108 | html.style.overflow = 'hidden'; |
| 109 | document.body.style.overflow = 'hidden'; |
| 110 | return () => { |
| 111 | html.style.overflow = prevHtml; |
| 112 | document.body.style.overflow = prevBody; |
| 113 | }; |
| 114 | }, []); |
| 115 | |
| 116 | // keep the ref in sync for the imperative scroll handlers, and start each |
| 117 | // newly-shown screen at the top |
| 118 | useEffect(() => { |
| 119 | pageIndexRef.current = pageIndex; |
| 120 | const screen = contentRef.current?.firstElementChild?.children[pageIndex]; |
| 121 | if (screen) screen.scrollTop = 0; |
| 122 | }, [pageIndex]); |
| 123 | |
| 124 | useEffect(() => { |
| 125 | const node = contentRef.current; |
| 126 | let lock = false; |
| 127 | // One gesture = one page change. The lock absorbs trackpad inertia |
| 128 | // (which fires a burst of +/- deltas) so the page can't flip-flop. |
| 129 | const go = (dir) => { |
| 130 | if (lock) return; |
| 131 | setPageIndex((cur) => { |
| 132 | const next = Math.min(Math.max(cur + (dir > 0 ? 1 : -1), 0), PAGE_COUNT - 1); |
| 133 | if (cur === next) return cur; |
| 134 | lock = true; |
| 135 | setTimeout(() => { |
| 136 | lock = false; |
| 137 | }, 1000); |
| 138 | return next; |
| 139 | }); |
| 140 | }; |
| 141 | // when the active screen's own content overflows, let it scroll natively |
| 142 | // until it hits the edge — only then do we snap to the next/prev screen |
| 143 | const canScrollWithin = (dir) => { |
| 144 | const screen = node.firstElementChild?.children[pageIndexRef.current]; |
| 145 | if (!screen) return false; |
| 146 | const {scrollTop, scrollHeight, clientHeight} = screen; |
| 147 | if (scrollHeight - clientHeight <= 2) return false; |
| 148 | return dir > 0 ? scrollTop + clientHeight < scrollHeight - 2 : scrollTop > 2; |
| 149 | }; |
| 150 | const onWheel = (e) => { |
| 151 | if (Math.abs(e.deltaY) < 8) return; |
nothing calls this directly
no outgoing calls
no test coverage detected