350 lines
No EOL
13 KiB
JavaScript
350 lines
No EOL
13 KiB
JavaScript
"use strict";
|
|
'use client';
|
|
|
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.PickerPopper = PickerPopper;
|
|
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
|
|
var React = _interopRequireWildcard(require("react"));
|
|
var _useSlotProps = _interopRequireDefault(require("@mui/utils/useSlotProps"));
|
|
var _Grow = _interopRequireDefault(require("@mui/material/Grow"));
|
|
var _Fade = _interopRequireDefault(require("@mui/material/Fade"));
|
|
var _Paper = _interopRequireDefault(require("@mui/material/Paper"));
|
|
var _Popper = _interopRequireDefault(require("@mui/material/Popper"));
|
|
var _Unstable_TrapFocus = _interopRequireDefault(require("@mui/material/Unstable_TrapFocus"));
|
|
var _useForkRef = _interopRequireDefault(require("@mui/utils/useForkRef"));
|
|
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
|
|
var _ownerDocument = _interopRequireDefault(require("@mui/utils/ownerDocument"));
|
|
var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
|
|
var _styles = require("@mui/material/styles");
|
|
var _pickerPopperClasses = require("./pickerPopperClasses");
|
|
var _utils = require("../../utils/utils");
|
|
var _usePickerPrivateContext = require("../../hooks/usePickerPrivateContext");
|
|
var _hooks = require("../../../hooks");
|
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
const _excluded = ["PaperComponent", "ownerState", "children", "paperSlotProps", "paperClasses", "onPaperClick", "onPaperTouchStart"];
|
|
const useUtilityClasses = classes => {
|
|
const slots = {
|
|
root: ['root'],
|
|
paper: ['paper']
|
|
};
|
|
return (0, _composeClasses.default)(slots, _pickerPopperClasses.getPickerPopperUtilityClass, classes);
|
|
};
|
|
const PickerPopperRoot = (0, _styles.styled)(_Popper.default, {
|
|
name: 'MuiPickerPopper',
|
|
slot: 'Root'
|
|
})(({
|
|
theme
|
|
}) => ({
|
|
zIndex: theme.zIndex.modal
|
|
}));
|
|
const PickerPopperPaper = (0, _styles.styled)(_Paper.default, {
|
|
name: 'MuiPickerPopper',
|
|
slot: 'Paper'
|
|
})({
|
|
outline: 0,
|
|
transformOrigin: 'top center',
|
|
variants: [{
|
|
props: ({
|
|
popperPlacement
|
|
}) => new Set(['top', 'top-start', 'top-end']).has(popperPlacement),
|
|
style: {
|
|
transformOrigin: 'bottom center'
|
|
}
|
|
}]
|
|
});
|
|
function clickedRootScrollbar(event, doc) {
|
|
return doc.documentElement.clientWidth < event.clientX || doc.documentElement.clientHeight < event.clientY;
|
|
}
|
|
/**
|
|
* Based on @mui/material/ClickAwayListener without the customization.
|
|
* We can probably strip away even more since children won't be portaled.
|
|
* @param {boolean} active Only listen to clicks when the popper is opened.
|
|
* @param {(event: MouseEvent | TouchEvent) => void} onClickAway The callback to call when clicking outside the popper.
|
|
* @returns {Array} The ref and event handler to listen to the outside clicks.
|
|
*/
|
|
function useClickAwayListener(active, onClickAway) {
|
|
const movedRef = React.useRef(false);
|
|
const syntheticEventRef = React.useRef(false);
|
|
const nodeRef = React.useRef(null);
|
|
const activatedRef = React.useRef(false);
|
|
React.useEffect(() => {
|
|
if (!active) {
|
|
return undefined;
|
|
}
|
|
|
|
// Ensure that this hook is not "activated" synchronously.
|
|
// https://github.com/facebook/react/issues/20074
|
|
function armClickAwayListener() {
|
|
activatedRef.current = true;
|
|
}
|
|
document.addEventListener('mousedown', armClickAwayListener, true);
|
|
document.addEventListener('touchstart', armClickAwayListener, true);
|
|
return () => {
|
|
document.removeEventListener('mousedown', armClickAwayListener, true);
|
|
document.removeEventListener('touchstart', armClickAwayListener, true);
|
|
activatedRef.current = false;
|
|
};
|
|
}, [active]);
|
|
|
|
// The handler doesn't take event.defaultPrevented into account:
|
|
//
|
|
// event.preventDefault() is meant to stop default behaviors like
|
|
// clicking a checkbox to check it, hitting a button to submit a form,
|
|
// and hitting left arrow to move the cursor in a text input etc.
|
|
// Only special HTML elements have these default behaviors.
|
|
const handleClickAway = (0, _useEventCallback.default)(event => {
|
|
if (!activatedRef.current) {
|
|
return;
|
|
}
|
|
|
|
// Given developers can stop the propagation of the synthetic event,
|
|
// we can only be confident with a positive value.
|
|
const insideReactTree = syntheticEventRef.current;
|
|
syntheticEventRef.current = false;
|
|
const doc = (0, _ownerDocument.default)(nodeRef.current);
|
|
|
|
// 1. IE11 support, which trigger the handleClickAway even after the unbind
|
|
// 2. The child might render null.
|
|
// 3. Behave like a blur listener.
|
|
if (!nodeRef.current ||
|
|
// is a TouchEvent?
|
|
'clientX' in event && clickedRootScrollbar(event, doc)) {
|
|
return;
|
|
}
|
|
|
|
// Do not act if user performed touchmove
|
|
if (movedRef.current) {
|
|
movedRef.current = false;
|
|
return;
|
|
}
|
|
let insideDOM;
|
|
|
|
// If not enough, can use https://github.com/DieterHolvoet/event-propagation-path/blob/master/propagationPath.js
|
|
if (event.composedPath) {
|
|
insideDOM = event.composedPath().indexOf(nodeRef.current) > -1;
|
|
} else {
|
|
insideDOM = !doc.documentElement.contains(event.target) || nodeRef.current.contains(event.target);
|
|
}
|
|
if (!insideDOM && !insideReactTree) {
|
|
onClickAway(event);
|
|
}
|
|
});
|
|
|
|
// Keep track of mouse/touch events that bubbled up through the portal.
|
|
const handleSynthetic = event => {
|
|
// Ignore events handled by our internal components
|
|
if (!event.defaultMuiPrevented) {
|
|
syntheticEventRef.current = true;
|
|
}
|
|
};
|
|
React.useEffect(() => {
|
|
if (active) {
|
|
const doc = (0, _ownerDocument.default)(nodeRef.current);
|
|
const handleTouchMove = () => {
|
|
movedRef.current = true;
|
|
};
|
|
doc.addEventListener('touchstart', handleClickAway);
|
|
doc.addEventListener('touchmove', handleTouchMove);
|
|
return () => {
|
|
doc.removeEventListener('touchstart', handleClickAway);
|
|
doc.removeEventListener('touchmove', handleTouchMove);
|
|
};
|
|
}
|
|
return undefined;
|
|
}, [active, handleClickAway]);
|
|
React.useEffect(() => {
|
|
// TODO This behavior is not tested automatically
|
|
// It's unclear whether this is due to different update semantics in test (batched in act() vs discrete on click).
|
|
// Or if this is a timing related issues due to different Transition components
|
|
// Once we get rid of all the manual scheduling (for example setTimeout(update, 0)) we can revisit this code+test.
|
|
if (active) {
|
|
const doc = (0, _ownerDocument.default)(nodeRef.current);
|
|
doc.addEventListener('click', handleClickAway);
|
|
return () => {
|
|
doc.removeEventListener('click', handleClickAway);
|
|
// cleanup `handleClickAway`
|
|
syntheticEventRef.current = false;
|
|
};
|
|
}
|
|
return undefined;
|
|
}, [active, handleClickAway]);
|
|
return [nodeRef, handleSynthetic, handleSynthetic];
|
|
}
|
|
const PickerPopperPaperWrapper = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
const {
|
|
PaperComponent,
|
|
ownerState,
|
|
children,
|
|
paperSlotProps,
|
|
paperClasses,
|
|
onPaperClick,
|
|
onPaperTouchStart
|
|
// picks up the style props provided by `Transition`
|
|
// https://mui.com/material-ui/transitions/#child-requirement
|
|
} = props,
|
|
other = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded);
|
|
const paperProps = (0, _useSlotProps.default)({
|
|
elementType: PaperComponent,
|
|
externalSlotProps: paperSlotProps,
|
|
additionalProps: {
|
|
tabIndex: -1,
|
|
elevation: 8,
|
|
ref
|
|
},
|
|
className: paperClasses,
|
|
ownerState
|
|
});
|
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(PaperComponent, (0, _extends2.default)({}, other, paperProps, {
|
|
onClick: event => {
|
|
onPaperClick(event);
|
|
paperProps.onClick?.(event);
|
|
},
|
|
onTouchStart: event => {
|
|
onPaperTouchStart(event);
|
|
paperProps.onTouchStart?.(event);
|
|
},
|
|
ownerState: ownerState,
|
|
children: children
|
|
}));
|
|
});
|
|
if (process.env.NODE_ENV !== "production") PickerPopperPaperWrapper.displayName = "PickerPopperPaperWrapper";
|
|
function PickerPopper(inProps) {
|
|
const props = (0, _styles.useThemeProps)({
|
|
props: inProps,
|
|
name: 'MuiPickerPopper'
|
|
});
|
|
const {
|
|
children,
|
|
placement = 'bottom-start',
|
|
slots,
|
|
slotProps,
|
|
classes: classesProp
|
|
} = props;
|
|
const {
|
|
open,
|
|
popupRef,
|
|
reduceAnimations
|
|
} = (0, _hooks.usePickerContext)();
|
|
const {
|
|
ownerState: pickerOwnerState,
|
|
rootRefObject
|
|
} = (0, _usePickerPrivateContext.usePickerPrivateContext)();
|
|
const {
|
|
dismissViews,
|
|
getCurrentViewMode,
|
|
onPopperExited,
|
|
triggerElement,
|
|
viewContainerRole
|
|
} = (0, _usePickerPrivateContext.usePickerPrivateContext)();
|
|
React.useEffect(() => {
|
|
function handleKeyDown(nativeEvent) {
|
|
if (open && nativeEvent.key === 'Escape') {
|
|
dismissViews();
|
|
}
|
|
}
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
return () => {
|
|
document.removeEventListener('keydown', handleKeyDown);
|
|
};
|
|
}, [dismissViews, open]);
|
|
const lastFocusedElementRef = React.useRef(null);
|
|
React.useEffect(() => {
|
|
if (viewContainerRole === 'tooltip' || getCurrentViewMode() === 'field') {
|
|
return;
|
|
}
|
|
if (open) {
|
|
lastFocusedElementRef.current = (0, _utils.getActiveElement)(rootRefObject.current);
|
|
} else if (lastFocusedElementRef.current && lastFocusedElementRef.current instanceof HTMLElement) {
|
|
// make sure the button is flushed with updated label, before returning focus to it
|
|
// avoids issue, where screen reader could fail to announce selected date after selection
|
|
setTimeout(() => {
|
|
if (lastFocusedElementRef.current instanceof HTMLElement) {
|
|
lastFocusedElementRef.current.focus();
|
|
}
|
|
});
|
|
}
|
|
}, [open, viewContainerRole, getCurrentViewMode, rootRefObject]);
|
|
const classes = useUtilityClasses(classesProp);
|
|
const handleClickAway = (0, _useEventCallback.default)(() => {
|
|
if (viewContainerRole === 'tooltip') {
|
|
(0, _utils.executeInTheNextEventLoopTick)(() => {
|
|
if (rootRefObject.current?.contains((0, _utils.getActiveElement)(rootRefObject.current)) || popupRef.current?.contains((0, _utils.getActiveElement)(popupRef.current))) {
|
|
return;
|
|
}
|
|
dismissViews();
|
|
});
|
|
} else {
|
|
dismissViews();
|
|
}
|
|
});
|
|
const [clickAwayRef, onPaperClick, onPaperTouchStart] = useClickAwayListener(open, handleClickAway);
|
|
const paperRef = React.useRef(null);
|
|
const handleRef = (0, _useForkRef.default)(paperRef, popupRef);
|
|
const handlePaperRef = (0, _useForkRef.default)(handleRef, clickAwayRef);
|
|
const handleKeyDown = event => {
|
|
if (event.key === 'Escape') {
|
|
// stop the propagation to avoid closing parent modal
|
|
event.stopPropagation();
|
|
dismissViews();
|
|
}
|
|
};
|
|
const Transition = slots?.desktopTransition ?? reduceAnimations ? _Fade.default : _Grow.default;
|
|
const FocusTrap = slots?.desktopTrapFocus ?? _Unstable_TrapFocus.default;
|
|
const Paper = slots?.desktopPaper ?? PickerPopperPaper;
|
|
const Popper = slots?.popper ?? PickerPopperRoot;
|
|
const popperProps = (0, _useSlotProps.default)({
|
|
elementType: Popper,
|
|
externalSlotProps: slotProps?.popper,
|
|
additionalProps: {
|
|
transition: true,
|
|
role: viewContainerRole == null ? undefined : viewContainerRole,
|
|
open,
|
|
placement,
|
|
anchorEl: triggerElement,
|
|
onKeyDown: handleKeyDown
|
|
},
|
|
className: classes.root,
|
|
ownerState: pickerOwnerState
|
|
});
|
|
const ownerState = React.useMemo(() => (0, _extends2.default)({}, pickerOwnerState, {
|
|
popperPlacement: popperProps.placement
|
|
}), [pickerOwnerState, popperProps.placement]);
|
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(Popper, (0, _extends2.default)({}, popperProps, {
|
|
children: ({
|
|
TransitionProps
|
|
}) => /*#__PURE__*/(0, _jsxRuntime.jsx)(FocusTrap, (0, _extends2.default)({
|
|
open: open,
|
|
disableAutoFocus: true
|
|
// pickers are managing focus position manually
|
|
// without this prop the focus is returned to the button before `aria-label` is updated
|
|
// which would force screen readers to read too old label
|
|
,
|
|
disableRestoreFocus: true,
|
|
disableEnforceFocus: viewContainerRole === 'tooltip',
|
|
isEnabled: () => true
|
|
}, slotProps?.desktopTrapFocus, {
|
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Transition, (0, _extends2.default)({}, TransitionProps, slotProps?.desktopTransition, {
|
|
onExited: event => {
|
|
onPopperExited?.();
|
|
slotProps?.desktopTransition?.onExited?.(event);
|
|
TransitionProps?.onExited?.();
|
|
},
|
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(PickerPopperPaperWrapper, {
|
|
PaperComponent: Paper,
|
|
ownerState: ownerState,
|
|
ref: handlePaperRef,
|
|
onPaperClick: onPaperClick,
|
|
onPaperTouchStart: onPaperTouchStart,
|
|
paperClasses: classes.paper,
|
|
paperSlotProps: slotProps?.desktopPaper,
|
|
children: children
|
|
})
|
|
}))
|
|
}))
|
|
}));
|
|
} |