Use same scroll handling for both web hooks
This commit is contained in:
@@ -30,6 +30,7 @@ import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
|
|||||||
import {useKeyboardHandlers} from '#/components/images/Gallery/useKeyboardHandlers'
|
import {useKeyboardHandlers} from '#/components/images/Gallery/useKeyboardHandlers'
|
||||||
import {usePointerHandlers} from '#/components/images/Gallery/usePointerHandlers'
|
import {usePointerHandlers} from '#/components/images/Gallery/usePointerHandlers'
|
||||||
import {CONTAINER_ASPECT_RATIO, ITEM_GAP, MIN_PEEK} from '#/components/images/Gallery/const'
|
import {CONTAINER_ASPECT_RATIO, ITEM_GAP, MIN_PEEK} from '#/components/images/Gallery/const'
|
||||||
|
import {IS_WEB} from '#/env'
|
||||||
|
|
||||||
interface GalleryProps {
|
interface GalleryProps {
|
||||||
images: AppBskyEmbedImages.ViewImage[]
|
images: AppBskyEmbedImages.ViewImage[]
|
||||||
@@ -55,6 +56,7 @@ export function GalleryBleed({children}: {children: React.ReactNode}) {
|
|||||||
const [bleedWidth, setBleedWidth] = useState(0)
|
const [bleedWidth, setBleedWidth] = useState(0)
|
||||||
|
|
||||||
if (!isValidElement(children)) {
|
if (!isValidElement(children)) {
|
||||||
|
|
||||||
throw new Error('GalleryBleed children must be a single React element')
|
throw new Error('GalleryBleed children must be a single React element')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,22 +134,22 @@ export function Gallery({
|
|||||||
const itemRefsRef = useRef<Map<number, View>>(new Map())
|
const itemRefsRef = useRef<Map<number, View>>(new Map())
|
||||||
const currentIndexRef = useRef(0)
|
const currentIndexRef = useRef(0)
|
||||||
|
|
||||||
const scrollToIndex = (index: number, animated = true) => {
|
const scrollTo = (offset: number) => {
|
||||||
let offset = 0
|
flatListRef.current?.scrollToOffset({offset, animated: false})
|
||||||
for (let i = 0; i < index; i++) {
|
}
|
||||||
offset += (itemWidthsRef.current.get(i) ?? 0) + ITEM_GAP
|
|
||||||
}
|
const onSettle = (index: number) => {
|
||||||
flatListRef.current?.scrollToOffset({offset, animated})
|
if (!IS_WEB) return
|
||||||
|
const el = itemRefsRef.current.get(index) as unknown as HTMLElement | null
|
||||||
|
el?.focus({preventScroll: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
useKeyboardHandlers({
|
useKeyboardHandlers({
|
||||||
flatListRef,
|
flatListRef,
|
||||||
|
itemWidthsRef,
|
||||||
currentIndexRef,
|
currentIndexRef,
|
||||||
scrollToIndex(index: number) {
|
scrollTo,
|
||||||
scrollToIndex(index)
|
onSettle,
|
||||||
const el = itemRefsRef.current.get(index) as unknown as HTMLElement | null
|
|
||||||
el?.focus({preventScroll: true})
|
|
||||||
},
|
|
||||||
imageCount: images.length,
|
imageCount: images.length,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -155,10 +157,8 @@ export function Gallery({
|
|||||||
flatListRef,
|
flatListRef,
|
||||||
itemWidthsRef,
|
itemWidthsRef,
|
||||||
currentIndexRef,
|
currentIndexRef,
|
||||||
onSettle(index: number) {
|
scrollTo,
|
||||||
const el = itemRefsRef.current.get(index) as unknown as HTMLElement | null
|
onSettle,
|
||||||
el?.focus({preventScroll: true})
|
|
||||||
},
|
|
||||||
imageCount: images.length,
|
imageCount: images.length,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
export function useKeyboardHandlers(_args: {
|
export function useKeyboardHandlers(_args: {
|
||||||
flatListRef: any
|
flatListRef: any
|
||||||
|
itemWidthsRef: any
|
||||||
currentIndexRef: any
|
currentIndexRef: any
|
||||||
scrollToIndex: any
|
scrollTo: any
|
||||||
|
onSettle: any
|
||||||
imageCount: any
|
imageCount: any
|
||||||
}) {}
|
}) {}
|
||||||
|
|||||||
@@ -1,43 +1,89 @@
|
|||||||
import {useEffect} from 'react'
|
import {useEffect} from 'react'
|
||||||
import {type FlatList} from 'react-native'
|
import {type FlatList} from 'react-native'
|
||||||
|
|
||||||
|
import {ITEM_GAP} from '#/components/images/Gallery/const'
|
||||||
|
import {tween} from '#/components/images/Gallery/tween'
|
||||||
|
|
||||||
|
const SETTLE_DURATION = 600
|
||||||
|
|
||||||
|
function getOffsetForIndex(
|
||||||
|
itemWidths: Map<number, number>,
|
||||||
|
index: number,
|
||||||
|
): number {
|
||||||
|
let offset = 0
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
offset += (itemWidths.get(i) ?? 0) + ITEM_GAP
|
||||||
|
}
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
export function useKeyboardHandlers({
|
export function useKeyboardHandlers({
|
||||||
flatListRef,
|
flatListRef,
|
||||||
|
itemWidthsRef,
|
||||||
currentIndexRef,
|
currentIndexRef,
|
||||||
scrollToIndex,
|
scrollTo,
|
||||||
|
onSettle,
|
||||||
imageCount,
|
imageCount,
|
||||||
}: {
|
}: {
|
||||||
flatListRef: React.RefObject<FlatList | null>
|
flatListRef: React.RefObject<FlatList | null>
|
||||||
|
itemWidthsRef: React.RefObject<Map<number, number>>
|
||||||
currentIndexRef: React.RefObject<number>
|
currentIndexRef: React.RefObject<number>
|
||||||
scrollToIndex: (index: number, animated?: boolean) => void
|
scrollTo: (offset: number) => void
|
||||||
|
onSettle: (index: number) => void
|
||||||
imageCount: number
|
imageCount: number
|
||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (imageCount <= 1) return
|
if (imageCount <= 1) return
|
||||||
|
|
||||||
|
let stopTween: (() => void) | null = null
|
||||||
|
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
const el = flatListRef.current?.getScrollableNode() as unknown as HTMLElement | null
|
const el =
|
||||||
|
flatListRef.current?.getScrollableNode() as unknown as HTMLElement | null
|
||||||
if (!el || !el.contains(document.activeElement)) return
|
if (!el || !el.contains(document.activeElement)) return
|
||||||
|
|
||||||
const current = currentIndexRef.current
|
const current = currentIndexRef.current
|
||||||
|
let targetIndex: number | undefined
|
||||||
|
|
||||||
if (e.key === 'ArrowRight' || (e.key === 'Tab' && !e.shiftKey)) {
|
if (e.key === 'ArrowRight' || (e.key === 'Tab' && !e.shiftKey)) {
|
||||||
const next = current + 1
|
if (current < imageCount - 1) {
|
||||||
if (next < imageCount) {
|
targetIndex = current + 1
|
||||||
e.preventDefault()
|
|
||||||
scrollToIndex(next)
|
|
||||||
currentIndexRef.current = next
|
|
||||||
}
|
}
|
||||||
} else if (e.key === 'ArrowLeft' || (e.key === 'Tab' && e.shiftKey)) {
|
} else if (e.key === 'ArrowLeft' || (e.key === 'Tab' && e.shiftKey)) {
|
||||||
const prev = current - 1
|
if (current > 0) {
|
||||||
if (prev >= 0) {
|
targetIndex = current - 1
|
||||||
e.preventDefault()
|
|
||||||
scrollToIndex(prev)
|
|
||||||
currentIndexRef.current = prev
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (targetIndex != null) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (stopTween) {
|
||||||
|
stopTween()
|
||||||
|
stopTween = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const from = el.scrollLeft
|
||||||
|
const to = getOffsetForIndex(itemWidthsRef.current, targetIndex)
|
||||||
|
const idx = targetIndex
|
||||||
|
|
||||||
|
stopTween = tween(from, to, SETTLE_DURATION)(
|
||||||
|
v => {
|
||||||
|
scrollTo(v)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
stopTween = null
|
||||||
|
currentIndexRef.current = idx
|
||||||
|
onSettle(idx)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('keydown', onKeyDown)
|
window.addEventListener('keydown', onKeyDown)
|
||||||
return () => window.removeEventListener('keydown', onKeyDown)
|
return () => {
|
||||||
}, [flatListRef, currentIndexRef, scrollToIndex, imageCount])
|
window.removeEventListener('keydown', onKeyDown)
|
||||||
|
if (stopTween) stopTween()
|
||||||
|
}
|
||||||
|
}, [flatListRef, itemWidthsRef, currentIndexRef, scrollTo, onSettle, imageCount])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export function usePointerHandlers(_args: {
|
|||||||
flatListRef: any
|
flatListRef: any
|
||||||
itemWidthsRef: any
|
itemWidthsRef: any
|
||||||
currentIndexRef: any
|
currentIndexRef: any
|
||||||
|
scrollTo: any
|
||||||
onSettle: any
|
onSettle: any
|
||||||
imageCount: any
|
imageCount: any
|
||||||
}) {}
|
}) {}
|
||||||
|
|||||||
@@ -51,12 +51,14 @@ export function usePointerHandlers({
|
|||||||
flatListRef,
|
flatListRef,
|
||||||
itemWidthsRef,
|
itemWidthsRef,
|
||||||
currentIndexRef,
|
currentIndexRef,
|
||||||
|
scrollTo,
|
||||||
onSettle,
|
onSettle,
|
||||||
imageCount,
|
imageCount,
|
||||||
}: {
|
}: {
|
||||||
flatListRef: React.RefObject<FlatList | null>
|
flatListRef: React.RefObject<FlatList | null>
|
||||||
itemWidthsRef: React.RefObject<Map<number, number>>
|
itemWidthsRef: React.RefObject<Map<number, number>>
|
||||||
currentIndexRef: React.RefObject<number>
|
currentIndexRef: React.RefObject<number>
|
||||||
|
scrollTo: (offset: number) => void
|
||||||
onSettle: (index: number) => void
|
onSettle: (index: number) => void
|
||||||
imageCount: number
|
imageCount: number
|
||||||
}) {
|
}) {
|
||||||
@@ -126,10 +128,10 @@ export function usePointerHandlers({
|
|||||||
velo = (delta - prevDelta) / (elapsed * FRAME_MS)
|
velo = (delta - prevDelta) / (elapsed * FRAME_MS)
|
||||||
t = e.timeStamp
|
t = e.timeStamp
|
||||||
|
|
||||||
el.scrollLeft = dragScrollLeft - delta
|
scrollTo(dragScrollLeft - delta)
|
||||||
|
|
||||||
// Update current index from scroll position
|
// Update current index from scroll position
|
||||||
const offsetX = el.scrollLeft
|
const offsetX = dragScrollLeft - delta
|
||||||
let accumulated = 0
|
let accumulated = 0
|
||||||
for (let i = 0; i < imageCount; i++) {
|
for (let i = 0; i < imageCount; i++) {
|
||||||
const w = (itemWidthsRef.current.get(i) ?? 0) + ITEM_GAP
|
const w = (itemWidthsRef.current.get(i) ?? 0) + ITEM_GAP
|
||||||
@@ -184,7 +186,7 @@ export function usePointerHandlers({
|
|||||||
|
|
||||||
stopTween = tween(from, to, SETTLE_DURATION)(
|
stopTween = tween(from, to, SETTLE_DURATION)(
|
||||||
v => {
|
v => {
|
||||||
el.scrollLeft = v
|
scrollTo(v)
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
stopTween = null
|
stopTween = null
|
||||||
@@ -207,5 +209,5 @@ export function usePointerHandlers({
|
|||||||
el.style.cursor = ''
|
el.style.cursor = ''
|
||||||
el.style.userSelect = ''
|
el.style.userSelect = ''
|
||||||
}
|
}
|
||||||
}, [flatListRef, itemWidthsRef, currentIndexRef, onSettle, imageCount])
|
}, [flatListRef, itemWidthsRef, currentIndexRef, scrollTo, onSettle, imageCount])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user