element-plus
Version:
A Component Library for Vue 3
1 lines • 7.54 kB
Source Map (JSON)
{"version":3,"file":"menu.mjs","names":[],"sources":["../../../../../../packages/components/cascader-panel/src/menu.vue"],"sourcesContent":["<template>\n <template v-if=\"virtualScroll\">\n <div\n :key=\"menuId\"\n :class=\"ns.b()\"\n @mousemove=\"handleMouseMove\"\n @mouseleave=\"clearHoverZone\"\n >\n <el-fixed-size-list\n ref=\"virtualListRef\"\n :height=\"height\"\n :item-size=\"itemSize\"\n :data=\"nodes\"\n :total=\"nodes.length\"\n :class-name=\"ns.e('list')\"\n inner-element=\"ul\"\n :inner-props=\"{\n role: 'menu',\n class: ns.is('empty', isEmpty),\n }\"\n >\n <template #default=\"{ data, index: nodeIndex, style }\">\n <el-cascader-node\n :key=\"data[nodeIndex].uid\"\n :node=\"data[nodeIndex]\"\n :menu-id=\"menuId\"\n :style=\"style\"\n @expand=\"handleExpand\"\n />\n </template>\n </el-fixed-size-list>\n <div v-if=\"isLoading\" :class=\"ns.e('empty-text')\">\n <el-icon :size=\"14\" :class=\"ns.is('loading')\">\n <Loading />\n </el-icon>\n {{ t('el.cascader.loading') }}\n </div>\n <div v-else-if=\"isEmpty\" :class=\"ns.e('empty-text')\">\n <slot name=\"empty\">{{ t('el.cascader.noData') }}</slot>\n </div>\n <!-- eslint-disable vue/html-self-closing -->\n <svg\n v-else-if=\"panel?.isHoverMenu\"\n ref=\"hoverZone\"\n :class=\"ns.e('hover-zone')\"\n ></svg>\n <!-- eslint-enable vue/html-self-closing -->\n </div>\n </template>\n <el-scrollbar\n v-else\n :key=\"menuId\"\n tag=\"ul\"\n role=\"menu\"\n :class=\"ns.b()\"\n :wrap-class=\"ns.e('wrap')\"\n :view-class=\"[ns.e('list'), ns.is('empty', isEmpty)]\"\n @mousemove=\"handleMouseMove\"\n @mouseleave=\"clearHoverZone\"\n >\n <el-cascader-node\n v-for=\"node in nodes\"\n :key=\"node.uid\"\n :node=\"node\"\n :menu-id=\"menuId\"\n @expand=\"handleExpand\"\n />\n <div v-if=\"isLoading\" :class=\"ns.e('empty-text')\">\n <el-icon :size=\"14\" :class=\"ns.is('loading')\">\n <Loading />\n </el-icon>\n {{ t('el.cascader.loading') }}\n </div>\n <div v-else-if=\"isEmpty\" :class=\"ns.e('empty-text')\">\n <slot name=\"empty\">{{ t('el.cascader.noData') }}</slot>\n </div>\n <!-- eslint-disable vue/html-self-closing -->\n <svg\n v-else-if=\"panel?.isHoverMenu\"\n ref=\"hoverZone\"\n :class=\"ns.e('hover-zone')\"\n ></svg>\n <!-- eslint-enable vue/html-self-closing -->\n </el-scrollbar>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, getCurrentInstance, inject, nextTick, ref } from 'vue'\nimport { clamp } from 'lodash-unified'\nimport ElScrollbar from '@element-plus/components/scrollbar'\nimport { FixedSizeList as ElFixedSizeList } from '@element-plus/components/virtual-list'\nimport { useId, useLocale, useNamespace } from '@element-plus/hooks'\nimport { Loading } from '@element-plus/icons-vue'\nimport ElIcon from '@element-plus/components/icon'\nimport { focusNode } from '@element-plus/utils'\nimport ElCascaderNode from './node.vue'\nimport { CASCADER_PANEL_INJECTION_KEY } from './types'\nimport { CASCADER_PANEL_HEIGHT, CASCADER_PANEL_ITEM_SIZE } from './config'\n\nimport type { CascaderNode } from './types'\nimport type { CascaderCommonProps } from './config'\nimport type { FixedSizeListInstance } from '@element-plus/components/virtual-list'\n\ndefineOptions({\n name: 'ElCascaderMenu',\n})\n\nconst props = withDefaults(\n defineProps<\n {\n nodes: CascaderNode[]\n index: number\n } & Pick<CascaderCommonProps, 'virtualScroll' | 'itemSize' | 'height'>\n >(),\n {\n virtualScroll: false,\n itemSize: CASCADER_PANEL_ITEM_SIZE,\n height: CASCADER_PANEL_HEIGHT,\n }\n)\n\nconst instance = getCurrentInstance()!\nconst ns = useNamespace('cascader-menu')\n\nconst { t } = useLocale()\nconst id = useId()\nlet activeNode: HTMLElement\nlet hoverTimer: number | undefined\n\nconst panel = inject(CASCADER_PANEL_INJECTION_KEY)!\n\nconst hoverZone = ref<SVGSVGElement>()\nconst virtualListRef = ref<FixedSizeListInstance>()\n\nconst isEmpty = computed(() => !props.nodes.length)\nconst isLoading = computed(() => !panel.initialLoaded)\nconst menuId = computed(() => `${id.value}-${props.index}`)\n\nconst getActiveNodeIndex = () => {\n let activeNodeId: number | undefined\n\n if (panel.expandingNode) {\n const { level, pathNodes } = panel.expandingNode\n if (props.index < level) {\n activeNodeId = pathNodes[props.index]?.uid\n } else if (props.index === level && panel.checkedNodes.length > 0) {\n activeNodeId = panel.checkedNodes[0]?.pathNodes[props.index]?.uid\n }\n } else if (\n panel.checkedNodes.length > 0 &&\n props.index < panel.checkedNodes[0].pathNodes.length\n ) {\n activeNodeId = panel.checkedNodes[0].pathNodes[props.index]?.uid\n }\n\n return activeNodeId !== undefined\n ? props.nodes.findIndex((node) => node.uid === activeNodeId)\n : -1\n}\n\nconst getNodeIndexById = (nodeId: string | undefined) => {\n if (!nodeId) return -1\n return props.nodes.findIndex(\n (node) => `${menuId.value}-${node.uid}` === nodeId\n )\n}\n\nconst scrollToItem = (index: number) => {\n const targetIndex = clamp(index, 0, props.nodes.length - 1)\n virtualListRef.value?.scrollToItem(targetIndex)\n}\n\nconst focusNodeAt = (index: number) => {\n if (!props.nodes.length) return\n const targetIndex = clamp(index, 0, props.nodes.length - 1)\n scrollToItem(targetIndex)\n nextTick(() => {\n const node = (instance.vnode.el as HTMLElement)?.querySelector<HTMLElement>(\n `#${menuId.value}-${props.nodes[targetIndex].uid}`\n )\n if (node) focusNode(node)\n })\n}\n\nconst handleExpand = (e: MouseEvent) => {\n activeNode = e.target as HTMLElement\n}\n\nconst handleMouseMove = (e: MouseEvent) => {\n if (!panel.isHoverMenu || !activeNode || !hoverZone.value) return\n\n if (activeNode.contains(e.target as HTMLElement)) {\n clearHoverTimer()\n\n const el = instance.vnode.el as HTMLElement\n const { left } = el.getBoundingClientRect()\n const { offsetWidth, offsetHeight } = el\n const startX = e.clientX - left\n const top = activeNode.offsetTop\n const bottom = top + activeNode.offsetHeight\n\n const scrollTop = props.virtualScroll\n ? virtualListRef.value?.states?.scrollOffset || 0\n : el.querySelector(`.${ns.e('wrap')}`)?.scrollTop || 0\n\n hoverZone.value.innerHTML = `\n <path style=\"pointer-events: auto;\" fill=\"transparent\" d=\"M${startX} ${top} L${offsetWidth} ${scrollTop} V${top} Z\" />\n <path style=\"pointer-events: auto;\" fill=\"transparent\" d=\"M${startX} ${bottom} L${offsetWidth} ${offsetHeight + scrollTop} V${bottom} Z\" />\n `\n } else if (!hoverTimer) {\n hoverTimer = window.setTimeout(clearHoverZone, panel.config.hoverThreshold)\n }\n}\n\nconst clearHoverTimer = () => {\n if (!hoverTimer) return\n clearTimeout(hoverTimer)\n hoverTimer = undefined\n}\n\nconst clearHoverZone = () => {\n if (!hoverZone.value) return\n hoverZone.value.innerHTML = ''\n clearHoverTimer()\n}\n\ndefineExpose({\n getActiveNodeIndex,\n getNodeIndexById,\n scrollToItem,\n focusNodeAt,\n virtualListRef,\n get $el() {\n return instance.vnode.el as HTMLElement\n },\n})\n</script>\n"],"mappings":""}