UNPKG

@base-ui-components/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

153 lines (150 loc) 5.81 kB
"use strict"; 'use client'; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.useButton = useButton; var React = _interopRequireWildcard(require("react")); var _dom = require("@floating-ui/utils/dom"); var _useStableCallback = require("@base-ui-components/utils/useStableCallback"); var _error = require("@base-ui-components/utils/error"); var _useIsoLayoutEffect = require("@base-ui-components/utils/useIsoLayoutEffect"); var _mergeProps = require("../merge-props"); var _CompositeRootContext = require("../composite/root/CompositeRootContext"); var _useFocusableWhenDisabled = require("../utils/useFocusableWhenDisabled"); function useButton(parameters = {}) { const { disabled = false, focusableWhenDisabled, tabIndex = 0, native: isNativeButton = true } = parameters; const elementRef = React.useRef(null); const isCompositeItem = (0, _CompositeRootContext.useCompositeRootContext)(true) !== undefined; const isValidLink = (0, _useStableCallback.useStableCallback)(() => { const element = elementRef.current; return Boolean(element?.tagName === 'A' && element?.href); }); const { props: focusableWhenDisabledProps } = (0, _useFocusableWhenDisabled.useFocusableWhenDisabled)({ focusableWhenDisabled, disabled, composite: isCompositeItem, tabIndex, isNativeButton }); if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line react-hooks/rules-of-hooks React.useEffect(() => { if (!elementRef.current) { return; } const isButtonTag = elementRef.current.tagName === 'BUTTON'; if (isNativeButton) { if (!isButtonTag) { (0, _error.error)('A component that acts as a button was not rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is a real <button>, or set the `nativeButton` prop on the component to `false`.'); } } else if (isButtonTag) { (0, _error.error)('A component that acts as a button was rendered as a native <button>, which does not match the default. Ensure that the element passed to the `render` prop of the component is not a real <button>, or set the `nativeButton` prop on the component to `true`.'); } }, [isNativeButton]); } // handles a disabled composite button rendering another button, e.g. // <Toolbar.Button disabled render={<Menu.Trigger />} /> // the `disabled` prop needs to pass through 2 `useButton`s then finally // delete the `disabled` attribute from DOM const updateDisabled = React.useCallback(() => { const element = elementRef.current; if (!isButtonElement(element)) { return; } if (isCompositeItem && disabled && focusableWhenDisabledProps.disabled === undefined && element.disabled) { element.disabled = false; } }, [disabled, focusableWhenDisabledProps.disabled, isCompositeItem]); (0, _useIsoLayoutEffect.useIsoLayoutEffect)(updateDisabled, [updateDisabled]); const getButtonProps = React.useCallback((externalProps = {}) => { const { onClick: externalOnClick, onMouseDown: externalOnMouseDown, onKeyUp: externalOnKeyUp, onKeyDown: externalOnKeyDown, onPointerDown: externalOnPointerDown, ...otherExternalProps } = externalProps; const type = isNativeButton ? 'button' : undefined; return (0, _mergeProps.mergeProps)({ type, onClick(event) { if (disabled) { event.preventDefault(); return; } externalOnClick?.(event); }, onMouseDown(event) { if (!disabled) { externalOnMouseDown?.(event); } }, onKeyDown(event) { if (!disabled) { (0, _mergeProps.makeEventPreventable)(event); externalOnKeyDown?.(event); } if (event.baseUIHandlerPrevented) { return; } const shouldClick = event.target === event.currentTarget && !isNativeButton && !isValidLink() && !disabled; const isEnterKey = event.key === 'Enter'; const isSpaceKey = event.key === ' '; // Keyboard accessibility for non interactive elements if (shouldClick) { if (isSpaceKey || isEnterKey) { event.preventDefault(); } if (isEnterKey) { externalOnClick?.(event); } } }, onKeyUp(event) { // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0 // Keyboard accessibility for non interactive elements if (!disabled) { (0, _mergeProps.makeEventPreventable)(event); externalOnKeyUp?.(event); } if (event.baseUIHandlerPrevented) { return; } if (event.target === event.currentTarget && !isNativeButton && !disabled && event.key === ' ') { externalOnClick?.(event); } }, onPointerDown(event) { if (disabled) { event.preventDefault(); return; } externalOnPointerDown?.(event); } }, !isNativeButton ? { role: 'button' } : undefined, focusableWhenDisabledProps, otherExternalProps); }, [disabled, focusableWhenDisabledProps, isNativeButton, isValidLink]); const buttonRef = (0, _useStableCallback.useStableCallback)(element => { elementRef.current = element; updateDisabled(); }); return { getButtonProps, buttonRef }; } function isButtonElement(elem) { return (0, _dom.isHTMLElement)(elem) && elem.tagName === 'BUTTON'; }