Add swipe-to-switch between tabs on mobile

Touch swipe on the content area moves between tabs (left = next, right =
previous), gated to non-radar views so map pan gestures keep working.
Vertical scroll with |dx|<|dy| is untouched; 60px horizontal threshold.
This commit is contained in:
hermes-explorigin 2026-08-30 14:05:08 +00:00
parent 36d3625d61
commit cc6b573748
2 changed files with 56 additions and 3 deletions

28
dist/index.html vendored
View File

@ -8270,6 +8270,26 @@ function App($$anchor, $$props) {
window.addEventListener("hashchange", onHash);
return () => window.removeEventListener("hashchange", onHash);
});
const SWIPE_THRESHOLD = 60;
let touchAnchor = /* @__PURE__ */ state(null);
function onSwipeStart(e) {
if (get(viewMode) === "radar" || !e.touches || !e.touches[0]) return;
set(touchAnchor, {
x: e.touches[0].clientX,
y: e.touches[0].clientY
}, true);
}
function onSwipeEnd(e) {
if (get(viewMode) === "radar" || !get(touchAnchor) || !e.changedTouches || !e.changedTouches[0]) return;
const t = e.changedTouches[0];
const dx = t.clientX - get(touchAnchor).x;
const dy = t.clientY - get(touchAnchor).y;
set(touchAnchor, null);
if (Math.abs(dx) < SWIPE_THRESHOLD || Math.abs(dx) < Math.abs(dy)) return;
const idx = VALID_VIEWS.indexOf(get(viewMode));
const next = VALID_VIEWS[Math.min(Math.max(idx + (dx < 0 ? 1 : -1), 0), VALID_VIEWS.length - 1)];
if (next !== get(viewMode)) setView(next);
}
let initDone = /* @__PURE__ */ state(false);
let geoError = /* @__PURE__ */ state("");
user_effect(() => {
@ -8509,6 +8529,8 @@ function App($$anchor, $$props) {
delegated("click", button_12, () => setView("daily"));
delegated("click", button_13, () => setView("detail"));
delegated("click", button_14, () => setView("radar"));
delegated("touchstart", div_10, onSwipeStart, void 0, true);
delegated("touchend", div_10, onSwipeEnd);
append($$anchor, fragment);
};
if_block(node_8, ($$render) => {
@ -8547,7 +8569,11 @@ function App($$anchor, $$props) {
append($$anchor, div);
pop();
}
delegate(["click"]);
delegate([
"click",
"touchstart",
"touchend"
]);
mount(App, { target: document.getElementById("app") });
//#endregion</script>
<style rel="stylesheet" crossorigin>/* ===== CSS Reset & Base ===== */

View File

@ -10,7 +10,7 @@
import NotificationBell from './components/NotificationBell.svelte'
import SettingsDialog from './components/SettingsDialog.svelte'
import AddLocationDialog from './components/AddLocationDialog.svelte'
import { parseHash, viewToHash } from './lib/router.js'
import { parseHash, viewToHash, VALID_VIEWS } from './lib/router.js'
// Hash-routable tab: initialize from the URL hash so a shared link like
// #/radar opens straight to that tab.
@ -36,6 +36,33 @@
return () => window.removeEventListener('hashchange', onHash)
})
// --- Mobile swipe-to-switch tabs ---
// Horizontal swipe on the content area moves between tabs (touch only).
// Gated to non-radar views: the radar map handles its own pan gestures via
// pointer events, so a swipe there should pan the map, not switch tabs.
const SWIPE_THRESHOLD = 60 // px of horizontal travel before it counts as a swipe
let touchAnchor = $state(null)
function onSwipeStart(e) {
if (viewMode === 'radar' || !e.touches || !e.touches[0]) return
touchAnchor = { x: e.touches[0].clientX, y: e.touches[0].clientY }
}
function onSwipeEnd(e) {
if (viewMode === 'radar' || !touchAnchor || !e.changedTouches || !e.changedTouches[0]) return
const t = e.changedTouches[0]
const dx = t.clientX - touchAnchor.x
const dy = t.clientY - touchAnchor.y
touchAnchor = null
// Require a mostly-horizontal swipe past the threshold so vertical
// scrolling stays untouched.
if (Math.abs(dx) < SWIPE_THRESHOLD || Math.abs(dx) < Math.abs(dy)) return
const idx = VALID_VIEWS.indexOf(viewMode)
const step = dx < 0 ? 1 : -1 // left swipe = next tab, right = previous
const next = VALID_VIEWS[Math.min(Math.max(idx + step, 0), VALID_VIEWS.length - 1)]
if (next !== viewMode) setView(next)
}
let initDone = $state(false)
let geoError = $state('')
@ -244,7 +271,7 @@
</div>
<!-- Content area -->
<div class="content-area">
<div class="content-area" ontouchstart={onSwipeStart} ontouchend={onSwipeEnd}>
{#if viewMode === 'current'}
<div class="animate-fade-in">
<CurrentWeather />