Files
Native-Router-Vue/packages/core/src/components/NativeGestureLink.vue

105 lines
2.6 KiB
Vue
Executable File

<script setup lang="ts">
import { computed, onBeforeUnmount, ref, useAttrs, type PropType } from "vue";
import type { RouteLocationRaw } from "vue-router";
import type { NativePresentationName } from "../types";
import { createPointerGesture, sourceRect } from "./gestures";
import { useNativeRouter } from "./lifecycle";
defineOptions({ name: "NativeGestureLink", inheritAttrs: false });
const props = defineProps({
to: {
type: [String, Object] as PropType<RouteLocationRaw>,
required: true,
},
presentation: {
type: String as PropType<NativePresentationName>,
default: "reveal",
},
replace: Boolean,
direction: {
type: String as PropType<"left" | "right" | "any">,
default: "any",
},
as: { type: String, default: "div" },
});
const attrs = useAttrs();
const passthroughAttrs = computed(() => {
const result = { ...attrs };
delete result.onPointerdown;
delete result.onPointermove;
delete result.onPointerup;
delete result.onPointercancel;
delete result.onClick;
return result;
});
const runtime = useNativeRouter();
const root = ref<HTMLElement | null>(null);
let dragDistance = 0;
let suppressClick = false;
const gesture = createPointerGesture(
() => root.value,
runtime,
async (direction) =>
runtime.beginInteractive("push", props.to, {
presentation: props.presentation,
replace: props.replace,
direction,
sourceRect: root.value ? sourceRect(root.value) : undefined,
}),
(dx) => {
if (props.direction === "left" && dx >= 0) return null;
if (props.direction === "right" && dx <= 0) return null;
return dx < 0 ? "forward" : "back";
},
);
onBeforeUnmount(() => void runtime.cancelInteractive());
function pointerDown(event: PointerEvent) {
// Component-owned gestures outrank their containing navigator.
event.stopPropagation();
dragDistance = 0;
suppressClick = false;
gesture.down(event);
}
function pointerMove(event: PointerEvent) {
if (event.buttons) {
dragDistance += Math.abs(event.movementX);
if (dragDistance > 8) suppressClick = true;
}
void gesture.move(event);
}
function click(event: MouseEvent) {
if (suppressClick) {
suppressClick = false;
event.preventDefault();
event.stopPropagation();
return;
}
void runtime.push(props.to, {
presentation: props.presentation,
replace: props.replace,
});
}
</script>
<template>
<component
:is="as"
ref="root"
v-bind="passthroughAttrs"
class="nvr-gesture-link"
@pointerdown="pointerDown"
@pointermove="pointerMove"
@pointerup="gesture.up"
@pointercancel="gesture.cancel"
@click="click"
>
<slot />
</component>
</template>