1+ // // Subscribing can be enabled if we want reactivity
2+ // export type Subscriber<HistoryState> = (stack: ReadonlyArray<HistoryState>, pointer: number) => void;
3+ //
4+
5+ export type HistoryState = { hash : string , index : number , view ?: string }
6+
7+ class HistoryManager {
8+ private stack : HistoryState [ ] = [ ] ;
9+ // When pointer = -1, unset pointer; When pointer = -2, don't handle hashes
10+ private pointer = - 1 ;
11+ // private subscribers = new Set<Subscriber<HistoryState>>();
12+
13+ constructor ( ) {
14+ console . debug ( "HistoryManager: " , this ) ;
15+ }
16+
17+ // on navigation to a new page
18+ push ( state : HistoryState ) {
19+ if ( this . pointer < this . stack . length - 1 ) {
20+ this . stack . length = this . pointer + 1 ; // Replace branch
21+ }
22+ this . pointer = this . stack . length ;
23+ state . index = this . pointer ;
24+
25+ this . stack . push ( state ) ;
26+ this . APIpush ( ) ;
27+ // this.notify();
28+ }
29+
30+ replace ( state : HistoryState ) {
31+ state . index = this . pointer ;
32+ if ( this . pointer >= 0 ) {
33+ this . stack [ this . pointer ] = state ;
34+ } else {
35+ this . push ( state ) ;
36+ }
37+
38+ this . APIreplace ( ) ;
39+ // this.notify();
40+ }
41+
42+ back ( ) : HistoryState | undefined {
43+ if ( this . pointer > 0 ) {
44+ this . pointer -- ;
45+ // this.notify();
46+ this . APIreplace ( ) ;
47+ return this . current ;
48+ }
49+ return undefined ;
50+ }
51+
52+ forward ( ) : HistoryState | undefined {
53+ if ( this . pointer < this . stack . length - 1 ) {
54+ this . pointer ++ ;
55+ // this.notify();
56+ this . APIreplace ( ) ;
57+ return this . current ;
58+ }
59+ return undefined ;
60+ }
61+
62+ goto ( idx : number ) : HistoryState | undefined {
63+ if ( idx < 0 || idx >= this . stack . length ) {
64+ return undefined ;
65+ }
66+
67+ this . pointer = idx ;
68+ this . APIreplace ( ) ; // Make sure to handle our final detination
69+
70+
71+
72+ let recreatePointer = this . pointer ;
73+ // Don't handle hashes while recreating and navigating history
74+ this . pointer = - 2 ;
75+
76+ // Recreate lost history
77+ for ( ; recreatePointer + 1 < this . length - 1 ; recreatePointer -- ) {
78+ this . APIpush ( this . stack [ recreatePointer ] ) ;
79+ }
80+ // Navigate
81+ for ( let i = this . length - 1 ; i > idx ; i -- ) {
82+ history . back ( ) ;
83+ }
84+ for ( let i = 0 ; i < idx ; i ++ ) {
85+ history . forward ( ) ;
86+ }
87+
88+ this . pointer = idx ;
89+ // this.notify();
90+ return this . current ;
91+ }
92+
93+ get current ( ) : HistoryState | undefined {
94+ return this . stack [ this . pointer ] ?? { hash : "" , index : 0 } ;
95+ }
96+
97+ // read-only copy of the stack
98+ toArray ( ) : ReadonlyArray < HistoryState > {
99+ return this . stack . slice ( ) ;
100+ }
101+
102+ get length ( ) : number {
103+ return this . stack . length ;
104+ }
105+
106+ get index ( ) : number {
107+ return this . pointer ;
108+ }
109+
110+ clear ( ) {
111+ this . stack = [ ] ;
112+ this . pointer = - 1 ;
113+ // this.notify();
114+ }
115+
116+ private APIpush ( state : HistoryState = this . current ! ) : void {
117+ const view = state . view ?? state . hash ;
118+ history . pushState ( state , "" , new URL ( view , location . href ) ) ;
119+ }
120+
121+ private APIreplace ( state : HistoryState = this . current ! ) : void {
122+ const view = state . view ?? state . hash ;
123+ history . replaceState ( state , "" , new URL ( view , location . href ) ) ;
124+ }
125+
126+
127+
128+ // // subscribe to changes; returns an unsubscribe function
129+ // subscribe(fn: Subscriber<HistoryState>): () => void {
130+ // this.subscribers.add(fn);
131+ // // call immediately with snapshot
132+ // fn(this.toArray(), this.pointer);
133+ // return () => {
134+ // this.subscribers.delete(fn);
135+ // };
136+ //
137+
138+ // private notify() {
139+ // const ROstack = this.toArray();
140+ // const idx = this.pointer;
141+ // for (const subscriber of Array.from(this.subscribers)) {
142+ // try {
143+ // subscriber(ROstack, idx);
144+ // } catch (error) {
145+ // console.error(`History subscriber encountered error: ${error}`);
146+ // }
147+ // }
148+ // }
149+
150+ }
151+
152+ declare global {
153+ interface Window { __historyManager__ ?: HistoryManager }
154+ }
155+
156+
157+
158+ window . __historyManager__ = new HistoryManager ( ) ;
159+ export const historyManager = window . __historyManager__ ;
160+ export default historyManager ;
0 commit comments