UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

631 lines (628 loc) • 24.5 kB
/** * DevExtreme (cjs/__internal/ui/context_menu/menu_base.js) * Version: 25.1.5 * Build date: Wed Sep 03 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _devices = _interopRequireDefault(require("../../../core/devices")); var _renderer = _interopRequireDefault(require("../../../core/renderer")); var _common = require("../../../core/utils/common"); var _iterator = require("../../../core/utils/iterator"); var _type = require("../../../core/utils/type"); var _m_ink_ripple = require("../../core/utils/m_ink_ripple"); var _item = _interopRequireDefault(require("../../ui/collection/item")); var _menu_baseEdit = _interopRequireDefault(require("../../ui/context_menu/menu_base.edit.strategy")); var _hierarchical_collection_widget = _interopRequireDefault(require("../../ui/hierarchical_collection/hierarchical_collection_widget")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e } } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function(n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) { ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]) } } return n }, _extends.apply(null, arguments) } const DX_MENU_CLASS = "dx-menu"; const DX_MENU_NO_ICONS_CLASS = "dx-menu-no-icons"; const DX_MENU_BASE_CLASS = "dx-menu-base"; const ITEM_CLASS = "dx-menu-item"; const DX_ITEM_CONTENT_CLASS = `${ITEM_CLASS}-content`; const DX_MENU_SELECTED_ITEM_CLASS = `${ITEM_CLASS}-selected`; const DX_MENU_ITEM_WRAPPER_CLASS = `${ITEM_CLASS}-wrapper`; const DX_MENU_ITEMS_CONTAINER_CLASS = "dx-menu-items-container"; const DX_MENU_ITEM_EXPANDED_CLASS = `${ITEM_CLASS}-expanded`; const DX_MENU_SEPARATOR_CLASS = "dx-menu-separator"; const DX_MENU_ITEM_LAST_GROUP_ITEM = "dx-menu-last-group-item"; const DX_ITEM_HAS_TEXT = `${ITEM_CLASS}-has-text`; const DX_ITEM_HAS_ICON = `${ITEM_CLASS}-has-icon`; const DX_ITEM_HAS_SUBMENU = `${ITEM_CLASS}-has-submenu`; const DX_MENU_ITEM_POPOUT_CLASS = `${ITEM_CLASS}-popout`; const DX_MENU_ITEM_POPOUT_CONTAINER_CLASS = `${DX_MENU_ITEM_POPOUT_CLASS}-container`; const DX_MENU_ITEM_CAPTION_CLASS = `${ITEM_CLASS}-text`; const SINGLE_SELECTION_MODE = "single"; const DEFAULT_DELAY = { show: 50, hide: 300 }; const DX_MENU_ITEM_CAPTION_URL_CLASS = `${DX_MENU_ITEM_CAPTION_CLASS}-with-url`; const DX_ICON_WITH_URL_CLASS = "dx-icon-with-url"; const ITEM_URL_CLASS = "dx-item-url"; const DX_MENU_ITEM_DATA_KEY = "dxMenuItemDataKey"; class MenuBase extends _hierarchical_collection_widget.default { _getDefaultOptions() { return _extends({}, super._getDefaultOptions(), { items: [], cssClass: "", activeStateEnabled: true, showSubmenuMode: { name: "onHover", delay: { show: 50, hide: 300 } }, animation: { show: { type: "fade", from: 0, to: 1, duration: 100 }, hide: { type: "fade", from: 1, to: 0, duration: 100 } }, selectByClick: false, focusOnSelectedItem: false, keyExpr: null, _itemAttributes: { role: "menuitem" }, useInkRipple: false }) } _itemDataKey() { return "dxMenuItemDataKey" } _itemClass() { return ITEM_CLASS } _setAriaSelectionAttribute($itemElement, isSelected) {} _selectedItemClass() { return DX_MENU_SELECTED_ITEM_CLASS } _widgetClass() { return "dx-menu-base" } _focusTarget() { return this._itemContainer() } _clean() { this.option("focusedElement", null); super._clean() } _supportedKeys() { return _extends({}, super._supportedKeys(), { space: () => { const { focusedElement: focusedElement } = this.option(); const $item = (0, _renderer.default)(focusedElement); if (!$item.length || !this._isSelectionEnabled()) { return } this.selectItem($item[0]) }, pageUp: _common.noop, pageDown: _common.noop }) } _isSelectionEnabled() { const { selectionMode: selectionMode } = this.option(); return "single" === selectionMode } _init() { super._init(); this._activeStateUnit = `.${ITEM_CLASS}`; this._renderSelectedItem(); this._initActions() } _getLinkContainer(iconContainer, textContainer, itemData) { const { linkAttr: linkAttr, url: url } = itemData; null === iconContainer || void 0 === iconContainer || iconContainer.addClass("dx-icon-with-url"); null === textContainer || void 0 === textContainer || textContainer.addClass(DX_MENU_ITEM_CAPTION_URL_CLASS); return super._getLinkContainer(iconContainer, textContainer, { linkAttr: linkAttr, url: url }) } _addContent($container, itemData) { const { html: html, url: url } = itemData; if (url) { $container.html(html); const link = this._getLinkContainer(this._getIconContainer(itemData), this._getTextContainer(itemData), itemData); $container.append(link) } else { super._addContent($container, itemData) } $container.append(this._getPopoutContainer(itemData)); this._addContentClasses(itemData, $container.parent()) } _getTextContainer(itemData) { const { text: text } = itemData; if (!text) { return (0, _renderer.default)() } const $itemContainer = (0, _renderer.default)("<span>").addClass(DX_MENU_ITEM_CAPTION_CLASS); const itemText = (0, _type.isPlainObject)(itemData) ? text : String(itemData); return $itemContainer.text(itemText) } _getItemExtraPropNames() { return ["url", "linkAttr"] } _getPopoutContainer(itemData) { const { items: items } = itemData; if (!(null !== items && void 0 !== items && items.length)) { return (0, _renderer.default)() } const $popOutImage = (0, _renderer.default)("<div>").addClass(DX_MENU_ITEM_POPOUT_CLASS); const $popOutContainer = (0, _renderer.default)("<span>").addClass(DX_MENU_ITEM_POPOUT_CONTAINER_CLASS); $popOutContainer.append($popOutImage); return $popOutContainer } _getDataAdapterOptions() { return { rootValue: 0, multipleSelection: false, recursiveSelection: false, recursiveExpansion: false, searchValue: "" } } _selectByItem(selectedItem) { if (!selectedItem) { return } const nodeToSelect = this._dataAdapter.getNodeByItem(selectedItem); if (nodeToSelect) { this._dataAdapter.toggleSelection(nodeToSelect.internalFields.key, true) } } _renderSelectedItem() { const selectedKeys = this._dataAdapter.getSelectedNodesKeys(); const selectedKey = selectedKeys.length && selectedKeys[0]; const selectedItem = this.option("selectedItem"); if (!selectedKey) { this._selectByItem(selectedItem); return } const node = this._dataAdapter.getNodeByKey(selectedKey); if (!node || false === node.selectable) { return } if (!selectedItem) { this.option("selectedItem", node.internalFields.item); return } if (selectedItem !== node.internalFields.item) { this._dataAdapter.toggleSelection(selectedKey, false); this._selectByItem(selectedItem) } } _initActions() {} _initMarkup() { super._initMarkup(); const { useInkRipple: useInkRipple } = this.option(); if (useInkRipple) { this._renderInkRipple() } } _renderInkRipple() { this._inkRipple = (0, _m_ink_ripple.render)() } _toggleActiveState($element, value, event) { super._toggleActiveState($element, value); if (!this._inkRipple) { return } const config = { element: $element, event: event }; if (value) { this._inkRipple.showWave(config) } else { this._inkRipple.hideWave(config) } } _getShowSubmenuMode() { const { showSubmenuMode: showSubmenuMode } = this.option(); const showMode = (0, _type.isObject)(showSubmenuMode) ? showSubmenuMode.name : showSubmenuMode; return this._isDesktopDevice() ? showMode : "onClick" } _isDesktopDevice() { return "desktop" === _devices.default.real().deviceType } _initEditStrategy() { this._editStrategy = new _menu_baseEdit.default(this) } _addCustomCssClass($element) { const { cssClass: cssClass } = this.option(); if (cssClass) { $element.addClass(cssClass) } } _hoverStartHandler(e) { const $itemElement = this._getItemElementByEventArgs(e); if (!$itemElement || this._isItemDisabled($itemElement)) { return } e.stopPropagation(); if ("onHover" === this._getShowSubmenuMode()) { const submenuDelay = this._getSubmenuDelay(); if (0 === submenuDelay) { this._showSubmenu($itemElement) } else { clearTimeout(this._showSubmenusTimeout); this._showSubmenusTimeout = setTimeout(this._showSubmenu.bind(this, $itemElement), submenuDelay) } } } _getAvailableItems($itemElements) { return super._getAvailableItems($itemElements).filter(((_index, item) => "hidden" !== (0, _renderer.default)(item).css("visibility"))) } _isItemDisabled($item) { return this._disabledGetter($item.data(this._itemDataKey())) } _showSubmenu($itemElement) { this._addExpandedClass($itemElement) } _addExpandedClass(itemElement) { (0, _renderer.default)(itemElement).addClass(DX_MENU_ITEM_EXPANDED_CLASS) } _getSubmenuDelay() { const { showSubmenuMode: showSubmenuMode } = this.option(); const delay = (0, _type.isObject)(showSubmenuMode) ? showSubmenuMode.delay : void 0; if (!(0, _type.isDefined)(delay)) { return DEFAULT_DELAY.show } if ((0, _type.isObject)(delay)) { return delay.show ?? DEFAULT_DELAY.show } return delay } _getItemElementByEventArgs(eventArgs) { let $target = (0, _renderer.default)(eventArgs.target); if ($target.hasClass(this._itemClass()) || $target.get(0) === eventArgs.currentTarget) { return $target } while (!$target.hasClass(this._itemClass())) { $target = $target.parent(); if ($target.hasClass("dx-submenu")) { return null } } return $target } _hoverEndHandler(event) { clearTimeout(this._showSubmenusTimeout) } _hasSubmenu(node) { return !!(null !== node && void 0 !== node && node.internalFields.childrenKeys.length) } _renderContentImpl() { this._renderItems(this._dataAdapter.getRootNodes()) } _renderItems(nodes, $submenuContainer) { if (!nodes.length) { return } this.hasIcons = false; const $nodeContainer = this._renderContainer(this.$element(), null === $submenuContainer || void 0 === $submenuContainer ? void 0 : $submenuContainer[0]); let firstVisibleIndex = -1; let nextGroupFirstIndex = -1; (0, _iterator.each)(nodes, ((index, node) => { const isVisibleNode = false !== node.visible; if (isVisibleNode && firstVisibleIndex < 0) { firstVisibleIndex = index } const isBeginGroup = firstVisibleIndex < index && (node.beginGroup || index === nextGroupFirstIndex); if (isBeginGroup) { nextGroupFirstIndex = isVisibleNode ? index : index + 1 } if (index === nextGroupFirstIndex && firstVisibleIndex < index) { this._renderSeparator($nodeContainer) } this._renderItem(index, node, $nodeContainer) })); if (!this.hasIcons) { $nodeContainer.addClass("dx-menu-no-icons") } } _renderContainer($wrapper, submenuContainer) { const $container = (0, _renderer.default)("<ul>"); this.setAria("role", "none", $container); return $container.appendTo($wrapper).addClass("dx-menu-items-container") } _createDOMElement($nodeContainer) { const $node = (0, _renderer.default)("<li>"); this.setAria("role", "none", $node); return $node.appendTo($nodeContainer).addClass(DX_MENU_ITEM_WRAPPER_CLASS) } _renderItem(index, node, $nodeContainer, $nodeElement) { var _items; const { items: items = [] } = this.option(); const $node = $nodeElement ?? this._createDOMElement($nodeContainer); if (null !== (_items = items[index + 1]) && void 0 !== _items && _items.beginGroup) { $node.addClass("dx-menu-last-group-item") } const $itemFrame = super._renderItem(index, node.internalFields.item, $node); if (node.internalFields.item === this.option("selectedItem")) { $itemFrame.addClass(DX_MENU_SELECTED_ITEM_CLASS) } $itemFrame.attr("tabIndex", -1); if (this._hasSubmenu(node)) { this.setAria("haspopup", "true", $itemFrame) } return $itemFrame } _renderItemFrame(index, itemData, $itemContainer) { const $itemFrame = $itemContainer.children(`.${ITEM_CLASS}`); return $itemFrame.length ? $itemFrame : super._renderItemFrame(index, itemData, $itemContainer) } _refreshItem($item, item) { const node = this._dataAdapter.getNodeByItem(item); if (!node) { return } const index = $item.data(this._itemIndexKey()); const $nodeContainer = $item.closest("ul"); const $nodeElement = $item.closest("li"); this._renderItem(index, node, $nodeContainer, $nodeElement) } _addContentClasses(itemData, $itemFrame) { const hasText = itemData.text ? !!itemData.text.length : false; const hasIcon = !!itemData.icon; const hasSubmenu = itemData.items ? !!itemData.items.length : false; $itemFrame.toggleClass(DX_ITEM_HAS_TEXT, hasText); $itemFrame.toggleClass(DX_ITEM_HAS_ICON, hasIcon); if (!this.hasIcons) { this.hasIcons = hasIcon } $itemFrame.toggleClass(DX_ITEM_HAS_SUBMENU, hasSubmenu) } _getItemContent($itemFrame) { let $itemContent = super._getItemContent($itemFrame); if (!$itemContent.length) { $itemContent = $itemFrame.children(`.${DX_ITEM_CONTENT_CLASS}`) } return $itemContent } _postprocessRenderItem(args) { const $itemElement = (0, _renderer.default)(args.itemElement); const selectedIndex = this._dataAdapter.getSelectedNodesKeys(); if (!selectedIndex.length || !this._selectedGetter(args.itemData) || !this._isItemSelectable(args.itemData)) { this._setAriaSelectionAttribute($itemElement, "false"); return } const node = this._dataAdapter.getNodeByItem(args.itemData); if (node && node.internalFields.key === selectedIndex[0]) { $itemElement.addClass(this._selectedItemClass()); this._setAriaSelectionAttribute($itemElement, "true") } else { this._setAriaSelectionAttribute($itemElement, "false") } } _isItemSelectable(item) { return false !== item.selectable } _renderSeparator($itemsContainer) { (0, _renderer.default)("<li>").appendTo($itemsContainer).addClass("dx-menu-separator") } _itemClickHandler(e) { if (e._skipHandling) { return } const itemClickActionHandler = this._createAction(this._updateSubmenuVisibilityOnClick.bind(this)); this._itemDXEventHandler(e, "onItemClick", {}, { beforeExecute: this._itemClick, afterExecute: itemClickActionHandler.bind(this) }); e._skipHandling = true } _isUrlItem(item) { return !!item && "url" in item && !!item.url } _itemClick(actionArgs) { var _actionArgs$args; const { event: event, itemData: itemData } = (null === (_actionArgs$args = actionArgs.args) || void 0 === _actionArgs$args ? void 0 : _actionArgs$args[0]) ?? {}; if (!event) { return } const $itemElement = this._getItemElementByEventArgs(event); const link = null === $itemElement || void 0 === $itemElement ? void 0 : $itemElement.find(".dx-item-url")[0]; if (!this._isUrlItem(itemData) || !link) { return } const isNativeLinkClick = (0, _renderer.default)(event.target).closest(".dx-item-url").length; if (isNativeLinkClick) { return } this._clickByLink(link) } _updateSubmenuVisibilityOnClick(actionArgs) { this._updateSelectedItemOnClick(actionArgs); if ("onClick" === this._getShowSubmenuMode()) { var _actionArgs$args2; const itemElement = null === (_actionArgs$args2 = actionArgs.args) || void 0 === _actionArgs$args2 ? void 0 : _actionArgs$args2[0].itemElement; if (itemElement) { this._addExpandedClass(itemElement) } } } _updateSelectedItemOnClick(actionArgs) { const args = actionArgs.args ? actionArgs.args[0] : actionArgs; const { itemData: itemData } = args; if (!itemData || !this._isItemSelectAllowed(itemData)) { return } const selectedItemKey = this._dataAdapter.getSelectedNodesKeys(); const selectedNode = selectedItemKey.length && this._dataAdapter.getNodeByKey(selectedItemKey[0]); if (selectedNode) { this._toggleItemSelection(selectedNode, false) } if (!selectedNode || selectedNode.internalFields.item !== itemData) { this.selectItem(itemData) } else { this._fireSelectionChangeEvent(null, this.option("selectedItem")); this._setOptionWithoutOptionChange("selectedItem", null) } } _isItemSelectAllowed(item) { const { selectByClick: selectByClick } = this.option(); const isSelectByClickEnabled = this._isSelectionEnabled() && selectByClick; return !this._isContainerEmpty() && isSelectByClickEnabled && this._isItemSelectable(item) && !this._itemsGetter(item) } _isContainerEmpty() { return this._itemContainer().is(":empty") } _syncSelectionOptions() { return (0, _common.asyncNoop)() } _optionChanged(args) { switch (args.name) { case "showSubmenuMode": break; case "selectedItem": { const node = args.value ? this._dataAdapter.getNodeByItem(args.value) : null; const selectedKey = this._dataAdapter.getSelectedNodesKeys()[0]; if (node && node.internalFields.key !== selectedKey) { if (false === node.selectable) { break } const selectedNode = this._dataAdapter.getNodeByKey(selectedKey); if (selectedKey && selectedNode) { this._toggleItemSelection(selectedNode, false) } this._toggleItemSelection(node, true) } break } case "cssClass": case "position": case "selectByClick": case "animation": case "useInkRipple": this._invalidate(); break; default: super._optionChanged(args) } } _toggleItemSelection(node, value) { const itemElement = this._getElementByItem(node.internalFields.item); if (itemElement) { (0, _renderer.default)(itemElement).toggleClass(DX_MENU_SELECTED_ITEM_CLASS) } this._dataAdapter.toggleSelection(node.internalFields.key, value) } _getElementByItem(itemData) { let result = (0, _renderer.default)(); (0, _iterator.each)(this._itemElements(), ((_index, $itemElement) => { if ((0, _renderer.default)($itemElement).data(this._itemDataKey()) !== itemData) { return true } result = $itemElement; return false })); return result } _updateSelectedItems() {} _updateSelectedItem(addedItem, removedItem) { if (addedItem || removedItem) { this._fireSelectionChangeEvent(addedItem, removedItem) } } _fireSelectionChangeEvent(addedItem, removedItem) { this._createActionByOption("onSelectionChanged", { excludeValidators: ["disabled", "readOnly"] })({ addedItems: [addedItem], removedItems: [removedItem] }) } selectItem(itemElement) { const itemData = (item = itemElement, "object" === typeof item && "nodeType" in item && !!item.nodeType) ? this._getItemData(itemElement) : itemElement; var item; const selectedKey = this._dataAdapter.getSelectedNodesKeys()[0]; const selectedItem = this.option("selectedItem"); const node = this._dataAdapter.getNodeByItem(itemData); if (node && node.internalFields.key !== selectedKey) { const selectedNode = this._dataAdapter.getNodeByKey(selectedKey); if (selectedKey && selectedNode) { this._toggleItemSelection(selectedNode, false) } this._toggleItemSelection(node, true); this._updateSelectedItem(itemData, selectedItem); this._setOptionWithoutOptionChange("selectedItem", itemData) } } unselectItem(itemElement) { const itemData = itemElement.nodeType ? this._getItemData(itemElement) : itemElement; const node = this._dataAdapter.getNodeByItem(itemData); const selectedItem = this.option("selectedItem"); if (null !== node && void 0 !== node && node.internalFields.selected) { this._toggleItemSelection(node, false); this._updateSelectedItem(null, selectedItem); this._setOptionWithoutOptionChange("selectedItem", null) } } } MenuBase.ItemClass = _item.default; var _default = exports.default = MenuBase;