Skip to content

Commit

Permalink
fix: extract a safariKeyboardStore and use closing state to hide the …
Browse files Browse the repository at this point in the history
…alert
  • Loading branch information
trevinhofmann committed Feb 16, 2025
1 parent 1ca6d5d commit e06af0a
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 24 deletions.
30 changes: 6 additions & 24 deletions src/hooks/usePositionFixed.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,9 @@
/** Position fixed breaks in mobile Safari when the keyboard is up. This module provides functionality to emulate position:fixed by changing all top navigation to position:absolute and updating on scroll. */
import { useEffect } from 'react'
import { token } from '../../styled-system/tokens'
import { isIOS, isSafari, isTouch } from '../browser'
import * as selection from '../device/selection'
import reactMinistore from '../stores/react-ministore'
import safariKeyboardStore from '../stores/safariKeyboardStore'
import viewportStore from '../stores/viewport'
import once from '../util/once'
import useScrollTop from './useScrollTop'

/** Default mode is fixed. Absolute mode emulates positioned fixed by switching to position absolute and updating the y position on scroll. */
const positionFixedStore = reactMinistore<'fixed' | 'absolute' | null>(null)

/** Initializes handler to switch position fixed elements to position absolute when the keyboard is up on mobile Safari. Initialized once, permanently since the platform cannot change. */
const initEventHandler = once(() => {
/** Updates the positionFixedStore state based on if the keyboard is visible. */
const updatePositionFixed = () => {
const keyboardIsVisible = selection.isActive()
positionFixedStore.update(keyboardIsVisible ? 'absolute' : 'fixed')
}

if (isTouch && isSafari() && !isIOS) {
updatePositionFixed()
document.addEventListener('selectionchange', updatePositionFixed)
}
})

/** Emulates position fixed on mobile Safari with positon absolute. Returns { position, overflowX, top } in absolute mode. */
const usePositionFixed = ({
fromBottom,
Expand All @@ -40,12 +19,13 @@ const usePositionFixed = ({
overflowX?: 'hidden' | 'visible'
top?: string
bottom?: string
display?: 'none'
} => {
const position = positionFixedStore.useState()
const safariKeyboard = safariKeyboardStore.useState()
const position = safariKeyboard.open ? 'absolute' : 'fixed'
const scrollTop = useScrollTop({ disabled: position === 'fixed' })
const { innerHeight, currentKeyboardHeight } = viewportStore.useState()

useEffect(initEventHandler, [])
let top, bottom
if (position === 'absolute') {
top = fromBottom
Expand All @@ -63,6 +43,8 @@ const usePositionFixed = ({
/* spacing.safeAreaTop applies for rounded screens */
top,
bottom,
/* Hide bottom elements while the keyboard is closing */
display: fromBottom && safariKeyboard.closing ? 'none' : undefined,
}
}

Expand Down
38 changes: 38 additions & 0 deletions src/stores/safariKeyboardStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { isIOS, isSafari, isTouch } from '../browser'
import * as selection from '../device/selection'
import reactMinistore from './react-ministore'

const VIRTUAL_KEYBOARD_CLOSE_DURATION = 800

const safariKeyboardStore = reactMinistore<{
open: boolean
closing: boolean
}>({
open: false,
closing: false,
})

/** Updates the safariKeyboardStore state based on the selection. */
const updateKeyboardState = () => {
const keyboardIsVisible = selection.isActive()
if (safariKeyboardStore.getState().open && !keyboardIsVisible) {
safariKeyboardStore.update({
closing: true,
})
setTimeout(() => {
safariKeyboardStore.update({
closing: false,
})
}, VIRTUAL_KEYBOARD_CLOSE_DURATION)
}
safariKeyboardStore.update({
open: keyboardIsVisible,
})
}

if (isTouch && isSafari() && !isIOS) {
updateKeyboardState()
document.addEventListener('selectionchange', updateKeyboardState)
}

export default safariKeyboardStore

0 comments on commit e06af0a

Please sign in to comment.