rspace-online/shared/view-history.ts

59 lines
1.6 KiB
TypeScript

/**
* ViewHistory — lightweight in-app navigation stack for rApps.
*
* Each rApp with hierarchical views instantiates one, calls push()
* on forward navigation, and back() from the back button. Replaces
* hardcoded data-back targets with a proper history stack.
*/
export interface ViewEntry<V extends string> {
view: V;
context?: Record<string, unknown>;
}
const MAX_DEPTH = 20;
export class ViewHistory<V extends string> {
private stack: ViewEntry<V>[] = [];
private root: V;
constructor(rootView: V) {
this.root = rootView;
}
/** Record a forward navigation. Skips if top of stack is same view+context. */
push(view: V, context?: Record<string, unknown>): void {
const top = this.stack[this.stack.length - 1];
if (top && top.view === view) return; // skip duplicate
this.stack.push({ view, context });
if (this.stack.length > MAX_DEPTH) this.stack.shift();
}
/** Pop and return the previous entry, or null if at root. */
back(): ViewEntry<V> | null {
if (this.stack.length <= 1) {
this.stack = [];
return { view: this.root };
}
this.stack.pop(); // remove current
return this.stack[this.stack.length - 1] ?? { view: this.root };
}
/** True when there's history to go back to. */
get canGoBack(): boolean {
return this.stack.length > 1;
}
/** Peek at the previous entry without popping. */
peekBack(): ViewEntry<V> | null {
if (this.stack.length <= 1) return { view: this.root };
return this.stack[this.stack.length - 2] ?? null;
}
/** Clear the stack and reset to a root view (e.g. on space switch). */
reset(rootView?: V): void {
if (rootView !== undefined) this.root = rootView;
this.stack = [];
}
}