105 lines
2.6 KiB
Vue
Executable File
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>
|