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);
|
window.addEventListener("hashchange", onHash);
|
||||||
return () => window.removeEventListener("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 initDone = /* @__PURE__ */ state(false);
|
||||||
let geoError = /* @__PURE__ */ state("");
|
let geoError = /* @__PURE__ */ state("");
|
||||||
user_effect(() => {
|
user_effect(() => {
|
||||||
@ -8509,6 +8529,8 @@ function App($$anchor, $$props) {
|
|||||||
delegated("click", button_12, () => setView("daily"));
|
delegated("click", button_12, () => setView("daily"));
|
||||||
delegated("click", button_13, () => setView("detail"));
|
delegated("click", button_13, () => setView("detail"));
|
||||||
delegated("click", button_14, () => setView("radar"));
|
delegated("click", button_14, () => setView("radar"));
|
||||||
|
delegated("touchstart", div_10, onSwipeStart, void 0, true);
|
||||||
|
delegated("touchend", div_10, onSwipeEnd);
|
||||||
append($$anchor, fragment);
|
append($$anchor, fragment);
|
||||||
};
|
};
|
||||||
if_block(node_8, ($$render) => {
|
if_block(node_8, ($$render) => {
|
||||||
@ -8547,7 +8569,11 @@ function App($$anchor, $$props) {
|
|||||||
append($$anchor, div);
|
append($$anchor, div);
|
||||||
pop();
|
pop();
|
||||||
}
|
}
|
||||||
delegate(["click"]);
|
delegate([
|
||||||
|
"click",
|
||||||
|
"touchstart",
|
||||||
|
"touchend"
|
||||||
|
]);
|
||||||
mount(App, { target: document.getElementById("app") });
|
mount(App, { target: document.getElementById("app") });
|
||||||
//#endregion</script>
|
//#endregion</script>
|
||||||
<style rel="stylesheet" crossorigin>/* ===== CSS Reset & Base ===== */
|
<style rel="stylesheet" crossorigin>/* ===== CSS Reset & Base ===== */
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
import NotificationBell from './components/NotificationBell.svelte'
|
import NotificationBell from './components/NotificationBell.svelte'
|
||||||
import SettingsDialog from './components/SettingsDialog.svelte'
|
import SettingsDialog from './components/SettingsDialog.svelte'
|
||||||
import AddLocationDialog from './components/AddLocationDialog.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
|
// Hash-routable tab: initialize from the URL hash so a shared link like
|
||||||
// #/radar opens straight to that tab.
|
// #/radar opens straight to that tab.
|
||||||
@ -36,6 +36,33 @@
|
|||||||
return () => window.removeEventListener('hashchange', onHash)
|
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 initDone = $state(false)
|
||||||
let geoError = $state('')
|
let geoError = $state('')
|
||||||
|
|
||||||
@ -244,7 +271,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content area -->
|
<!-- Content area -->
|
||||||
<div class="content-area">
|
<div class="content-area" ontouchstart={onSwipeStart} ontouchend={onSwipeEnd}>
|
||||||
{#if viewMode === 'current'}
|
{#if viewMode === 'current'}
|
||||||
<div class="animate-fade-in">
|
<div class="animate-fade-in">
|
||||||
<CurrentWeather />
|
<CurrentWeather />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user