UNPKG

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