Restore feed scroll position on Back; enlarge the detail back button
- The dex feed remembers scrollY on teardown (ui.feedScroll) and restores it on re-mount, so viewing a Pokémon and going back lands where you were. Reset to top when the filter, sort, direction or game changes. - Router no longer force-scrolls after mount; it scrolls to top on the loading placeholder and each view manages its own position (detail pages land at top, feed and search restore). - .phero__back is now a rounded pill with a real 40px tap target. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
0d95f9c93a
commit
b0e3655dbf
@ -45,6 +45,9 @@ export function initRouter(host) {
|
|||||||
|
|
||||||
const pending = el('div', { class: 'view view--loading' }, 'Loading…');
|
const pending = el('div', { class: 'view view--loading' }, 'Loading…');
|
||||||
host.replaceChildren(pending);
|
host.replaceChildren(pending);
|
||||||
|
// Views manage their own scroll: detail pages jump to the top, the feed
|
||||||
|
// and search restore where the user left off.
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const node = await route.view(match);
|
const node = await route.view(match);
|
||||||
@ -64,7 +67,6 @@ export function initRouter(host) {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rerenderCurrent = render;
|
rerenderCurrent = render;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export const ui = createStore('pdx.ui', {
|
|||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
searchScroll: 0,
|
searchScroll: 0,
|
||||||
searchTab: 'pokemon',
|
searchTab: 'pokemon',
|
||||||
|
feedScroll: 0,
|
||||||
sort: 'dex',
|
sort: 'dex',
|
||||||
sortDesc: false,
|
sortDesc: false,
|
||||||
filter: 'all',
|
filter: 'all',
|
||||||
|
|||||||
@ -761,6 +761,21 @@
|
|||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
.phero__back {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex: none;
|
||||||
|
padding: 8px 14px 8px 11px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
.phero__back:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.32);
|
||||||
|
}
|
||||||
.phero__num {
|
.phero__num {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|||||||
@ -108,6 +108,7 @@ export async function DexGrid() {
|
|||||||
ui.set({ filter: f.key });
|
ui.set({ filter: f.key });
|
||||||
chipEls.forEach((c, i) => c.btn.classList.toggle('is-active', FILTERS[i].key === f.key));
|
chipEls.forEach((c, i) => c.btn.classList.toggle('is-active', FILTERS[i].key === f.key));
|
||||||
paintGrid();
|
paintGrid();
|
||||||
|
resetScroll();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
el('span', {}, f.label),
|
el('span', {}, f.label),
|
||||||
@ -127,6 +128,7 @@ export async function DexGrid() {
|
|||||||
ui.set({ sort: sortKey, sortDesc });
|
ui.set({ sort: sortKey, sortDesc });
|
||||||
syncDirBtn();
|
syncDirBtn();
|
||||||
paintGrid();
|
paintGrid();
|
||||||
|
resetScroll();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...SORTS.map((s) => el('option', { value: s.key, selected: s.key === sortKey }, s.label)),
|
...SORTS.map((s) => el('option', { value: s.key, selected: s.key === sortKey }, s.label)),
|
||||||
@ -140,6 +142,7 @@ export async function DexGrid() {
|
|||||||
ui.set({ sortDesc });
|
ui.set({ sortDesc });
|
||||||
syncDirBtn();
|
syncDirBtn();
|
||||||
paintGrid();
|
paintGrid();
|
||||||
|
resetScroll();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
function syncDirBtn() {
|
function syncDirBtn() {
|
||||||
@ -148,6 +151,12 @@ export async function DexGrid() {
|
|||||||
}
|
}
|
||||||
syncDirBtn();
|
syncDirBtn();
|
||||||
|
|
||||||
|
// Changing the list order/contents makes the old scroll offset meaningless.
|
||||||
|
function resetScroll() {
|
||||||
|
ui.set({ feedScroll: 0 });
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
view.append(
|
view.append(
|
||||||
el(
|
el(
|
||||||
'header',
|
'header',
|
||||||
@ -262,15 +271,25 @@ export async function DexGrid() {
|
|||||||
paintGrid();
|
paintGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mountedVG = settings.get().versionGroup;
|
||||||
rebuild();
|
rebuild();
|
||||||
|
// Return to where the user last scrolled (e.g. after viewing a Pokémon).
|
||||||
|
requestAnimationFrame(() => window.scrollTo(0, ui.get().feedScroll || 0));
|
||||||
|
|
||||||
const offSettings = settings.subscribe(rebuild);
|
const offSettings = settings.subscribe((s) => {
|
||||||
|
if (s.versionGroup !== mountedVG) {
|
||||||
|
mountedVG = s.versionGroup;
|
||||||
|
ui.set({ feedScroll: 0 });
|
||||||
|
}
|
||||||
|
rebuild();
|
||||||
|
});
|
||||||
const offSelection = selection.subscribe(() => {
|
const offSelection = selection.subscribe(() => {
|
||||||
refreshMeta();
|
refreshMeta();
|
||||||
if (filterKey !== 'all' && filterKey !== 'legendary') paintGrid();
|
if (filterKey !== 'all' && filterKey !== 'legendary') paintGrid();
|
||||||
else if (sortKey === 'caught') paintGrid();
|
else if (sortKey === 'caught') paintGrid();
|
||||||
});
|
});
|
||||||
onTeardown(view, () => {
|
onTeardown(view, () => {
|
||||||
|
ui.set({ feedScroll: window.scrollY });
|
||||||
offSettings();
|
offSettings();
|
||||||
offSelection();
|
offSelection();
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user