···33import {NativeScrollEvent} from 'react-native'
44import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
55import {useShellLayout} from '#/state/shell/shell-layout'
66-import {isWeb} from 'platform/detection'
66+import {isNative} from 'platform/detection'
77import {useSharedValue, interpolate} from 'react-native-reanimated'
88+99+const WEB_HIDE_SHELL_THRESHOLD = 200
810911function clamp(num: number, min: number, max: number) {
1012 'worklet'
···2123 const onBeginDrag = useCallback(
2224 (e: NativeScrollEvent) => {
2325 'worklet'
2424- startDragOffset.value = e.contentOffset.y
2525- startMode.value = mode.value
2626+ if (isNative) {
2727+ startDragOffset.value = e.contentOffset.y
2828+ startMode.value = mode.value
2929+ }
2630 },
2731 [mode, startDragOffset, startMode],
2832 )
···3034 const onEndDrag = useCallback(
3135 (e: NativeScrollEvent) => {
3236 'worklet'
3333- startDragOffset.value = null
3434- startMode.value = null
3535- if (e.contentOffset.y < headerHeight.value / 2) {
3636- // If we're close to the top, show the shell.
3737- setMode(false)
3838- } else {
3939- // Snap to whichever state is the closest.
4040- setMode(Math.round(mode.value) === 1)
3737+ if (isNative) {
3838+ startDragOffset.value = null
3939+ startMode.value = null
4040+ if (e.contentOffset.y < headerHeight.value / 2) {
4141+ // If we're close to the top, show the shell.
4242+ setMode(false)
4343+ } else {
4444+ // Snap to whichever state is the closest.
4545+ setMode(Math.round(mode.value) === 1)
4646+ }
4147 }
4248 },
4349 [startDragOffset, startMode, setMode, mode, headerHeight],
···4652 const onScroll = useCallback(
4753 (e: NativeScrollEvent) => {
4854 'worklet'
4949- if (startDragOffset.value === null || startMode.value === null) {
5050- if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) {
5151- // If we're close enough to the top, always show the shell.
5252- // Even if we're not dragging.
5353- setMode(false)
5555+ if (isNative) {
5656+ if (startDragOffset.value === null || startMode.value === null) {
5757+ if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) {
5858+ // If we're close enough to the top, always show the shell.
5959+ // Even if we're not dragging.
6060+ setMode(false)
6161+ }
5462 return
5563 }
5656- if (isWeb) {
5757- // On the web, there is no concept of "starting" the drag.
5858- // When we get the first scroll event, we consider that the start.
5959- startDragOffset.value = e.contentOffset.y
6060- startMode.value = mode.value
6464+6565+ // The "mode" value is always between 0 and 1.
6666+ // Figure out how much to move it based on the current dragged distance.
6767+ const dy = e.contentOffset.y - startDragOffset.value
6868+ const dProgress = interpolate(
6969+ dy,
7070+ [-headerHeight.value, headerHeight.value],
7171+ [-1, 1],
7272+ )
7373+ const newValue = clamp(startMode.value + dProgress, 0, 1)
7474+ if (newValue !== mode.value) {
7575+ // Manually adjust the value. This won't be (and shouldn't be) animated.
7676+ mode.value = newValue
6177 }
6262- return
6363- }
6464-6565- // The "mode" value is always between 0 and 1.
6666- // Figure out how much to move it based on the current dragged distance.
6767- const dy = e.contentOffset.y - startDragOffset.value
6868- const dProgress = interpolate(
6969- dy,
7070- [-headerHeight.value, headerHeight.value],
7171- [-1, 1],
7272- )
7373- const newValue = clamp(startMode.value + dProgress, 0, 1)
7474- if (newValue !== mode.value) {
7575- // Manually adjust the value. This won't be (and shouldn't be) animated.
7676- mode.value = newValue
7777- }
7878- if (isWeb) {
7979- // On the web, there is no concept of "starting" the drag,
8080- // so we don't have any specific anchor point to calculate the distance.
8181- // Instead, update it continuosly along the way and diff with the last event.
7878+ } else {
7979+ // On the web, we don't try to follow the drag because we don't know when it ends.
8080+ // Instead, show/hide immediately based on whether we're scrolling up or down.
8181+ const dy = e.contentOffset.y - (startDragOffset.value ?? 0)
8282 startDragOffset.value = e.contentOffset.y
8383- startMode.value = mode.value
8383+8484+ if (dy < 0 || e.contentOffset.y < WEB_HIDE_SHELL_THRESHOLD) {
8585+ setMode(false)
8686+ } else if (dy > 0) {
8787+ setMode(true)
8888+ }
8489 }
8590 },
8691 [headerHeight, mode, setMode, startDragOffset, startMode],