diff --git a/packages/core/src/platform/createApp.ios.js b/packages/core/src/platform/createApp.ios.js index ffcae7d41..df19b814f 100644 --- a/packages/core/src/platform/createApp.ios.js +++ b/packages/core/src/platform/createApp.ios.js @@ -156,7 +156,7 @@ export default function createApp (option, config = {}) { if (navigation && hasOwn(global.__mpxPageStatusMap, navigation.pageId)) { global.__mpxPageStatusMap[navigation.pageId] = 'show' } - } else if (currentState === 'inactive') { + } else if (currentState === 'inactive' || currentState === 'background') { global.__mpxAppCbs.hide.forEach((cb) => { cb() }) @@ -191,7 +191,7 @@ export default function createApp (option, config = {}) { // 7.x替换headerBackTitleVisible // headerBackButtonDisplayMode: 'minimal', headerBackTitleVisible: false, - headerMode: 'screen', + // 安卓上会出现初始化时闪现导航条的问题 headerShown: false } if (headerBackImageProps) { diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 076dbe3ad..27c2224f2 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -490,7 +490,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const { Provider, useSafeAreaInsets, GestureHandlerRootView } = global.__navigationHelper const pageConfig = Object.assign({}, global.__mpxPageConfig, currentInject.pageConfig) const Page = ({ navigation, route }) => { - const [enabled, setEnabled] = useState(true) + const [enabled, setEnabled] = useState(false) const currentPageId = useMemo(() => ++pageId, []) const intersectionObservers = useRef({}) usePageStatus(navigation, currentPageId) diff --git a/packages/webpack-plugin/lib/platform/style/wx/index.js b/packages/webpack-plugin/lib/platform/style/wx/index.js index 327ff32b0..d28c27947 100644 --- a/packages/webpack-plugin/lib/platform/style/wx/index.js +++ b/packages/webpack-plugin/lib/platform/style/wx/index.js @@ -108,6 +108,32 @@ module.exports = function getSpec ({ warn, error }) { } return result } + // 解析 translateY(10px) 这种结构 + // 括号外作为key返回,括号内作为val返回 + const parseValueFromParentheses = (values) => { + let i = -1 + const len = values.length + let stack = 0 + let start = 0 + let key = '' + let val = '' + while (++i < len) { + const char = values[i] + if (char === '(') { + if (stack === 0) { + start = i + key = values.substring(0, start) + } + stack++ + } else if (char === ')') { + stack-- + if (stack === 0) { + val = values.substring(start, i + 1) + } + } + } + return { key, val } + } // const getDefaultValueFromVar = (str) => { // const totalVarExp = /^var\((.+)\)$/ // if (!totalVarExp.test(str)) return str @@ -378,61 +404,55 @@ module.exports = function getSpec ({ warn, error }) { const values = parseValues(value) const transform = [] values.forEach(item => { - const match = item.match(/([/\w]+)\(([^)]+)\)/) - if (match && match.length >= 3) { - let key = match[1] - const val = match[2] - switch (key) { - case 'translateX': - case 'translateY': - case 'scaleX': - case 'scaleY': - case 'rotateX': - case 'rotateY': - case 'rotateZ': - case 'rotate': - case 'skewX': - case 'skewY': - case 'perspective': - // 单个值处理 - transform.push({ [key]: val }) - break - case 'matrix': - case 'matrix3d': - transform.push({ [key]: parseValues(val, ',').map(val => +val) }) - break - case 'translate': - case 'scale': - case 'skew': - case 'rotate3d': // x y z angle - case 'translate3d': // x y 支持 z不支持 - case 'scale3d': // x y 支持 z不支持 - { - // 2 个以上的值处理 - key = key.replace('3d', '') - const vals = parseValues(val, ',').splice(0, key === 'rotate' ? 4 : 3) - // scale(.5) === scaleX(.5) scaleY(.5) - if (vals.length === 1 && key === 'scale') { - vals.push(vals[0]) - } - const xyz = ['X', 'Y', 'Z'] - transform.push(...vals.map((v, index) => { - if (key !== 'rotate' && index > 1) { - unsupportedPropError({ prop: `${key}Z`, value, selector }, { mode }) - } - return { [`${key}${xyz[index] || ''}`]: v.trim() } - })) - break + let { key, val } = parseValueFromParentheses(item) + switch (key) { + case 'translateX': + case 'translateY': + case 'scaleX': + case 'scaleY': + case 'rotateX': + case 'rotateY': + case 'rotateZ': + case 'rotate': + case 'skewX': + case 'skewY': + case 'perspective': + // 单个值处理 + transform.push({ [key]: val }) + break + case 'matrix': + case 'matrix3d': + transform.push({ [key]: parseValues(val, ',').map(val => +val) }) + break + case 'translate': + case 'scale': + case 'skew': + case 'rotate3d': // x y z angle + case 'translate3d': // x y 支持 z不支持 + case 'scale3d': // x y 支持 z不支持 + { + // 2 个以上的值处理 + key = key.replace('3d', '') + const vals = parseValues(val, ',').splice(0, key === 'rotate' ? 4 : 3) + // scale(.5) === scaleX(.5) scaleY(.5) + if (vals.length === 1 && key === 'scale') { + vals.push(vals[0]) + } + const xyz = ['X', 'Y', 'Z'] + transform.push(...vals.map((v, index) => { + if (key !== 'rotate' && index > 1) { + unsupportedPropError({ prop: `${key}Z`, value, selector }, { mode }) } - case 'translateZ': - case 'scaleZ': - default: - // 不支持的属性处理 - unsupportedPropError({ prop, value, selector }, { mode }) - break + return { [`${key}${xyz[index] || ''}`]: v.trim() } + })) + break } - } else { - error(`Property [${prop}] is invalid in ${selector}, received [${value}], please check again!`) + case 'translateZ': + case 'scaleZ': + default: + // 不支持的属性处理 + unsupportedPropError({ prop, value, selector }, { mode }) + break } }) return { diff --git a/packages/webpack-plugin/lib/platform/template/wx/component-config/movable-view.js b/packages/webpack-plugin/lib/platform/template/wx/component-config/movable-view.js index 77d53b44e..f24b45bf8 100644 --- a/packages/webpack-plugin/lib/platform/template/wx/component-config/movable-view.js +++ b/packages/webpack-plugin/lib/platform/template/wx/component-config/movable-view.js @@ -2,6 +2,8 @@ const TAG_NAME = 'movable-view' module.exports = function ({ print }) { const aliEventLog = print({ platform: 'ali', tag: TAG_NAME, isError: false, type: 'event' }) + const androidEventLog = print({ platform: 'android', tag: TAG_NAME, isError: false, type: 'event' }) + const iosEventLog = print({ platform: 'ios', tag: TAG_NAME, isError: false, type: 'event' }) const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false }) const androidPropLog = print({ platform: 'android', tag: TAG_NAME, isError: false }) const iosPropLog = print({ platform: 'ios', tag: TAG_NAME, isError: false }) @@ -27,7 +29,7 @@ module.exports = function ({ print }) { android: androidPropLog }, { - test: /^(inertia|damping|animation)$/, + test: /^(damping|friction|scale|scale-min|scale-max|scale-value)$/, ios: iosPropLog, android: androidPropLog } @@ -36,6 +38,11 @@ module.exports = function ({ print }) { { test: /^(htouchmove|vtouchmove)$/, ali: aliEventLog + }, + { + test: /^(bindscale)$/, + ios: iosEventLog, + android: androidEventLog } ] } diff --git a/packages/webpack-plugin/lib/runtime/components/react/context.ts b/packages/webpack-plugin/lib/runtime/components/react/context.ts index 5bb14fc15..39472e674 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/context.ts +++ b/packages/webpack-plugin/lib/runtime/components/react/context.ts @@ -33,6 +33,10 @@ export interface IntersectionObserver { } } +export interface ScrollViewContextValue { + gestureRef: React.RefObject | null +} + export const MovableAreaContext = createContext({ width: 0, height: 0 }) export const FormContext = createContext(null) @@ -52,3 +56,5 @@ export const IntersectionObserverContext = createContext(null) export const KeyboardAvoidContext = createContext(null) + +export const ScrollViewContext = createContext({ gestureRef: null }) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-input.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-input.tsx index be27905bb..4c1c56a46 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-input.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-input.tsx @@ -149,7 +149,7 @@ const Input = forwardRef, FinalInputProps 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, - 'adjust-position': adjustPosition = true, + 'adjust-position': adjustPosition = false, bindinput, bindfocus, bindblur, diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-movable-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-movable-view.tsx index 3a0a33025..38c4c64cb 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-movable-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-movable-view.tsx @@ -11,7 +11,7 @@ * ✘ scale-min * ✘ scale-max * ✘ scale-value - * ✘ animation + * ✔ animation * ✔ bindchange * ✘ bindscale * ✔ htouchmove diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-rich-text/index.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-rich-text/index.tsx index db6097df5..7e43b48c4 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-rich-text/index.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-rich-text/index.tsx @@ -3,10 +3,10 @@ * ✔ nodes */ import { View, ViewProps, ViewStyle } from 'react-native' -import { useRef, forwardRef, JSX, useState } from 'react' +import { useRef, forwardRef, JSX, useState, createElement } from 'react' import useInnerProps from '../getInnerListeners' import useNodesRef, { HandlerRef } from '../useNodesRef' // 引入辅助函数 -import { useTransformStyle, useLayout } from '../utils' +import { useTransformStyle, useLayout, extendObject } from '../utils' import { WebView, WebViewMessageEvent } from 'react-native-webview' import { generateHTML } from './html' @@ -91,28 +91,22 @@ const _RichText = forwardRef, _RichTextProps>(( layoutRef }) - const innerProps = useInnerProps(props, { + const innerProps = useInnerProps(props, extendObject({ ref: nodeRef, - style: { ...normalStyle, ...layoutStyle }, - ...layoutProps - }, [], { + style: extendObject(normalStyle, layoutStyle) + }, layoutProps), [], { layoutRef }) const html: string = typeof nodes === 'string' ? nodes : jsonToHtmlStr(nodes) - return ( - - { - setWebViewHeight(+event.nativeEvent.data) - }} - > - - + return createElement(View, innerProps, + createElement(WebView, { + source: { html: generateHTML(html) }, + onMessage: (event: WebViewMessageEvent) => { + setWebViewHeight(+event.nativeEvent.data) + } + }) ) }) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx index b058e622d..a65adab4d 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx @@ -33,13 +33,13 @@ */ import { ScrollView } from 'react-native-gesture-handler' import { View, RefreshControl, NativeSyntheticEvent, NativeScrollEvent, LayoutChangeEvent, ViewStyle } from 'react-native' -import { JSX, ReactNode, RefObject, useRef, useState, useEffect, forwardRef, useContext, createElement } from 'react' +import { JSX, ReactNode, RefObject, useRef, useState, useEffect, forwardRef, useContext, createElement, useMemo } from 'react' import { useAnimatedRef } from 'react-native-reanimated' import { warn } from '@mpxjs/utils' import useInnerProps, { getCustomEvent } from './getInnerListeners' import useNodesRef, { HandlerRef } from './useNodesRef' import { splitProps, splitStyle, useTransformStyle, useLayout, wrapChildren, extendObject, flatGesture, GestureHandler } from './utils' -import { IntersectionObserverContext } from './context' +import { IntersectionObserverContext, ScrollViewContext } from './context' interface ScrollViewProps { children?: ReactNode; @@ -194,6 +194,12 @@ const _ScrollView = forwardRef, S gestureRef: scrollViewRef }) + const contextValue = useMemo(() => { + return { + gestureRef: scrollViewRef + } + }, []) + const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef, onLayout }) if (scrollX && scrollY) { @@ -507,14 +513,17 @@ const _ScrollView = forwardRef, S }, (refresherDefaultStyle && refresherDefaultStyle !== 'none' ? { colors: refreshColor[refresherDefaultStyle] } : null))) : undefined }), - wrapChildren( - props, - { - hasVarDec, - varContext: varContextRef.current, - textStyle, - textProps - } + createElement(ScrollViewContext.Provider, + { value: contextValue }, + wrapChildren( + props, + { + hasVarDec, + varContext: varContextRef.current, + textStyle, + textProps + } + ) ) ) }) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-view.tsx index cdc8c02ae..dfe12e092 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-view.tsx @@ -12,9 +12,10 @@ import useAnimationHooks from './useAnimationHooks' import type { AnimationProp } from './useAnimationHooks' import { ExtendedViewStyle } from './types/common' import useNodesRef, { HandlerRef } from './useNodesRef' -import { parseUrl, PERCENT_REGEX, splitStyle, splitProps, useTransformStyle, wrapChildren, useLayout, renderImage, pickStyle, extendObject } from './utils' +import { parseUrl, PERCENT_REGEX, splitStyle, splitProps, useTransformStyle, wrapChildren, useLayout, renderImage, pickStyle, extendObject, useHoverStyle } from './utils' import { error } from '@mpxjs/utils' import LinearGradient from 'react-native-linear-gradient' +import { GestureDetector } from 'react-native-gesture-handler' export interface _ViewProps extends ViewProps { style?: ExtendedViewStyle @@ -683,8 +684,6 @@ const _View = forwardRef, _ViewProps>((viewProps, r animation } = props - const [isHover, setIsHover] = useState(false) - // 默认样式 const defaultStyle: ExtendedViewStyle = style.display === 'flex' ? { @@ -695,6 +694,8 @@ const _View = forwardRef, _ViewProps>((viewProps, r } : {} + const { isHover, enableHoverStyle, gesture } = useHoverStyle({ hoverStyle, hoverStartTime, hoverStayTime }) + const styleObj: ExtendedViewStyle = extendObject({}, defaultStyle, style, isHover ? hoverStyle as ExtendedViewStyle : {}) const { @@ -725,45 +726,6 @@ const _View = forwardRef, _ViewProps>((viewProps, r style: normalStyle }) - const dataRef = useRef<{ - startTimer?: ReturnType - stayTimer?: ReturnType - }>({}) - - useEffect(() => { - return () => { - dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer) - dataRef.current.stayTimer && clearTimeout(dataRef.current.stayTimer) - } - }, []) - - const setStartTimer = () => { - dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer) - dataRef.current.startTimer = setTimeout(() => { - setIsHover(true) - }, +hoverStartTime) - } - - const setStayTimer = () => { - dataRef.current.stayTimer && clearTimeout(dataRef.current.stayTimer) - dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer) - dataRef.current.stayTimer = setTimeout(() => { - setIsHover(false) - }, +hoverStayTime) - } - - function onTouchStart (e: NativeSyntheticEvent) { - const { bindtouchstart } = props - bindtouchstart && bindtouchstart(e) - setStartTimer() - } - - function onTouchEnd (e: NativeSyntheticEvent) { - const { bindtouchend } = props - bindtouchend && bindtouchend(e) - setStayTimer() - } - const { layoutRef, layoutStyle, @@ -789,13 +751,7 @@ const _View = forwardRef, _ViewProps>((viewProps, r ref: nodeRef, style: finalStyle }, - layoutProps, - hoverStyle - ? { - bindtouchstart: onTouchStart, - bindtouchend: onTouchEnd - } - : {} + layoutProps ), [ 'hover-start-time', 'hover-stay-time', @@ -816,9 +772,13 @@ const _View = forwardRef, _ViewProps>((viewProps, r enableFastImage }) - return enableAnimation - ? createElement(Animated.View, innerProps, childNode) + const BaseComponent = enableAnimation + ? createElement(Animated.View, extendObject({}, innerProps, { style: finalStyle }), childNode) : createElement(View, innerProps, childNode) + + return enableHoverStyle + ? createElement(GestureDetector, { gesture }, BaseComponent) + : BaseComponent }) _View.displayName = 'MpxView' diff --git a/packages/webpack-plugin/lib/runtime/components/react/useAnimationHooks.ts b/packages/webpack-plugin/lib/runtime/components/react/useAnimationHooks.ts index 77d1ac050..4dd83152e 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/useAnimationHooks.ts +++ b/packages/webpack-plugin/lib/runtime/components/react/useAnimationHooks.ts @@ -105,54 +105,76 @@ const parseValues = (str: string, char = ' ') => { } return result } +// 解析 translateY(10px) 这种结构 +// 括号外作为key返回,括号内作为val返回 +const parseValueFromParentheses = (values: string) => { + let i = -1 + const len = values.length + let stack = 0 + let start = 0 + let key = '' + let val = '' + while (++i < len) { + const char = values[i] + if (char === '(') { + if (stack === 0) { + start = i + key = values.substring(0, start) + } + stack++ + } else if (char === ')') { + stack-- + if (stack === 0) { + val = values.substring(start, i + 1) + } + } + } + return { key, val } +} // parse string transform, eg: transform: 'rotateX(45deg) rotateZ(0.785398rad)' const parseTransform = (transformStr: string) => { const values = parseValues(transformStr) const transform: {[propName: string]: string|number|number[]}[] = [] values.forEach(item => { - const match = item.match(/([/\w]+)\(([^)]+)\)/) - if (match && match.length >= 3) { - let key = match[1] - const val = match[2] - switch (key) { - case 'translateX': - case 'translateY': - case 'scaleX': - case 'scaleY': - case 'rotateX': - case 'rotateY': - case 'rotateZ': - case 'rotate': - case 'skewX': - case 'skewY': - case 'perspective': - // 单个值处理 - transform.push({ [key]: global.__formatValue(val) }) - break - case 'matrix': - case 'matrix3d': - transform.push({ [key]: parseValues(val, ',').map(val => +val) }) - break - case 'translate': - case 'scale': - case 'skew': - case 'rotate3d': // x y z angle - case 'translate3d': // x y 支持 z不支持 - case 'scale3d': // x y 支持 z不支持 - { - // 2 个以上的值处理 - key = key.replace('3d', '') - const vals = parseValues(val, ',').splice(0, key === 'rotate' ? 4 : 3) - // scale(.5) === scaleX(.5) scaleY(.5) - if (vals.length === 1 && key === 'scale') { - vals.push(vals[0]) - } - const xyz = ['X', 'Y', 'Z'] - transform.push(...vals.map((v, index) => { - return { [`${key}${xyz[index] || ''}`]: global.__formatValue(v.trim()) } - })) - break + let { key, val } = parseValueFromParentheses(item) + switch (key) { + case 'translateX': + case 'translateY': + case 'scaleX': + case 'scaleY': + case 'rotateX': + case 'rotateY': + case 'rotateZ': + case 'rotate': + case 'skewX': + case 'skewY': + case 'perspective': + // 单个值处理 + transform.push({ [key]: global.__formatValue(val) }) + break + case 'matrix': + case 'matrix3d': + transform.push({ [key]: parseValues(val, ',').map(val => +val) }) + break + case 'translate': + case 'scale': + case 'skew': + case 'rotate3d': // x y z angle + case 'translate3d': // x y 支持 z不支持 + case 'scale3d': // x y 支持 z不支持 + { + // 2 个以上的值处理 + key = key.replace('3d', '') + const vals = parseValues(val, ',').splice(0, key === 'rotate' ? 4 : 3) + // scale(.5) === scaleX(.5) scaleY(.5) + if (vals.length === 1 && key === 'scale') { + vals.push(vals[0]) } + const xyz = ['X', 'Y', 'Z'] + transform.push(...vals.map((v, index) => { + return { [`${key}${xyz[index] || ''}`]: global.__formatValue(v.trim()) } + })) + break } } }) diff --git a/packages/webpack-plugin/lib/runtime/components/react/utils.tsx b/packages/webpack-plugin/lib/runtime/components/react/utils.tsx index 724900636..16ba1f621 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/utils.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/utils.tsx @@ -1,11 +1,13 @@ import { useEffect, useCallback, useMemo, useRef, ReactNode, ReactElement, isValidElement, useContext, useState, Dispatch, SetStateAction, Children, cloneElement } from 'react' import { LayoutChangeEvent, TextStyle, ImageProps, Image } from 'react-native' import { isObject, isFunction, isNumber, hasOwn, diffAndCloneA, error, warn, getFocusedNavigation } from '@mpxjs/utils' -import { VarContext } from './context' +import { VarContext, ScrollViewContext } from './context' import { ExpressionParser, parseFunc, ReplaceSource } from './parser' import { initialWindowMetrics } from 'react-native-safe-area-context' import FastImage, { FastImageProps } from '@d11/react-native-fast-image' -import type { AnyFunc, ExtendedFunctionComponent } from './types/common' +import type { AnyFunc, ExtendedFunctionComponent, ExtendedViewStyle } from './types/common' +import { runOnJS } from 'react-native-reanimated' +import { Gesture } from 'react-native-gesture-handler' export const TEXT_STYLE_REGEX = /color|font.*|text.*|letterSpacing|lineHeight|includeFontPadding|writingDirection/ export const PERCENT_REGEX = /^\s*-?\d+(\.\d+)?%\s*$/ @@ -611,3 +613,66 @@ export function pickStyle (styleObj: Record = {}, pickedKeys: Array return acc }, {}) } + +export function useHoverStyle ({ hoverStyle, hoverStartTime, hoverStayTime, disabled } : { hoverStyle?: ExtendedViewStyle, hoverStartTime: number, hoverStayTime: number, disabled?: boolean }) { + const enableHoverStyle = !!hoverStyle + const enableHoverStyleRef = useRef(enableHoverStyle) + if (enableHoverStyleRef.current !== enableHoverStyle) { + throw new Error('[Mpx runtime error]: hover-class use should be stable in the component lifecycle.') + } + + if (!enableHoverStyle) return { enableHoverStyle } + + const gestureRef = useContext(ScrollViewContext).gestureRef + const [isHover, setIsHover] = useState(false) + const dataRef = useRef<{ + startTimer?: ReturnType + stayTimer?: ReturnType + }>({}) + + useEffect(() => { + return () => { + dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer) + dataRef.current.stayTimer && clearTimeout(dataRef.current.stayTimer) + } + }, []) + + const setStartTimer = () => { + dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer) + dataRef.current.startTimer = setTimeout(() => { + setIsHover(true) + }, +hoverStartTime) + } + + const setStayTimer = () => { + dataRef.current.stayTimer && clearTimeout(dataRef.current.stayTimer) + dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer) + dataRef.current.stayTimer = setTimeout(() => { + setIsHover(false) + }, +hoverStayTime) + } + + const gesture = useMemo(() => { + return Gesture.Pan() + .onTouchesDown(() => { + 'worklet' + if (disabled) return + runOnJS(setStartTimer)() + }) + .onTouchesUp(() => { + 'worklet' + if (disabled) return + runOnJS(setStayTimer)() + }) + }, [disabled]) + + if (gestureRef) { + gesture.simultaneousWithExternalGesture(gestureRef) + } + + return { + isHover, + gesture, + enableHoverStyle + } +}