The history stack has been global since the Android back gesture landed -- every navigation is an entry and `popstate` restores any of them in either direction. What the report describes as "back is tab-scoped" is that the only way back was a detail view's own button, which leaves the screen with the view it belongs to: click over to Tracks and the album you were reading is still one entry away with nothing on screen saying so. `<nav-history>` is that affordance, plus `nav.back` / `nav.forward` on Alt+Left / Alt+Right -- the browser's own combination, and clear of the bare arrows that seek, since a binding matches on its full canonical string. Forward is not back negated, which is why the old `pushedEntries` counter is gone rather than extended: `popstate` carries no direction and fires identically both ways, so one counter decremented on every pop reads a forward as a second back. Each entry carries its index and the shell keeps the current one and a high-water mark, which also survives a jump of more than one. The buttons dispatch the events the rest of the app already dispatches rather than calling `history` themselves -- the shell owns the guard that stops a press at the root leaving the app, and a second caller reaching for history is how the old `navStack` came to disagree with the platform. Below 900px the control stands down: the top bar is what runs out of room first below that, and nothing becomes unreachable -- the shortcuts are global at every width and the phone has the platform's gesture. Closes #6
41 lines
934 B
TypeScript
41 lines
934 B
TypeScript
import type {
|
|
ReactiveController,
|
|
ReactiveControllerHost,
|
|
} from 'lit';
|
|
import { historyStore, type HistoryDepth } from '../history-store';
|
|
|
|
/**
|
|
* HistoryController connects a Lit component to the HistoryStore.
|
|
*
|
|
* Usage in a component:
|
|
*
|
|
* private historyCtrl = new HistoryController(this);
|
|
*
|
|
* render() {
|
|
* const { canBack } = this.historyCtrl.depth;
|
|
* }
|
|
*/
|
|
export class HistoryController implements ReactiveController {
|
|
private host: ReactiveControllerHost;
|
|
private unsubscribe?: () => void;
|
|
|
|
constructor(host: ReactiveControllerHost) {
|
|
this.host = host;
|
|
host.addController(this);
|
|
}
|
|
|
|
hostConnected(): void {
|
|
this.unsubscribe = historyStore.subscribe(() => {
|
|
this.host.requestUpdate();
|
|
});
|
|
}
|
|
|
|
hostDisconnected(): void {
|
|
this.unsubscribe?.();
|
|
}
|
|
|
|
get depth(): HistoryDepth {
|
|
return historyStore.get();
|
|
}
|
|
}
|