about summary refs log tree commit diff
path: root/src/lib/hooks
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/hooks')
-rw-r--r--src/lib/hooks/useWebBodyScrollLock.ts28
-rw-r--r--src/lib/hooks/useWebScrollRestoration.native.ts3
-rw-r--r--src/lib/hooks/useWebScrollRestoration.ts52
3 files changed, 83 insertions, 0 deletions
diff --git a/src/lib/hooks/useWebBodyScrollLock.ts b/src/lib/hooks/useWebBodyScrollLock.ts
new file mode 100644
index 000000000..585f193f1
--- /dev/null
+++ b/src/lib/hooks/useWebBodyScrollLock.ts
@@ -0,0 +1,28 @@
+import {useEffect} from 'react'
+import {isWeb} from '#/platform/detection'
+
+let refCount = 0
+
+function incrementRefCount() {
+  if (refCount === 0) {
+    document.body.style.overflow = 'hidden'
+  }
+  refCount++
+}
+
+function decrementRefCount() {
+  refCount--
+  if (refCount === 0) {
+    document.body.style.overflow = ''
+  }
+}
+
+export function useWebBodyScrollLock(isLockActive: boolean) {
+  useEffect(() => {
+    if (!isWeb || !isLockActive) {
+      return
+    }
+    incrementRefCount()
+    return () => decrementRefCount()
+  })
+}
diff --git a/src/lib/hooks/useWebScrollRestoration.native.ts b/src/lib/hooks/useWebScrollRestoration.native.ts
new file mode 100644
index 000000000..c7d96607f
--- /dev/null
+++ b/src/lib/hooks/useWebScrollRestoration.native.ts
@@ -0,0 +1,3 @@
+export function useWebScrollRestoration() {
+  return undefined
+}
diff --git a/src/lib/hooks/useWebScrollRestoration.ts b/src/lib/hooks/useWebScrollRestoration.ts
new file mode 100644
index 000000000..f68fbf0f2
--- /dev/null
+++ b/src/lib/hooks/useWebScrollRestoration.ts
@@ -0,0 +1,52 @@
+import {useMemo, useState, useEffect} from 'react'
+import {EventArg, useNavigation} from '@react-navigation/core'
+
+if ('scrollRestoration' in history) {
+  // Tell the brower not to mess with the scroll.
+  // We're doing that manually below.
+  history.scrollRestoration = 'manual'
+}
+
+function createInitialScrollState() {
+  return {
+    scrollYs: new Map(),
+    focusedKey: null as string | null,
+  }
+}
+
+export function useWebScrollRestoration() {
+  const [state] = useState(createInitialScrollState)
+  const navigation = useNavigation()
+
+  useEffect(() => {
+    function onDispatch() {
+      if (state.focusedKey) {
+        // Remember where we were for later.
+        state.scrollYs.set(state.focusedKey, window.scrollY)
+        // TODO: Strictly speaking, this is a leak. We never clean up.
+        // This is because I'm not sure when it's appropriate to clean it up.
+        // It doesn't seem like popstate is enough because it can still Forward-Back again.
+        // Maybe we should use sessionStorage. Or check what Next.js is doing?
+      }
+    }
+    // We want to intercept any push/pop/replace *before* the re-render.
+    // There is no official way to do this yet, but this works okay for now.
+    // https://twitter.com/satya164/status/1737301243519725803
+    navigation.addListener('__unsafe_action__' as any, onDispatch)
+    return () => {
+      navigation.removeListener('__unsafe_action__' as any, onDispatch)
+    }
+  }, [state, navigation])
+
+  const screenListeners = useMemo(
+    () => ({
+      focus(e: EventArg<'focus', boolean | undefined, unknown>) {
+        const scrollY = state.scrollYs.get(e.target) ?? 0
+        window.scrollTo(0, scrollY)
+        state.focusedKey = e.target ?? null
+      },
+    }),
+    [state],
+  )
+  return screenListeners
+}