UNPKG

@fluentui/react-northstar

Version:
490 lines (485 loc) 17.8 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import _invoke from "lodash/invoke"; import { menuItemBehavior, submenuBehavior } from '@fluentui/accessibility'; import { EventListener } from '@fluentui/react-component-event-listener'; import { focusAsync, mergeVariablesOverrides, useTelemetry, useAutoControlled, useFluentContext, getElementType, useUnhandledProps, useAccessibility, useStyles, useContextSelectors, useOnIFrameFocus } from '@fluentui/react-bindings'; import { Ref, handleRef } from '@fluentui/react-component-ref'; import * as customPropTypes from '@fluentui/react-proptypes'; import * as PropTypes from 'prop-types'; import * as React from 'react'; import { childrenExist, createShorthand, doesNodeContainClick, commonPropTypes, isFromKeyboard as isEventFromKeyboard, setWhatInputSource } from '../../utils'; import { Menu } from './Menu'; import { MenuItemIcon } from './MenuItemIcon'; import { MenuItemContent } from './MenuItemContent'; import { MenuItemIndicator } from './MenuItemIndicator'; import { MenuItemWrapper } from './MenuItemWrapper'; import { Popper, partitionPopperPropsFromShorthand } from '../../utils/positioner'; import { MenuContext } from './menuContext'; import { ChevronEndIcon } from '@fluentui/react-icons-northstar'; export var menuItemClassName = 'ui-menu__item'; export var menuItemSlotClassNames = { submenu: menuItemClassName + "__submenu" }; /** * A MenuItem is an actionable item within a Menu. */ export var MenuItem = /*#__PURE__*/function () { var MenuItem = /*#__PURE__*/React.forwardRef(function (inputProps, ref) { var context = useFluentContext(); var _useTelemetry = useTelemetry(MenuItem.displayName, context.telemetry), setStart = _useTelemetry.setStart, setEnd = _useTelemetry.setEnd; setStart(); var parentProps = useContextSelectors(MenuContext, { active: function active(v) { return v.activeIndex === inputProps.index; }, onItemClick: function onItemClick(v) { return v.onItemClick; }, onItemSelect: function onItemSelect(v) { return v.onItemSelect; }, variables: function variables(v) { return v.variables; }, slotProps: function slotProps(v) { return v.slotProps.item; }, accessibility: function accessibility(v) { return v.behaviors.item; } }); // TODO: we should improve typings for the useContextSelectors var props = Object.assign({}, parentProps.slotProps, { active: parentProps.active, variables: parentProps.variables, accessibility: parentProps.accessibility }, inputProps); var _props$accessibility = props.accessibility, accessibility = _props$accessibility === void 0 ? menuItemBehavior : _props$accessibility, children = props.children, content = props.content, icon = props.icon, wrapper = props.wrapper, primary = props.primary, secondary = props.secondary, active = props.active, vertical = props.vertical, indicator = props.indicator, disabled = props.disabled, underlined = props.underlined, iconOnly = props.iconOnly, inSubmenu = props.inSubmenu, pills = props.pills, pointing = props.pointing, className = props.className, design = props.design, styles = props.styles, variables = props.variables, on = props.on, index = props.index; var _partitionPopperProps = partitionPopperPropsFromShorthand(props.menu), menu = _partitionPopperProps[0], positioningProps = _partitionPopperProps[1]; var _useAutoControlled = useAutoControlled({ defaultValue: props.defaultMenuOpen, value: props.menuOpen, initialValue: false }), menuOpen = _useAutoControlled[0], setMenuOpen = _useAutoControlled[1]; useOnIFrameFocus(menuOpen, context.target, function (e) { setMenuOpen(function (__) { _invoke(props, 'onMenuOpenChange', e, Object.assign({}, props, { menuOpen: false })); return false; }); }); var _React$useState = React.useState(false), isFromKeyboard = _React$useState[0], setIsFromKeyboard = _React$useState[1]; var ElementType = getElementType(props); var unhandledProps = useUnhandledProps(MenuItem.handledProps, props); var getA11yProps = useAccessibility(accessibility, { debugName: Menu.displayName, actionHandlers: { performClick: function performClick(event) { return !event.defaultPrevented && handleClick(event); }, openMenu: function (_openMenu) { function openMenu(_x) { return _openMenu.apply(this, arguments); } openMenu.toString = function () { return _openMenu.toString(); }; return openMenu; }(function (event) { return openMenu(event); }), closeAllMenusAndFocusNextParentItem: function closeAllMenusAndFocusNextParentItem(event) { return closeAllMenus(event); }, closeMenu: function (_closeMenu) { function closeMenu(_x2) { return _closeMenu.apply(this, arguments); } closeMenu.toString = function () { return _closeMenu.toString(); }; return closeMenu; }(function (event) { return closeMenu(event); }), closeMenuAndFocusTrigger: function closeMenuAndFocusTrigger(event) { return closeMenu(event, true); }, doNotNavigateNextParentItem: function doNotNavigateNextParentItem(event) { event.stopPropagation(); }, closeAllMenus: function (_closeAllMenus) { function closeAllMenus(_x3) { return _closeAllMenus.apply(this, arguments); } closeAllMenus.toString = function () { return _closeAllMenus.toString(); }; return closeAllMenus; }(function (event) { return closeAllMenus(event); }) }, mapPropsToBehavior: function mapPropsToBehavior() { return { menuOpen: menuOpen, hasMenu: !!menu, disabled: disabled, vertical: vertical, active: active // for tabBehavior }; }, rtl: context.rtl }); var _useStyles = useStyles(MenuItem.displayName, { className: menuItemClassName, mapPropsToStyles: function mapPropsToStyles() { return { primary: primary, underlined: underlined, active: active, vertical: vertical, pointing: pointing, secondary: secondary, disabled: disabled, iconOnly: iconOnly, pills: pills, inSubmenu: inSubmenu, isFromKeyboard: isFromKeyboard }; }, mapPropsToInlineStyles: function mapPropsToInlineStyles() { return { className: className, design: design, styles: styles, variables: mergeVariablesOverrides(parentProps.variables, variables) }; }, rtl: context.rtl }), classes = _useStyles.classes, resolvedStyles = _useStyles.styles; var menuRef = React.useRef(); var itemRef = React.useRef(); var handleWrapperBlur = function handleWrapperBlur(e) { if (!props.inSubmenu && !e.currentTarget.contains(e.relatedTarget)) { trySetMenuOpen(false, e); } }; var dismissOnScroll = function dismissOnScroll(e) { if (!isSubmenuOpen()) return; // we only need to dismiss if the scroll happens outside the menu if (!menuRef.current.contains(e.target)) { trySetMenuOpen(false, e); } }; var outsideClickHandler = function outsideClickHandler(e) { if (!isSubmenuOpen()) return; if (!doesNodeContainClick(itemRef.current, e, context.target) && !doesNodeContainClick(menuRef.current, e, context.target)) { trySetMenuOpen(false, e); } }; var performClick = function performClick(e) { if (menu) { if (doesNodeContainClick(menuRef.current, e, context.target)) { // submenu was clicked => close it and propagate trySetMenuOpen(false, e, function () { return focusAsync(itemRef.current); }); } else { // the menuItem element was clicked => toggle the open/close and stop propagation trySetMenuOpen(active && on !== 'hover' ? !menuOpen : true, e); e.stopPropagation(); e.preventDefault(); } } }; var handleClick = function handleClick(e) { if (disabled) { e.preventDefault(); return; } performClick(e); _invoke(props, 'onClick', e, props); _invoke(parentProps, 'onItemClick', e, props); }; var handleBlur = function handleBlur(e) { setIsFromKeyboard(false); _invoke(props, 'onBlur', e, props); }; var handleFocus = function handleFocus(e) { setIsFromKeyboard(isEventFromKeyboard()); _invoke(props, 'onFocus', e, props); }; var isSubmenuOpen = function isSubmenuOpen() { return !!(menu && menuOpen); }; var closeAllMenus = function closeAllMenus(e) { if (!isSubmenuOpen()) { return; } trySetMenuOpen(false, e, function () { if (!inSubmenu) { focusAsync(itemRef.current); } }); // avoid spacebar scrolling the page if (!inSubmenu) { e.preventDefault(); } }; var closeMenu = function closeMenu(e, forceTriggerFocus) { if (!isSubmenuOpen()) { return; } var shouldStopPropagation = inSubmenu || props.vertical; trySetMenuOpen(false, e, function () { if (forceTriggerFocus || shouldStopPropagation) { focusAsync(itemRef.current); } }); if (forceTriggerFocus || shouldStopPropagation) { e.stopPropagation(); } }; var openMenu = function openMenu(e) { if (menu && !menuOpen) { trySetMenuOpen(true, e); _invoke(parentProps, 'onItemSelect', e, index); _invoke(props, 'onActiveChanged', e, Object.assign({}, props, { active: true })); e.stopPropagation(); e.preventDefault(); } }; var rootHandlers = Object.assign({}, !wrapper && Object.assign({ onClick: handleClick }, on === 'hover' && { onMouseEnter: function onMouseEnter(e) { setWhatInputSource(context.target, 'mouse'); trySetMenuOpen(true, e); _invoke(props, 'onMouseEnter', e, props); _invoke(parentProps, 'onItemSelect', e, index); }, onMouseLeave: function onMouseLeave(e) { trySetMenuOpen(false, e); _invoke(props, 'onMouseLeave', e, props); } })); var trySetMenuOpen = function trySetMenuOpen(newValue, e, onStateChanged) { setMenuOpen(newValue); // The reason why post-effect is not passed as callback to trySetState method // is that in 'controlled' mode the post-effect is applied before final re-rendering // which cause a broken behavior: for e.g. when it is needed to focus submenu trigger on ESC. // TODO: all DOM post-effects should be applied at componentDidMount & componentDidUpdated stages. onStateChanged && onStateChanged(); _invoke(props, 'onMenuOpenChange', e, Object.assign({}, props, { menuOpen: newValue })); }; var menuItemInner = /*#__PURE__*/React.createElement(Ref, { innerRef: function innerRef(node) { itemRef.current = node; handleRef(ref, node); } }, /*#__PURE__*/React.createElement(ElementType, _extends({}, getA11yProps('root', Object.assign({ className: classes.root, disabled: disabled, onBlur: handleBlur, onFocus: handleFocus, onClick: handleClick }, unhandledProps)), rootHandlers), childrenExist(children) ? children : /*#__PURE__*/React.createElement(React.Fragment, null, createShorthand(MenuItemIcon, icon, { defaultProps: function defaultProps() { return getA11yProps('icon', { hasContent: !!content, iconOnly: iconOnly }); } }), createShorthand(MenuItemContent, content, { defaultProps: function defaultProps() { return getA11yProps('content', { hasIcon: !!icon, hasMenu: !!menu, inSubmenu: inSubmenu, vertical: vertical }); } }), menu && createShorthand(MenuItemIndicator, indicator, { defaultProps: function defaultProps() { return getA11yProps('indicator', { iconOnly: iconOnly, vertical: vertical, inSubmenu: inSubmenu, active: active, primary: primary, underlined: underlined }); } })))); var handleWrapperOverrides = function handleWrapperOverrides(predefinedProps) { return Object.assign({ onBlur: function onBlur(e) { handleWrapperBlur(e); _invoke(predefinedProps, 'onBlur', e, props); } }, on === 'hover' && { onMouseEnter: function onMouseEnter(e) { setWhatInputSource(context.target, 'mouse'); trySetMenuOpen(true, e); _invoke(predefinedProps, 'onMouseEnter', e, props); _invoke(parentProps, 'onItemSelect', e, index); }, onMouseLeave: function onMouseLeave(e) { trySetMenuOpen(false, e); _invoke(predefinedProps, 'onMouseLeave', e, props); } }); }; var maybeSubmenu = menu && menuOpen ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Ref, { innerRef: menuRef }, /*#__PURE__*/React.createElement(Popper, _extends({ align: vertical ? 'top' : context.rtl ? 'end' : 'start', position: vertical ? context.rtl ? 'before' : 'after' : 'below', targetRef: itemRef }, positioningProps), createShorthand(Menu, menu, { defaultProps: function defaultProps() { return { accessibility: submenuBehavior, className: menuItemSlotClassNames.submenu, vertical: true, primary: props.primary, secondary: props.secondary, submenu: true, styles: resolvedStyles.menu, indicator: props.indicator }; }, overrideProps: function overrideProps(predefinedProps) { return { onClick: function onClick(e) { handleClick(e); _invoke(predefinedProps, 'onClick', e, props); } }; } }))), /*#__PURE__*/React.createElement(EventListener, { listener: outsideClickHandler, target: context.target, type: "click" }), /*#__PURE__*/React.createElement(EventListener, { listener: dismissOnScroll, target: context.target, type: "wheel", capture: true }), /*#__PURE__*/React.createElement(EventListener, { listener: dismissOnScroll, target: context.target, type: "touchmove", capture: true })) : null; if (wrapper) { var wrapperElement = createShorthand(MenuItemWrapper, wrapper, { defaultProps: function defaultProps() { return getA11yProps('wrapper', { active: active, disabled: disabled, iconOnly: iconOnly, isFromKeyboard: isFromKeyboard, pills: pills, pointing: pointing, secondary: secondary, underlined: underlined, vertical: vertical, primary: primary, on: on, variables: variables }); }, overrideProps: function overrideProps(predefinedProps) { return Object.assign({ children: /*#__PURE__*/React.createElement(React.Fragment, null, menuItemInner, maybeSubmenu) }, handleWrapperOverrides(predefinedProps)); } }); setEnd(); return wrapperElement; } setEnd(); return menuItemInner; }); MenuItem.displayName = 'MenuItem'; MenuItem.propTypes = Object.assign({}, commonPropTypes.createCommon({ content: 'shorthand' }), { active: PropTypes.bool, disabled: PropTypes.bool, icon: customPropTypes.shorthandAllowingChildren, on: PropTypes.oneOf(['hover']), iconOnly: PropTypes.bool, index: PropTypes.number, itemPosition: PropTypes.number, itemsCount: PropTypes.number, onClick: PropTypes.func, onFocus: PropTypes.func, onBlur: PropTypes.func, pills: PropTypes.bool, pointing: PropTypes.oneOf(['start', 'end', true, false]), primary: customPropTypes.every([customPropTypes.disallow(['secondary']), PropTypes.bool]), secondary: customPropTypes.every([customPropTypes.disallow(['primary']), PropTypes.bool]), underlined: PropTypes.bool, vertical: PropTypes.bool, wrapper: PropTypes.oneOfType([PropTypes.node, PropTypes.object]), menu: PropTypes.oneOfType([customPropTypes.itemShorthand, customPropTypes.collectionShorthand]), menuOpen: PropTypes.bool, defaultMenuOpen: PropTypes.bool, onActiveChanged: PropTypes.func, inSubmenu: PropTypes.bool, indicator: customPropTypes.shorthandAllowingChildren, onMenuOpenChange: PropTypes.func }); MenuItem.handledProps = Object.keys(MenuItem.propTypes); MenuItem.shorthandConfig = { mappedProp: 'content' }; MenuItem.defaultProps = { as: 'a', wrapper: {}, indicator: /*#__PURE__*/React.createElement(ChevronEndIcon, { outline: true }) }; return MenuItem; }(); //# sourceMappingURL=MenuItem.js.map