Files
tools/vue/primitives/src/context-menu/ContextMenuTrigger.vue
T

95 lines
2.4 KiB
Vue

<script lang="ts">
import type { PrimitiveProps } from '../primitive';
/**
* The region that captures right-click (and touch long-press), preventing the
* native context menu and opening the menu anchored at the pointer position.
* Wrap whatever area should respond to a secondary click.
*/
export interface ContextMenuTriggerProps extends PrimitiveProps {
disabled?: boolean;
}
</script>
<script setup lang="ts">
import { computed, onScopeDispose, shallowRef } from 'vue';
import { useForwardExpose } from '@robonen/vue';
import { MenuAnchor, useMenuContext } from '../menu';
import { Primitive } from '../primitive';
import { useContextMenuRootContext } from './context';
const { disabled = false, as = 'span' } = defineProps<ContextMenuTriggerProps>();
const menuCtx = useMenuContext();
const ctxMenuCtx = useContextMenuRootContext();
const { forwardRef } = useForwardExpose();
const point = shallowRef({ x: 0, y: 0 });
const virtualEl = computed(() => ({
getBoundingClientRect: () => ({
x: point.value.x,
y: point.value.y,
width: 0,
height: 0,
top: point.value.y,
right: point.value.x,
bottom: point.value.y,
left: point.value.x,
toJSON: () => {},
}),
}));
let longPressTimer: ReturnType<typeof setTimeout> | undefined;
const LONG_PRESS_DELAY = 700;
function clearLongPress() {
clearTimeout(longPressTimer);
}
onScopeDispose(clearLongPress);
function handleContextMenu(event: MouseEvent) {
if (disabled) return;
clearLongPress();
point.value = { x: event.clientX, y: event.clientY };
event.preventDefault();
ctxMenuCtx.onOpenChange(true);
}
function handlePointerDown(event: PointerEvent) {
if (disabled || event.button !== 0) return;
if (event.pointerType !== 'touch') return;
clearLongPress();
longPressTimer = setTimeout(() => {
point.value = { x: event.clientX, y: event.clientY };
ctxMenuCtx.onOpenChange(true);
}, LONG_PRESS_DELAY);
}
function handlePointerCancel() {
clearLongPress();
}
function handlePointerUp() {
clearLongPress();
}
</script>
<template>
<MenuAnchor :reference="virtualEl">
<Primitive
:ref="forwardRef"
:as="as"
:data-state="menuCtx.open.value ? 'open' : 'closed'"
:data-disabled="disabled ? '' : undefined"
@contextmenu="handleContextMenu"
@pointerdown="handlePointerDown"
@pointercancel="handlePointerCancel"
@pointerup="handlePointerUp"
>
<slot />
</Primitive>
</MenuAnchor>
</template>