forked from microsoft/fluentui-react-native
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathuseButton.ts
118 lines (107 loc) · 4.37 KB
/
useButton.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import * as React from 'react';
import { Platform } from 'react-native';
import type { LayoutChangeEvent } from 'react-native';
import { useFluentTheme } from '@fluentui-react-native/framework';
import { usePressableState, useKeyProps, useOnPressWithFocus, useViewCommandFocus } from '@fluentui-react-native/interactive-hooks';
import { isHighContrast } from '@fluentui-react-native/theming-utils';
import type { ButtonProps, ButtonInfo } from './Button.types';
// On win32 we don't want to fire the onClick event if the Button
// hasn't received a key down event first. This prevents behavior
// like the button firing after you tab to it white Enter is pressed
// and then releasing Enter, or the Menu reopening since it closes
// onKeyDown while the Button operates onKeyUp.
const shouldOnlyFireIfPressed = Platform.OS === ('win32' as any);
let isProcessingKeyboardInvocation = false;
export const useButton = (props: ButtonProps): ButtonInfo => {
const defaultComponentRef = React.useRef(null);
const {
accessible,
accessibilityRole,
componentRef = defaultComponentRef,
disabled,
onBlur,
onClick,
onLayout,
loading,
enableFocusRing,
focusable,
...rest
} = props;
const isDisabled = !!disabled || !!loading;
// GH #1336: Set focusRef to null if button is disabled to prevent getting keyboard focus.
const focusRef = isDisabled ? null : componentRef;
const onClickWithFocus = useOnPressWithFocus(focusRef, onClick);
const onBlurInner = React.useCallback(
(e) => {
isProcessingKeyboardInvocation = false;
onBlur?.(e);
},
[onBlur],
);
const pressable = usePressableState({ ...rest, onPress: onClickWithFocus, onBlur: shouldOnlyFireIfPressed ? onBlurInner : onBlur });
const onKeyDown = React.useCallback(
(e) => {
if (!disabled && (e.nativeEvent.key === 'Enter' || e.nativeEvent.key === ' ')) {
isProcessingKeyboardInvocation = true;
}
},
[disabled],
);
const onKeyPress = React.useCallback(
(e) => {
if (isProcessingKeyboardInvocation) {
onClick?.(e);
isProcessingKeyboardInvocation = false;
}
},
[onClick],
);
const onKeyProps = useKeyProps(shouldOnlyFireIfPressed ? onKeyPress : onClick, ' ', 'Enter');
const hasTogglePattern = props.accessibilityActions && !!props.accessibilityActions.find((action) => action.name === 'Toggle');
const theme = useFluentTheme();
const shouldUseTwoToneFocusBorder = Platform.OS === ('win32' as any) && props.appearance === 'primary' && !isHighContrast(theme);
const [baseHeight, setBaseHeight] = React.useState<number | undefined>(undefined);
const [baseWidth, setBaseWidth] = React.useState<number | undefined>(undefined);
const onLayoutInner = React.useCallback(
(e: LayoutChangeEvent) => {
// Only run when shouldUseTwoToneFocusBorder so that state update doesn't
// affect platforms that don't need it.
if (shouldUseTwoToneFocusBorder) {
setBaseHeight(e.nativeEvent.layout.height);
setBaseWidth(e.nativeEvent.layout.width);
}
onLayout && onLayout(e);
},
[onLayout, setBaseHeight, setBaseWidth, shouldUseTwoToneFocusBorder],
);
return {
props: {
...onKeyProps,
...(Platform.OS === ('win32' as any) && { onKeyDown: onKeyDown }),
...pressable.props, // allow user key events to override those set by us
/**
* https://github.com/facebook/react-native/issues/34986
* Due to a bug in React Native, unconditionally passing this may cause unnecessary re-renders.
* Therefore, let's only pass it in if it's defined to limit this issue.
*/
...(isDisabled && { disabled: isDisabled }),
accessible: accessible ?? true,
accessibilityRole: accessibilityRole || 'button',
onAccessibilityTap: props.onAccessibilityTap || (!hasTogglePattern ? props.onClick : undefined),
accessibilityLabel: props.accessibilityLabel,
enableFocusRing: enableFocusRing ?? !shouldUseTwoToneFocusBorder,
focusable: focusable ?? !isDisabled,
ref: useViewCommandFocus(componentRef),
iconPosition: props.iconPosition || 'before',
loading,
onLayout: onLayoutInner,
},
state: {
...pressable.state,
pressed: pressable.state.pressed,
measuredWidth: baseWidth,
measuredHeight: baseHeight,
shouldUseTwoToneFocusBorder: shouldUseTwoToneFocusBorder,
},
};
};