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:
parent
36d3625d61
commit
cc6b573748
28
dist/index.html
vendored
28
dist/index.html
vendored
@ -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 ===== */
|
||||
|
||||
@ -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 />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user