import type { Ctx } from '@milkdown/kit/ctx' import { Icon } from '@milkdown/kit/component' import { computed, defineComponent, onUnmounted, ref, watch, watchEffect, type Ref, h, } from 'vue' import type { BlockEditFeatureConfig } from '..' import { keepAlive } from '../../../utils/keep-alive' import { getGroups } from './config' keepAlive(h) type MenuProps = { ctx: Ctx show: Ref filter: Ref hide: () => void config?: BlockEditFeatureConfig } export const Menu = defineComponent({ props: { ctx: { type: Object, required: true, }, show: { type: Object, required: true, }, filter: { type: Object, required: true, }, hide: { type: Function, required: true, }, config: { type: Object, required: false, }, }, setup({ ctx, show, filter, hide, config }) { const host = ref() const groupInfo = computed(() => getGroups(filter.value, config, ctx)) const hoverIndex = ref(0) const prevMousePosition = ref({ x: -999, y: -999 }) const onPointerMove = (e: MouseEvent) => { const { x, y } = e prevMousePosition.value = { x, y } } watch([groupInfo, show], () => { const { size } = groupInfo.value if (size === 0 && show.value) hide() else if (hoverIndex.value >= size) hoverIndex.value = 0 }) const onHover = ( index: number | ((prev: number) => number), after?: (index: number) => void ) => { const prevHoverIndex = hoverIndex.value const next = typeof index === 'function' ? index(prevHoverIndex) : index after?.(next) hoverIndex.value = next } const scrollToIndex = (index: number) => { const target = host.value?.querySelector( `[data-index="${index}"]` ) const scrollRoot = host.value?.querySelector('.menu-groups') if (!target || !scrollRoot) return scrollRoot.scrollTop = target.offsetTop - scrollRoot.offsetTop } const runByIndex = (index: number) => { const item = groupInfo.value.groups .flatMap((group) => group.items) .at(index) if (item?.onRun && ctx) item.onRun(ctx) hide() } const onKeydown = (e: KeyboardEvent) => { const { size, groups } = groupInfo.value if (e.key === 'Escape') { e.preventDefault() hide?.() return } if (e.key === 'ArrowDown') { e.preventDefault() return onHover( (index) => (index < size - 1 ? index + 1 : index), scrollToIndex ) } if (e.key === 'ArrowUp') { e.preventDefault() return onHover( (index) => (index <= 0 ? index : index - 1), scrollToIndex ) } if (e.key === 'ArrowLeft') { e.preventDefault() return onHover((index) => { const group = groups.find( (group) => group.range[0] <= index && group.range[1] > index ) if (!group) return index const prevGroup = groups[groups.indexOf(group) - 1] if (!prevGroup) return index return prevGroup.range[1] - 1 }, scrollToIndex) } if (e.key === 'ArrowRight') { e.preventDefault() return onHover((index) => { const group = groups.find( (group) => group.range[0] <= index && group.range[1] > index ) if (!group) return index const nextGroup = groups[groups.indexOf(group) + 1] if (!nextGroup) return index return nextGroup.range[0] }, scrollToIndex) } if (e.key === 'Enter') { e.preventDefault() runByIndex(hoverIndex.value) } } const getOnPointerEnter = (index: number) => (e: MouseEvent) => { const prevPos = prevMousePosition.value if (!prevPos) return const { x, y } = e if (x === prevPos.x && y === prevPos.y) return onHover(index) } watchEffect(() => { const isShown = show.value if (isShown) { window.addEventListener('keydown', onKeydown, { capture: true }) } else { window.removeEventListener('keydown', onKeydown, { capture: true }) } }) onUnmounted(() => { window.removeEventListener('keydown', onKeydown, { capture: true }) }) return () => { return (
e.preventDefault()}>
) } }, })