From 9bec4dcae002f36aa23231da11cb03a156b40110 Mon Sep 17 00:00:00 2001 From: schangxiang@126.com <schangxiang@126.com> Date: 周三, 30 4月 2025 16:24:16 +0800 Subject: [PATCH] 222 --- PipeLineLems/web/src/components/vue3-context-menu/ContextMenuItem.vue | 363 ++++++++++++++++++++++++++++++++------------------- 1 files changed, 229 insertions(+), 134 deletions(-) diff --git a/PipeLineLems/web/src/components/vue3-context-menu/ContextMenuItem.vue b/PipeLineLems/web/src/components/vue3-context-menu/ContextMenuItem.vue index 85252d1..f13ad9c 100644 --- a/PipeLineLems/web/src/components/vue3-context-menu/ContextMenuItem.vue +++ b/PipeLineLems/web/src/components/vue3-context-menu/ContextMenuItem.vue @@ -1,61 +1,133 @@ <template> - <div v-if="!hidden" class="mx-context-menu-item-wrapper" ref="menuItemRef" data-type="ContextMenuItem"> + <div + v-if="!hidden" + class="mx-context-menu-item-wrapper" + ref="menuItemRef" + data-type="ContextMenuItem" + > <!--Custom render--> - <VNodeRender v-if="globalHasSlot('itemRender')" :vnode="() => globalRenderSlot('itemRender', getItemDataForChildren())" /> - <VNodeRender v-else-if="customRender" :vnode="customRender" :data="getItemDataForChildren()" /> + <VNodeRender + v-if="globalHasSlot('itemRender')" + :vnode="() => globalRenderSlot('itemRender', getItemDataForChildren())" + /> + <VNodeRender + v-else-if="customRender" + :vnode="customRender" + :data="getItemDataForChildren()" + /> <!--Default item--> - <div + <div v-else :class="[ 'mx-context-menu-item', - (disabled ? 'disabled' : ''), - (keyBoardFocusMenu ? 'keyboard-focus' : ''), - (customClass ? (' ' + customClass) : ''), - (showSubMenu ? 'open' : ''), + disabled ? 'disabled' : '', + keyBoardFocusMenu ? 'keyboard-focus' : '', + customClass ? ' ' + customClass : '', + showSubMenu ? 'open' : '', ]" @click="onClick" + @touchstart="onTouchStart" @mouseenter="onMouseEnter" > <slot> <div class="mx-item-row"> - <div :class="[ - 'mx-icon-placeholder', - preserveIconWidth ? 'preserve-width': '', - ]"> + <div + :class="[ + 'mx-icon-placeholder', + preserveIconWidth ? 'preserve-width' : '', + ]" + > <slot name="icon"> - <VNodeRender v-if="globalHasSlot('itemIconRender')" :vnode="() => globalRenderSlot('itemIconRender', getItemDataForChildren())" /> - <svg v-else-if="typeof svgIcon === 'string' && svgIcon" class="icon svg" v-bind="svgProps"> + <VNodeRender + v-if="globalHasSlot('itemIconRender')" + :vnode=" + () => + globalRenderSlot('itemIconRender', getItemDataForChildren()) + " + /> + <svg + v-else-if="typeof svgIcon === 'string' && svgIcon" + class="icon svg" + v-bind="svgProps" + > <use :xlink:href="svgIcon"></use> </svg> - <VNodeRender v-else-if="(typeof icon !== 'string')" :vnode="icon" :data="icon" /> - <i v-else-if="typeof icon === 'string' && icon !== ''" :class="icon + ' icon '+ iconFontClass + ' ' + globalIconFontClass"></i> + <VNodeRender + v-else-if="typeof icon !== 'string'" + :vnode="icon" + :data="icon" + /> + <i + v-else-if="typeof icon === 'string' && icon !== ''" + :class=" + icon + ' icon ' + iconFontClass + ' ' + globalIconFontClass + " + ></i> </slot> <slot v-if="checked" name="check"> - <VNodeRender v-if="globalHasSlot('itemCheckRender')" :vnode="() => globalRenderSlot('itemCheckRender', getItemDataForChildren())" /> + <VNodeRender + v-if="globalHasSlot('itemCheckRender')" + :vnode=" + () => + globalRenderSlot( + 'itemCheckRender', + getItemDataForChildren() + ) + " + /> <ContextMenuIconCheck /> </slot> </div> <slot name="label"> - <VNodeRender v-if="globalHasSlot('itemLabelRender')" :vnode="() => globalRenderSlot('itemLabelRender', getItemDataForChildren())" /> - <span class="label" v-else-if="typeof label === 'string'">{{ label }}</span> + <VNodeRender + v-if="globalHasSlot('itemLabelRender')" + :vnode=" + () => + globalRenderSlot('itemLabelRender', getItemDataForChildren()) + " + /> + <span class="label" v-else-if="typeof label === 'string'">{{ + label + }}</span> <VNodeRender v-else :vnode="label" :data="label" /> </slot> </div> <div class="mx-item-row"> <slot v-if="shortcut" name="shortcut"> - <VNodeRender v-if="globalHasSlot('itemShortcutRender')" :vnode="() => globalRenderSlot('itemShortcutRender', getItemDataForChildren())" /> + <VNodeRender + v-if="globalHasSlot('itemShortcutRender')" + :vnode=" + () => + globalRenderSlot( + 'itemShortcutRender', + getItemDataForChildren() + ) + " + /> <span class="mx-shortcut">{{ shortcut }}</span> </slot> <slot v-if="showRightArrow" name="rightArrow"> - <VNodeRender v-if="globalHasSlot('itemRightArrowRender')" :vnode="() => globalRenderSlot('itemRightArrowRender', getItemDataForChildren())" /> + <VNodeRender + v-if="globalHasSlot('itemRightArrowRender')" + :vnode=" + () => + globalRenderSlot( + 'itemRightArrowRender', + getItemDataForChildren() + ) + " + /> <ContextMenuIconRight /> </slot> </div> </slot> </div> - + <!--Sub menu render--> - <Transition v-if="globalMenuTransitionProps" v-bind="globalMenuTransitionProps"> + <Transition + v-if="globalMenuTransitionProps" + v-bind="globalMenuTransitionProps" + > <slot v-if="showSubMenu" name="submenu"></slot> </Transition> <slot v-else-if="showSubMenu" name="submenu"></slot> @@ -63,8 +135,21 @@ </template> <script setup lang="ts"> -import { inject, nextTick, onBeforeUnmount, onMounted, type PropType, ref, type SVGAttributes, toRefs, type TransitionProps } from 'vue' -import type { MenuItemContext, SubMenuParentContext } from './ContextSubMenu.vue' +import { + inject, + nextTick, + onBeforeUnmount, + onMounted, + type PropType, + ref, + type SVGAttributes, + toRefs, + type TransitionProps, +} from 'vue' +import type { + MenuItemContext, + SubMenuParentContext, +} from './ContextSubMenu.vue' import type { GlobalHasSlot, GlobalRenderSlot } from './ContextMenu.vue' import type { MenuItem } from './ContextMenuDefine' import { VNodeRender } from './ContextMenuUtils' @@ -77,94 +162,94 @@ const props = defineProps({ /** - * Is this menu disabled? + * Is this menu disabled? */ disabled: { type: Boolean, - default: false + default: false, }, /** - * Is this menu hidden? + * Is this menu hidden? */ hidden: { type: Boolean, - default: false + default: false, }, customRender: { type: Function, - default: null + default: null, }, /** * Custom css class for submenu */ customClass: { type: String, - default: '' + default: '', }, clickHandler: { - type: Function as PropType<(e: MouseEvent|KeyboardEvent) => void>, - default: null + type: Function as PropType<(e: MouseEvent | KeyboardEvent) => void>, + default: null, }, /** * Menu label */ label: { type: [String, Object, Function], - default: '' + default: '', }, /** * Menu icon (for icon class) */ icon: { type: [String, Object, Function], - default: '' + default: '', }, /** * Custom icon library font class name. - * + * * Only for css font icon, If you use the svg icon, you do not need to use this. */ iconFontClass: { type: String, - default: 'iconfont' + default: 'iconfont', }, /** * Is this menu item checked? - * + * * The check mark are displayed on the left side of the icon, so it is not recommended to display the icon at the same time. */ checked: { type: Boolean, - default: false + default: false, }, /** * Shortcut key text display on the right. - * + * * The shortcut keys here are only for display. You need to handle the key events by yourself. */ shortcut: { type: String, - default: '' + default: '', }, /** * Display icons use svg symbol (`<use xlink:href="#icon-symbol-name">`) 锛� only valid when icon attribute is empty. */ svgIcon: { type: String, - default: '' + default: '', }, /** * The user-defined attribute of the svg tag, which is valid when using `svgIcon`. */ svgProps: { type: Object as PropType<SVGAttributes>, - default: null + default: null, }, /** * Should a fixed-width icon area be reserved for menu items without icon. (this item) - * + * * Default is true . - * + * * The width of icon area can be override with css var `--mx-menu-placeholder-width`. */ preserveIconWidth: { @@ -176,76 +261,85 @@ */ showRightArrow: { type: Boolean, - default: false + default: false, }, hasChildren: { type: Boolean, - default: false + default: false, }, /** * Should close menu when Click this menu item ? */ clickClose: { type: Boolean, - default: true + default: true, }, /** * When there are subitems in this item, is it allowed to trigger its own click event? Default is false */ clickableWhenHasChildren: { type: Boolean, - default: false + default: false, }, rawMenuItem: { type: Object as PropType<MenuItem>, - default: undefined + default: undefined, }, -}); -const emit = defineEmits([ - 'click', - 'subMenuOpen', - 'subMenuClose', -]) +}) +const emit = defineEmits(['click', 'subMenuOpen', 'subMenuClose']) -const { - clickHandler, clickClose, clickableWhenHasChildren, disabled, hidden, - label, icon, iconFontClass, - showRightArrow, shortcut, +const { + clickHandler, + clickClose, + clickableWhenHasChildren, + disabled, + hidden, + label, + icon, + iconFontClass, + showRightArrow, + shortcut, hasChildren, -} = toRefs(props); -const showSubMenu = ref(false); -const keyBoardFocusMenu = ref(false); +} = toRefs(props) +const showSubMenu = ref(false) +const keyBoardFocusMenu = ref(false) -const menuItemRef = ref<HTMLElement>(); +const menuItemRef = ref<HTMLElement>() -const globalHasSlot = inject('globalHasSlot') as GlobalHasSlot; -const globalRenderSlot = inject('globalRenderSlot') as GlobalRenderSlot; -const globalTheme = inject('globalTheme') as string; -const globalIconFontClass = inject('globalIconFontClass') as string; -const globalMenuTransitionProps = inject('globalMenuTransitionProps') as TransitionProps; -const globalClickCloseClassName = inject('globalClickCloseClassName') as string; -const globalIgnoreClickClassName = inject('globalIgnoreClickClassName') as string; -const globalCloseMenu = inject('globalCloseMenu') as (fromItem: MenuItem|undefined) => void; +const globalHasSlot = inject('globalHasSlot') as GlobalHasSlot +const globalRenderSlot = inject('globalRenderSlot') as GlobalRenderSlot +const globalTheme = inject('globalTheme') as string +const globalIconFontClass = inject('globalIconFontClass') as string +const globalMenuTransitionProps = inject( + 'globalMenuTransitionProps' +) as TransitionProps +const globalClickCloseClassName = inject('globalClickCloseClassName') as string +const globalIgnoreClickClassName = inject( + 'globalIgnoreClickClassName' +) as string +const globalCloseMenu = inject('globalCloseMenu') as ( + fromItem: MenuItem | undefined +) => void -const menuContext = inject('menuContext') as SubMenuParentContext; +const menuContext = inject('menuContext') as SubMenuParentContext //Instance Contet for keyboadr control -const menuItemInstance : MenuItemContext = { +const menuItemInstance: MenuItemContext = { showSubMenu: () => { if (showSubMenu.value) { //Mark current item - menuContext.markActiveMenuItem(menuItemInstance, true); - return true; + menuContext.markActiveMenuItem(menuItemInstance, true) + return true } else if (hasChildren.value) { - onMouseEnter(); - return true; + onMouseEnter() + return true } - return false; + return false }, isDisabledOrHidden: () => disabled.value || hidden.value, getElement: () => menuItemRef.value, - focus: () => keyBoardFocusMenu.value = true, - blur: () => keyBoardFocusMenu.value = false, + focus: () => (keyBoardFocusMenu.value = true), + blur: () => (keyBoardFocusMenu.value = false), click: onClick, } @@ -254,102 +348,104 @@ //褰撳墠鑿滃崟鏉$洰鏄湪鏁翠綋鍔犺浇瀹屾垚鍚庢墠鏄剧ず鐨勶紝姝ゆ椂鑿滃崟椤哄簭宸茬粡鏃犳硶鐭ラ亾锛� //鎵�浠ヨ繖閲岄渶瑕佸湪鐖剁骇鍏冪礌涓煡鎵惧緱鍑哄綋鍓嶈彍鍗曠殑浣嶇疆銆� // - //The current menu item is displayed after the overall loading is completed. - //At this time, the menu order cannot be known, so here we need to + //The current menu item is displayed after the overall loading is completed. + //At this time, the menu order cannot be known, so here we need to //find the position of the current menu in the parent element. nextTick(() => { - let index = 0; - const parentEl = menuContext.getElement(); + let index = 0 + const parentEl = menuContext.getElement() if (parentEl) { - let indexCounting = 0; + let indexCounting = 0 for (let i = 0; i < parentEl.children.length; i++) { - const el = parentEl.children[i]; + const el = parentEl.children[i] if (el.getAttribute('data-type') === 'ContextMenuItem') { if (el === menuItemRef.value) { - index = indexCounting; - break; + index = indexCounting + break } - indexCounting++; + indexCounting++ } } } //Insert to pos - menuContext.addChildMenuItem(menuItemInstance, index); - }); - } else - menuContext.addChildMenuItem(menuItemInstance); -}); + menuContext.addChildMenuItem(menuItemInstance, index) + }) + } else menuContext.addChildMenuItem(menuItemInstance) +}) onBeforeUnmount(() => { - menuContext.removeChildMenuItem(menuItemInstance); -}); + menuContext.removeChildMenuItem(menuItemInstance) +}) + +function onTouchStart(e: TouchEvent) { + e?.stopPropagation() +} //Click handler -function onClick(e: MouseEvent|KeyboardEvent) { +function onClick(e: MouseEvent | KeyboardEvent) { //Ignore clicking when disabled - if (disabled.value) - return; + if (disabled.value) return //Ignore clicking when click on some special elements if (e) { - const currentTarget = e.target as HTMLElement; - if (currentTarget.classList.contains('mx-context-no-clickable')) - return; - if (globalIgnoreClickClassName && currentTarget.classList.contains(globalIgnoreClickClassName)) - return; - if (globalClickCloseClassName && currentTarget.classList.contains(globalClickCloseClassName)) { - e.stopPropagation(); - globalCloseMenu(props.rawMenuItem); - return; + const currentTarget = e.target as HTMLElement + if (currentTarget.classList.contains('mx-context-no-clickable')) return + if ( + globalIgnoreClickClassName && + currentTarget.classList.contains(globalIgnoreClickClassName) + ) + return + if ( + globalClickCloseClassName && + currentTarget.classList.contains(globalClickCloseClassName) + ) { + e.stopPropagation() + globalCloseMenu(props.rawMenuItem) + return } } //Has submenu? if (hasChildren.value) { if (clickableWhenHasChildren.value) { - if (typeof clickHandler.value === 'function') - clickHandler.value(e); - emit('click', e); - } - else if (!showSubMenu.value) - onMouseEnter(); + if (typeof clickHandler.value === 'function') clickHandler.value(e) + emit('click', e) + } else if (!showSubMenu.value) onMouseEnter() } else { //Call hander from options - if (typeof clickHandler.value === 'function') - clickHandler.value(e); - emit('click', e); + if (typeof clickHandler.value === 'function') clickHandler.value(e) + emit('click', e) if (clickClose.value) { //emit close - globalCloseMenu(props.rawMenuItem); + globalCloseMenu(props.rawMenuItem) } } } //MouseEnter handler: show item submenu function onMouseEnter(e?: MouseEvent) { //Clear keyBoard focus style - keyBoardFocusMenu.value = false; + keyBoardFocusMenu.value = false //绛夊緟涓�涓欢鏃讹紝浠ラ槻姝㈢敤鎴疯繃蹇Щ鍔ㄩ紶鏍囧鑷磋彍鍗曢殣钘� //Wait for a delay to prevent the menu from being hidden due to the user moving the mouse too fast if (!menuContext.checkCloseOtherSubMenuTimeOut()) - menuContext.closeOtherSubMenu(); + menuContext.closeOtherSubMenu() if (!disabled.value) { //Mark current item - menuContext.markActiveMenuItem(menuItemInstance); + menuContext.markActiveMenuItem(menuItemInstance) if (hasChildren.value) { - if (!e) - menuContext.markThisOpenedByKeyBoard(); + if (!e) menuContext.markThisOpenedByKeyBoard() //Open sub menu menuContext.addOpenedSubMenu(() => { - keyBoardFocusMenu.value = false; - showSubMenu.value = false; - emit('subMenuClose'); - }); - showSubMenu.value = true; - emit('subMenuOpen'); + keyBoardFocusMenu.value = false + showSubMenu.value = false + emit('subMenuClose') + }) + showSubMenu.value = true + emit('subMenuOpen') } } } - + //Data for custom render function getItemDataForChildren() { return { @@ -373,8 +469,7 @@ defineExpose({ showSubMenu, keyBoardFocusMenu, -}); +}) </script> -<style> -</style> +<style></style> -- Gitblit v1.9.3