fix: edge swipe on iOS safari

This commit is contained in:
arabcoders 2026-04-24 23:27:53 +03:00
parent 30a0c30d94
commit 73410f2acd
3 changed files with 133 additions and 6 deletions

View file

@ -534,6 +534,7 @@ import { useMediaQuery } from '~/composables/useMediaQuery';
import type { toastPosition } from '~/composables/useNotification';
import LimitsPage from '~/components/LimitsPage.vue';
import { formatPageTitle, parse_api_response, request, syncOpacity, uri } from '~/utils';
import { getSidebarSwipeMode } from '~/utils/sidebarSwipe';
import type { YTDLPOption } from '~/types/ytdlp';
import { useDialog } from '~/composables/useDialog';
import Dialog from '~/components/Dialog.vue';
@ -557,7 +558,6 @@ type SidebarSection = {
type ColorModePreference = 'system' | 'light' | 'dark';
type SwipeMode = 'open' | 'close';
const MOBILE_SIDEBAR_EDGE_WIDTH = 32;
const MOBILE_SIDEBAR_MIN_SWIPE_DISTANCE = 64;
const socket = useAppSocket();
@ -655,11 +655,11 @@ const handleSwipeStart = (event: TouchEvent): void => {
return;
}
const swipeMode: SwipeMode | null = showSidebar.value
? 'close'
: touch.clientX <= MOBILE_SIDEBAR_EDGE_WIDTH
? 'open'
: null;
const swipeMode: SwipeMode | null = getSidebarSwipeMode(
showSidebar.value,
touch.clientX,
navigator,
);
if (!swipeMode) {
resetSwipe();

View file

@ -0,0 +1,54 @@
type SidebarSwipeMode = 'open' | 'close';
type NavigatorLike = {
userAgent?: string;
platform?: string;
maxTouchPoints?: number;
};
const MOBILE_SIDEBAR_EDGE_WIDTH = 32;
const IOS_NAVIGATION_EDGE_WIDTH = 24;
const isAppleMobileTouchNavigator = (nav?: NavigatorLike): boolean => {
if (!nav) {
return false;
}
const userAgent = nav.userAgent ?? '';
const platform = nav.platform ?? '';
const maxTouchPoints = Number(nav.maxTouchPoints ?? 0);
const isiPhoneLike = /(iPhone|iPod|iPad)/i.test(userAgent);
const isiPadDesktopMode = platform === 'MacIntel' && maxTouchPoints > 1;
return /AppleWebKit/i.test(userAgent) && (isiPhoneLike || isiPadDesktopMode);
};
const canStartSidebarOpenSwipe = (
touchX: number,
nav?: NavigatorLike,
edgeWidth: number = MOBILE_SIDEBAR_EDGE_WIDTH,
): boolean => {
const reservedEdge = isAppleMobileTouchNavigator(nav) ? IOS_NAVIGATION_EDGE_WIDTH : 0;
return touchX > reservedEdge && touchX <= reservedEdge + edgeWidth;
};
const getSidebarSwipeMode = (
sidebarOpen: boolean,
touchX: number,
nav?: NavigatorLike,
): SidebarSwipeMode | null => {
if (sidebarOpen) {
return 'close';
}
return canStartSidebarOpenSwipe(touchX, nav) ? 'open' : null;
};
export {
MOBILE_SIDEBAR_EDGE_WIDTH,
IOS_NAVIGATION_EDGE_WIDTH,
canStartSidebarOpenSwipe,
getSidebarSwipeMode,
isAppleMobileTouchNavigator,
};
export type { NavigatorLike, SidebarSwipeMode };

View file

@ -0,0 +1,73 @@
import { describe, expect, it } from 'bun:test';
import {
IOS_NAVIGATION_EDGE_WIDTH,
MOBILE_SIDEBAR_EDGE_WIDTH,
canStartSidebarOpenSwipe,
getSidebarSwipeMode,
isAppleMobileTouchNavigator,
} from '~/utils/sidebarSwipe';
describe('sidebarSwipe', () => {
it('detects apple mobile webkit navigators', () => {
expect(
isAppleMobileTouchNavigator({
userAgent:
'Mozilla/5.0 (iPhone; CPU iPhone OS 18_4 like Mac OS X) AppleWebKit/605.1.15 Version/18.4 Mobile/15E148 Safari/604.1',
platform: 'iPhone',
maxTouchPoints: 5,
}),
).toBe(true);
expect(
isAppleMobileTouchNavigator({
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 Version/18.4 Mobile/15E148 Safari/604.1',
platform: 'MacIntel',
maxTouchPoints: 5,
}),
).toBe(true);
expect(
isAppleMobileTouchNavigator({
userAgent:
'Mozilla/5.0 (Linux; Android 15) AppleWebKit/537.36 Chrome/147.0.0.0 Mobile Safari/537.36',
platform: 'Linux armv8l',
maxTouchPoints: 5,
}),
).toBe(false);
});
it('reserves the ios navigation edge and starts the sidebar swipe just inside it', () => {
const nav = {
userAgent:
'Mozilla/5.0 (iPhone; CPU iPhone OS 18_4 like Mac OS X) AppleWebKit/605.1.15 Version/18.4 Mobile/15E148 Safari/604.1',
platform: 'iPhone',
maxTouchPoints: 5,
};
expect(canStartSidebarOpenSwipe(IOS_NAVIGATION_EDGE_WIDTH, nav)).toBe(false);
expect(canStartSidebarOpenSwipe(IOS_NAVIGATION_EDGE_WIDTH + 1, nav)).toBe(true);
expect(canStartSidebarOpenSwipe(IOS_NAVIGATION_EDGE_WIDTH + MOBILE_SIDEBAR_EDGE_WIDTH, nav)).toBe(true);
expect(canStartSidebarOpenSwipe(IOS_NAVIGATION_EDGE_WIDTH + MOBILE_SIDEBAR_EDGE_WIDTH + 1, nav)).toBe(
false,
);
});
it('keeps the original left-edge open band on non-apple mobile browsers', () => {
const nav = {
userAgent:
'Mozilla/5.0 (Linux; Android 15) AppleWebKit/537.36 Chrome/147.0.0.0 Mobile Safari/537.36',
platform: 'Linux armv8l',
maxTouchPoints: 5,
};
expect(canStartSidebarOpenSwipe(1, nav)).toBe(true);
expect(canStartSidebarOpenSwipe(MOBILE_SIDEBAR_EDGE_WIDTH, nav)).toBe(true);
expect(canStartSidebarOpenSwipe(MOBILE_SIDEBAR_EDGE_WIDTH + 1, nav)).toBe(false);
});
it('returns close while the sidebar is already open', () => {
expect(getSidebarSwipeMode(true, 4)).toBe('close');
});
});