@tamagui/sheet 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +3 -2
- package/dist/cjs/GestureDetectorWrapper.native.js.map +1 -1
- package/dist/cjs/GestureSheetContext.native.js.map +1 -1
- package/dist/cjs/Sheet.native.js.map +1 -1
- package/dist/cjs/SheetContext.native.js.map +1 -1
- package/dist/cjs/SheetController.native.js.map +1 -1
- package/dist/cjs/SheetImplementationCustom.cjs +41 -64
- package/dist/cjs/SheetImplementationCustom.native.js +56 -69
- package/dist/cjs/SheetImplementationCustom.native.js.map +1 -1
- package/dist/cjs/SheetScrollView.cjs +57 -5
- package/dist/cjs/SheetScrollView.native.js +60 -5
- package/dist/cjs/SheetScrollView.native.js.map +1 -1
- package/dist/cjs/constants.native.js.map +1 -1
- package/dist/cjs/contexts.native.js.map +1 -1
- package/dist/cjs/controller.native.js.map +1 -1
- package/dist/cjs/createSheet.cjs +5 -2
- package/dist/cjs/createSheet.native.js +5 -2
- package/dist/cjs/createSheet.native.js.map +1 -1
- package/dist/cjs/gestureState.native.js.map +1 -1
- package/dist/cjs/helpers.native.js.map +1 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/keyboardAvoidance.cjs +52 -1
- package/dist/cjs/keyboardAvoidance.native.js +54 -1
- package/dist/cjs/keyboardAvoidance.native.js.map +1 -1
- package/dist/cjs/nativeSheet.cjs +0 -1
- package/dist/cjs/nativeSheet.native.js +0 -1
- package/dist/cjs/nativeSheet.native.js.map +1 -1
- package/dist/cjs/setupGestureHandler.native.js.map +1 -1
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/cjs/useGestureHandlerPan.cjs +25 -10
- package/dist/cjs/useGestureHandlerPan.native.js +27 -10
- package/dist/cjs/useGestureHandlerPan.native.js.map +1 -1
- package/dist/cjs/useKeyboardControllerSheet.cjs +10 -4
- package/dist/cjs/useSafeAreaInsets.cjs +44 -0
- package/dist/cjs/useSafeAreaInsets.native.js +48 -0
- package/dist/cjs/useSafeAreaInsets.native.js.map +1 -0
- package/dist/cjs/useSheet.native.js.map +1 -1
- package/dist/cjs/useSheetController.native.js.map +1 -1
- package/dist/cjs/useSheetOffscreenSize.native.js.map +1 -1
- package/dist/cjs/useSheetOpenState.native.js.map +1 -1
- package/dist/cjs/useSheetProviderProps.native.js.map +1 -1
- package/dist/cjs/useSheetScrollViewGestures.cjs +12 -2
- package/dist/cjs/useSheetScrollViewGestures.native.js +4 -0
- package/dist/cjs/useSheetScrollViewGestures.native.js.map +1 -1
- package/dist/cjs/webViewport.cjs +26 -1
- package/dist/cjs/webViewport.native.js +28 -1
- package/dist/cjs/webViewport.native.js.map +1 -1
- package/dist/esm/SheetImplementationCustom.mjs +48 -73
- package/dist/esm/SheetImplementationCustom.mjs.map +1 -1
- package/dist/esm/SheetImplementationCustom.native.js +70 -89
- package/dist/esm/SheetImplementationCustom.native.js.map +1 -1
- package/dist/esm/SheetScrollView.mjs +58 -6
- package/dist/esm/SheetScrollView.mjs.map +1 -1
- package/dist/esm/SheetScrollView.native.js +61 -6
- package/dist/esm/SheetScrollView.native.js.map +1 -1
- package/dist/esm/createSheet.mjs +7 -5
- package/dist/esm/createSheet.mjs.map +1 -1
- package/dist/esm/createSheet.native.js +17 -14
- package/dist/esm/createSheet.native.js.map +1 -1
- package/dist/esm/keyboardAvoidance.mjs +50 -1
- package/dist/esm/keyboardAvoidance.mjs.map +1 -1
- package/dist/esm/keyboardAvoidance.native.js +52 -1
- package/dist/esm/keyboardAvoidance.native.js.map +1 -1
- package/dist/esm/nativeSheet.mjs +0 -1
- package/dist/esm/nativeSheet.mjs.map +1 -1
- package/dist/esm/nativeSheet.native.js +0 -1
- package/dist/esm/nativeSheet.native.js.map +1 -1
- package/dist/esm/useGestureHandlerPan.mjs +25 -10
- package/dist/esm/useGestureHandlerPan.mjs.map +1 -1
- package/dist/esm/useGestureHandlerPan.native.js +27 -10
- package/dist/esm/useGestureHandlerPan.native.js.map +1 -1
- package/dist/esm/useKeyboardControllerSheet.mjs +11 -5
- package/dist/esm/useKeyboardControllerSheet.mjs.map +1 -1
- package/dist/esm/useSafeAreaInsets.mjs +7 -0
- package/dist/esm/useSafeAreaInsets.mjs.map +1 -0
- package/dist/esm/useSafeAreaInsets.native.js +8 -0
- package/dist/esm/useSafeAreaInsets.native.js.map +1 -0
- package/dist/esm/useSheetProviderProps.mjs.map +1 -1
- package/dist/esm/useSheetProviderProps.native.js.map +1 -1
- package/dist/esm/useSheetScrollViewGestures.mjs +12 -2
- package/dist/esm/useSheetScrollViewGestures.mjs.map +1 -1
- package/dist/esm/useSheetScrollViewGestures.native.js +4 -0
- package/dist/esm/useSheetScrollViewGestures.native.js.map +1 -1
- package/dist/esm/webViewport.mjs +23 -2
- package/dist/esm/webViewport.mjs.map +1 -1
- package/dist/esm/webViewport.native.js +25 -2
- package/dist/esm/webViewport.native.js.map +1 -1
- package/dist/jsx/SheetImplementationCustom.mjs +48 -73
- package/dist/jsx/SheetImplementationCustom.mjs.map +1 -1
- package/dist/jsx/SheetImplementationCustom.native.js +56 -69
- package/dist/jsx/SheetImplementationCustom.native.js.map +1 -1
- package/dist/jsx/SheetScrollView.mjs +58 -6
- package/dist/jsx/SheetScrollView.mjs.map +1 -1
- package/dist/jsx/SheetScrollView.native.js +60 -5
- package/dist/jsx/SheetScrollView.native.js.map +1 -1
- package/dist/jsx/createSheet.mjs +7 -5
- package/dist/jsx/createSheet.mjs.map +1 -1
- package/dist/jsx/createSheet.native.js +5 -2
- package/dist/jsx/createSheet.native.js.map +1 -1
- package/dist/jsx/keyboardAvoidance.mjs +50 -1
- package/dist/jsx/keyboardAvoidance.mjs.map +1 -1
- package/dist/jsx/keyboardAvoidance.native.js +54 -1
- package/dist/jsx/keyboardAvoidance.native.js.map +1 -1
- package/dist/jsx/nativeSheet.mjs +0 -1
- package/dist/jsx/nativeSheet.mjs.map +1 -1
- package/dist/jsx/nativeSheet.native.js +0 -1
- package/dist/jsx/nativeSheet.native.js.map +1 -1
- package/dist/jsx/useGestureHandlerPan.mjs +25 -10
- package/dist/jsx/useGestureHandlerPan.mjs.map +1 -1
- package/dist/jsx/useGestureHandlerPan.native.js +27 -10
- package/dist/jsx/useGestureHandlerPan.native.js.map +1 -1
- package/dist/jsx/useKeyboardControllerSheet.mjs +11 -5
- package/dist/jsx/useKeyboardControllerSheet.mjs.map +1 -1
- package/dist/jsx/useSafeAreaInsets.mjs +7 -0
- package/dist/jsx/useSafeAreaInsets.mjs.map +1 -0
- package/dist/jsx/useSafeAreaInsets.native.js +48 -0
- package/dist/jsx/useSafeAreaInsets.native.js.map +1 -0
- package/dist/jsx/useSheetProviderProps.mjs.map +1 -1
- package/dist/jsx/useSheetProviderProps.native.js.map +1 -1
- package/dist/jsx/useSheetScrollViewGestures.mjs +12 -2
- package/dist/jsx/useSheetScrollViewGestures.mjs.map +1 -1
- package/dist/jsx/useSheetScrollViewGestures.native.js +4 -0
- package/dist/jsx/useSheetScrollViewGestures.native.js.map +1 -1
- package/dist/jsx/webViewport.mjs +23 -2
- package/dist/jsx/webViewport.mjs.map +1 -1
- package/dist/jsx/webViewport.native.js +28 -1
- package/dist/jsx/webViewport.native.js.map +1 -1
- package/package.json +24 -25
- package/src/SheetImplementationCustom.tsx +147 -212
- package/src/SheetScrollView.tsx +81 -21
- package/src/createSheet.tsx +18 -6
- package/src/keyboardAvoidance.ts +91 -0
- package/src/nativeSheet.tsx +0 -1
- package/src/useGestureHandlerPan.tsx +29 -12
- package/src/useKeyboardControllerSheet.ts +35 -14
- package/src/useSafeAreaInsets.native.ts +29 -0
- package/src/useSafeAreaInsets.ts +19 -0
- package/src/useSheetProviderProps.tsx +4 -10
- package/src/useSheetScrollViewGestures.native.ts +4 -0
- package/src/useSheetScrollViewGestures.ts +19 -8
- package/src/webViewport.ts +56 -8
- package/test/keyboardAvoidance.test.ts +218 -2
- package/types/SheetContext.d.ts +0 -1
- package/types/SheetContext.d.ts.map +1 -1
- package/types/SheetImplementationCustom.d.ts.map +1 -1
- package/types/SheetScrollView.d.ts.map +1 -1
- package/types/createSheet.d.ts +12 -12
- package/types/createSheet.d.ts.map +1 -1
- package/types/keyboardAvoidance.d.ts +18 -0
- package/types/keyboardAvoidance.d.ts.map +1 -1
- package/types/nativeSheet.d.ts.map +1 -1
- package/types/useGestureHandlerPan.d.ts +4 -1
- package/types/useGestureHandlerPan.d.ts.map +1 -1
- package/types/useKeyboardControllerSheet.d.ts +5 -5
- package/types/useKeyboardControllerSheet.d.ts.map +1 -1
- package/types/useSafeAreaInsets.d.ts +10 -0
- package/types/useSafeAreaInsets.d.ts.map +1 -0
- package/types/useSafeAreaInsets.native.d.ts +20 -0
- package/types/useSafeAreaInsets.native.d.ts.map +1 -0
- package/types/useSheetProviderProps.d.ts +0 -1
- package/types/useSheetProviderProps.d.ts.map +1 -1
- package/types/useSheetScrollViewGestures.d.ts.map +1 -1
- package/types/useSheetScrollViewGestures.native.d.ts.map +1 -1
- package/types/webViewport.d.ts +13 -7
- package/types/webViewport.d.ts.map +1 -1
package/src/SheetScrollView.tsx
CHANGED
|
@@ -10,7 +10,13 @@ import { getGestureHandlerState, isGestureHandlerEnabled } from './gestureState'
|
|
|
10
10
|
import { useSheetContext } from './SheetContext'
|
|
11
11
|
import type { SheetScopedProps } from './types'
|
|
12
12
|
import { useSheetScrollViewGestures } from './useSheetScrollViewGestures'
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getStableLayoutViewportHeight,
|
|
15
|
+
getWebKeyboardBottomInset,
|
|
16
|
+
getWebKeyboardResizeHeight,
|
|
17
|
+
isEditableElement,
|
|
18
|
+
MIN_KEYBOARD_HEIGHT,
|
|
19
|
+
} from './webViewport'
|
|
14
20
|
|
|
15
21
|
const SHEET_SCROLL_VIEW_NAME = 'SheetScrollView'
|
|
16
22
|
|
|
@@ -39,7 +45,7 @@ export const SheetScrollView = React.forwardRef<
|
|
|
39
45
|
// height freeze engages on the same render that would otherwise collapse it.
|
|
40
46
|
const isKeyboardVisible =
|
|
41
47
|
context.isKeyboardVisible === true ||
|
|
42
|
-
(isWeb &&
|
|
48
|
+
(isWeb && getWebKeyboardResizeHeight() >= MIN_KEYBOARD_HEIGHT)
|
|
43
49
|
const [scrollEnabled] = useControllableState({
|
|
44
50
|
prop: scrollEnabledProp,
|
|
45
51
|
defaultProp: true,
|
|
@@ -67,27 +73,15 @@ export const SheetScrollView = React.forwardRef<
|
|
|
67
73
|
}
|
|
68
74
|
: { flex: 1 }
|
|
69
75
|
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// when the keyboard is open the sheet stays ANCHORED at the bottom and keeps
|
|
78
|
-
// its full pre-keyboard height — the keyboard overlays its lower part and the
|
|
79
|
-
// keyboardOccludedHeight tail padding (added to the scroll content below) +
|
|
80
|
-
// browser scroll-into-view lift the focused input above the keyboard. so we
|
|
81
|
-
// pin the height to the sheet's authoritative frozenFrameHeight, overriding
|
|
82
|
-
// any consumer maxHeight (on web that's often tied to useWindowDimensions,
|
|
83
|
-
// which SHRINKS when the keyboard opens and would otherwise collapse the
|
|
84
|
-
// sheet). holding the height constant means nothing animates on keyboard
|
|
85
|
-
// open/close — no jump/teleport. applied AFTER {...props} so it wins.
|
|
76
|
+
// when the keyboard is open, pin the scroll view to the sheet's pre-keyboard
|
|
77
|
+
// frame height (frozenFrameHeight), overriding any consumer maxHeight. on web
|
|
78
|
+
// that maxHeight is often tied to useWindowDimensions, which SHRINKS when the
|
|
79
|
+
// keyboard opens and would otherwise collapse the sheet. holding the height
|
|
80
|
+
// constant means the web frame can translate without resizing. applied
|
|
81
|
+
// AFTER {...props} so it wins.
|
|
86
82
|
const keyboardFrozenOverride =
|
|
87
83
|
hasFit && isKeyboardVisible && frozenFrameHeight > 0
|
|
88
|
-
?
|
|
89
|
-
? { maxHeight: frozenFrameHeight }
|
|
90
|
-
: { height: frozenFrameHeight, maxHeight: frozenFrameHeight }
|
|
84
|
+
? { height: frozenFrameHeight, maxHeight: frozenFrameHeight }
|
|
91
85
|
: null
|
|
92
86
|
|
|
93
87
|
const panGestureRef = gestureContext?.panGestureRef
|
|
@@ -97,6 +91,7 @@ export const SheetScrollView = React.forwardRef<
|
|
|
97
91
|
// RNGH scroll locking state
|
|
98
92
|
const currentScrollOffset = useRef(0)
|
|
99
93
|
const lockedScrollY = useRef(0)
|
|
94
|
+
const focusedInputScrollFrame = useRef(0)
|
|
100
95
|
|
|
101
96
|
const setScrollEnabled = (next: boolean, lockTo?: number) => {
|
|
102
97
|
if (!next) {
|
|
@@ -114,6 +109,67 @@ export const SheetScrollView = React.forwardRef<
|
|
|
114
109
|
scrollRef.current?.scrollTo?.({ x: 0, y, animated: false })
|
|
115
110
|
}
|
|
116
111
|
|
|
112
|
+
const scrollFocusedInputClearOfKeyboard = React.useCallback(() => {
|
|
113
|
+
if (!isWeb || !hasFit || !isKeyboardVisible || keyboardOccludedHeight <= 0) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
const node = scrollRef.current?.getScrollableNode() as HTMLElement | undefined
|
|
117
|
+
const active = document.activeElement as HTMLElement | null
|
|
118
|
+
if (!node || !active || !isEditableElement(active) || !node.contains(active)) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const keyboardHeight = Math.max(keyboardOccludedHeight, getWebKeyboardBottomInset())
|
|
123
|
+
if (keyboardHeight <= 0) return
|
|
124
|
+
|
|
125
|
+
const activeRect = active.getBoundingClientRect()
|
|
126
|
+
const nodeRect = node.getBoundingClientRect()
|
|
127
|
+
const margin = 12
|
|
128
|
+
const keyboardTop = getStableLayoutViewportHeight() - keyboardHeight
|
|
129
|
+
const visibleTop = nodeRect.top + margin
|
|
130
|
+
const visibleBottom = Math.min(nodeRect.bottom, keyboardTop) - margin
|
|
131
|
+
let nextScrollTop = node.scrollTop
|
|
132
|
+
|
|
133
|
+
if (activeRect.bottom > visibleBottom) {
|
|
134
|
+
nextScrollTop += Math.ceil(activeRect.bottom - visibleBottom)
|
|
135
|
+
} else if (activeRect.top < visibleTop) {
|
|
136
|
+
nextScrollTop -= Math.ceil(visibleTop - activeRect.top)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const maxScrollTop = Math.max(0, node.scrollHeight - node.clientHeight)
|
|
140
|
+
nextScrollTop = Math.max(0, Math.min(maxScrollTop, nextScrollTop))
|
|
141
|
+
if (nextScrollTop === node.scrollTop) return
|
|
142
|
+
|
|
143
|
+
node.scrollTop = nextScrollTop
|
|
144
|
+
currentScrollOffset.current = nextScrollTop
|
|
145
|
+
scrollBridge.y = nextScrollTop
|
|
146
|
+
if (nextScrollTop > 0) scrollBridge.scrollStartY = -1
|
|
147
|
+
}, [hasFit, isKeyboardVisible, keyboardOccludedHeight, scrollBridge])
|
|
148
|
+
|
|
149
|
+
const scheduleFocusedInputScroll = React.useCallback(() => {
|
|
150
|
+
if (!isWeb || !hasFit) return
|
|
151
|
+
cancelAnimationFrame(focusedInputScrollFrame.current)
|
|
152
|
+
focusedInputScrollFrame.current = requestAnimationFrame(() => {
|
|
153
|
+
scrollFocusedInputClearOfKeyboard()
|
|
154
|
+
focusedInputScrollFrame.current = requestAnimationFrame(
|
|
155
|
+
scrollFocusedInputClearOfKeyboard
|
|
156
|
+
)
|
|
157
|
+
})
|
|
158
|
+
}, [hasFit, scrollFocusedInputClearOfKeyboard])
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (!isWeb || !hasFit) return
|
|
162
|
+
scheduleFocusedInputScroll()
|
|
163
|
+
window.addEventListener('focusin', scheduleFocusedInputScroll)
|
|
164
|
+
window.visualViewport?.addEventListener('resize', scheduleFocusedInputScroll)
|
|
165
|
+
|
|
166
|
+
return () => {
|
|
167
|
+
cancelAnimationFrame(focusedInputScrollFrame.current)
|
|
168
|
+
window.removeEventListener('focusin', scheduleFocusedInputScroll)
|
|
169
|
+
window.visualViewport?.removeEventListener('resize', scheduleFocusedInputScroll)
|
|
170
|
+
}
|
|
171
|
+
}, [hasFit, scheduleFocusedInputScroll])
|
|
172
|
+
|
|
117
173
|
useEffect(() => {
|
|
118
174
|
setHasScrollView(true)
|
|
119
175
|
if (isGestureHandlerEnabled()) {
|
|
@@ -160,6 +216,9 @@ export const SheetScrollView = React.forwardRef<
|
|
|
160
216
|
if (height !== contentHeight.current) {
|
|
161
217
|
contentHeight.current = height
|
|
162
218
|
updateScrollable()
|
|
219
|
+
if (keyboardOccludedHeight > 0) {
|
|
220
|
+
scheduleFocusedInputScroll()
|
|
221
|
+
}
|
|
163
222
|
}
|
|
164
223
|
}}
|
|
165
224
|
>
|
|
@@ -244,6 +303,7 @@ export const SheetScrollView = React.forwardRef<
|
|
|
244
303
|
scrollEnabled={scrollEnabled}
|
|
245
304
|
onScroll={(e) => {
|
|
246
305
|
const { y } = e.nativeEvent.contentOffset
|
|
306
|
+
currentScrollOffset.current = y
|
|
247
307
|
scrollBridge.y = y
|
|
248
308
|
if (y > 0) scrollBridge.scrollStartY = -1
|
|
249
309
|
onScroll?.(e)
|
package/src/createSheet.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
2
|
-
import { useIsomorphicLayoutEffect } from '@tamagui/constants'
|
|
2
|
+
import { isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
|
|
3
3
|
import type {
|
|
4
4
|
GetProps,
|
|
5
5
|
ViewProps,
|
|
@@ -25,6 +25,7 @@ import { SheetScrollView } from './SheetScrollView'
|
|
|
25
25
|
import type { SheetProps, SheetScopedProps } from './types'
|
|
26
26
|
import { useSheetController } from './useSheetController'
|
|
27
27
|
import { useSheetOffscreenSize } from './useSheetOffscreenSize'
|
|
28
|
+
import { getMaxViewportHeight } from './webViewport'
|
|
28
29
|
|
|
29
30
|
type SharedSheetProps = {
|
|
30
31
|
open?: boolean
|
|
@@ -136,9 +137,9 @@ export function createSheet<
|
|
|
136
137
|
|
|
137
138
|
type ExtraFrameProps = {
|
|
138
139
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
140
|
+
* by default the sheet adds a view below its bottom that extends past the
|
|
141
|
+
* largest visible viewport height. this covers spring overshoot when opening
|
|
142
|
+
* so page content never shows through below the sheet.
|
|
142
143
|
*/
|
|
143
144
|
disableHideBottomOverflow?: boolean
|
|
144
145
|
|
|
@@ -225,14 +226,25 @@ export function createSheet<
|
|
|
225
226
|
<Frame
|
|
226
227
|
{...props}
|
|
227
228
|
componentName="SheetCover"
|
|
229
|
+
data-sheet-cover=""
|
|
228
230
|
children={null}
|
|
229
231
|
// Don't inherit testID - this is a visual helper element
|
|
230
232
|
testID={undefined}
|
|
231
233
|
id={undefined}
|
|
232
234
|
position="absolute"
|
|
233
|
-
bottom
|
|
235
|
+
// anchor the cover's top at the sheet's bottom edge (top: 100% of the
|
|
236
|
+
// container), then extend it downward. on web extend it past the
|
|
237
|
+
// largest viewport safari can reveal as its chrome retracts, so the
|
|
238
|
+
// sheet background covers the bottom instead of revealing the page.
|
|
239
|
+
// native keeps frameSize.
|
|
240
|
+
top="100%"
|
|
234
241
|
zIndex={-1}
|
|
235
|
-
height={
|
|
242
|
+
height={
|
|
243
|
+
isWeb
|
|
244
|
+
? Math.max(context.frameSize, getMaxViewportHeight())
|
|
245
|
+
: context.frameSize
|
|
246
|
+
}
|
|
247
|
+
maxHeight={isWeb ? 'none' : undefined}
|
|
236
248
|
left={0}
|
|
237
249
|
right={0}
|
|
238
250
|
borderWidth={0}
|
package/src/keyboardAvoidance.ts
CHANGED
|
@@ -1,3 +1,94 @@
|
|
|
1
|
+
export function getKeyboardAdjustedSheetY({
|
|
2
|
+
sheetY,
|
|
3
|
+
screenSize,
|
|
4
|
+
isKeyboardVisible,
|
|
5
|
+
keyboardHeight,
|
|
6
|
+
shouldTranslate,
|
|
7
|
+
safeAreaTop,
|
|
8
|
+
}: {
|
|
9
|
+
sheetY: number
|
|
10
|
+
screenSize: number
|
|
11
|
+
isKeyboardVisible: boolean
|
|
12
|
+
keyboardHeight: number
|
|
13
|
+
shouldTranslate: boolean
|
|
14
|
+
safeAreaTop: number
|
|
15
|
+
}) {
|
|
16
|
+
if (
|
|
17
|
+
!shouldTranslate ||
|
|
18
|
+
!isKeyboardVisible ||
|
|
19
|
+
keyboardHeight <= 0 ||
|
|
20
|
+
screenSize <= 0 ||
|
|
21
|
+
sheetY >= screenSize
|
|
22
|
+
) {
|
|
23
|
+
return sheetY
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return Math.max(safeAreaTop, sheetY - keyboardHeight)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getSheetReleasePosition({
|
|
30
|
+
positions,
|
|
31
|
+
projectedEnd,
|
|
32
|
+
currentPosition,
|
|
33
|
+
frameSize,
|
|
34
|
+
dismissOnSnapToBottom,
|
|
35
|
+
snapPointsMode,
|
|
36
|
+
isKeyboardVisible,
|
|
37
|
+
isWeb,
|
|
38
|
+
}: {
|
|
39
|
+
positions: number[]
|
|
40
|
+
projectedEnd: number
|
|
41
|
+
currentPosition: number
|
|
42
|
+
frameSize: number
|
|
43
|
+
dismissOnSnapToBottom: boolean
|
|
44
|
+
snapPointsMode: 'percent' | 'constant' | 'fit' | 'mixed'
|
|
45
|
+
isKeyboardVisible: boolean
|
|
46
|
+
isWeb: boolean
|
|
47
|
+
}) {
|
|
48
|
+
let closestPoint = 0
|
|
49
|
+
let dist = Number.POSITIVE_INFINITY
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < positions.length; i++) {
|
|
52
|
+
const position = positions[i]
|
|
53
|
+
const curDist = Math.abs(projectedEnd - position)
|
|
54
|
+
if (curDist < dist) {
|
|
55
|
+
dist = curDist
|
|
56
|
+
closestPoint = i
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// the keyboard-open dismiss threshold is a WEB keyboard-handoff fix
|
|
61
|
+
// (75de9c9694). on native the keyboard is opaque and the sheet snaps purely by
|
|
62
|
+
// projected position (the pre-rework behavior), so gate it to web only —
|
|
63
|
+
// otherwise a native fit+dismiss sheet snaps to the wrong point with the
|
|
64
|
+
// keyboard up.
|
|
65
|
+
const dismissPoint = positions.length - 1
|
|
66
|
+
const isKeyboardFitDismiss =
|
|
67
|
+
isWeb &&
|
|
68
|
+
dismissOnSnapToBottom &&
|
|
69
|
+
isKeyboardVisible &&
|
|
70
|
+
snapPointsMode === 'fit' &&
|
|
71
|
+
positions.length === 2 &&
|
|
72
|
+
closestPoint === dismissPoint
|
|
73
|
+
|
|
74
|
+
if (!isKeyboardFitDismiss) {
|
|
75
|
+
return closestPoint
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const openPosition = positions[0]
|
|
79
|
+
const dismissPosition = positions[dismissPoint]
|
|
80
|
+
const dismissDistance = dismissPosition - openPosition
|
|
81
|
+
if (dismissDistance <= 0 || frameSize <= 0) {
|
|
82
|
+
return closestPoint
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const draggedDistance = Math.max(0, currentPosition - openPosition)
|
|
86
|
+
const thresholdBase = Math.min(frameSize, dismissDistance)
|
|
87
|
+
const dismissThreshold = Math.max(120, thresholdBase * 0.35)
|
|
88
|
+
|
|
89
|
+
return draggedDistance >= dismissThreshold ? closestPoint : 0
|
|
90
|
+
}
|
|
91
|
+
|
|
1
92
|
export function getKeyboardOccludedHeight({
|
|
2
93
|
frameSize,
|
|
3
94
|
isKeyboardVisible,
|
package/src/nativeSheet.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { isWeb } from '@tamagui/constants'
|
|
1
2
|
import { useCallback, useMemo, useRef, type RefObject } from 'react'
|
|
2
3
|
import { getGestureHandlerState, isGestureHandlerEnabled } from './gestureState'
|
|
3
|
-
import
|
|
4
|
+
import { getSheetReleasePosition } from './keyboardAvoidance'
|
|
5
|
+
import type { ScrollBridge, SnapPointsMode } from './types'
|
|
4
6
|
|
|
5
7
|
// threshold in pixels for considering sheet "at top" position
|
|
6
8
|
// allows for small measurement variations
|
|
@@ -18,6 +20,9 @@ interface GesturePanConfig {
|
|
|
18
20
|
resisted: (val: number, minY: number) => number
|
|
19
21
|
disableDrag?: boolean
|
|
20
22
|
isShowingInnerSheet?: boolean
|
|
23
|
+
dismissOnSnapToBottom?: boolean
|
|
24
|
+
snapPointsMode?: SnapPointsMode
|
|
25
|
+
isKeyboardVisible?: boolean
|
|
21
26
|
// set the animated position directly (for smooth dragging)
|
|
22
27
|
setAnimatedPosition: (val: number) => void
|
|
23
28
|
// ref to scroll gesture for simultaneousWithExternalGesture
|
|
@@ -69,6 +74,16 @@ export function useGestureHandlerPan(config: GesturePanConfig): GesturePanResult
|
|
|
69
74
|
|
|
70
75
|
const gestureHandlerEnabled = isGestureHandlerEnabled()
|
|
71
76
|
const panGestureRef = useRef<any>(null)
|
|
77
|
+
const releaseConfigRef = useRef({
|
|
78
|
+
dismissOnSnapToBottom: config.dismissOnSnapToBottom === true,
|
|
79
|
+
snapPointsMode: config.snapPointsMode ?? 'percent',
|
|
80
|
+
isKeyboardVisible: config.isKeyboardVisible === true,
|
|
81
|
+
})
|
|
82
|
+
releaseConfigRef.current = {
|
|
83
|
+
dismissOnSnapToBottom: config.dismissOnSnapToBottom === true,
|
|
84
|
+
snapPointsMode: config.snapPointsMode ?? 'percent',
|
|
85
|
+
isKeyboardVisible: config.isKeyboardVisible === true,
|
|
86
|
+
}
|
|
72
87
|
|
|
73
88
|
// use refs for values that need to persist across gesture lifecycle
|
|
74
89
|
// (useMemo closure variables get reset when gesture is recreated)
|
|
@@ -86,6 +101,7 @@ export function useGestureHandlerPan(config: GesturePanConfig): GesturePanResult
|
|
|
86
101
|
// causing positions to revert. Frozen positions ensure stable snap calculation.
|
|
87
102
|
frozenPositions: [] as number[],
|
|
88
103
|
frozenMinY: 0,
|
|
104
|
+
frozenIsKeyboardVisible: false,
|
|
89
105
|
// whether pan gesture actually started (vs just a tap in onBegin)
|
|
90
106
|
panStarted: false,
|
|
91
107
|
})
|
|
@@ -156,6 +172,7 @@ export function useGestureHandlerPan(config: GesturePanConfig): GesturePanResult
|
|
|
156
172
|
// doesn't change snap targets mid-gesture
|
|
157
173
|
gs.frozenPositions = [...positions]
|
|
158
174
|
gs.frozenMinY = minY
|
|
175
|
+
gs.frozenIsKeyboardVisible = releaseConfigRef.current.isKeyboardVisible
|
|
159
176
|
|
|
160
177
|
// if sheet not at top, DISABLE SCROLL immediately and lock to 0
|
|
161
178
|
// this prevents scroll from firing before pan takes over
|
|
@@ -292,17 +309,17 @@ export function useGestureHandlerPan(config: GesturePanConfig): GesturePanResult
|
|
|
292
309
|
const velocity = velocityY / 1000
|
|
293
310
|
const projectedEnd = currentPos + frameSize * velocity * 0.2
|
|
294
311
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
312
|
+
const releaseConfig = releaseConfigRef.current
|
|
313
|
+
const closestPoint = getSheetReleasePosition({
|
|
314
|
+
positions: snapPositions,
|
|
315
|
+
projectedEnd,
|
|
316
|
+
currentPosition: currentPos,
|
|
317
|
+
frameSize,
|
|
318
|
+
dismissOnSnapToBottom: releaseConfig.dismissOnSnapToBottom,
|
|
319
|
+
snapPointsMode: releaseConfig.snapPointsMode,
|
|
320
|
+
isKeyboardVisible: gs.frozenIsKeyboardVisible,
|
|
321
|
+
isWeb,
|
|
322
|
+
})
|
|
306
323
|
|
|
307
324
|
onEnd(closestPoint)
|
|
308
325
|
})
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Web implementation of the keyboard controller sheet hook.
|
|
3
3
|
*
|
|
4
4
|
* Mobile browsers don't expose a keyboard API, but they do resize the
|
|
5
|
-
* VisualViewport when the soft keyboard opens. We
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* VisualViewport when the soft keyboard opens. We use the viewport shrink
|
|
6
|
+
* (`clientHeight - visualViewport.height`) to detect the keyboard, then feed the
|
|
7
|
+
* bottom layout inset (`clientHeight - (offsetTop + height)`) into
|
|
8
|
+
* SheetImplementationCustom. The bottom inset accounts for iOS Safari panning
|
|
9
|
+
* the visual viewport during focus, so the sheet doesn't over-lift.
|
|
10
10
|
*
|
|
11
11
|
* Without this, a bottom sheet on mobile web stays pinned behind the keyboard:
|
|
12
12
|
* react-native-web's Dimensions tracks the (shrinking) VisualViewport, so any
|
|
@@ -21,7 +21,8 @@ import type {
|
|
|
21
21
|
KeyboardControllerSheetResult,
|
|
22
22
|
} from './types'
|
|
23
23
|
import {
|
|
24
|
-
|
|
24
|
+
getWebKeyboardBottomInset,
|
|
25
|
+
getWebKeyboardResizeHeight,
|
|
25
26
|
isEditableElement,
|
|
26
27
|
MIN_KEYBOARD_HEIGHT,
|
|
27
28
|
} from './webViewport'
|
|
@@ -31,8 +32,26 @@ export function useKeyboardControllerSheet(
|
|
|
31
32
|
): KeyboardControllerSheetResult {
|
|
32
33
|
const { enabled } = options
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
// initialize from the CURRENT viewport, not blindly from 0/false. when the
|
|
36
|
+
// sheet opens with the soft keyboard already up (e.g. it was raised by a field
|
|
37
|
+
// on the page behind, or by an autofocus whose keyboard wins the race against
|
|
38
|
+
// the first layout), a false initial value makes the sheet's very first render
|
|
39
|
+
// believe the keyboard is down — it captures a keyboard-free baseline from a
|
|
40
|
+
// keyboard-shrunk layout and the anchor/seed machinery has to recover. reading
|
|
41
|
+
// the viewport synchronously here removes that first-render race: the sheet
|
|
42
|
+
// knows the keyboard is up on render 1 and takes the seed path deterministically.
|
|
43
|
+
const [keyboardHeight, setKeyboardHeight] = useState(() =>
|
|
44
|
+
isWeb && enabled && typeof window !== 'undefined' && window.visualViewport
|
|
45
|
+
? (() => {
|
|
46
|
+
const resizeHeight = getWebKeyboardResizeHeight()
|
|
47
|
+
return resizeHeight >= MIN_KEYBOARD_HEIGHT &&
|
|
48
|
+
isEditableElement(document.activeElement)
|
|
49
|
+
? getWebKeyboardBottomInset()
|
|
50
|
+
: 0
|
|
51
|
+
})()
|
|
52
|
+
: 0
|
|
53
|
+
)
|
|
54
|
+
const [isKeyboardVisible, setIsKeyboardVisible] = useState(() => keyboardHeight > 0)
|
|
36
55
|
|
|
37
56
|
// action-sheet pattern: pause keyboard hide events during drag so the sheet
|
|
38
57
|
// position doesn't revert mid-gesture when a TextInput blurs.
|
|
@@ -66,8 +85,9 @@ export function useKeyboardControllerSheet(
|
|
|
66
85
|
if (!vv) return
|
|
67
86
|
|
|
68
87
|
const update = () => {
|
|
69
|
-
const
|
|
70
|
-
const
|
|
88
|
+
const resizeHeight = getWebKeyboardResizeHeight()
|
|
89
|
+
const height = getWebKeyboardBottomInset()
|
|
90
|
+
const tall = resizeHeight >= MIN_KEYBOARD_HEIGHT
|
|
71
91
|
// require an editable element focused to *show* — this rules out URL-bar
|
|
72
92
|
// collapse and other viewport changes that aren't a keyboard. but stay
|
|
73
93
|
// visible while the viewport remains shrunk even if focus momentarily
|
|
@@ -93,11 +113,11 @@ export function useKeyboardControllerSheet(
|
|
|
93
113
|
setKeyboardHeight((prev) => (prev === height ? prev : height))
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
// sheet
|
|
99
|
-
// shifting viewport, making the sheet jump.
|
|
116
|
+
// resize tracks keyboard open/close. scroll tracks iOS Safari's focus pan:
|
|
117
|
+
// visualViewport.offsetTop can change after the height has settled, and the
|
|
118
|
+
// sheet cap must move in that same layout coordinate space.
|
|
100
119
|
vv.addEventListener('resize', update)
|
|
120
|
+
vv.addEventListener('scroll', update)
|
|
101
121
|
// focus changes flip editable state without necessarily resizing the viewport
|
|
102
122
|
window.addEventListener('focusin', update)
|
|
103
123
|
window.addEventListener('focusout', update)
|
|
@@ -106,6 +126,7 @@ export function useKeyboardControllerSheet(
|
|
|
106
126
|
|
|
107
127
|
return () => {
|
|
108
128
|
vv.removeEventListener('resize', update)
|
|
129
|
+
vv.removeEventListener('scroll', update)
|
|
109
130
|
window.removeEventListener('focusin', update)
|
|
110
131
|
window.removeEventListener('focusout', update)
|
|
111
132
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { SafeAreaInsetsContext as RNSafeAreaInsetsContext } from 'react-native-safe-area-context'
|
|
3
|
+
|
|
4
|
+
import type { SafeAreaInsets } from './useSafeAreaInsets'
|
|
5
|
+
|
|
6
|
+
// the real react-native-safe-area-context insets context, re-typed to the local
|
|
7
|
+
// shape so consumers don't depend on the package's types. exported so the Sheet
|
|
8
|
+
// can re-propagate it across the portal.
|
|
9
|
+
export const SafeAreaInsetsContext =
|
|
10
|
+
RNSafeAreaInsetsContext as unknown as React.Context<SafeAreaInsets | null>
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Live safe-area insets (notch / status bar / home indicator) read from the
|
|
14
|
+
* context the app's SafeAreaProvider provides.
|
|
15
|
+
*
|
|
16
|
+
* Read this in a component BODY that renders inside the provider. The Sheet does
|
|
17
|
+
* exactly that — only its modal CONTENT is teleported out through the portal, so
|
|
18
|
+
* the body still sees the real insets. This is the one read that works in real
|
|
19
|
+
* native bundles:
|
|
20
|
+
* - `getSafeArea()` (the @tamagui/native abstraction) is frequently NOT enabled
|
|
21
|
+
* against the instance a component reads, so it returns 0.
|
|
22
|
+
* - a dynamic `require('react-native-safe-area-context')` throws "Unknown named
|
|
23
|
+
* module" in metro/rolldown bundles where the package isn't in the importing
|
|
24
|
+
* module's graph.
|
|
25
|
+
* A static import + context read avoids both.
|
|
26
|
+
*/
|
|
27
|
+
export function useSafeAreaInsets(): SafeAreaInsets | null {
|
|
28
|
+
return React.useContext(SafeAreaInsetsContext)
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
export interface SafeAreaInsets {
|
|
4
|
+
top: number
|
|
5
|
+
right: number
|
|
6
|
+
bottom: number
|
|
7
|
+
left: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// web uses CSS env(safe-area-inset-*) for safe areas, so there is no native
|
|
11
|
+
// safe-area context to read here — the keyboard-avoidance path uses the visual
|
|
12
|
+
// viewport offset instead. this stub keeps the import resolvable on web (where
|
|
13
|
+
// react-native-safe-area-context isn't a dependency) and is the canonical type
|
|
14
|
+
// source for both platform variants.
|
|
15
|
+
export const SafeAreaInsetsContext = React.createContext<SafeAreaInsets | null>(null)
|
|
16
|
+
|
|
17
|
+
export function useSafeAreaInsets(): SafeAreaInsets | null {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
@@ -14,17 +14,11 @@ export type SheetContextValue = ReturnType<typeof useSheetProviderProps> & {
|
|
|
14
14
|
// the fit-mode height freeze must persist for the whole time the keyboard is
|
|
15
15
|
// open, not just while occluded, or it releases and the sheet collapses.
|
|
16
16
|
isKeyboardVisible: boolean
|
|
17
|
-
// the sheet's
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
17
|
+
// the sheet's pre-keyboard frame height (web). SheetScrollView pins its height
|
|
18
|
+
// to this while the keyboard is open so the frame translates at full size
|
|
19
|
+
// instead of collapsing to a consumer maxHeight that shrank with the viewport.
|
|
20
|
+
// 0 when not applicable.
|
|
21
21
|
keyboardStableFrameHeight: number
|
|
22
|
-
// AUTOFOCUS-ON-OPEN seed phase (web). while reconstructing the pre-keyboard
|
|
23
|
-
// baseline, SheetScrollView must UNCLIP (apply keyboardStableFrameHeight as
|
|
24
|
-
// maxHeight only, not a fixed height) so it sizes to its content and the sheet
|
|
25
|
-
// can measure the true content height. once settled the fixed-height pin
|
|
26
|
-
// applies. false outside the seed window.
|
|
27
|
-
isKeyboardSeeding: boolean
|
|
28
22
|
setHasScrollView: (val: boolean) => void
|
|
29
23
|
}
|
|
30
24
|
|
|
@@ -131,6 +131,10 @@ export function useSheetScrollViewGestures({
|
|
|
131
131
|
} else {
|
|
132
132
|
panHandles = !hasScrollableContent
|
|
133
133
|
if (!panHandles) {
|
|
134
|
+
s.handoffOccurred = false
|
|
135
|
+
s.handoffDragOffset = 0
|
|
136
|
+
s.scrollEngaged = currentScrollY > 0
|
|
137
|
+
s.prevScrollY = currentScrollY
|
|
134
138
|
s.isScrolling = true
|
|
135
139
|
scrollBridge.scrollLock = true
|
|
136
140
|
setScrollEnabled(true)
|
|
@@ -8,6 +8,7 @@ interface GestureState {
|
|
|
8
8
|
startY: number
|
|
9
9
|
lastY: number
|
|
10
10
|
owner: GestureOwner
|
|
11
|
+
hadPanOwner: boolean
|
|
11
12
|
panDragOffset: number
|
|
12
13
|
dys: number[]
|
|
13
14
|
scrollYAtGestureStart: number
|
|
@@ -30,6 +31,7 @@ export function useSheetScrollViewGestures({
|
|
|
30
31
|
startY: 0,
|
|
31
32
|
lastY: 0,
|
|
32
33
|
owner: 'none',
|
|
34
|
+
hadPanOwner: false,
|
|
33
35
|
panDragOffset: 0,
|
|
34
36
|
dys: [],
|
|
35
37
|
scrollYAtGestureStart: 0,
|
|
@@ -69,6 +71,7 @@ export function useSheetScrollViewGestures({
|
|
|
69
71
|
startY: touch.pageY,
|
|
70
72
|
lastY: touch.pageY,
|
|
71
73
|
owner: 'none',
|
|
74
|
+
hadPanOwner: false,
|
|
72
75
|
panDragOffset: 0,
|
|
73
76
|
dys: [],
|
|
74
77
|
scrollYAtGestureStart: currentScrollY,
|
|
@@ -117,6 +120,7 @@ export function useSheetScrollViewGestures({
|
|
|
117
120
|
// handle transitions
|
|
118
121
|
if (newOwner !== s.owner) {
|
|
119
122
|
if (newOwner === 'pan') {
|
|
123
|
+
s.hadPanOwner = true
|
|
120
124
|
s.panDragOffset = 0
|
|
121
125
|
s.dys = []
|
|
122
126
|
// re-baseline the pan origin to the sheet's CURRENT position so the
|
|
@@ -128,7 +132,11 @@ export function useSheetScrollViewGestures({
|
|
|
128
132
|
} else {
|
|
129
133
|
scrollBridge.setParentDragging(false)
|
|
130
134
|
scrollBridge.scrollLock = false
|
|
131
|
-
|
|
135
|
+
if (s.hadPanOwner) {
|
|
136
|
+
disableScroll()
|
|
137
|
+
} else {
|
|
138
|
+
enableScroll()
|
|
139
|
+
}
|
|
132
140
|
}
|
|
133
141
|
s.owner = newOwner
|
|
134
142
|
}
|
|
@@ -141,19 +149,21 @@ export function useSheetScrollViewGestures({
|
|
|
141
149
|
|
|
142
150
|
s.dys.push(dy)
|
|
143
151
|
if (s.dys.length > 100) s.dys = s.dys.slice(-10)
|
|
144
|
-
} else if (s.owner === 'scroll' && !e.isTrusted) {
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
152
|
+
} else if (s.owner === 'scroll' && (!e.isTrusted || s.hadPanOwner)) {
|
|
153
|
+
// synthetic events don't trigger native overflow scroll, so tests need us
|
|
154
|
+
// to move scrollTop directly. real mixed gestures need the same direct
|
|
155
|
+
// path after pan ownership: ios safari may not resume native scrolling
|
|
156
|
+
// mid-touch after an earlier prevented touchmove. pure real scrolls still
|
|
157
|
+
// use native scrolling; mixed pan↔scroll gestures keep overflow hidden
|
|
158
|
+
// and are driven here until touchend.
|
|
159
|
+
if (e.cancelable) e.preventDefault()
|
|
151
160
|
const scrollDelta = -dy
|
|
152
161
|
const maxScrollY = node.scrollHeight - node.clientHeight
|
|
153
162
|
const newScrollY = Math.max(0, Math.min(maxScrollY, currentScrollY + scrollDelta))
|
|
154
163
|
if (newScrollY !== currentScrollY) {
|
|
155
164
|
node.scrollTop = newScrollY
|
|
156
165
|
scrollBridge.y = newScrollY
|
|
166
|
+
if (newScrollY > 0) scrollBridge.scrollStartY = -1
|
|
157
167
|
}
|
|
158
168
|
}
|
|
159
169
|
}
|
|
@@ -183,6 +193,7 @@ export function useSheetScrollViewGestures({
|
|
|
183
193
|
|
|
184
194
|
enableScroll()
|
|
185
195
|
s.owner = 'none'
|
|
196
|
+
s.hadPanOwner = false
|
|
186
197
|
s.panDragOffset = 0
|
|
187
198
|
s.dys = []
|
|
188
199
|
scrollBridge.scrollNodeTouched = false
|