95 lines
2.4 KiB
Vue
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>
|