fix: Prevent swiping AND scrolling on shopping list (#7659)

This commit is contained in:
Michael Genson
2026-05-24 13:28:58 -05:00
committed by GitHub
parent 16163a9189
commit 3b2bcca639

View File

@@ -10,24 +10,9 @@
}" }"
> >
<v-row <v-row
v-touch="{ ref="swipeRowRef"
move: ({ originalEvent: { touches: [{ screenX, screenY }] } }) => { v-touch="{ move: onSwipeMove, start: onSwipeStart, end: onSwipeEnd }"
swipeInfo.touchendX = screenX; style="touch-action: pan-y;"
swipeInfo.touchendY = screenY;
},
start: ({ originalEvent: { touches: [{ screenX, screenY }] } }) => {
swipeInfo.touchstartX = screenX;
swipeInfo.touchstartY = screenY;
},
end: () => {
if (swiping < SWIPE_THRESHOLD) {
swipeInfo = {};
return;
}
swipeInfo = {};
toggleChecked();
},
}"
no-gutters no-gutters
class="flex-nowrap align-center" class="flex-nowrap align-center"
> >
@@ -214,9 +199,23 @@ const emit = defineEmits<{
}>(); }>();
const SWIPE_THRESHOLD = 50; const SWIPE_THRESHOLD = 50;
const SCROLL_THRESHOLD = 50;
const { isRtl } = useRtl(); const { isRtl } = useRtl();
const swipeRowRef = ref<InstanceType<typeof import("vuetify/components").VRow> | null>(null);
onMounted(() => {
const el = swipeRowRef.value?.$el as HTMLElement | undefined;
if (!el) return;
el.addEventListener(
"touchmove",
(e: TouchEvent) => {
if (swipeInfo.value.gesture === "swipe") {
e.preventDefault();
}
},
{ passive: false },
);
});
const i18n = useI18n(); const i18n = useI18n();
const displayRecipeRefs = ref(false); const displayRecipeRefs = ref(false);
const itemLabelCols = computed<string>(() => (model.value?.checked ? "auto" : "6")); const itemLabelCols = computed<string>(() => (model.value?.checked ? "auto" : "6"));
@@ -267,22 +266,66 @@ function save() {
edit.value = false; edit.value = false;
} }
const swipeInfo: Ref<{ touchstartX?: number; touchendX?: number; touchstartY?: number; touchendY?: number }> = ref({}); type SwipeGesture = null | "scroll" | "swipe";
const swiping = computed(() => {
const { touchstartX, touchendX, touchstartY, touchendY } = swipeInfo.value ?? {};
if (touchstartX === undefined || touchendX === undefined) {
return 0;
}
const deltaX = isRtl.value ? touchstartX - touchendX : touchendX - touchstartX;
// If there's significant vertical movement, treat as a scroll gesture and ignore const swipeInfo = ref({
if (touchstartY !== undefined && touchendY !== undefined) { touchstartX: 0,
const deltaY = Math.abs(touchendY - touchstartY); touchstartY: 0,
if (deltaY > SCROLL_THRESHOLD) { touchendX: 0,
return 0; touchendY: 0,
gesture: null as SwipeGesture,
});
function getSwipePoint(e: any) {
const touch = e?.touches?.[0] ?? e?.changedTouches?.[0] ?? e;
return { x: touch?.clientX ?? 0, y: touch?.clientY ?? 0 };
}
function resetSwipe() {
swipeInfo.value = { touchstartX: 0, touchstartY: 0, touchendX: 0, touchendY: 0, gesture: null };
}
function onSwipeStart(payload: any) {
const { x, y } = getSwipePoint(payload.originalEvent);
swipeInfo.value = { touchstartX: x, touchstartY: y, touchendX: x, touchendY: y, gesture: null };
}
function onSwipeMove(payload: any) {
const { x, y } = getSwipePoint(payload.originalEvent);
swipeInfo.value.touchendX = x;
swipeInfo.value.touchendY = y;
if (!swipeInfo.value.gesture) {
const deltaX = Math.abs(x - swipeInfo.value.touchstartX);
const deltaY = Math.abs(y - swipeInfo.value.touchstartY);
if (deltaY > 8 && deltaY > deltaX) {
swipeInfo.value.gesture = "scroll";
}
else if (deltaX > 8 && deltaX > deltaY) {
swipeInfo.value.gesture = "swipe";
}
else if (deltaX > 8 || deltaY > 8) {
// Diagonal / ambiguous — default to scroll
swipeInfo.value.gesture = "scroll";
} }
} }
return Math.min(Math.max(0, deltaX), 100); }
function onSwipeEnd() {
if (swipeInfo.value.gesture === "swipe" && swiping.value >= SWIPE_THRESHOLD) {
toggleChecked();
}
resetSwipe();
}
const swiping = computed(() => {
if (swipeInfo.value.gesture !== "swipe") {
return 0;
}
const deltaX = isRtl.value
? swipeInfo.value.touchstartX - swipeInfo.value.touchendX
: swipeInfo.value.touchendX - swipeInfo.value.touchstartX;
return Math.max(0, Math.min(deltaX, 100));
}); });
const recipeList = computed<RecipeSummary[]>(() => { const recipeList = computed<RecipeSummary[]>(() => {