UNPKG

@coreui/react-pro

Version:

UI Components Library for React.js

227 lines (224 loc) 9.8 kB
import { __rest } from '../../node_modules/tslib/tslib.es6.js'; import React, { forwardRef, useRef, useState, useCallback, useMemo, useEffect } from 'react'; import PropTypes from 'prop-types'; import classNames from '../../_virtual/index.js'; import { CDropdownContext } from './CDropdownContext.js'; import { usePopper } from '../../hooks/usePopper.js'; import getNextActiveElement from '../../utils/getNextActiveElement.js'; import isRTL from '../../utils/isRTL.js'; import { useForkedRef } from '../../hooks/useForkedRef.js'; import { placementPropType } from '../../props.js'; import { getPlacement, getReferenceElement } from './utils.js'; import { CFocusTrap } from '../focus-trap/CFocusTrap.js'; const CDropdown = forwardRef((_a, ref) => { var { children, alignment, as = 'div', autoClose = true, className, container, dark, direction, offset = [0, 2], onHide, onShow, placement = 'bottom-start', popper = true, popperConfig, portal = false, reference = 'toggle', variant = 'btn-group', visible = false } = _a, rest = __rest(_a, ["children", "alignment", "as", "autoClose", "className", "container", "dark", "direction", "offset", "onHide", "onShow", "placement", "popper", "popperConfig", "portal", "reference", "variant", "visible"]); const dropdownRef = useRef(null); const dropdownMenuRef = useRef(null); const forkedRef = useForkedRef(ref, dropdownRef); const [dropdownToggleElement, setDropdownToggleElement] = useState(null); const [pendingKeyDownEvent, setPendingKeyDownEvent] = useState(null); const [_visible, setVisible] = useState(visible); const { initPopper, destroyPopper } = usePopper(); const dropdownToggleRef = useCallback((node) => { if (node) { setDropdownToggleElement(node); } }, []); const allowPopperUse = popper && typeof alignment !== 'object'; const Component = variant === 'nav-item' ? 'li' : as; const computedPopperConfig = useMemo(() => { const defaultPopperConfig = { modifiers: [ { name: 'offset', options: { offset, }, }, ], placement: getPlacement(placement, direction, alignment, isRTL(dropdownMenuRef.current)), }; return Object.assign(Object.assign({}, defaultPopperConfig), (typeof popperConfig === 'function' ? popperConfig(defaultPopperConfig) : popperConfig)); }, [offset, placement, direction, alignment, popperConfig]); useEffect(() => { if (visible) { handleShow(); } else { handleHide(); } }, [visible]); useEffect(() => { return () => { if (_visible) { handleHide(); } }; }, []); useEffect(() => { const referenceElement = getReferenceElement(reference, dropdownToggleElement, dropdownRef); const menuElement = dropdownMenuRef.current; if (allowPopperUse && menuElement && referenceElement && _visible) { initPopper(referenceElement, menuElement, computedPopperConfig); } }, [dropdownToggleElement, reference]); useEffect(() => { if (pendingKeyDownEvent !== null) { handleKeydown(pendingKeyDownEvent); setPendingKeyDownEvent(null); } }, [pendingKeyDownEvent]); const handleHide = useCallback(() => { setVisible(false); const menuElement = dropdownMenuRef.current; const toggleElement = dropdownToggleElement; if (allowPopperUse) { destroyPopper(); } menuElement === null || menuElement === void 0 ? void 0 : menuElement.removeEventListener('keydown', handleKeydown); toggleElement === null || toggleElement === void 0 ? void 0 : toggleElement.removeEventListener('keydown', handleKeydown); window.removeEventListener('click', handleClick); window.removeEventListener('keyup', handleKeyup); onHide === null || onHide === void 0 ? void 0 : onHide(); }, [allowPopperUse, dropdownToggleElement, destroyPopper, onHide]); const handleKeydown = useCallback((event) => { if (!dropdownMenuRef.current) { return; } if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { event.preventDefault(); const target = event.target; const items = [ ...dropdownMenuRef.current.querySelectorAll('.dropdown-item:not(.disabled):not(:disabled)'), ]; getNextActiveElement(items, target, event.key === 'ArrowDown', true).focus(); } }, []); const handleKeyup = useCallback((event) => { if (autoClose === false) { return; } if (event.key === 'Escape') { handleHide(); dropdownToggleElement === null || dropdownToggleElement === void 0 ? void 0 : dropdownToggleElement.focus(); } }, [autoClose, dropdownToggleElement, handleHide]); const handleClick = useCallback((event) => { if (!dropdownToggleElement || !dropdownMenuRef.current) { return; } if (event.button === 2) { return; } const composedPath = event.composedPath(); const isOnToggle = composedPath.includes(dropdownToggleElement); const isOnMenu = composedPath.includes(dropdownMenuRef.current); if (isOnToggle) { return; } const target = event.target; const FORM_TAG_RE = /^(input|select|option|textarea|form|button|label)$/i; if (isOnMenu && target && FORM_TAG_RE.test(target.tagName)) { return; } if (autoClose === true || (autoClose === 'inside' && isOnMenu) || (autoClose === 'outside' && !isOnMenu)) { handleHide(); } }, [autoClose, dropdownToggleElement, handleHide]); const handleShow = useCallback((event) => { const menuElement = dropdownMenuRef.current; const referenceElement = getReferenceElement(reference, dropdownToggleElement, dropdownRef); const toggleElement = dropdownToggleElement; if (menuElement && referenceElement && toggleElement) { setVisible(true); if (allowPopperUse) { initPopper(referenceElement, menuElement, computedPopperConfig); } toggleElement.focus(); toggleElement.addEventListener('keydown', handleKeydown); menuElement.addEventListener('keydown', handleKeydown); window.addEventListener('click', handleClick); window.addEventListener('keyup', handleKeyup); if (event && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) { setPendingKeyDownEvent(event); } onShow === null || onShow === void 0 ? void 0 : onShow(); } }, [ allowPopperUse, computedPopperConfig, dropdownToggleElement, reference, handleClick, handleKeydown, handleKeyup, initPopper, onShow, ]); const contextValues = useMemo(() => ({ alignment, container, dark, dropdownMenuRef, dropdownToggleRef, handleHide, handleShow, popper: allowPopperUse, portal, variant, visible: _visible, }), [ alignment, container, dark, dropdownMenuRef, dropdownToggleRef, handleHide, handleShow, allowPopperUse, portal, variant, _visible, ]); return (React.createElement(CDropdownContext.Provider, { value: contextValues }, React.createElement(CFocusTrap, { active: portal && _visible, additionalContainer: dropdownMenuRef, restoreFocus: true }, variant === 'input-group' ? (React.createElement(React.Fragment, null, children)) : (React.createElement(Component, Object.assign({ className: classNames(variant === 'nav-item' ? 'nav-item dropdown' : variant, { 'dropdown-center': direction === 'center', 'dropup dropup-center': direction === 'dropup-center', [`${direction}`]: direction && direction !== 'center' && direction !== 'dropup-center', }, className) }, rest, { ref: forkedRef }), children))))); }); const alignmentDirection = PropTypes.oneOf(['start', 'end']); CDropdown.propTypes = { alignment: PropTypes.oneOfType([ alignmentDirection, PropTypes.shape({ xs: alignmentDirection.isRequired }), PropTypes.shape({ sm: alignmentDirection.isRequired }), PropTypes.shape({ md: alignmentDirection.isRequired }), PropTypes.shape({ lg: alignmentDirection.isRequired }), PropTypes.shape({ xl: alignmentDirection.isRequired }), PropTypes.shape({ xxl: alignmentDirection.isRequired }), ]), as: PropTypes.elementType, autoClose: PropTypes.oneOfType([ PropTypes.bool, PropTypes.oneOf(['inside', 'outside']), ]), children: PropTypes.node, className: PropTypes.string, dark: PropTypes.bool, direction: PropTypes.oneOf(['center', 'dropup', 'dropup-center', 'dropend', 'dropstart']), offset: PropTypes.any, // TODO: find good proptype onHide: PropTypes.func, onShow: PropTypes.func, placement: placementPropType, popper: PropTypes.bool, popperConfig: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), portal: PropTypes.bool, variant: PropTypes.oneOf(['btn-group', 'dropdown', 'input-group', 'nav-item']), visible: PropTypes.bool, }; CDropdown.displayName = 'CDropdown'; export { CDropdown }; //# sourceMappingURL=CDropdown.js.map