UNPKG

@ariakit/react-core

Version:

Ariakit React core

341 lines (311 loc) 11.4 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _T3QB4FR3cjs = require('./T3QB4FR3.cjs'); var _WBFXWJUHcjs = require('./WBFXWJUH.cjs'); var _MZ2HG624cjs = require('./MZ2HG624.cjs'); // src/focusable/focusable.tsx var _dom = require('@ariakit/core/utils/dom'); var _events = require('@ariakit/core/utils/events'); var _focus = require('@ariakit/core/utils/focus'); var _misc = require('@ariakit/core/utils/misc'); var _platform = require('@ariakit/core/utils/platform'); var _react = require('react'); var TagName = "div"; var isSafariBrowser = _platform.isSafari.call(void 0, ); var alwaysFocusVisibleInputTypes = [ "text", "search", "url", "tel", "email", "password", "number", "date", "month", "week", "time", "datetime", "datetime-local" ]; var safariFocusAncestorSymbol = Symbol("safariFocusAncestor"); function isSafariFocusAncestor(element) { if (!element) return false; return !!element[safariFocusAncestorSymbol]; } function markSafariFocusAncestor(element, value) { if (!element) return; element[safariFocusAncestorSymbol] = value; } function isAlwaysFocusVisible(element) { const { tagName, readOnly, type } = element; if (tagName === "TEXTAREA" && !readOnly) return true; if (tagName === "SELECT" && !readOnly) return true; if (tagName === "INPUT" && !readOnly) { return alwaysFocusVisibleInputTypes.includes(type); } if (element.isContentEditable) return true; const role = element.getAttribute("role"); if (role === "combobox" && element.dataset.name) { return true; } return false; } function getLabels(element) { if ("labels" in element) { return element.labels; } return null; } function isNativeCheckboxOrRadio(element) { const tagName = element.tagName.toLowerCase(); if (tagName === "input" && element.type) { return element.type === "radio" || element.type === "checkbox"; } return false; } function isNativeTabbable(tagName) { if (!tagName) return true; return tagName === "button" || tagName === "summary" || tagName === "input" || tagName === "select" || tagName === "textarea" || tagName === "a"; } function supportsDisabledAttribute(tagName) { if (!tagName) return true; return tagName === "button" || tagName === "input" || tagName === "select" || tagName === "textarea"; } function getTabIndex(focusable, trulyDisabled, nativeTabbable, supportsDisabled, tabIndexProp) { if (!focusable) { return tabIndexProp; } if (trulyDisabled) { if (nativeTabbable && !supportsDisabled) { return -1; } return; } if (nativeTabbable) { return tabIndexProp; } return tabIndexProp || 0; } function useDisableEvent(onEvent, disabled) { return _MZ2HG624cjs.useEvent.call(void 0, (event) => { onEvent == null ? void 0 : onEvent(event); if (event.defaultPrevented) return; if (disabled) { event.stopPropagation(); event.preventDefault(); } }); } var hasInstalledGlobalEventListeners = false; var isKeyboardModality = true; function onGlobalMouseDown(event) { const target = event.target; if (target && "hasAttribute" in target) { if (!target.hasAttribute("data-focus-visible")) { isKeyboardModality = false; } } } function onGlobalKeyDown(event) { if (event.metaKey) return; if (event.ctrlKey) return; if (event.altKey) return; isKeyboardModality = true; } var useFocusable = _WBFXWJUHcjs.createHook.call(void 0, function useFocusable2({ focusable = true, accessibleWhenDisabled, autoFocus, onFocusVisible, ...props }) { const ref = _react.useRef.call(void 0, null); _react.useEffect.call(void 0, () => { if (!focusable) return; if (hasInstalledGlobalEventListeners) return; _events.addGlobalEventListener.call(void 0, "mousedown", onGlobalMouseDown, true); _events.addGlobalEventListener.call(void 0, "keydown", onGlobalKeyDown, true); hasInstalledGlobalEventListeners = true; }, [focusable]); if (isSafariBrowser) { _react.useEffect.call(void 0, () => { if (!focusable) return; const element = ref.current; if (!element) return; if (!isNativeCheckboxOrRadio(element)) return; const labels = getLabels(element); if (!labels) return; const onMouseUp = () => queueMicrotask(() => element.focus()); for (const label of labels) { label.addEventListener("mouseup", onMouseUp); } return () => { for (const label of labels) { label.removeEventListener("mouseup", onMouseUp); } }; }, [focusable]); } const disabled = focusable && _misc.disabledFromProps.call(void 0, props); const trulyDisabled = !!disabled && !accessibleWhenDisabled; const [focusVisible, setFocusVisible] = _react.useState.call(void 0, false); _react.useEffect.call(void 0, () => { if (!focusable) return; if (trulyDisabled && focusVisible) { setFocusVisible(false); } }, [focusable, trulyDisabled, focusVisible]); _react.useEffect.call(void 0, () => { if (!focusable) return; if (!focusVisible) return; const element = ref.current; if (!element) return; if (typeof IntersectionObserver === "undefined") return; const observer = new IntersectionObserver(() => { if (!_focus.isFocusable.call(void 0, element)) { setFocusVisible(false); } }); observer.observe(element); return () => observer.disconnect(); }, [focusable, focusVisible]); const onKeyPressCapture = useDisableEvent( props.onKeyPressCapture, disabled ); const onMouseDownCapture = useDisableEvent( props.onMouseDownCapture, disabled ); const onClickCapture = useDisableEvent(props.onClickCapture, disabled); const onMouseDownProp = props.onMouseDown; const onMouseDown = _MZ2HG624cjs.useEvent.call(void 0, (event) => { onMouseDownProp == null ? void 0 : onMouseDownProp(event); if (event.defaultPrevented) return; if (!focusable) return; const element = event.currentTarget; if (!isSafariBrowser) return; if (_events.isPortalEvent.call(void 0, event)) return; if (!_dom.isButton.call(void 0, element) && !isNativeCheckboxOrRadio(element)) return; let receivedFocus = false; const onFocus = () => { receivedFocus = true; }; const options = { capture: true, once: true }; element.addEventListener("focusin", onFocus, options); const focusableContainer = _focus.getClosestFocusable.call(void 0, element.parentElement); markSafariFocusAncestor(focusableContainer, true); _events.queueBeforeEvent.call(void 0, element, "mouseup", () => { element.removeEventListener("focusin", onFocus, true); markSafariFocusAncestor(focusableContainer, false); if (receivedFocus) return; _focus.focusIfNeeded.call(void 0, element); }); }); const handleFocusVisible = (event, currentTarget) => { if (currentTarget) { event.currentTarget = currentTarget; } if (!focusable) return; const element = event.currentTarget; if (!element) return; if (!_focus.hasFocus.call(void 0, element)) return; onFocusVisible == null ? void 0 : onFocusVisible(event); if (event.defaultPrevented) return; element.dataset.focusVisible = "true"; setFocusVisible(true); }; const onKeyDownCaptureProp = props.onKeyDownCapture; const onKeyDownCapture = _MZ2HG624cjs.useEvent.call(void 0, (event) => { onKeyDownCaptureProp == null ? void 0 : onKeyDownCaptureProp(event); if (event.defaultPrevented) return; if (!focusable) return; if (focusVisible) return; if (event.metaKey) return; if (event.altKey) return; if (event.ctrlKey) return; if (!_events.isSelfTarget.call(void 0, event)) return; const element = event.currentTarget; const applyFocusVisible = () => handleFocusVisible(event, element); _events.queueBeforeEvent.call(void 0, element, "focusout", applyFocusVisible); }); const onFocusCaptureProp = props.onFocusCapture; const onFocusCapture = _MZ2HG624cjs.useEvent.call(void 0, (event) => { onFocusCaptureProp == null ? void 0 : onFocusCaptureProp(event); if (event.defaultPrevented) return; if (!focusable) return; if (!_events.isSelfTarget.call(void 0, event)) { setFocusVisible(false); return; } const element = event.currentTarget; const applyFocusVisible = () => handleFocusVisible(event, element); if (isKeyboardModality || isAlwaysFocusVisible(event.target)) { _events.queueBeforeEvent.call(void 0, event.target, "focusout", applyFocusVisible); } else { setFocusVisible(false); } }); const onBlurProp = props.onBlur; const onBlur = _MZ2HG624cjs.useEvent.call(void 0, (event) => { onBlurProp == null ? void 0 : onBlurProp(event); if (!focusable) return; if (!_events.isFocusEventOutside.call(void 0, event)) return; event.currentTarget.removeAttribute("data-focus-visible"); setFocusVisible(false); }); const autoFocusOnShow = _react.useContext.call(void 0, _T3QB4FR3cjs.FocusableContext); const autoFocusRef = _MZ2HG624cjs.useEvent.call(void 0, (element) => { if (!focusable) return; if (!autoFocus) return; if (!element) return; if (!autoFocusOnShow) return; queueMicrotask(() => { if (_focus.hasFocus.call(void 0, element)) return; if (!_focus.isFocusable.call(void 0, element)) return; element.focus(); }); }); const tagName = _MZ2HG624cjs.useTagName.call(void 0, ref); const nativeTabbable = focusable && isNativeTabbable(tagName); const supportsDisabled = focusable && supportsDisabledAttribute(tagName); const styleProp = props.style; const style = _react.useMemo.call(void 0, () => { if (trulyDisabled) { return { pointerEvents: "none", ...styleProp }; } return styleProp; }, [trulyDisabled, styleProp]); props = { "data-focus-visible": focusable && focusVisible || void 0, "data-autofocus": autoFocus || void 0, "aria-disabled": disabled || void 0, ...props, ref: _MZ2HG624cjs.useMergeRefs.call(void 0, ref, autoFocusRef, props.ref), style, tabIndex: getTabIndex( focusable, trulyDisabled, nativeTabbable, supportsDisabled, props.tabIndex ), disabled: supportsDisabled && trulyDisabled ? true : void 0, // TODO: Test Focusable contentEditable. contentEditable: disabled ? void 0 : props.contentEditable, onKeyPressCapture, onClickCapture, onMouseDownCapture, onMouseDown, onKeyDownCapture, onFocusCapture, onBlur }; return _misc.removeUndefinedValues.call(void 0, props); } ); var Focusable = _WBFXWJUHcjs.forwardRef.call(void 0, function Focusable2(props) { const htmlProps = useFocusable(props); return _WBFXWJUHcjs.createElement.call(void 0, TagName, htmlProps); }); exports.isSafariFocusAncestor = isSafariFocusAncestor; exports.useFocusable = useFocusable; exports.Focusable = Focusable;