create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
230 lines (206 loc) • 6.28 kB
JavaScript
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
;
import type { HoverEventsConfig } from '../../modules/useHover';
import type { PressResponderConfig } from '../../modules/usePressEvents/PressResponder';
import type { ViewProps } from '../View';
import * as React from 'react';
import { forwardRef, memo, useMemo, useState, useRef } from 'react';
import useMergeRefs from '../../modules/useMergeRefs';
import useHover from '../../modules/useHover';
import usePressEvents from '../../modules/usePressEvents';
import StyleSheet from '../StyleSheet';
import View from '../View';
export type StateCallbackType = $ReadOnly<{|
focused: boolean,
hovered: boolean,
pressed: boolean
|}>;
type ViewStyleProp = $PropertyType<ViewProps, 'style'>;
type Props = {
...ViewProps,
children: React.Node | ((state: StateCallbackType) => React.Node),
// Duration (in milliseconds) from `onPressIn` before `onLongPress` is called.
delayLongPress?: ?number,
// Duration (in milliseconds) from `onPressStart` is called after pointerdown
delayPressIn?: ?number,
// Duration (in milliseconds) from `onPressEnd` is called after pointerup.
delayPressOut?: ?number,
// Whether the press behavior is disabled.
disabled?: ?boolean,
// Called when the view is hovered
onHoverIn?: $PropertyType<HoverEventsConfig, 'onHoverStart'>,
// Called when the view is no longer hovered
onHoverOut?: $PropertyType<HoverEventsConfig, 'onHoverEnd'>,
// Called when this view's layout changes
onLayout?: $PropertyType<ViewProps, 'onLayout'>,
// Called when a long-tap gesture is detected.
onLongPress?: $PropertyType<PressResponderConfig, 'onLongPress'>,
// Called when a single tap gesture is detected.
onPress?: $PropertyType<PressResponderConfig, 'onPress'>,
// Called when a touch is engaged, before `onPress`.
onPressIn?: $PropertyType<PressResponderConfig, 'onPressStart'>,
// Called when a touch is moving, after `onPressIn`.
onPressMove?: $PropertyType<PressResponderConfig, 'onPressMove'>,
// Called when a touch is released, before `onPress`.
onPressOut?: $PropertyType<PressResponderConfig, 'onPressEnd'>,
style?: ViewStyleProp | ((state: StateCallbackType) => ViewStyleProp),
/**
* Used only for documentation or testing (e.g. snapshot testing).
*/
testOnly_hovered?: ?boolean,
testOnly_pressed?: ?boolean
};
/**
* Component used to build display components that should respond to whether the
* component is currently pressed or not.
*/
function Pressable(props: Props, forwardedRef): React.Node {
const {
children,
delayLongPress,
delayPressIn,
delayPressOut,
disabled,
focusable,
onBlur,
onContextMenu,
onFocus,
onHoverIn,
onHoverOut,
onKeyDown,
onLongPress,
onPress,
onPressMove,
onPressIn,
onPressOut,
style,
testOnly_hovered,
testOnly_pressed,
...rest
} = props;
const [hovered, setHovered] = useForceableState(testOnly_hovered === true);
const [focused, setFocused] = useForceableState(false);
const [pressed, setPressed] = useForceableState(testOnly_pressed === true);
const hostRef = useRef(null);
const setRef = useMergeRefs(forwardedRef, hostRef);
const pressConfig = useMemo(
() => ({
delayLongPress,
delayPressStart: delayPressIn,
delayPressEnd: delayPressOut,
disabled,
onLongPress,
onPress,
onPressChange: setPressed,
onPressStart: onPressIn,
onPressMove,
onPressEnd: onPressOut
}),
[
delayLongPress,
delayPressIn,
delayPressOut,
disabled,
onLongPress,
onPress,
onPressIn,
onPressMove,
onPressOut,
setPressed
]
);
const pressEventHandlers = usePressEvents(hostRef, pressConfig);
const { onContextMenu: onContextMenuPress, onKeyDown: onKeyDownPress } = pressEventHandlers;
useHover(hostRef, {
contain: true,
disabled,
onHoverChange: setHovered,
onHoverStart: onHoverIn,
onHoverEnd: onHoverOut
});
const interactionState = { hovered, focused, pressed };
const blurHandler = React.useCallback(
(e) => {
if (e.nativeEvent.target === hostRef.current) {
setFocused(false);
if (onBlur != null) {
onBlur(e);
}
}
},
[hostRef, setFocused, onBlur]
);
const focusHandler = React.useCallback(
(e) => {
if (e.nativeEvent.target === hostRef.current) {
setFocused(true);
if (onFocus != null) {
onFocus(e);
}
}
},
[hostRef, setFocused, onFocus]
);
const contextMenuHandler = React.useCallback(
(e) => {
if (onContextMenuPress != null) {
onContextMenuPress(e);
}
if (onContextMenu != null) {
onContextMenu(e);
}
},
[onContextMenu, onContextMenuPress]
);
const keyDownHandler = React.useCallback(
(e) => {
if (onKeyDownPress != null) {
onKeyDownPress(e);
}
if (onKeyDown != null) {
onKeyDown(e);
}
},
[onKeyDown, onKeyDownPress]
);
return (
<View
{...rest}
{...pressEventHandlers}
accessibilityDisabled={disabled}
focusable={!disabled && focusable !== false}
onBlur={blurHandler}
onContextMenu={contextMenuHandler}
onFocus={focusHandler}
onKeyDown={keyDownHandler}
ref={setRef}
style={[
!disabled && styles.root,
typeof style === 'function' ? style(interactionState) : style
]}
>
{typeof children === 'function' ? children(interactionState) : children}
</View>
);
}
function useForceableState(forced: boolean): [boolean, (boolean) => void] {
const [bool, setBool] = useState(false);
return [bool || forced, setBool];
}
const styles = StyleSheet.create({
root: {
cursor: 'pointer',
touchAction: 'manipulation'
}
});
const MemoedPressable = memo(forwardRef(Pressable));
MemoedPressable.displayName = 'Pressable';
export default (MemoedPressable: React.AbstractComponent<Props, React.ElementRef<typeof View>>);