@coreui/react-pro
Version:
UI Components Library for React.js
227 lines (224 loc) • 9.8 kB
JavaScript
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