Skip to content

Commit 7f2d4a0

Browse files
authored
Fix mobile metadata back-button getting stuck (regression of dart-lang#7428) (dart-lang#9481)
1 parent 8d84356 commit 7f2d4a0

1 file changed

Lines changed: 38 additions & 39 deletions

File tree

pkg/web_app/lib/src/mobile_nav.dart

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)