New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@szhsin/react-menu

Package Overview
Dependencies
Maintainers
1
Versions
84
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@szhsin/react-menu - npm Package Compare versions

Comparing version 2.0.1 to 2.1.0

28

dist/index.d.ts

@@ -14,3 +14,3 @@ import React = require('react');

export type MenuItemTypeProp = 'checkbox' | 'radio';
export type FocusPosition = 'initial' | 'first' | 'last';
export type FocusPosition = 'initial' | 'first' | 'last' | number;
export type CloseReason = 'click' | 'cancel' | 'blur' | 'scroll';

@@ -116,3 +116,3 @@ type DirStyleKey = '$left' | '$right' | '$top' | '$bottom';

// Menu, SubMenu and ControlledMenu
interface SharedMenuProps extends Omit<BaseProps, 'styles'> {
interface BaseMenuProps extends Omit<BaseProps, 'styles'> {
menuClassName?: ClassNameProp<MenuModifiers>;

@@ -133,3 +133,3 @@ menuStyles?: StylesProp<MenuModifiers, MenuStyleKeys>;

// Menu and ControlledMenu
interface BaseMenuProps extends MenuStateOptions, SharedMenuProps {
interface RootMenuProps extends BaseMenuProps, MenuStateOptions {
containerProps?: Omit<React.HTMLAttributes<HTMLElement>, 'className'>;

@@ -148,2 +148,13 @@ boundingBoxRef?: React.RefObject<Element | RectElement>;

export interface MenuInstance {
openMenu: (position?: FocusPosition, alwaysUpdate?: boolean) => void;
closeMenu: () => void;
}
// Menu and SubMenu
interface UncontrolledMenuProps {
instanceRef?: React.Ref<MenuInstance>;
onMenuChange?: EventHandler<MenuChangeEvent>;
}
//

@@ -166,5 +177,4 @@ // MenuButton

// ----------------------------------------------------------------------
export interface MenuProps extends BaseMenuProps {
export interface MenuProps extends RootMenuProps, UncontrolledMenuProps {
menuButton: RenderProp<MenuButtonModifiers, React.ReactElement>;
onMenuChange?: EventHandler<MenuChangeEvent>;
}

@@ -177,3 +187,3 @@

// ----------------------------------------------------------------------
export interface ControlledMenuProps extends BaseMenuProps {
export interface ControlledMenuProps extends RootMenuProps {
anchorPoint?: {

@@ -188,3 +198,4 @@ x: number;

menuItemFocus?: {
position: FocusPosition
position?: FocusPosition;
alwaysUpdate?: boolean;
};

@@ -206,6 +217,5 @@ onClose?: EventHandler<MenuCloseEvent>;

export interface SubMenuProps extends SharedMenuProps, Hoverable {
export interface SubMenuProps extends BaseMenuProps, Hoverable, UncontrolledMenuProps {
itemProps?: BaseProps<SubMenuItemModifiers>;
label?: RenderProp<SubMenuItemModifiers>;
onMenuChange?: EventHandler<MenuChangeEvent>;
}

@@ -212,0 +222,0 @@

@@ -1,2 +0,2 @@

import React, { Children, cloneElement, forwardRef, useContext, useState, useMemo, useLayoutEffect, useEffect, useRef, useReducer, useCallback, memo } from 'react';
import React, { Children, cloneElement, forwardRef, useContext, useState, useMemo, useLayoutEffect, useEffect, useRef, useReducer, useCallback, useImperativeHandle, memo } from 'react';
import ReactDOM, { unstable_batchedUpdates, createPortal } from 'react-dom';

@@ -66,8 +66,8 @@ import PropTypes from 'prop-types';

const isProd = process.env.NODE_ENV === 'production';
const isMenuOpen = state => !!state && state[0] === 'o';
const batchedUpdates = unstable_batchedUpdates || (callback => callback());
const values = Object.values || (obj => Object.keys(obj).map(key => obj[key]));
const floatEqual = (a, b, diff = 0.0001) => Math.abs(a - b) < diff;
const isProd = process.env.NODE_ENV === 'production';
const isMenuOpen = state => state === 'open' || state === 'opening';
const getTransition = (transition, name) => Boolean(transition && transition[name]) || transition === true;
const getTransition = (transition, name) => !!(transition && transition[name]) || transition === true;
const safeCall = (fn, ...args) => typeof fn === 'function' ? fn(...args) : fn;

@@ -136,4 +136,4 @@ const getName = component => component && component['_szhsinMenu'];

if (!isProd && index === undefined && !isDisabled) {
const error = `[react-menu] Validate item '${node && node.toString()}' failed.
You're probably creating your own components or HOC over MenuItem, SubMenu or FocusableItem.
const error = `[React-Menu] Validate item '${node && node.toString()}' failed.
You're probably creating wrapping components or HOC over MenuItem, SubMenu or FocusableItem.
To create wrapping components, see: https://codesandbox.io/s/react-menu-wrapping-q0b59

@@ -179,5 +179,5 @@ To create HOCs, see: https://codesandbox.io/s/react-menu-hoc-0bipn`;

if (name === 'MenuGroup') {
const takeOverflow = Boolean(child.props.takeOverflow);
const takeOverflow = !!child.props.takeOverflow;
const descOverflow = desc.descendOverflow;
if (!isProd && (descendOverflow === descOverflow ? descOverflow : takeOverflow)) throw new Error('[react-menu] Only one MenuGroup in a menu is allowed to have takeOverflow prop.');
if (!isProd && (descendOverflow === descOverflow ? descOverflow : takeOverflow)) throw new Error('[React-Menu] Only one MenuGroup in a menu is allowed to have takeOverflow prop.');
descendOverflow = descendOverflow || descOverflow || takeOverflow;

@@ -203,3 +203,3 @@ }

});
const sharedMenuPropTypes = {
const menuPropTypes = {
className: PropTypes.string,

@@ -216,3 +216,3 @@ ...stylePropTypes('menu'),

};
const menuPropTypesBase = { ...sharedMenuPropTypes,
const rootMenuPropTypes = { ...menuPropTypes,
containerProps: PropTypes.object,

@@ -238,3 +238,7 @@ initialMounted: PropTypes.bool,

};
const sharedMenuDefaultProp = {
const uncontrolledMenuPropTypes = {
instanceRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
onMenuChange: PropTypes.func
};
const menuDefaultProps = {
offsetX: 0,

@@ -247,3 +251,3 @@ offsetY: 0,

};
const menuDefaultPropsBase = { ...sharedMenuDefaultProp,
const rootMenuDefaultProps = { ...menuDefaultProps,
reposition: 'auto',

@@ -269,5 +273,5 @@ viewScroll: 'initial',

const [active, setActive] = useState(false);
const activeKeys = [Keys.SPACE, Keys.ENTER, ...moreKeys];
const activeKeys = [Keys.ENTER, Keys.SPACE, ...moreKeys];
const cancelActive = () => setActive(false);
const cancelActive = () => active && setActive(false);

@@ -282,3 +286,3 @@ return {

onKeyDown: e => {
if (isHovering && !isDisabled && activeKeys.includes(e.key)) {
if (!active && isHovering && !isDisabled && activeKeys.indexOf(e.key) !== -1) {
setActive(true);

@@ -288,3 +292,3 @@ }

onKeyUp: e => {
if (activeKeys.includes(e.key)) {
if (activeKeys.indexOf(e.key) !== -1) {
setActive(false);

@@ -294,3 +298,3 @@ }

onBlur: e => {
if (!e.currentTarget.contains(e.relatedTarget)) {
if (active && !e.currentTarget.contains(e.relatedTarget)) {
setActive(false);

@@ -349,3 +353,3 @@ }

const sanitiseKey = key => key.charAt(0) === '$' ? key.slice(1) : key;
const sanitiseKey = key => key[0] === '$' ? key.slice(1) : key;

@@ -408,3 +412,3 @@ const useFlatStyles = (styles, modifiers) => useMemo(() => {

const onBlur = e => {
if (!e.currentTarget.contains(e.relatedTarget)) {
if (isHovering && !e.currentTarget.contains(e.relatedTarget)) {
dispatch({

@@ -479,9 +483,8 @@ type: HoverIndexActionTypes.UNSET,

const menuState = useMenuState(options);
const [menuItemFocus, setMenuItemFocus] = useState({
position: FocusPositions.INITIAL
});
const [menuItemFocus, setMenuItemFocus] = useState({});
const openMenu = (position = FocusPositions.INITIAL) => {
const openMenu = (position, alwaysUpdate) => {
setMenuItemFocus({
position
position,
alwaysUpdate
});

@@ -512,2 +515,3 @@ menuState.toggleMenu(true);

"aria-disabled": disabled || undefined,
type: "button",
disabled: disabled

@@ -1073,3 +1077,3 @@ }, restProps, {

case Keys.SPACE:
if (e.target && e.target.className.includes(menuClass)) {
if (e.target && e.target.className.indexOf(menuClass) !== -1) {
e.preventDefault();

@@ -1097,3 +1101,3 @@ }

if (!containerRef.current) {
if (!isProd) throw new Error('[react-menu] Menu cannot be positioned properly as container ref is null. If you initialise isOpen prop to true for ControlledMenu, please see this link for a solution: https://github.com/szhsin/react-menu/issues/2#issuecomment-719166062');
if (!isProd) throw new Error('[React-Menu] Menu cannot be positioned properly as container ref is null. If you initialise isOpen prop to true for ControlledMenu, please see this link for a solution: https://github.com/szhsin/react-menu/issues/2#issuecomment-719166062');
return;

@@ -1235,3 +1239,3 @@ }

}, [rootAnchorRef, anchorScrollingRef, scrollingRef, isOpen, overflow, onClose, viewScroll, handlePosition]);
const hasOverflow = Boolean(overflowData) && overflowData.overflowAmt > 0;
const hasOverflow = !!overflowData && overflowData.overflowAmt > 0;
useEffect(() => {

@@ -1287,16 +1291,27 @@ if (hasOverflow || !isOpen || !parentScrollingRef) return;

if (!closeTransition) setOverflowData();
return;
}
const id = setTimeout(() => {
if (!isOpen || !menuRef.current || menuRef.current.contains(document.activeElement)) return;
if (!menuRef.current) return;
const {
position,
alwaysUpdate
} = menuItemFocus || {};
if (!alwaysUpdate && menuRef.current.contains(document.activeElement)) return;
if (_captureFocus) menuRef.current.focus();
if (menuItemFocus.position === FocusPositions.FIRST) {
if (position === FocusPositions.FIRST) {
dispatch({
type: HoverIndexActionTypes.FIRST
});
} else if (menuItemFocus.position === FocusPositions.LAST) {
} else if (position === FocusPositions.LAST) {
dispatch({
type: HoverIndexActionTypes.LAST
});
} else if (position >= 0 && position < menuItemsCount.current) {
dispatch({
type: HoverIndexActionTypes.SET,
index: position
});
}

@@ -1532,3 +1547,3 @@ }, openTransition ? 170 : 100);

});
ControlledMenu.propTypes = { ...menuPropTypesBase,
ControlledMenu.propTypes = { ...rootMenuPropTypes,
state: PropTypes.oneOf(values(MenuStateMap)),

@@ -1543,10 +1558,9 @@ anchorPoint: PropTypes.exact({

menuItemFocus: PropTypes.exact({
position: PropTypes.string
position: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
alwaysUpdate: PropTypes.bool
}),
onClose: PropTypes.func
};
ControlledMenu.defaultProps = { ...menuDefaultPropsBase,
menuItemFocus: {
position: FocusPositions.INITIAL
}
ControlledMenu.defaultProps = { ...rootMenuDefaultProps,
menuItemFocus: {}
};

@@ -1558,2 +1572,3 @@

menuButton,
instanceRef,
onMenuChange,

@@ -1577,3 +1592,3 @@ ...restProps

if (skipOpen.current) return;
openMenu(e.detail === 0 ? FocusPositions.FIRST : FocusPositions.INITIAL);
openMenu(e.detail === 0 ? FocusPositions.FIRST : undefined);
};

@@ -1617,2 +1632,6 @@

useMenuChange(onMenuChange, isOpen);
useImperativeHandle(instanceRef, () => ({
openMenu,
closeMenu: () => toggleMenu(false)
}));
const menuProps = { ...restProps,

@@ -1628,7 +1647,7 @@ ...stateProps,

});
Menu.propTypes = { ...menuPropTypesBase,
menuButton: PropTypes.oneOfType([PropTypes.element, PropTypes.func]).isRequired,
onMenuChange: PropTypes.func
Menu.propTypes = { ...rootMenuPropTypes,
...uncontrolledMenuPropTypes,
menuButton: PropTypes.oneOfType([PropTypes.element, PropTypes.func]).isRequired
};
Menu.defaultProps = menuDefaultPropsBase;
Menu.defaultProps = rootMenuDefaultProps;

@@ -1643,2 +1662,3 @@ const SubMenu = withHovering( /*#__PURE__*/memo(function SubMenu({

isHovering,
instanceRef,
captureFocus: _1,

@@ -1649,3 +1669,3 @@ repositionFlag: _2,

}) {
const isDisabled = Boolean(disabled);
const isDisabled = !!disabled;
validateIndex(index, isDisabled, label);

@@ -1692,2 +1712,7 @@ const {

const setHover = () => !isHovering && dispatch({
type: HoverIndexActionTypes.SET,
index
});
const delayOpen = delay => {

@@ -1734,4 +1759,4 @@ dispatch({

if (isOpen) {
itemRef.current.focus();
toggleMenu(false);
itemRef.current.focus();
handled = true;

@@ -1758,4 +1783,4 @@ }

switch (e.key) {
case Keys.ENTER:
case Keys.SPACE:
case Keys.ENTER:
case Keys.RIGHT:

@@ -1781,2 +1806,16 @@ openMenu(FocusPositions.FIRST);

useMenuChange(onMenuChange, isOpen);
useImperativeHandle(instanceRef, () => ({
openMenu: (...args) => {
if (isParentOpen) {
setHover();
openMenu(...args);
}
},
closeMenu: () => {
if (isOpen) {
itemRef.current.focus();
toggleMenu(false);
}
}
}));
const modifiers = useMemo(() => Object.freeze({

@@ -1797,6 +1836,3 @@ open: isOpen,

onMouseLeave: handleMouseLeave,
onMouseDown: () => !isHovering && dispatch({
type: HoverIndexActionTypes.SET,
index
}),
onMouseDown: setHover,
onClick: handleClick,

@@ -1844,10 +1880,10 @@ onKeyUp: handleKeyUp

}), 'SubMenu');
SubMenu.propTypes = { ...sharedMenuPropTypes,
SubMenu.propTypes = { ...menuPropTypes,
...uncontrolledMenuPropTypes,
disabled: PropTypes.bool,
label: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
itemProps: PropTypes.shape({ ...stylePropTypes()
}),
onMenuChange: PropTypes.func
})
};
SubMenu.defaultProps = { ...sharedMenuDefaultProp,
SubMenu.defaultProps = { ...menuDefaultProps,
direction: 'right'

@@ -1871,3 +1907,3 @@ };

}) {
const isDisabled = Boolean(disabled);
const isDisabled = !!disabled;
validateIndex(index, isDisabled, children);

@@ -1891,4 +1927,4 @@ const ref = useRef();

const isCheckBox = type === 'checkbox';
const isAnchor = Boolean(href) && !isDisabled && !isRadio && !isCheckBox;
const isChecked = isRadio ? radioGroup.value === value : isCheckBox ? Boolean(checked) : false;
const isAnchor = !!href && !isDisabled && !isRadio && !isCheckBox;
const isChecked = isRadio ? radioGroup.value === value : isCheckBox ? !!checked : false;

@@ -1918,4 +1954,4 @@ const handleClick = e => {

switch (e.key) {
case Keys.ENTER:
case Keys.SPACE:
case Keys.ENTER:
if (isAnchor) {

@@ -2000,3 +2036,3 @@ ref.current.click();

}) {
const isDisabled = Boolean(disabled);
const isDisabled = !!disabled;
validateIndex(index, isDisabled, children);

@@ -2003,0 +2039,0 @@ const ref = useRef(null);

{
"name": "@szhsin/react-menu",
"version": "2.0.1",
"version": "2.1.0",
"description": "React component for building accessible menu, dropdown, submenu, context menu and more.",

@@ -37,3 +37,3 @@ "author": "Zheng Song",

"prop-types": "^15.7.2",
"react-transition-state": "^0.3.0"
"react-transition-state": "^1.0.1"
},

@@ -40,0 +40,0 @@ "devDependencies": {

# React-Menu
> An accessible, responsive, and customisable React menu library.
> An accessible React menu library.
**[Live examples and documentation](https://szhsin.github.io/react-menu/)**
**[Live examples and docs](https://szhsin.github.io/react-menu/)**

@@ -15,4 +15,4 @@ [![NPM](https://img.shields.io/npm/v/@szhsin/react-menu.svg)](https://www.npmjs.com/package/@szhsin/react-menu)

- Unlimited levels of submenu.
- Supports dropdown or context menu.
- Supports radio and checkbox menu items.
- Supports context menu.
- Flexible menu positioning.

@@ -38,25 +38,27 @@ - Customisable styling.

import {
Menu,
MenuItem,
MenuButton,
SubMenu
Menu,
MenuItem,
MenuButton,
SubMenu
} from '@szhsin/react-menu';
import '@szhsin/react-menu/dist/index.css';
export default function Example() {
return (
<Menu menuButton={<MenuButton>Open menu</MenuButton>}>
<MenuItem>New File</MenuItem>
<SubMenu label="Open">
<MenuItem>index.html</MenuItem>
<MenuItem>example.js</MenuItem>
<MenuItem>about.css</MenuItem>
</SubMenu>
<MenuItem>Save</MenuItem>
</Menu>
);
export default function App() {
return (
<Menu menuButton={<MenuButton>Open menu</MenuButton>}>
<MenuItem>New File</MenuItem>
<MenuItem>Save</MenuItem>
<SubMenu label="Edit">
<MenuItem>Cut</MenuItem>
<MenuItem>Copy</MenuItem>
<MenuItem>Paste</MenuItem>
</SubMenu>
<MenuItem>Print...</MenuItem>
</Menu>
);
}
```
[More examples and documentation](https://szhsin.github.io/react-menu/)
[Edit on CodeSandbox](https://codesandbox.io/s/react-menu-basic-3ez3c)<br>
**[Visit more examples and docs](https://szhsin.github.io/react-menu/)**

@@ -63,0 +65,0 @@ ## License

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc