From a656447aa58440e9a41fa39e1c5d6ff3d859b64e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Nov 2023 08:56:48 +0900
Subject: enhance(frontend): improve pull to refresh

---
 .../frontend/src/components/MkPullToRefresh.vue    | 72 +++++++++++++---------
 1 file changed, 43 insertions(+), 29 deletions(-)

(limited to 'packages/frontend/src/components/MkPullToRefresh.vue')

diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index f3f5660143..64c4d6e91d 100644
--- a/packages/frontend/src/components/MkPullToRefresh.vue
+++ b/packages/frontend/src/components/MkPullToRefresh.vue
@@ -23,9 +23,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted } from 'vue';
+import { onMounted, onUnmounted, watch } from 'vue';
 import { deviceKind } from '@/scripts/device-kind.js';
 import { i18n } from '@/i18n.js';
+import { getScrollContainer } from '@/scripts/scroll.js';
 
 const SCROLL_STOP = 10;
 const MAX_PULL_DISTANCE = Infinity;
@@ -57,18 +58,6 @@ const emit = defineEmits<{
 	(ev: 'refresh'): void;
 }>();
 
-function getScrollableParentElement(node) {
-	if (node == null) {
-		return null;
-	}
-
-	if (node.scrollHeight > node.clientHeight) {
-		return node;
-	} else {
-		return getScrollableParentElement(node.parentNode);
-	}
-}
-
 function getScreenY(event) {
 	if (supportPointerDesktop) {
 		return event.screenY;
@@ -138,12 +127,9 @@ function moveEnd() {
 	}
 }
 
-function moving(event) {
+function moving(event: TouchEvent | PointerEvent) {
 	if (!isPullStart || isRefreshing || disabled) return;
 
-	if (!scrollEl) {
-		scrollEl = getScrollableParentElement(rootEl);
-	}
 	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance)) {
 		pullDistance = 0;
 		isPullEnd = false;
@@ -159,6 +145,10 @@ function moving(event) {
 	const moveHeight = moveScreenY - startScreenY!;
 	pullDistance = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
 
+	if (pullDistance > 0) {
+		if (event.cancelable) event.preventDefault();
+	}
+
 	isPullEnd = pullDistance >= FIRE_THRESHOLD;
 }
 
@@ -178,24 +168,48 @@ function setDisabled(value) {
 	disabled = value;
 }
 
-onMounted(() => {
-	// マウス操作でpull to refreshするのは不便そう
-	//supportPointerDesktop = !!window.PointerEvent && deviceKind === 'desktop';
+function onScrollContainerScroll() {
+	const scrollPos = scrollEl!.scrollTop;
 
-	if (supportPointerDesktop) {
-		rootEl.addEventListener('pointerdown', moveStart);
-		// ポインターの場合、ポップアップ系の動作をするとdownだけ発火されてupが発火されないため
-		window.addEventListener('pointerup', moveEnd);
-		rootEl.addEventListener('pointermove', moving, { passive: true });
+	// When at the top of the page, disable vertical overscroll so passive touch listeners can take over.
+	if (scrollPos === 0) {
+		scrollEl!.style.touchAction = 'pan-x pan-down pinch-zoom';
+		registerEventListenersForReadyToPull();
 	} else {
-		rootEl.addEventListener('touchstart', moveStart);
-		rootEl.addEventListener('touchend', moveEnd);
-		rootEl.addEventListener('touchmove', moving, { passive: true });
+		scrollEl!.style.touchAction = 'auto';
+		unregisterEventListenersForReadyToPull();
 	}
+}
+
+function registerEventListenersForReadyToPull() {
+	if (rootEl == null) return;
+	rootEl.addEventListener('touchstart', moveStart, { passive: true });
+	rootEl.addEventListener('touchmove', moving, { passive: false }); // passive: falseにしないとpreventDefaultが使えない
+}
+
+function unregisterEventListenersForReadyToPull() {
+	if (rootEl == null) return;
+	rootEl.removeEventListener('touchstart', moveStart);
+	rootEl.removeEventListener('touchmove', moving);
+}
+
+onMounted(() => {
+	if (rootEl == null) return;
+
+	scrollEl = getScrollContainer(rootEl);
+	if (scrollEl == null) return;
+
+	scrollEl.addEventListener('scroll', onScrollContainerScroll, { passive: true });
+
+	rootEl.addEventListener('touchend', moveEnd, { passive: true });
+
+	registerEventListenersForReadyToPull();
 });
 
 onUnmounted(() => {
-	if (supportPointerDesktop) window.removeEventListener('pointerup', moveEnd);
+	if (scrollEl) scrollEl.removeEventListener('scroll', onScrollContainerScroll);
+
+	unregisterEventListenersForReadyToPull();
 });
 
 defineExpose({
-- 
cgit v1.2.3-freya

