UNPKG

6.18 kBJavaScriptView Raw
1'use client';
2
3import _extends from "@babel/runtime/helpers/esm/extends";
4import * as React from 'react';
5import { unstable_useForkRef as useForkRef, unstable_useIsFocusVisible as useIsFocusVisible } from '@mui/utils';
6import { extractEventHandlers } from '../utils/extractEventHandlers';
7import { useRootElementName } from '../utils/useRootElementName';
8/**
9 *
10 * Demos:
11 *
12 * - [Button](https://mui.com/base-ui/react-button/#hook)
13 *
14 * API:
15 *
16 * - [useButton API](https://mui.com/base-ui/react-button/hooks-api/#use-button)
17 */
18export function useButton(parameters = {}) {
19 const {
20 disabled = false,
21 focusableWhenDisabled,
22 href,
23 rootRef: externalRef,
24 tabIndex,
25 to,
26 type,
27 rootElementName: rootElementNameProp
28 } = parameters;
29 const buttonRef = React.useRef();
30 const [active, setActive] = React.useState(false);
31 const {
32 isFocusVisibleRef,
33 onFocus: handleFocusVisible,
34 onBlur: handleBlurVisible,
35 ref: focusVisibleRef
36 } = useIsFocusVisible();
37 const [focusVisible, setFocusVisible] = React.useState(false);
38 if (disabled && !focusableWhenDisabled && focusVisible) {
39 setFocusVisible(false);
40 }
41 React.useEffect(() => {
42 isFocusVisibleRef.current = focusVisible;
43 }, [focusVisible, isFocusVisibleRef]);
44 const [rootElementName, updateRootElementName] = useRootElementName({
45 rootElementName: rootElementNameProp ?? (href || to ? 'a' : undefined),
46 componentName: 'Button'
47 });
48 const createHandleMouseLeave = otherHandlers => event => {
49 if (focusVisible) {
50 event.preventDefault();
51 }
52 otherHandlers.onMouseLeave?.(event);
53 };
54 const createHandleBlur = otherHandlers => event => {
55 handleBlurVisible(event);
56 if (isFocusVisibleRef.current === false) {
57 setFocusVisible(false);
58 }
59 otherHandlers.onBlur?.(event);
60 };
61 const createHandleFocus = otherHandlers => event => {
62 // Fix for https://github.com/facebook/react/issues/7769
63 if (!buttonRef.current) {
64 buttonRef.current = event.currentTarget;
65 }
66 handleFocusVisible(event);
67 if (isFocusVisibleRef.current === true) {
68 setFocusVisible(true);
69 otherHandlers.onFocusVisible?.(event);
70 }
71 otherHandlers.onFocus?.(event);
72 };
73 const isNativeButton = () => {
74 const button = buttonRef.current;
75 return rootElementName === 'BUTTON' || rootElementName === 'INPUT' && ['button', 'submit', 'reset'].includes(button?.type) || rootElementName === 'A' && button?.href;
76 };
77 const createHandleClick = otherHandlers => event => {
78 if (!disabled) {
79 otherHandlers.onClick?.(event);
80 }
81 };
82 const createHandleMouseDown = otherHandlers => event => {
83 if (!disabled) {
84 setActive(true);
85 document.addEventListener('mouseup', () => {
86 setActive(false);
87 }, {
88 once: true
89 });
90 }
91 otherHandlers.onMouseDown?.(event);
92 };
93 const createHandleKeyDown = otherHandlers => event => {
94 otherHandlers.onKeyDown?.(event);
95 if (event.defaultMuiPrevented) {
96 return;
97 }
98 if (event.target === event.currentTarget && !isNativeButton() && event.key === ' ') {
99 event.preventDefault();
100 }
101 if (event.target === event.currentTarget && event.key === ' ' && !disabled) {
102 setActive(true);
103 }
104
105 // Keyboard accessibility for non interactive elements
106 if (event.target === event.currentTarget && !isNativeButton() && event.key === 'Enter' && !disabled) {
107 otherHandlers.onClick?.(event);
108 event.preventDefault();
109 }
110 };
111 const createHandleKeyUp = otherHandlers => event => {
112 // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
113 // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
114
115 if (event.target === event.currentTarget) {
116 setActive(false);
117 }
118 otherHandlers.onKeyUp?.(event);
119
120 // Keyboard accessibility for non interactive elements
121 if (event.target === event.currentTarget && !isNativeButton() && !disabled && event.key === ' ' && !event.defaultMuiPrevented) {
122 otherHandlers.onClick?.(event);
123 }
124 };
125 const handleRef = useForkRef(updateRootElementName, externalRef, focusVisibleRef, buttonRef);
126 const buttonProps = {};
127 if (tabIndex !== undefined) {
128 buttonProps.tabIndex = tabIndex;
129 }
130 if (rootElementName === 'BUTTON') {
131 buttonProps.type = type ?? 'button';
132 if (focusableWhenDisabled) {
133 buttonProps['aria-disabled'] = disabled;
134 } else {
135 buttonProps.disabled = disabled;
136 }
137 } else if (rootElementName === 'INPUT') {
138 if (type && ['button', 'submit', 'reset'].includes(type)) {
139 if (focusableWhenDisabled) {
140 buttonProps['aria-disabled'] = disabled;
141 } else {
142 buttonProps.disabled = disabled;
143 }
144 }
145 } else if (rootElementName !== '') {
146 if (!href && !to) {
147 buttonProps.role = 'button';
148 buttonProps.tabIndex = tabIndex ?? 0;
149 }
150 if (disabled) {
151 buttonProps['aria-disabled'] = disabled;
152 buttonProps.tabIndex = focusableWhenDisabled ? tabIndex ?? 0 : -1;
153 }
154 }
155 const getRootProps = (externalProps = {}) => {
156 const externalEventHandlers = _extends({}, extractEventHandlers(parameters), extractEventHandlers(externalProps));
157 const props = _extends({
158 type
159 }, externalEventHandlers, buttonProps, externalProps, {
160 onBlur: createHandleBlur(externalEventHandlers),
161 onClick: createHandleClick(externalEventHandlers),
162 onFocus: createHandleFocus(externalEventHandlers),
163 onKeyDown: createHandleKeyDown(externalEventHandlers),
164 onKeyUp: createHandleKeyUp(externalEventHandlers),
165 onMouseDown: createHandleMouseDown(externalEventHandlers),
166 onMouseLeave: createHandleMouseLeave(externalEventHandlers),
167 ref: handleRef
168 });
169
170 // onFocusVisible can be present on the props or parameters,
171 // but it's not a valid React event handler so it must not be forwarded to the inner component.
172 // If present, it will be handled by the focus handler.
173 delete props.onFocusVisible;
174 return props;
175 };
176 return {
177 getRootProps,
178 focusVisible,
179 setFocusVisible,
180 active,
181 rootRef: handleRef
182 };
183}
\No newline at end of file