@gjsify/adwaita-web 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/adwaita-web.css +2742 -263
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/elements/adw-about-dialog.d.ts +77 -0
- package/lib/types/elements/adw-action-row.d.ts +15 -0
- package/lib/types/elements/adw-alert-dialog.d.ts +80 -0
- package/lib/types/elements/adw-avatar.d.ts +9 -0
- package/lib/types/elements/adw-banner.d.ts +9 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +35 -0
- package/lib/types/elements/adw-button-content.d.ts +10 -0
- package/lib/types/elements/adw-button-row.d.ts +11 -0
- package/lib/types/elements/adw-button.d.ts +11 -0
- package/lib/types/elements/adw-carousel.d.ts +64 -0
- package/lib/types/elements/adw-clamp.d.ts +6 -0
- package/lib/types/elements/adw-combo-row.d.ts +3 -0
- package/lib/types/elements/adw-entry-row.d.ts +10 -0
- package/lib/types/elements/adw-entry.d.ts +11 -0
- package/lib/types/elements/adw-expander-row.d.ts +26 -0
- package/lib/types/elements/adw-header-bar.d.ts +4 -0
- package/lib/types/elements/adw-inline-view-switcher.d.ts +30 -0
- package/lib/types/elements/adw-navigation-split-view.d.ts +15 -0
- package/lib/types/elements/adw-navigation-view.d.ts +66 -0
- package/lib/types/elements/adw-password-entry-row.d.ts +16 -0
- package/lib/types/elements/adw-preferences-dialog.d.ts +36 -0
- package/lib/types/elements/adw-preferences-group.d.ts +8 -0
- package/lib/types/elements/adw-sidebar.d.ts +29 -0
- package/lib/types/elements/adw-spin-row.d.ts +3 -0
- package/lib/types/elements/adw-spinner.d.ts +6 -0
- package/lib/types/elements/adw-split-button.d.ts +18 -0
- package/lib/types/elements/adw-status-page.d.ts +11 -0
- package/lib/types/elements/adw-switch-row.d.ts +3 -0
- package/lib/types/elements/adw-tab-view.d.ts +24 -0
- package/lib/types/elements/adw-toast-overlay.d.ts +19 -4
- package/lib/types/elements/adw-toggle-group.d.ts +19 -0
- package/lib/types/elements/adw-toolbar-view.d.ts +13 -0
- package/lib/types/elements/adw-view-switcher.d.ts +29 -0
- package/lib/types/elements/adw-window-title.d.ts +9 -0
- package/lib/types/elements/adw-wrap-box.d.ts +13 -0
- package/lib/types/index.d.ts +29 -0
- package/lib/types/styles.generated.d.ts +1 -0
- package/package.json +5 -5
- package/scss/_about_dialog.scss +257 -0
- package/scss/_action_row.scss +65 -0
- package/scss/_alert_dialog.scss +151 -0
- package/scss/_avatar.scss +31 -0
- package/scss/_banner.scss +46 -0
- package/scss/_bottom_sheet.scss +113 -0
- package/scss/_button.scss +168 -0
- package/scss/_button_content.scss +48 -0
- package/scss/_button_row.scss +100 -0
- package/scss/_carousel.scss +130 -0
- package/scss/_clamp.scss +11 -0
- package/scss/_entry.scss +49 -0
- package/scss/_entry_row.scss +54 -0
- package/scss/_expander_row.scss +150 -0
- package/scss/_headerbar.scss +1 -1
- package/scss/_inline_view_switcher.scss +174 -0
- package/scss/_navigation_split_view.scss +48 -0
- package/scss/_navigation_view.scss +80 -0
- package/scss/_overlay_split_view.scss +19 -0
- package/scss/_password_entry_row.scss +67 -0
- package/scss/_preferences.scss +26 -3
- package/scss/_preferences_dialog.scss +154 -0
- package/scss/_row.scss +31 -0
- package/scss/_sidebar.scss +213 -0
- package/scss/_spin_row.scss +0 -1
- package/scss/_spinner.scss +28 -0
- package/scss/_split_button.scss +222 -0
- package/scss/_status_page.scss +40 -0
- package/scss/_switch_row.scss +0 -1
- package/scss/_tab_view.scss +193 -0
- package/scss/_theme.scss +12 -0
- package/scss/_toast.scss +122 -12
- package/scss/_toggle_group.scss +124 -0
- package/scss/_toolbar_view.scss +31 -0
- package/scss/_variables.scss +19 -0
- package/scss/_view_switcher.scss +133 -0
- package/scss/_window_title.scss +33 -0
- package/scss/_wrap_box.scss +20 -0
- package/scss/adwaita-skin.scss +38 -0
- package/src/elements/adw-about-dialog.ts +629 -0
- package/src/elements/adw-action-row.ts +82 -0
- package/src/elements/adw-alert-dialog.ts +385 -0
- package/src/elements/adw-avatar.ts +108 -0
- package/src/elements/adw-banner.ts +57 -0
- package/src/elements/adw-bottom-sheet.ts +205 -0
- package/src/elements/adw-button-content.ts +69 -0
- package/src/elements/adw-button-row.ts +75 -0
- package/src/elements/adw-button.ts +81 -0
- package/src/elements/adw-carousel.ts +321 -0
- package/src/elements/adw-clamp.ts +28 -0
- package/src/elements/adw-combo-row.ts +32 -20
- package/src/elements/adw-entry-row.ts +69 -0
- package/src/elements/adw-entry.ts +61 -0
- package/src/elements/adw-expander-row.ts +167 -0
- package/src/elements/adw-header-bar.ts +21 -8
- package/src/elements/adw-inline-view-switcher.ts +222 -0
- package/src/elements/adw-navigation-split-view.ts +89 -0
- package/src/elements/adw-navigation-view.ts +344 -0
- package/src/elements/adw-password-entry-row.ts +121 -0
- package/src/elements/adw-preferences-dialog.ts +240 -0
- package/src/elements/adw-preferences-group.ts +79 -12
- package/src/elements/adw-sidebar.ts +238 -0
- package/src/elements/adw-spin-row.ts +28 -14
- package/src/elements/adw-spinner.ts +31 -0
- package/src/elements/adw-split-button.ts +206 -0
- package/src/elements/adw-status-page.ts +66 -0
- package/src/elements/adw-switch-row.ts +29 -23
- package/src/elements/adw-tab-view.ts +184 -0
- package/src/elements/adw-toast-overlay.ts +98 -19
- package/src/elements/adw-toggle-group.ts +126 -0
- package/src/elements/adw-toolbar-view.ts +60 -0
- package/src/elements/adw-view-switcher.ts +200 -0
- package/src/elements/adw-window-title.ts +48 -0
- package/src/elements/adw-wrap-box.ts +139 -0
- package/src/index.js +1 -1
- package/src/index.ts +46 -3
- package/src/styles.generated.js +2976 -0
- package/src/styles2.generated.js +2976 -0
- package/style.css.d.ts +17 -4
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
// <adw-about-dialog> — The standard Adwaita "About" dialog (the web counterpart
|
|
2
|
+
// of Adw.AboutDialog). It is a modal AdwDialog (a full-cover scrim + a centred,
|
|
3
|
+
// scrollable floating sheet) hosting a navigation view: the main page shows the
|
|
4
|
+
// app icon, name, developer name and a version pill, with preference rows that
|
|
5
|
+
// navigate to the Details, Credits and Legal subpages.
|
|
6
|
+
//
|
|
7
|
+
// Like Adw.AboutDialog the dialog is presented imperatively — `present()` (or
|
|
8
|
+
// setting the `open` attribute) reveals it; Escape, a scrim click, the header
|
|
9
|
+
// close button, or `close()` dismisses it. The header title fades in once the
|
|
10
|
+
// main page is scrolled (mirroring AdwAboutDialog's update_headerbar_cb).
|
|
11
|
+
//
|
|
12
|
+
// Properties / attributes (all reflected, mirroring the AdwAboutDialog GObject
|
|
13
|
+
// properties of the same name, hyphenated):
|
|
14
|
+
// application-name the application's display name (shown as a title-1)
|
|
15
|
+
// application-icon a symbolic icon name (with or without -symbolic) — the
|
|
16
|
+
// large app icon; falls back to a generic glyph
|
|
17
|
+
// developer-name the developer / vendor line below the name
|
|
18
|
+
// version the version, shown as a clickable pill
|
|
19
|
+
// comments a longer description (shown on the Details page)
|
|
20
|
+
// website the application's website URL (a Website link row)
|
|
21
|
+
// support-url a support-forum URL (a Support Questions link row)
|
|
22
|
+
// issue-url an issue-tracker URL (a Report an Issue link row)
|
|
23
|
+
// copyright the copyright line (shown on the Legal page)
|
|
24
|
+
// license the license text/name (shown on the Legal page); when a
|
|
25
|
+
// `license-url` is also given the name becomes a link
|
|
26
|
+
// license-url a URL the license name links to
|
|
27
|
+
// open (boolean) whether the dialog is presented
|
|
28
|
+
//
|
|
29
|
+
// List-valued credits are set via properties (string arrays), mirroring the
|
|
30
|
+
// AdwAboutDialog `developers` / `designers` / `artists` / `documenters`
|
|
31
|
+
// properties — entries may be `Name`, `Name <email>` or `Name https://url`,
|
|
32
|
+
// matching the AdwAboutDialog credit syntax.
|
|
33
|
+
//
|
|
34
|
+
// Events (CustomEvent, bubbles):
|
|
35
|
+
// `notify::open` (detail = { open }) when the presented state changes —
|
|
36
|
+
// mirrors the Adw.Dialog `open` notification.
|
|
37
|
+
// `closed` when the dialog is dismissed — mirrors Adw.Dialog::closed.
|
|
38
|
+
// `activate-link` (detail = { uri }) when a link row is activated — mirrors
|
|
39
|
+
// AdwAboutDialog::activate-link (not cancelled here; the browser navigates).
|
|
40
|
+
//
|
|
41
|
+
// Reference: refs/libadwaita/src/adw-about-dialog.c (AdwAboutDialog behaviour)
|
|
42
|
+
// Reference: refs/libadwaita/src/adw-about-dialog.ui (navigation page tree)
|
|
43
|
+
// Reference: refs/libadwaita/src/adw-dialog.c (floating-sheet present/close)
|
|
44
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_dialogs.scss (floating sheet)
|
|
45
|
+
// Reference: refs/adwaita-web/adwaita-web/scss/_about_dialog.scss (web layout)
|
|
46
|
+
// Copyright (c) 2022-2024 GNOME Foundation Inc. / Purism SPC (libadwaita). LGPLv2.1+.
|
|
47
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
48
|
+
|
|
49
|
+
/** A credit person — a plain name, or a name paired with a link (email / URL). */
|
|
50
|
+
interface CreditEntry {
|
|
51
|
+
name: string;
|
|
52
|
+
uri: string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Parse an AdwAboutDialog credit string: `Name`, `Name <email>`, `Name https://…`. */
|
|
56
|
+
function parseCredit(raw: string): CreditEntry {
|
|
57
|
+
const trimmed = raw.trim();
|
|
58
|
+
// `Name <email-or-url>` — the bracketed part is the link target.
|
|
59
|
+
const angle = /^(.*?)\s*<([^>]+)>$/.exec(trimmed);
|
|
60
|
+
if (angle) {
|
|
61
|
+
const target = angle[2].trim();
|
|
62
|
+
const uri = target.includes('@') && !/^[a-z][a-z0-9+.-]*:/i.test(target) ? `mailto:${target}` : target;
|
|
63
|
+
return { name: angle[1].trim(), uri };
|
|
64
|
+
}
|
|
65
|
+
// `Name https://url` — a trailing bare URL is the link target.
|
|
66
|
+
const url = /^(.*?)\s+((?:https?|mailto):\S+)$/.exec(trimmed);
|
|
67
|
+
if (url) {
|
|
68
|
+
return { name: url[1].trim(), uri: url[2].trim() };
|
|
69
|
+
}
|
|
70
|
+
return { name: trimmed, uri: null };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class AdwAboutDialog extends HTMLElement {
|
|
74
|
+
private _initialized = false;
|
|
75
|
+
|
|
76
|
+
// Layout nodes (rebuilt from attributes on each render).
|
|
77
|
+
private _scrimEl!: HTMLDivElement;
|
|
78
|
+
private _sheetEl!: HTMLDivElement;
|
|
79
|
+
private _navView!: HTMLElement;
|
|
80
|
+
private _scrollEl!: HTMLDivElement;
|
|
81
|
+
private _mainToolbarEl!: HTMLDivElement;
|
|
82
|
+
|
|
83
|
+
// List-valued credit properties (no attribute form — set imperatively).
|
|
84
|
+
private _developers: string[] = [];
|
|
85
|
+
private _designers: string[] = [];
|
|
86
|
+
private _artists: string[] = [];
|
|
87
|
+
private _documenters: string[] = [];
|
|
88
|
+
|
|
89
|
+
static get observedAttributes() {
|
|
90
|
+
return [
|
|
91
|
+
'application-name',
|
|
92
|
+
'application-icon',
|
|
93
|
+
'developer-name',
|
|
94
|
+
'version',
|
|
95
|
+
'comments',
|
|
96
|
+
'website',
|
|
97
|
+
'support-url',
|
|
98
|
+
'issue-url',
|
|
99
|
+
'copyright',
|
|
100
|
+
'license',
|
|
101
|
+
'license-url',
|
|
102
|
+
'open',
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// --- Reflected scalar properties ---
|
|
107
|
+
|
|
108
|
+
get applicationName(): string {
|
|
109
|
+
return this.getAttribute('application-name') ?? '';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
set applicationName(value: string) {
|
|
113
|
+
this.setAttribute('application-name', value);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get applicationIcon(): string {
|
|
117
|
+
return this.getAttribute('application-icon') ?? '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
set applicationIcon(value: string) {
|
|
121
|
+
this.setAttribute('application-icon', value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
get developerName(): string {
|
|
125
|
+
return this.getAttribute('developer-name') ?? '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
set developerName(value: string) {
|
|
129
|
+
this.setAttribute('developer-name', value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get version(): string {
|
|
133
|
+
return this.getAttribute('version') ?? '';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
set version(value: string) {
|
|
137
|
+
this.setAttribute('version', value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get comments(): string {
|
|
141
|
+
return this.getAttribute('comments') ?? '';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
set comments(value: string) {
|
|
145
|
+
this.setAttribute('comments', value);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get website(): string {
|
|
149
|
+
return this.getAttribute('website') ?? '';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
set website(value: string) {
|
|
153
|
+
this.setAttribute('website', value);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get supportUrl(): string {
|
|
157
|
+
return this.getAttribute('support-url') ?? '';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
set supportUrl(value: string) {
|
|
161
|
+
this.setAttribute('support-url', value);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get issueUrl(): string {
|
|
165
|
+
return this.getAttribute('issue-url') ?? '';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
set issueUrl(value: string) {
|
|
169
|
+
this.setAttribute('issue-url', value);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
get copyright(): string {
|
|
173
|
+
return this.getAttribute('copyright') ?? '';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
set copyright(value: string) {
|
|
177
|
+
this.setAttribute('copyright', value);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
get license(): string {
|
|
181
|
+
return this.getAttribute('license') ?? '';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
set license(value: string) {
|
|
185
|
+
this.setAttribute('license', value);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
get licenseUrl(): string {
|
|
189
|
+
return this.getAttribute('license-url') ?? '';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
set licenseUrl(value: string) {
|
|
193
|
+
this.setAttribute('license-url', value);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Whether the dialog is presented. */
|
|
197
|
+
get open(): boolean {
|
|
198
|
+
return this.hasAttribute('open');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
set open(value: boolean) {
|
|
202
|
+
if (value) this.setAttribute('open', '');
|
|
203
|
+
else this.removeAttribute('open');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- List-valued credit properties (no attribute form) ---
|
|
207
|
+
|
|
208
|
+
get developers(): string[] {
|
|
209
|
+
return this._developers;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
set developers(value: string[]) {
|
|
213
|
+
this._developers = [...value];
|
|
214
|
+
if (this._initialized) this._render();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
get designers(): string[] {
|
|
218
|
+
return this._designers;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
set designers(value: string[]) {
|
|
222
|
+
this._designers = [...value];
|
|
223
|
+
if (this._initialized) this._render();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
get artists(): string[] {
|
|
227
|
+
return this._artists;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
set artists(value: string[]) {
|
|
231
|
+
this._artists = [...value];
|
|
232
|
+
if (this._initialized) this._render();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
get documenters(): string[] {
|
|
236
|
+
return this._documenters;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
set documenters(value: string[]) {
|
|
240
|
+
this._documenters = [...value];
|
|
241
|
+
if (this._initialized) this._render();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Present the dialog (the web counterpart of Adw.Dialog.present()). */
|
|
245
|
+
present(): void {
|
|
246
|
+
this.open = true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Dismiss the dialog (the web counterpart of Adw.Dialog.close()). */
|
|
250
|
+
close(): void {
|
|
251
|
+
this.open = false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
connectedCallback() {
|
|
255
|
+
if (this._initialized) return;
|
|
256
|
+
this._initialized = true;
|
|
257
|
+
|
|
258
|
+
// The full-cover modal scrim — clicking it dismisses the dialog.
|
|
259
|
+
this._scrimEl = document.createElement('div');
|
|
260
|
+
this._scrimEl.className = 'adw-about-dialog-scrim';
|
|
261
|
+
this._scrimEl.addEventListener('click', () => this.close());
|
|
262
|
+
|
|
263
|
+
// The centred floating sheet.
|
|
264
|
+
this._sheetEl = document.createElement('div');
|
|
265
|
+
this._sheetEl.className = 'adw-about-dialog-sheet';
|
|
266
|
+
this._sheetEl.setAttribute('role', 'dialog');
|
|
267
|
+
this._sheetEl.setAttribute('aria-modal', 'true');
|
|
268
|
+
|
|
269
|
+
this.replaceChildren(this._scrimEl, this._sheetEl);
|
|
270
|
+
|
|
271
|
+
// Escape dismisses while open (mirrors AdwDialog's close shortcut).
|
|
272
|
+
this.addEventListener('keydown', (event) => {
|
|
273
|
+
if (event.key === 'Escape' && this.open) {
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
this.close();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
this._render();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
|
|
283
|
+
if (!this._initialized) return;
|
|
284
|
+
if (oldValue === newValue) return;
|
|
285
|
+
if (name === 'open') {
|
|
286
|
+
this._syncOpen();
|
|
287
|
+
this.dispatchEvent(new CustomEvent('notify::open', { bubbles: true, detail: { open: this.open } }));
|
|
288
|
+
if (!this.open) this.dispatchEvent(new CustomEvent('closed', { bubbles: true }));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
this._render();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Reflect the presented state onto the host + reset the navigation stack. */
|
|
295
|
+
private _syncOpen(): void {
|
|
296
|
+
this.classList.toggle('open', this.open);
|
|
297
|
+
if (this.open) {
|
|
298
|
+
// Always present the main page; subpages are reached via the rows.
|
|
299
|
+
const nav = this._navView as unknown as { replace?: (tags: string[]) => void };
|
|
300
|
+
nav.replace?.(['main']);
|
|
301
|
+
this._scrollEl.scrollTop = 0;
|
|
302
|
+
this._updateHeaderTitle();
|
|
303
|
+
// Move focus into the sheet for keyboard dismissal.
|
|
304
|
+
requestAnimationFrame(() => this._sheetEl.focus());
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Rebuild the sheet contents from the current attributes / credit lists. */
|
|
309
|
+
private _render(): void {
|
|
310
|
+
const appName = this.applicationName || 'Application';
|
|
311
|
+
this._sheetEl.setAttribute('aria-label', `About ${appName}`);
|
|
312
|
+
|
|
313
|
+
// The navigation view (main page + subpages) lives inside the sheet.
|
|
314
|
+
this._navView = document.createElement('adw-navigation-view');
|
|
315
|
+
this._navView.append(this._buildMainPage(appName));
|
|
316
|
+
|
|
317
|
+
const detailsPage = this._buildDetailsPage();
|
|
318
|
+
if (detailsPage) this._navView.append(detailsPage);
|
|
319
|
+
|
|
320
|
+
const creditsPage = this._buildCreditsPage();
|
|
321
|
+
if (creditsPage) this._navView.append(creditsPage);
|
|
322
|
+
|
|
323
|
+
const legalPage = this._buildLegalPage();
|
|
324
|
+
if (legalPage) this._navView.append(legalPage);
|
|
325
|
+
|
|
326
|
+
this._sheetEl.replaceChildren(this._navView);
|
|
327
|
+
this._sheetEl.tabIndex = -1;
|
|
328
|
+
|
|
329
|
+
this.classList.toggle('open', this.open);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** A navigation page wrapping a header bar (with the close button) + content. */
|
|
333
|
+
private _buildPage(tag: string, title: string, showClose: boolean): {
|
|
334
|
+
page: HTMLElement;
|
|
335
|
+
body: HTMLDivElement;
|
|
336
|
+
header: HTMLElement;
|
|
337
|
+
} {
|
|
338
|
+
const page = document.createElement('adw-navigation-page');
|
|
339
|
+
page.setAttribute('title', title);
|
|
340
|
+
page.setAttribute('tag', tag);
|
|
341
|
+
|
|
342
|
+
const header = document.createElement('adw-header-bar');
|
|
343
|
+
// The main page's title is hidden until the page is scrolled (CSS), the
|
|
344
|
+
// subpages always show it (AdwAboutDialog update_headerbar_cb).
|
|
345
|
+
header.setAttribute('title', title);
|
|
346
|
+
|
|
347
|
+
if (showClose) {
|
|
348
|
+
// The close button — drawn with a CSS "×" glyph (no window-close
|
|
349
|
+
// symbolic icon ships in @gjsify/adwaita-icons, the same approach as
|
|
350
|
+
// adw-tab-view's close affordance).
|
|
351
|
+
const close = document.createElement('button');
|
|
352
|
+
close.type = 'button';
|
|
353
|
+
close.className = 'adw-button flat circular adw-about-dialog-close';
|
|
354
|
+
close.setAttribute('aria-label', 'Close');
|
|
355
|
+
close.addEventListener('click', () => this.close());
|
|
356
|
+
const endApply = () => {
|
|
357
|
+
const end = (header as { endSection?: HTMLElement | null }).endSection ?? null;
|
|
358
|
+
if (end) end.appendChild(close);
|
|
359
|
+
};
|
|
360
|
+
// The header bar builds its sections in its own connectedCallback;
|
|
361
|
+
// append the close button once that has run.
|
|
362
|
+
queueMicrotask(endApply);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const scroll = document.createElement('div');
|
|
366
|
+
scroll.className = 'adw-about-dialog-scroll';
|
|
367
|
+
|
|
368
|
+
const clamp = document.createElement('div');
|
|
369
|
+
clamp.className = 'adw-about-dialog-clamp';
|
|
370
|
+
const body = document.createElement('div');
|
|
371
|
+
body.className = 'adw-about-dialog-page-body';
|
|
372
|
+
clamp.appendChild(body);
|
|
373
|
+
scroll.appendChild(clamp);
|
|
374
|
+
|
|
375
|
+
const toolbar = document.createElement('div');
|
|
376
|
+
toolbar.className = 'adw-about-dialog-toolbar-view';
|
|
377
|
+
toolbar.append(header, scroll);
|
|
378
|
+
page.appendChild(toolbar);
|
|
379
|
+
|
|
380
|
+
if (tag === 'main') {
|
|
381
|
+
this._scrollEl = scroll;
|
|
382
|
+
this._mainToolbarEl = toolbar;
|
|
383
|
+
// The main page's header title fades in once the content scrolls.
|
|
384
|
+
toolbar.classList.add('main-page');
|
|
385
|
+
scroll.addEventListener('scroll', () => this._updateHeaderTitle());
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return { page, body, header };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** The main page: icon, name, developer, version pill, navigation rows. */
|
|
392
|
+
private _buildMainPage(appName: string): HTMLElement {
|
|
393
|
+
const { page, body } = this._buildPage('main', `About ${appName}`, true);
|
|
394
|
+
|
|
395
|
+
// App icon — a large symbolic glyph. A given application-icon resolves
|
|
396
|
+
// to its `.adw-icon--<name>` mask class; absent one, fall back to the
|
|
397
|
+
// GTK default generic `application-x-executable` glyph.
|
|
398
|
+
const icon = (this.applicationIcon || 'application-x-executable').replace(/-symbolic$/, '');
|
|
399
|
+
const iconEl = document.createElement('span');
|
|
400
|
+
iconEl.className = `adw-about-dialog-icon adw-icon adw-icon--${icon}`;
|
|
401
|
+
iconEl.setAttribute('aria-hidden', 'true');
|
|
402
|
+
body.appendChild(iconEl);
|
|
403
|
+
|
|
404
|
+
// App name — title-1.
|
|
405
|
+
const nameEl = document.createElement('span');
|
|
406
|
+
nameEl.className = 'adw-about-dialog-name';
|
|
407
|
+
nameEl.textContent = appName;
|
|
408
|
+
body.appendChild(nameEl);
|
|
409
|
+
|
|
410
|
+
// Developer / vendor.
|
|
411
|
+
if (this.developerName) {
|
|
412
|
+
const devEl = document.createElement('span');
|
|
413
|
+
devEl.className = 'adw-about-dialog-developer';
|
|
414
|
+
devEl.textContent = this.developerName;
|
|
415
|
+
body.appendChild(devEl);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Version pill.
|
|
419
|
+
if (this.version) {
|
|
420
|
+
const versionEl = document.createElement('span');
|
|
421
|
+
versionEl.className = 'adw-about-dialog-version';
|
|
422
|
+
versionEl.textContent = this.version;
|
|
423
|
+
body.appendChild(versionEl);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// The navigation preference rows (Details / Credits / Legal). Only the
|
|
427
|
+
// rows whose corresponding subpage has content are shown — mirroring the
|
|
428
|
+
// AdwAboutDialog group visibility bindings.
|
|
429
|
+
const group = document.createElement('adw-preferences-group');
|
|
430
|
+
let hasRows = false;
|
|
431
|
+
|
|
432
|
+
if (this._hasDetails()) {
|
|
433
|
+
group.appendChild(this._buildNavRow('Details', 'details'));
|
|
434
|
+
hasRows = true;
|
|
435
|
+
}
|
|
436
|
+
if (this._hasCredits()) {
|
|
437
|
+
group.appendChild(this._buildNavRow('Credits', 'credits'));
|
|
438
|
+
hasRows = true;
|
|
439
|
+
}
|
|
440
|
+
if (this._hasLegal()) {
|
|
441
|
+
group.appendChild(this._buildNavRow('Legal', 'legal'));
|
|
442
|
+
hasRows = true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (hasRows) {
|
|
446
|
+
const groupWrap = document.createElement('div');
|
|
447
|
+
groupWrap.className = 'adw-about-dialog-rows';
|
|
448
|
+
groupWrap.appendChild(group);
|
|
449
|
+
body.appendChild(groupWrap);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return page;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** An activatable row that pushes a subpage, with a trailing go-next chevron. */
|
|
456
|
+
private _buildNavRow(title: string, tag: string): HTMLElement {
|
|
457
|
+
const row = document.createElement('adw-action-row');
|
|
458
|
+
row.setAttribute('title', title);
|
|
459
|
+
row.setAttribute('activatable', '');
|
|
460
|
+
|
|
461
|
+
const chevron = document.createElement('span');
|
|
462
|
+
chevron.setAttribute('slot', 'suffix');
|
|
463
|
+
chevron.className = 'adw-icon adw-icon--go-next adw-about-dialog-chevron';
|
|
464
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
465
|
+
row.appendChild(chevron);
|
|
466
|
+
|
|
467
|
+
row.addEventListener('activated', () => {
|
|
468
|
+
const nav = this._navView as unknown as { push?: (tag: string) => void };
|
|
469
|
+
nav.push?.(tag);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
return row;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private _hasDetails(): boolean {
|
|
476
|
+
return Boolean(this.comments || this.website || this.supportUrl || this.issueUrl);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private _hasCredits(): boolean {
|
|
480
|
+
return (
|
|
481
|
+
this._developers.length > 0 ||
|
|
482
|
+
this._designers.length > 0 ||
|
|
483
|
+
this._artists.length > 0 ||
|
|
484
|
+
this._documenters.length > 0
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private _hasLegal(): boolean {
|
|
489
|
+
return Boolean(this.copyright || this.license);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** The Details page: comments + website / support / issue link rows. */
|
|
493
|
+
private _buildDetailsPage(): HTMLElement | null {
|
|
494
|
+
if (!this._hasDetails()) return null;
|
|
495
|
+
const { page, body } = this._buildPage('details', 'Details', false);
|
|
496
|
+
|
|
497
|
+
if (this.comments) {
|
|
498
|
+
const comments = document.createElement('p');
|
|
499
|
+
comments.className = 'adw-about-dialog-comments';
|
|
500
|
+
comments.textContent = this.comments;
|
|
501
|
+
body.appendChild(comments);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const links: Array<[string, string]> = [];
|
|
505
|
+
if (this.website) links.push(['Website', this.website]);
|
|
506
|
+
if (this.supportUrl) links.push(['Support Questions', this.supportUrl]);
|
|
507
|
+
if (this.issueUrl) links.push(['Report an Issue', this.issueUrl]);
|
|
508
|
+
|
|
509
|
+
if (links.length > 0) {
|
|
510
|
+
const group = document.createElement('adw-preferences-group');
|
|
511
|
+
for (const [title, uri] of links) group.appendChild(this._buildLinkRow(title, uri));
|
|
512
|
+
const wrap = document.createElement('div');
|
|
513
|
+
wrap.className = 'adw-about-dialog-rows';
|
|
514
|
+
wrap.appendChild(group);
|
|
515
|
+
body.appendChild(wrap);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return page;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** A link row that opens a URL in a new tab and emits `activate-link`. */
|
|
522
|
+
private _buildLinkRow(title: string, uri: string): HTMLElement {
|
|
523
|
+
const row = document.createElement('adw-action-row');
|
|
524
|
+
row.setAttribute('title', title);
|
|
525
|
+
row.setAttribute('activatable', '');
|
|
526
|
+
row.classList.add('adw-about-dialog-link-row');
|
|
527
|
+
|
|
528
|
+
const chevron = document.createElement('span');
|
|
529
|
+
chevron.setAttribute('slot', 'suffix');
|
|
530
|
+
chevron.className = 'adw-icon adw-icon--go-next adw-about-dialog-chevron';
|
|
531
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
532
|
+
row.appendChild(chevron);
|
|
533
|
+
|
|
534
|
+
row.addEventListener('activated', () => {
|
|
535
|
+
this.dispatchEvent(new CustomEvent('activate-link', { bubbles: true, detail: { uri } }));
|
|
536
|
+
window.open(uri, '_blank', 'noopener,noreferrer');
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
return row;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** The Credits page: a group per non-empty credit section. */
|
|
543
|
+
private _buildCreditsPage(): HTMLElement | null {
|
|
544
|
+
if (!this._hasCredits()) return null;
|
|
545
|
+
const { page, body } = this._buildPage('credits', 'Credits', false);
|
|
546
|
+
|
|
547
|
+
const sections: Array<[string, string[]]> = [
|
|
548
|
+
['Code by', this._developers],
|
|
549
|
+
['Design by', this._designers],
|
|
550
|
+
['Artwork by', this._artists],
|
|
551
|
+
['Documentation by', this._documenters],
|
|
552
|
+
];
|
|
553
|
+
|
|
554
|
+
for (const [title, people] of sections) {
|
|
555
|
+
if (people.length === 0) continue;
|
|
556
|
+
const group = document.createElement('adw-preferences-group');
|
|
557
|
+
group.setAttribute('title', title);
|
|
558
|
+
for (const person of people) group.appendChild(this._buildCreditRow(person));
|
|
559
|
+
const wrap = document.createElement('div');
|
|
560
|
+
wrap.className = 'adw-about-dialog-rows';
|
|
561
|
+
wrap.appendChild(group);
|
|
562
|
+
body.appendChild(wrap);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return page;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/** A credit row — a plain title, or a link row when the entry carries a URI. */
|
|
569
|
+
private _buildCreditRow(raw: string): HTMLElement {
|
|
570
|
+
const { name, uri } = parseCredit(raw);
|
|
571
|
+
const row = document.createElement('adw-action-row');
|
|
572
|
+
row.setAttribute('title', name);
|
|
573
|
+
|
|
574
|
+
if (uri) {
|
|
575
|
+
row.setAttribute('activatable', '');
|
|
576
|
+
row.classList.add('adw-about-dialog-link-row');
|
|
577
|
+
const chevron = document.createElement('span');
|
|
578
|
+
chevron.setAttribute('slot', 'suffix');
|
|
579
|
+
chevron.className = 'adw-icon adw-icon--go-next adw-about-dialog-chevron';
|
|
580
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
581
|
+
row.appendChild(chevron);
|
|
582
|
+
row.addEventListener('activated', () => {
|
|
583
|
+
this.dispatchEvent(new CustomEvent('activate-link', { bubbles: true, detail: { uri } }));
|
|
584
|
+
window.open(uri, '_blank', 'noopener,noreferrer');
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return row;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** The Legal page: copyright + license text. */
|
|
592
|
+
private _buildLegalPage(): HTMLElement | null {
|
|
593
|
+
if (!this._hasLegal()) return null;
|
|
594
|
+
const { page, body } = this._buildPage('legal', 'Legal', false);
|
|
595
|
+
|
|
596
|
+
if (this.copyright) {
|
|
597
|
+
const copyright = document.createElement('p');
|
|
598
|
+
copyright.className = 'adw-about-dialog-copyright';
|
|
599
|
+
copyright.textContent = this.copyright;
|
|
600
|
+
body.appendChild(copyright);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (this.license) {
|
|
604
|
+
const license = document.createElement('p');
|
|
605
|
+
license.className = 'adw-about-dialog-license';
|
|
606
|
+
if (this.licenseUrl) {
|
|
607
|
+
const link = document.createElement('a');
|
|
608
|
+
link.href = this.licenseUrl;
|
|
609
|
+
link.target = '_blank';
|
|
610
|
+
link.rel = 'noopener noreferrer';
|
|
611
|
+
link.textContent = this.license;
|
|
612
|
+
license.appendChild(link);
|
|
613
|
+
} else {
|
|
614
|
+
license.textContent = this.license;
|
|
615
|
+
}
|
|
616
|
+
body.appendChild(license);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return page;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** Show the header title only once the main page is scrolled (AdwAboutDialog). */
|
|
623
|
+
private _updateHeaderTitle(): void {
|
|
624
|
+
if (!this._scrollEl || !this._mainToolbarEl) return;
|
|
625
|
+
this._mainToolbarEl.classList.toggle('scrolled', this._scrollEl.scrollTop > 0);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
customElements.define('adw-about-dialog', AdwAboutDialog);
|