Skip to content

Commit b940213

Browse files
authored
Use switch widget instead of dismiss (#8075)
* Use switch widget instead of dismiss * Fix hacks * Fix tests * Remove dismiss widget
1 parent a87dc2a commit b940213

File tree

6 files changed

+136
-123
lines changed

6 files changed

+136
-123
lines changed

app/lib/frontend/templates/views/shared/layout.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,11 @@ d.Node pageLayoutNode({
201201
d.div(
202202
classes: ['dismisser'],
203203
attributes: {
204-
'data-widget': 'dismiss',
205-
'data-dismiss-target': '.announcement-banner',
206-
'data-dismiss-message-id': announcementBannerHash,
204+
'data-widget': 'switch',
205+
'data-switch-target': '.announcement-banner',
206+
'data-switch-enabled': 'dismissed',
207+
'data-switch-initial-state': 'false',
208+
'data-switch-state-id': announcementBannerHash,
207209
},
208210
text: 'x',
209211
),

pkg/web_app/lib/src/widget/dismiss/widget.dart

Lines changed: 0 additions & 117 deletions
This file was deleted.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:js_interop';
6+
7+
import 'package:collection/collection.dart';
8+
import 'package:web/web.dart';
9+
10+
import '../../web_util.dart';
11+
12+
/// Create a switch widget on [element].
13+
///
14+
/// A `data-switch-target` is required, this must be a CSS selector for the
15+
/// element(s) that are affected when this widget is clicked.
16+
///
17+
/// The optional `data-switch-initial-state` property may be used to provide an
18+
/// initial state. The initial state is used if state can be loaded from
19+
/// `localStorage` (because there is none). If not provided, initial state is
20+
/// derived from document state.
21+
///
22+
/// The optional `data-switch-state-id` property may be used to provide an
23+
/// identifier for the sttae of this switch in `localStorage`. If supplied state
24+
/// will be sync'ed across windows.
25+
///
26+
/// The optional `data-switch-enabled` property may be used to specify a space
27+
/// separated list of classes to be applied to `data-switch-target` when the
28+
/// switch is enabled.
29+
///
30+
/// The optional `data-switch-disabled` property may be used to specify a space
31+
/// separated list of classes to be applied to `data-switch-target` when the
32+
/// switch is disabled.
33+
void create(HTMLElement element, Map<String, String> options) {
34+
final target = options['target'];
35+
if (target == null) {
36+
throw UnsupportedError('data-switch-target required');
37+
}
38+
final initialState_ = options['initial-state'];
39+
final stateId = options['state-id'];
40+
final enabledClassList = (options['enabled'] ?? '')
41+
.split(' ')
42+
.where((s) => s.isNotEmpty)
43+
.toSet()
44+
.toList();
45+
final disabledClassList = (options['disabled'] ?? '')
46+
.split(' ')
47+
.where((s) => s.isNotEmpty)
48+
.toSet()
49+
.toList();
50+
51+
void applyState(bool enabled) {
52+
for (final e in document.querySelectorAll(target).toList()) {
53+
if (e.isA<HTMLElement>()) {
54+
final element = e as HTMLHtmlElement;
55+
if (enabled) {
56+
for (final c in enabledClassList) {
57+
element.classList.add(c);
58+
}
59+
for (final c in disabledClassList) {
60+
element.classList.remove(c);
61+
}
62+
} else {
63+
for (final c in enabledClassList) {
64+
element.classList.remove(c);
65+
}
66+
for (final c in disabledClassList) {
67+
element.classList.add(c);
68+
}
69+
}
70+
}
71+
}
72+
}
73+
74+
bool? initialState;
75+
if (stateId != null) {
76+
void onStorage(StorageEvent e) {
77+
if (e.key == 'switch-$stateId' && e.storageArea == window.localStorage) {
78+
applyState(e.newValue == 'true');
79+
}
80+
}
81+
82+
window.addEventListener('storage', onStorage.toJS);
83+
final state = window.localStorage.getItem('switch-$stateId');
84+
if (state != null) {
85+
initialState = state == 'true';
86+
}
87+
}
88+
89+
// Set initialState, if present
90+
if (initialState_ != null) {
91+
initialState ??= initialState_ == 'true';
92+
}
93+
// If there are no classes to be applied, this is weird, but then we can't
94+
// infer an initial state.
95+
if (enabledClassList.isEmpty && disabledClassList.isEmpty) {
96+
initialState ??= false;
97+
}
98+
// Infer initial state from document state, unless loaded from localStorage
99+
initialState ??= document
100+
.querySelectorAll(target)
101+
.toList()
102+
.where((e) => e.isA<HTMLElement>())
103+
.map((e) => e as HTMLElement)
104+
.every(
105+
(e) =>
106+
enabledClassList.every((c) => e.classList.contains(c)) &&
107+
disabledClassList.none((c) => e.classList.contains(c)),
108+
);
109+
110+
applyState(initialState);
111+
var state = initialState;
112+
113+
void onClick(MouseEvent e) {
114+
if (e.defaultPrevented) {
115+
return;
116+
}
117+
e.preventDefault();
118+
119+
state = !state;
120+
applyState(state);
121+
if (stateId != null) {
122+
window.localStorage.setItem('switch-$stateId', state.toString());
123+
}
124+
}
125+
126+
element.addEventListener('click', onClick.toJS);
127+
}

pkg/web_app/lib/src/widget/widget.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import 'package:web/web.dart';
1010

1111
import '../web_util.dart';
1212
import 'completion/widget.dart' deferred as completion;
13-
import 'dismiss/widget.dart' deferred as dismiss;
13+
import 'switch/widget.dart' deferred as switch_;
1414

1515
/// Function to create an instance of the widget given an element and options.
1616
///
@@ -32,7 +32,7 @@ typedef _WidgetLoaderFn = FutureOr<_WidgetFn> Function();
3232
/// Map from widget name to widget loader
3333
final _widgets = <String, _WidgetLoaderFn>{
3434
'completion': () => completion.loadLibrary().then((_) => completion.create),
35-
'dismiss': () => dismiss.loadLibrary().then((_) => dismiss.create),
35+
'switch': () => switch_.loadLibrary().then((_) => switch_.create),
3636
};
3737

3838
Future<_WidgetFn> _noSuchWidget() async =>

pkg/web_app/test/deferred_import_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ void main() {
3737
],
3838
'completion/': [],
3939
'dismiss/': [],
40+
'switch/': [],
4041
};
4142

4243
for (final file in files) {

pkg/web_css/lib/src/_base.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ pre {
400400
}
401401

402402
&.dismissed {
403-
display: none;
403+
visibility: hidden;
404404
}
405405

406406
z-index: 0;

0 commit comments

Comments
 (0)