@itwin/core-react
Version:
A react component library of iTwin.js UI general purpose components
177 lines • 8.21 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module ContextMenu
*/
import * as React from "react";
import classnames from "classnames";
import { ConditionalBooleanValue } from "@itwin/appui-abstract";
import { ContextMenu } from "./ContextMenu.js";
import { ContextMenuDirection } from "./ContextMenuDirection.js";
import { TildeFinder } from "./TildeFinder.js";
import { Icon } from "../icons/IconComponent.js";
import { SvgCaretRightSmall } from "@itwin/itwinui-icons-react";
import { Badge } from "../badge/Badge.js";
/** Submenu wrapper class for use within a [[ContextMenu]] component.
* @public
* @deprecated in 4.16.0. Use `subMenuItems` property {@link https://itwinui.bentley.com/docs/dropdownmenu#submenu iTwinUI MenuItem} component instead.
*/
export class ContextSubMenu extends React.Component {
_menuElement = null;
_subMenuElement = null;
_menuButtonElement = null;
_lastLabel;
_parsedLabel;
static defaultProps = {
direction: ContextMenuDirection.BottomRight,
disabled: false,
hidden: false,
autoflip: true,
isSelected: false,
selectedIndex: 0,
};
state;
constructor(props) {
super(props);
this.state = {
opened: false,
direction: props.direction,
};
}
render() {
const { label, opened, direction, onOutsideClick, onEsc, autoflip, edgeLimit, selectedIndex, floating, parentMenu, parentSubmenu, onSelect, icon, disabled, hidden, onHover, isSelected, onHotKeyParsed, children, onClick, className, badgeType, // eslint-disable-line @typescript-eslint/no-deprecated
badgeKind, hideIconContainer, ...props } = this.props;
const onOutsideClickWrapper = (event) => {
this.close();
onOutsideClick && onOutsideClick(event);
};
const contextMenuProps = {
onOutsideClick: onOutsideClickWrapper,
onSelect,
onEsc,
autoflip,
edgeLimit,
selectedIndex,
floating,
parentMenu,
};
const renderDirection = this.state.direction;
const isDisabled = ConditionalBooleanValue.getValue(disabled);
const isHidden = ConditionalBooleanValue.getValue(hidden);
if (this._lastLabel !== label) {
this._parsedLabel = TildeFinder.findAfterTilde(label).node;
this._lastLabel = label;
}
return (React.createElement("div", { className: classnames("core-context-submenu", ContextMenu.getCSSClassNameFromDirection(renderDirection), className),
// eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
onMouseOver: this._handleMouseOver, ref: (el) => {
this._subMenuElement = el;
}, "data-testid": "core-context-submenu", ...props },
React.createElement("div", { onClick: this._handleClick, ref: (el) => {
this._menuButtonElement = el;
}, className: classnames("core-context-menu-item", "core-context-submenu-container", isDisabled && "core-context-menu-disabled", isHidden && "core-context-menu-hidden", isSelected && "core-context-menu-is-selected"), "data-testid": "core-context-submenu-container", role: "menuitem", tabIndex: isSelected ? 0 : -1, "aria-disabled": isDisabled, "aria-hidden": isHidden, "aria-haspopup": true },
!hideIconContainer && (React.createElement("div", { className: "core-context-menu-icon" }, icon !== undefined && React.createElement(Icon, { iconSpec: icon }))),
React.createElement("div", { className: "core-context-menu-content" }, this._parsedLabel),
React.createElement("div", { className: classnames("core-context-submenu-arrow", "icon") },
React.createElement(Icon, { iconSpec: React.createElement(SvgCaretRightSmall, null) })),
(badgeKind || badgeType) && (React.createElement("div", { className: "core-context-menu-badge" },
React.createElement(Badge, { type: badgeKind || badgeType })))),
React.createElement(ContextMenu, { ref: (el) => {
this._menuElement = el;
}, className: "core-context-submenu-popup", opened: this.state.opened, direction: renderDirection, parentSubmenu: this, ...contextMenuProps }, children)));
}
componentDidMount() {
document.addEventListener("click", this._handleClickGlobal);
this._updateHotkey(this.props.label);
this.checkRenderDirection();
}
componentWillUnmount() {
document.removeEventListener("click", this._handleClickGlobal);
}
/** @internal */
componentDidUpdate(prevProps, prevState) {
const direction = this.props.direction;
if ((this.state.opened !== prevState.opened &&
direction !== this.state.direction) ||
prevProps.direction !== direction)
this.checkRenderDirection();
if (this.props.label !== prevProps.label) {
this._updateHotkey(this.props.label);
}
}
getWindow() {
const el = this._subMenuElement;
const parentDocument = el.ownerDocument;
return parentDocument.defaultView;
}
checkRenderDirection() {
const { autoflip } = this.props;
const parentWindow = this.getWindow();
let renderDirection = this.state.direction;
if (parentWindow && autoflip && this._menuElement) {
const menuRect = this._menuElement.getRect();
renderDirection = ContextMenu.autoFlip(renderDirection, menuRect, parentWindow.innerWidth, parentWindow.innerHeight);
if (renderDirection !== this.state.direction)
this.setState({ direction: renderDirection });
}
}
_updateHotkey = (node) => {
let hotKey;
const isDisabled = ConditionalBooleanValue.getValue(this.props.disabled);
const isHidden = ConditionalBooleanValue.getValue(this.props.hidden);
if (!isDisabled && !isHidden)
hotKey = TildeFinder.findAfterTilde(node).character;
else
hotKey = undefined;
if (hotKey && hotKey !== this.state.hotKey) {
this.setState({ hotKey });
if (this.props.onHotKeyParsed)
this.props.onHotKeyParsed(hotKey);
}
};
select = () => {
this.setState({ opened: true }, () => {
if (this._menuElement)
this._menuElement.focus();
if (this.props.onSelect !== undefined)
this.props.onSelect(undefined);
});
};
close = (propagate) => {
this.setState({ opened: false }, () => {
if (this._menuElement)
this._menuElement.blur();
});
if (propagate &&
this.props.parentMenu &&
this.props.parentMenu.props.parentSubmenu) {
this.props.parentMenu.props.parentSubmenu.close(true);
}
};
_handleMouseOver = (_event) => {
if (this._menuButtonElement &&
this._menuButtonElement.style.visibility !== "hidden" &&
this.props.onHover) {
this.props.onHover();
}
};
_handleClick = (event) => {
event.stopPropagation();
const isDisabled = ConditionalBooleanValue.getValue(this.props.disabled);
if (!isDisabled) {
if (this.props.onClick !== undefined)
this.props.onClick(event);
if (this.props.opened)
this.close();
else
this.select();
}
};
_handleClickGlobal = (event) => {
if (this._subMenuElement && !this._subMenuElement.contains(event.target))
this.setState((_prevState) => ({ opened: false }));
};
}
//# sourceMappingURL=ContextSubMenu.js.map