@@ -38,43 +38,55 @@ void _setEventForDetailMetadataToggle() {
3838 double ? origX, origY;
3939
4040 var isVisible = false ;
41- // ignore: cancel_subscriptions
42- StreamSubscription <void >? stateSubscription;
4341 final titleElem = document.head? .querySelector ('title' ) as HTMLElement ? ;
4442 final currentTitle = titleElem? .innerText.trim ();
4543 final currentUrl = window.location.toString ();
44+
45+ Future <void > applyVisibility (bool visible) async {
46+ if (visible == isVisible) return ;
47+ isVisible = visible;
48+
49+ document.querySelector ('.detail-wrapper' )? .classList.toggle ('-active' );
50+ document.querySelector ('.detail-metadata' )? .classList.toggle ('-active' );
51+ await window.animationFrame;
52+ if (visible) {
53+ // store scroll position and scroll to the top
54+ origX = window.scrollX;
55+ origY = window.scrollY;
56+ window.scrollTo (0. toJS, 0 );
57+ } else {
58+ // restore scroll position
59+ window.scrollTo ((origX ?? 0 ).toJS, origY ?? 0 );
60+ origX = null ;
61+ origY = null ;
62+ }
63+ }
64+
65+ // Registered eagerly, not lazily on the first click, so a back-navigation
66+ // is never missed regardless of how long the open/close animation takes.
67+ window.onPopState.listen ((event) async {
68+ final state = event.state.dartify ();
69+ // only react on events that are relevant to this component
70+ if (state is Map &&
71+ state['type' ] == 'detail-metadata' &&
72+ state['url' ] == currentUrl) {
73+ await applyVisibility (state['visible' ] == true );
74+ }
75+ });
76+
4677 document.querySelectorAll ('.detail-metadata-toggle' ).toElementList ().forEach ((
4778 e,
4879 ) {
49- e.onClick.listen ((_) async {
50- Future <void > toggle () async {
51- isVisible = ! isVisible;
52-
53- document.querySelector ('.detail-wrapper' )? .classList.toggle ('-active' );
54- document.querySelector ('.detail-metadata' )? .classList.toggle ('-active' );
55- await window.animationFrame;
56- if (origX == null ) {
57- // store scroll position and scroll to the top
58- origX = window.scrollX;
59- origY = window.scrollY;
60- window.scrollTo (0. toJS, 0 );
61- } else {
62- // restore scroll position
63- window.scrollTo (origX! .toJS, origY! );
64- origX = null ;
65- }
66- }
67-
80+ e.onClick.listen ((_) {
6881 // when activating, set the current state to hide the view, and the next
69- // state to activate the view
82+ // state to activate the view
7083 if (! isVisible) {
71- await toggle ();
7284 window.history.replaceState (
7385 {
7486 'type' : 'detail-metadata' ,
7587 'url' : currentUrl,
7688 'visible' : false ,
77- }.toJSBox ,
89+ }.jsify () ,
7890 '' ,
7991 null ,
8092 );
@@ -83,27 +95,14 @@ void _setEventForDetailMetadataToggle() {
8395 'type' : 'detail-metadata' ,
8496 'url' : currentUrl,
8597 'visible' : true ,
86- }.toJSBox ,
98+ }.jsify () ,
8799 currentTitle ?? '' ,
88100 null ,
89101 );
102+ unawaited (applyVisibility (true ));
90103 } else {
91104 window.history.back ();
92105 }
93-
94- // only listen to state events after the first initialization
95- stateSubscription ?? = window.onPopState.listen ((event) async {
96- final state = event.state is Map ? event.state as Map : null ;
97- // only react on events that are relevant to this component
98- if (state is Map &&
99- state['type' ] == 'detail-metadata' &&
100- state['url' ] == currentUrl) {
101- final shouldBeVisible = state['visible' ] == true ;
102- if (shouldBeVisible != isVisible) {
103- await toggle ();
104- }
105- }
106- });
107106 });
108107 });
109108}
0 commit comments