Skip to content

Commit

Permalink
fix(ContextMenu): reoptimize components and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
crlang committed Jul 1, 2022
1 parent d854c63 commit b3d4439
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 159 deletions.
2 changes: 1 addition & 1 deletion src/components/ContextMenu/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { createContextMenu, destroyContextMenu } from './src/createContextMenu'

export * from './src/typing'
export type { ContextMenuItem, CreateContextMenuOptions } from './src/typing'
229 changes: 110 additions & 119 deletions src/components/ContextMenu/src/ContextMenu.vue
Original file line number Diff line number Diff line change
@@ -1,54 +1,87 @@
<script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing'
import type { FunctionalComponent, CSSProperties } from 'vue'
import type { ContextMenuItem, ItemContentProps, Axis } from './typing'
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue'
import { ElMenu, ElMenuItem, ElSubMenu } from 'element-plus'
import Icon from '@/components/Icon'
import { ElMenu, ElMenuItem, ElSubMenu, ElDivider } from 'element-plus'
const prefixCls = 'context-menu'
const props = {
width: { type: Number, default: 156 },
customEvent: { type: Object as PropType<Event>, default: null },
styles: { type: Object as PropType<CSSProperties> },
showIcon: { type: Boolean, default: true },
axis: {
// The position of the right mouse button click
type: Object as PropType<Axis>,
default() {
return { x: 0, y: 0 }
},
},
items: {
// The most important list, if not, will not be displayed
type: Array as PropType<ContextMenuItem[]>,
default() {
return []
},
},
}
const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
const { item } = props
return (
<div class={`${prefixCls}__text`} onClick={props.handler.bind(null, item)}>
{props.showIcon && item.icon && <Icon class='mr-2' name={item.icon} />}
<span>{item.label}</span>
</div>
)
}
import { useDesign } from '@/hooks/web/useDesign'
export default defineComponent({
name: 'ContextMenu',
props,
props: {
/**
* 右击菜单项的数据
*
* Right-click menu item data
*/
items: {
type: Array as PropType<ContextMenuItem[]>,
default() {
return []
},
},
/**
* 右击菜单的宽度
*
* Right-click menu width
*/
width: {
type: Number,
default: 156,
},
/**
* 右击的DOM的事件
*
* Right-clicked DOM events
*/
event: {
type: Object as PropType<Event>,
default: null,
},
/**
* 右击菜单的样式
*
* Right-click menu style
*/
styles: Object as PropType<CSSProperties>,
/**
* 是否显示图标
*
* Whether to show the icon
*/
showIcon: {
type: Boolean,
default: true,
},
/**
* 右击菜单的轴偏移
*
* Axis Offset from right-click menu
*/
axis: {
type: Object as PropType<Axis>,
default() {
return { x: 0, y: 0 }
},
},
},
setup(props) {
const wrapRef = ref(null)
const showRef = ref(false)
const { prefixCls } = useDesign('context-menu')
/**
* 处理菜单样式
*
* Handling menu styles
*/
const getStyle = computed((): CSSProperties => {
const { axis, items, styles, width } = props
const { x, y } = axis || { x: 0, y: 0 }
const menuHeight = (items || []).length * 40
const menuHeight = (items || []).length * 44
const menuWidth = width
const body = document.body
Expand All @@ -62,26 +95,41 @@ export default defineComponent({
}
})
onMounted(() => {
nextTick(() => (showRef.value = true))
})
onUnmounted(() => {
const el = unref(wrapRef)
el && document.body.removeChild(el)
})
/**
* 处理菜单项内容
*
* Handling menu item content
*/
const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
const { item, handler, showIcon } = props
return (
<div class={`${prefixCls}__text`} onClick={handler.bind(null, item)}>
{showIcon && item.icon && <Icon class='mr-2' name={item.icon} />}
<span>{item.label}</span>
</div>
)
}
/**
* 处理点击动作
*
* Handling clicks
*/
function handleAction(item: ContextMenuItem, e: MouseEvent) {
const { handler, disabled } = item
if (disabled) {
return
}
if (disabled) return
showRef.value = false
e?.stopPropagation()
e?.preventDefault()
handler?.()
}
/**
* 渲染菜单项
*
* Render menu item
*/
function renderMenuItem(items: ContextMenuItem[]) {
return items.map((item) => {
const { disabled, label, children, divider = false } = item
Expand All @@ -95,10 +143,9 @@ export default defineComponent({
if (!children || children.length === 0) {
return (
<>
<ElMenuItem disabled={disabled} class={`${prefixCls}__item`} index={label}>
<ElMenuItem disabled={disabled} class={`${prefixCls}__item ${divider ? 'is-divider' : ''}`} index={label}>
<ItemContent {...contentProps} />
</ElMenuItem>
{divider ? <ElDivider key={`d-${label}`} /> : null}
</>
)
}
Expand All @@ -114,10 +161,19 @@ export default defineComponent({
)
})
}
onMounted(() => {
nextTick(() => (showRef.value = true))
})
onUnmounted(() => {
const el = unref(wrapRef)
el && document.body.removeChild(el)
})
return () => {
if (!unref(showRef)) {
return null
}
if (!unref(showRef)) return null
const { items } = props
return (
<ElMenu
Expand All @@ -137,69 +193,4 @@ export default defineComponent({
})
</script>

<style lang="scss">
@mixin item-style {
li {
display: inline-block;
width: 100%;
height: $default-height;
margin: 0 !important;
line-height: $default-height;
&.el-sub-menu {
.el-sub-menu {
&__title {
height: $default-height;
line-height: $default-height;
}
}
}
&:not(.is-disabled):hover {
color: var(--text-primary-color);
background-color: #f1f2f3;
}
}
}
$default-height: 44px;
.context-menu {
@include item-style();
position: fixed;
top: 0;
left: 0;
z-index: 200;
display: block;
width: 156px;
margin: 0;
list-style: none;
background-color: var(--background-primary-color);
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 0.25rem;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.1), 0 1px 5px 0 rgba(0, 0, 0, 0.06);
background-clip: padding-box;
user-select: none;
&__text {
display: flex;
align-items: center;
span {
width: auto !important;
height: auto !important;
font-size: 14px;
visibility: visible !important;
}
}
&__popup {
@include item-style();
> ul {
padding: 0;
}
}
}
</style>
<style lang="scss" src="./index.scss"></style>
48 changes: 27 additions & 21 deletions src/components/ContextMenu/src/createContextMenu.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import contextMenuVue from './ContextMenu.vue'
import { isClient } from '@/utils/is'
import { CreateContextOptions, ContextMenuProps } from './typing'
import type { CreateContextMenuOptions, ContextMenuProps } from './typing'

import { createVNode, render } from 'vue'

import contextMenuVue from './ContextMenu.vue'

const menuManager: {
domList: Element[]
resolve: Fn
Expand All @@ -11,33 +12,35 @@ const menuManager: {
resolve: () => {},
}

export const createContextMenu = function (options: CreateContextOptions) {
const { event } = options || {}
/**
* 创建右键菜单
*
* Create a right-click menu
* @param options CreateContextMenuOptions
*/
export const createContextMenu = function (options: CreateContextMenuOptions) {
const { event = null, showIcon = true, styles = {}, items, width } = options || {}

event && event?.preventDefault()

if (!isClient) {
return
}
if (typeof window === 'undefined') return

return new Promise((resolve) => {
const body = document.body

const container = document.createElement('div')
const propsData: Partial<ContextMenuProps> = {}
if (options.styles) {
propsData.styles = options.styles
}

if (options.items) {
propsData.items = options.items
}

if (options.event) {
propsData.customEvent = event
propsData.axis = { x: event.clientX, y: event.clientY }
const propsData: Partial<ContextMenuProps> = {
event,
styles,
showIcon,
items,
width,
axis: { x: event?.clientX || 0, y: event?.clientY || 0 },
}

/** create VNode */
const vm = createVNode(contextMenuVue, propsData)
/** render VNode */
render(vm, container)

const handleClick = function () {
Expand All @@ -51,7 +54,7 @@ export const createContextMenu = function (options: CreateContextOptions) {
try {
dom && body.removeChild(dom)
} catch (error) {
// continue regardless of error
// continue
}
})
body.removeEventListener('click', handleClick)
Expand All @@ -69,6 +72,9 @@ export const createContextMenu = function (options: CreateContextOptions) {
})
}

/**
* 销毁右键菜单
*/
export const destroyContextMenu = function () {
if (menuManager) {
menuManager.resolve('')
Expand Down
Loading

0 comments on commit b3d4439

Please sign in to comment.