UNPKG

reakit

Version:

Toolkit for building accessible rich web apps with React

191 lines (159 loc) 7.72 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../_rollupPluginBabelHelpers-8f9a8751.js'); var createComponent = require('reakit-system/createComponent'); var createHook = require('reakit-system/createHook'); require('reakit-utils/shallowEqual'); var React = require('react'); var useForkRef = require('reakit-utils/useForkRef'); var isButton = require('reakit-utils/isButton'); var reakitWarning = require('reakit-warning'); var useLiveRef = require('reakit-utils/useLiveRef'); var useIsomorphicEffect = require('reakit-utils/useIsomorphicEffect'); var hasFocusWithin = require('reakit-utils/hasFocusWithin'); var isPortalEvent = require('reakit-utils/isPortalEvent'); var dom = require('reakit-utils/dom'); var tabbable = require('reakit-utils/tabbable'); var Role_Role = require('../Role/Role.js'); // Automatically generated var TABBABLE_KEYS = ["disabled", "focusable"]; var isSafariOrFirefoxOnMac = dom.isUA("Mac") && !dom.isUA("Chrome") && (dom.isUA("Safari") || dom.isUA("Firefox")); function focusIfNeeded(element) { if (!hasFocusWithin.hasFocusWithin(element) && tabbable.isFocusable(element)) { element.focus(); } } function isNativeTabbable(element) { return ["BUTTON", "INPUT", "SELECT", "TEXTAREA", "A"].includes(element.tagName); } function supportsDisabledAttribute(element) { return ["BUTTON", "INPUT", "SELECT", "TEXTAREA"].includes(element.tagName); } function getTabIndex(trulyDisabled, nativeTabbable, supportsDisabled, htmlTabIndex) { if (trulyDisabled) { if (nativeTabbable && !supportsDisabled) { // Anchor, audio and video tags don't support the `disabled` attribute. // We must pass tabIndex={-1} so they don't receive focus on tab. return -1; } // Elements that support the `disabled` attribute don't need tabIndex. return undefined; } if (nativeTabbable) { // If the element is enabled and it's natively tabbable, we don't need to // specify a tabIndex attribute unless it's explicitly set by the user. return htmlTabIndex; } // If the element is enabled and is not natively tabbable, we have to // fallback tabIndex={0}. return htmlTabIndex || 0; } function useDisableEvent(htmlEventRef, disabled) { return React.useCallback(function (event) { var _htmlEventRef$current; (_htmlEventRef$current = htmlEventRef.current) === null || _htmlEventRef$current === void 0 ? void 0 : _htmlEventRef$current.call(htmlEventRef, event); if (event.defaultPrevented) return; if (disabled) { event.stopPropagation(); event.preventDefault(); } }, [htmlEventRef, disabled]); } var useTabbable = createHook.createHook({ name: "Tabbable", compose: Role_Role.useRole, keys: TABBABLE_KEYS, useOptions: function useOptions(options, _ref) { var disabled = _ref.disabled; return _rollupPluginBabelHelpers._objectSpread2({ disabled: disabled }, options); }, useProps: function useProps(options, _ref2) { var htmlRef = _ref2.ref, htmlTabIndex = _ref2.tabIndex, htmlOnClickCapture = _ref2.onClickCapture, htmlOnMouseDownCapture = _ref2.onMouseDownCapture, htmlOnMouseDown = _ref2.onMouseDown, htmlOnKeyPressCapture = _ref2.onKeyPressCapture, htmlStyle = _ref2.style, htmlProps = _rollupPluginBabelHelpers._objectWithoutPropertiesLoose(_ref2, ["ref", "tabIndex", "onClickCapture", "onMouseDownCapture", "onMouseDown", "onKeyPressCapture", "style"]); var ref = React.useRef(null); var onClickCaptureRef = useLiveRef.useLiveRef(htmlOnClickCapture); var onMouseDownCaptureRef = useLiveRef.useLiveRef(htmlOnMouseDownCapture); var onMouseDownRef = useLiveRef.useLiveRef(htmlOnMouseDown); var onKeyPressCaptureRef = useLiveRef.useLiveRef(htmlOnKeyPressCapture); var trulyDisabled = !!options.disabled && !options.focusable; var _React$useState = React.useState(true), nativeTabbable = _React$useState[0], setNativeTabbable = _React$useState[1]; var _React$useState2 = React.useState(true), supportsDisabled = _React$useState2[0], setSupportsDisabled = _React$useState2[1]; var style = options.disabled ? _rollupPluginBabelHelpers._objectSpread2({ pointerEvents: "none" }, htmlStyle) : htmlStyle; useIsomorphicEffect.useIsomorphicEffect(function () { var tabbable = ref.current; if (!tabbable) { process.env.NODE_ENV !== "production" ? reakitWarning.warning(true, "Can't determine if the element is a native tabbable element because `ref` wasn't passed to the component.", "See https://reakit.io/docs/tabbable") : void 0; return; } if (!isNativeTabbable(tabbable)) { setNativeTabbable(false); } if (!supportsDisabledAttribute(tabbable)) { setSupportsDisabled(false); } }, []); var onClickCapture = useDisableEvent(onClickCaptureRef, options.disabled); var onMouseDownCapture = useDisableEvent(onMouseDownCaptureRef, options.disabled); var onKeyPressCapture = useDisableEvent(onKeyPressCaptureRef, options.disabled); var onMouseDown = React.useCallback(function (event) { var _onMouseDownRef$curre; (_onMouseDownRef$curre = onMouseDownRef.current) === null || _onMouseDownRef$curre === void 0 ? void 0 : _onMouseDownRef$curre.call(onMouseDownRef, event); var element = event.currentTarget; if (event.defaultPrevented) return; // Safari and Firefox on MacOS don't focus on buttons on mouse down // like other browsers/platforms. Instead, they focus on the closest // focusable ancestor element, which is ultimately the body element. So // we make sure to give focus to the tabbable element on mouse down so // it works consistently across browsers. if (!isSafariOrFirefoxOnMac) return; if (isPortalEvent.isPortalEvent(event)) return; if (!isButton.isButton(element)) return; // We can't focus right away after on mouse down, otherwise it would // prevent drag events from happening. So we schedule the focus to the // next animation frame. var raf = requestAnimationFrame(function () { element.removeEventListener("mouseup", focusImmediately, true); focusIfNeeded(element); }); // If mouseUp happens before the next animation frame (which is common // on touch screens or by just tapping the trackpad on MacBook's), we // cancel the animation frame and immediately focus on the element. var focusImmediately = function focusImmediately() { cancelAnimationFrame(raf); focusIfNeeded(element); }; // By listening to the event in the capture phase, we make sure the // focus event is fired before the onMouseUp and onMouseUpCapture React // events, which is aligned with the default browser behavior. element.addEventListener("mouseup", focusImmediately, { once: true, capture: true }); }, []); return _rollupPluginBabelHelpers._objectSpread2({ ref: useForkRef.useForkRef(ref, htmlRef), style: style, tabIndex: getTabIndex(trulyDisabled, nativeTabbable, supportsDisabled, htmlTabIndex), disabled: trulyDisabled && supportsDisabled ? true : undefined, "aria-disabled": options.disabled ? true : undefined, onClickCapture: onClickCapture, onMouseDownCapture: onMouseDownCapture, onMouseDown: onMouseDown, onKeyPressCapture: onKeyPressCapture }, htmlProps); } }); var Tabbable = createComponent.createComponent({ as: "div", useHook: useTabbable }); exports.Tabbable = Tabbable; exports.useTabbable = useTabbable;