@gjsify/adwaita-web 0.36.0 → 0.37.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/dist/adwaita-web.css +433 -33
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
- package/lib/types/breakpoints.d.ts +10 -12
- package/lib/types/elements/adw-about-dialog.d.ts +19 -36
- package/lib/types/elements/adw-action-row.d.ts +9 -12
- package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
- package/lib/types/elements/adw-avatar.d.ts +1 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
- package/lib/types/elements/adw-button-content.d.ts +3 -4
- package/lib/types/elements/adw-button-row.d.ts +1 -1
- package/lib/types/elements/adw-carousel.d.ts +28 -49
- package/lib/types/elements/adw-checks.d.ts +2 -8
- package/lib/types/elements/adw-combo-row.d.ts +6 -6
- package/lib/types/elements/adw-data-grid.d.ts +5 -10
- package/lib/types/elements/adw-drop-down.d.ts +3 -11
- package/lib/types/elements/adw-entry-row.d.ts +20 -23
- package/lib/types/elements/adw-icon.d.ts +2 -4
- package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
- package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
- package/lib/types/elements/adw-navigation-view.d.ts +2 -10
- package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
- package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
- package/lib/types/elements/adw-popover.d.ts +2 -7
- package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
- package/lib/types/elements/adw-preferences-group.d.ts +12 -14
- package/lib/types/elements/adw-progress-bar.d.ts +3 -4
- package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
- package/lib/types/elements/adw-sidebar.d.ts +5 -10
- package/lib/types/elements/adw-spinner.d.ts +3 -5
- package/lib/types/elements/adw-split-button.d.ts +6 -16
- package/lib/types/elements/adw-switch-row.d.ts +3 -3
- package/lib/types/elements/adw-tab-view.d.ts +16 -26
- package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
- package/lib/types/elements/adw-view-stack.d.ts +7 -17
- package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
- package/lib/types/elements/adw-view-switcher.d.ts +18 -38
- package/lib/types/elements/adw-wrap-box.d.ts +7 -17
- package/lib/types/elements/view-switcher-dom.d.ts +10 -13
- package/lib/types/index.d.ts +1 -0
- package/lib/types/source-view/hex.d.ts +7 -13
- package/lib/types/style-classes.spec.d.ts +11 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +6 -6
- package/scss/_about_dialog.scss +3 -2
- package/scss/_action_row.scss +33 -0
- package/scss/_avatar.scss +12 -0
- package/scss/_button.scss +44 -22
- package/scss/_card.scss +11 -8
- package/scss/_checks.scss +2 -4
- package/scss/_dialog.scss +3 -2
- package/scss/_headerbar.scss +23 -0
- package/scss/_popover.scss +8 -4
- package/scss/_preferences.scss +7 -7
- package/scss/_progress_bar.scss +9 -6
- package/scss/_reset.scss +1 -1
- package/scss/_shortcut_label.scss +80 -0
- package/scss/_sidebar.scss +57 -0
- package/scss/_status_page.scss +20 -0
- package/scss/_style_classes.scss +407 -0
- package/scss/_switch.scss +4 -3
- package/scss/_variables.scss +25 -0
- package/scss/adwaita-skin.scss +12 -0
- package/src/adw-about-dialog.spec.ts +34 -49
- package/src/adw-action-rows.spec.ts +25 -33
- package/src/adw-avatar.spec.ts +51 -20
- package/src/adw-banner.spec.ts +5 -8
- package/src/adw-button-content.spec.ts +9 -12
- package/src/adw-button-row.spec.ts +2 -4
- package/src/adw-carousel.spec.ts +40 -53
- package/src/adw-checks.spec.ts +36 -47
- package/src/adw-drop-down.spec.ts +25 -32
- package/src/adw-header-bar.spec.ts +5 -7
- package/src/adw-icon.spec.ts +23 -32
- package/src/adw-progress-bar.spec.ts +10 -12
- package/src/adw-row-state.spec.ts +9 -10
- package/src/adw-shortcut-label.spec.ts +112 -0
- package/src/adw-sidebar.spec.ts +17 -24
- package/src/adw-switch.spec.ts +11 -17
- package/src/adw-tab-view.spec.ts +7 -10
- package/src/adw-toast-overlay.spec.ts +7 -11
- package/src/adw-view-switcher-bar.spec.ts +10 -14
- package/src/adw-wrap-box.spec.ts +25 -36
- package/src/bottom-sheet.spec.ts +30 -36
- package/src/breakpoints.ts +15 -18
- package/src/chrome.spec.ts +13 -21
- package/src/elements/adw-about-dialog.ts +71 -114
- package/src/elements/adw-action-row.ts +22 -37
- package/src/elements/adw-alert-dialog.ts +25 -50
- package/src/elements/adw-avatar.ts +40 -15
- package/src/elements/adw-banner.ts +14 -18
- package/src/elements/adw-bottom-sheet.ts +31 -52
- package/src/elements/adw-button-content.ts +30 -37
- package/src/elements/adw-button-row.ts +14 -24
- package/src/elements/adw-carousel.ts +87 -148
- package/src/elements/adw-checks.ts +43 -56
- package/src/elements/adw-clamp.ts +8 -9
- package/src/elements/adw-combo-row.ts +17 -25
- package/src/elements/adw-data-grid.ts +26 -38
- package/src/elements/adw-dialog.ts +25 -56
- package/src/elements/adw-drop-down.ts +31 -51
- package/src/elements/adw-entry-row.ts +39 -46
- package/src/elements/adw-expander-row.ts +30 -53
- package/src/elements/adw-icon.ts +30 -53
- package/src/elements/adw-inline-view-switcher.ts +38 -67
- package/src/elements/adw-menu-button.ts +28 -58
- package/src/elements/adw-navigation-split-view.ts +35 -47
- package/src/elements/adw-navigation-view.ts +12 -28
- package/src/elements/adw-overlay-split-view.ts +100 -175
- package/src/elements/adw-password-entry-row.ts +18 -28
- package/src/elements/adw-popover.ts +28 -44
- package/src/elements/adw-preferences-dialog.ts +41 -66
- package/src/elements/adw-preferences-group.ts +33 -46
- package/src/elements/adw-progress-bar.ts +27 -47
- package/src/elements/adw-shortcut-label.ts +138 -0
- package/src/elements/adw-sidebar.ts +31 -39
- package/src/elements/adw-spin-row.ts +7 -14
- package/src/elements/adw-spinner.ts +31 -43
- package/src/elements/adw-split-button.ts +45 -75
- package/src/elements/adw-switch-row.ts +29 -46
- package/src/elements/adw-switch.ts +28 -47
- package/src/elements/adw-tab-view.ts +62 -103
- package/src/elements/adw-toast-overlay.ts +28 -35
- package/src/elements/adw-toggle-group.ts +9 -16
- package/src/elements/adw-view-stack.ts +32 -51
- package/src/elements/adw-view-switcher-bar.ts +26 -50
- package/src/elements/adw-view-switcher.ts +60 -110
- package/src/elements/adw-window-title.ts +4 -4
- package/src/elements/adw-wrap-box.ts +33 -59
- package/src/elements/view-switcher-dom.ts +15 -22
- package/src/entry-rows.spec.ts +18 -24
- package/src/index.ts +13 -15
- package/src/navigation-view.spec.ts +7 -10
- package/src/preferences-search.ts +11 -15
- package/src/preferences.spec.ts +13 -15
- package/src/source-view/adw-source-view.ts +6 -19
- package/src/source-view/hex.ts +7 -13
- package/src/split-button.spec.ts +32 -44
- package/src/split-views.spec.ts +83 -149
- package/src/style-classes.spec.ts +181 -0
- package/src/style-isolation.spec.ts +3 -6
- package/src/styles.generated.ts +1 -1
- package/src/tab-view.spec.ts +26 -35
- package/src/test.browser.mts +4 -0
- package/src/view-stack.spec.ts +23 -34
- package/src/view-switcher.spec.ts +56 -84
|
@@ -17,10 +17,9 @@
|
|
|
17
17
|
// open (boolean — whether the dialog is shown; default closed)
|
|
18
18
|
// prefer-wide-layout (boolean — lay responses out horizontally when they fit)
|
|
19
19
|
//
|
|
20
|
-
// Buttons are stacked vertically by default (the Adw.AlertDialog default at
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// heuristic in spirit.
|
|
20
|
+
// Buttons are stacked vertically by default (the Adw.AlertDialog default at medium
|
|
21
|
+
// sizes) and laid out horizontally when prefer-wide-layout is set and there are no more
|
|
22
|
+
// than two of them — the libadwaita measure/allocate heuristic in spirit, not to the px.
|
|
24
23
|
//
|
|
25
24
|
// Events:
|
|
26
25
|
// `response` (CustomEvent, bubbles, `detail = { response }`) when a response
|
|
@@ -29,13 +28,10 @@
|
|
|
29
28
|
// `notify::heading` / `notify::body` / `notify::open` (CustomEvent, bubbles)
|
|
30
29
|
// mirroring the matching GObject properties.
|
|
31
30
|
//
|
|
32
|
-
// The RESPONSE MODEL — the
|
|
33
|
-
// default/close-response semantics and the resolve-to-chosen-id contract — is
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// render half: the scrim + floating sheet, one `<button>` per registered
|
|
37
|
-
// response, and the `response` event. `@gjsify/adwaita-nativescript` composes the
|
|
38
|
-
// same model, so both ports share one behaviour.
|
|
31
|
+
// The RESPONSE MODEL — the registry (id/label/appearance/enabled), the
|
|
32
|
+
// default/close-response semantics and the resolve-to-chosen-id contract — is HEADLESS
|
|
33
|
+
// and lives in `@gjsify/adwaita-core` (ADR 0004) as {@link AdwAlertResponses}, composed
|
|
34
|
+
// by `@gjsify/adwaita-nativescript` too. This element keeps only the DOM render half.
|
|
39
35
|
//
|
|
40
36
|
// Reference: refs/libadwaita/src/adw-alert-dialog.c (AdwAlertDialog behaviour)
|
|
41
37
|
// Reference: refs/libadwaita/src/adw-dialog.c (shared dialog base — Escape/close)
|
|
@@ -76,16 +72,14 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
76
72
|
private _model = new AdwAlertResponses();
|
|
77
73
|
/** The rendered button per registered response id (the DOM half of the model). */
|
|
78
74
|
private readonly _buttons = new Map<string, HTMLButtonElement>();
|
|
79
|
-
// Set while a response is being emitted from a button so the close handler
|
|
80
|
-
//
|
|
81
|
-
// block_close_response flag).
|
|
75
|
+
// Set while a response is being emitted from a button, so the close handler does not
|
|
76
|
+
// also fire the close-response (AdwAlertDialog's `block_close_response`).
|
|
82
77
|
private _blockCloseResponse = false;
|
|
83
78
|
|
|
84
79
|
static get observedAttributes() {
|
|
85
80
|
return ['heading', 'body', 'open', 'prefer-wide-layout'];
|
|
86
81
|
}
|
|
87
82
|
|
|
88
|
-
/** The heading of the dialog. */
|
|
89
83
|
get heading(): string {
|
|
90
84
|
return this.getAttribute('heading') ?? '';
|
|
91
85
|
}
|
|
@@ -94,7 +88,6 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
94
88
|
this.setAttribute('heading', value);
|
|
95
89
|
}
|
|
96
90
|
|
|
97
|
-
/** The body text of the dialog. */
|
|
98
91
|
get body(): string {
|
|
99
92
|
return this.getAttribute('body') ?? '';
|
|
100
93
|
}
|
|
@@ -103,7 +96,6 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
103
96
|
this.setAttribute('body', value);
|
|
104
97
|
}
|
|
105
98
|
|
|
106
|
-
/** Whether the dialog is currently shown. */
|
|
107
99
|
get open(): boolean {
|
|
108
100
|
return this.hasAttribute('open');
|
|
109
101
|
}
|
|
@@ -145,18 +137,16 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
145
137
|
if (this._initialized) return;
|
|
146
138
|
this._initialized = true;
|
|
147
139
|
|
|
148
|
-
//
|
|
140
|
+
// Declared <adw-alert-response> children are snapshotted before the element takes
|
|
149
141
|
// over the subtree; everything else becomes the extra child.
|
|
150
142
|
const declared = Array.from(this.querySelectorAll(':scope > adw-alert-response')) as AdwAlertResponse[];
|
|
151
143
|
const declaredSet = new Set<Node>(declared);
|
|
152
144
|
const extraChildren = Array.from(this.childNodes).filter((n) => !declaredSet.has(n));
|
|
153
145
|
|
|
154
|
-
// The fixed full-cover scrim. Clicking it dismisses the dialog.
|
|
155
146
|
this._scrimEl = document.createElement('div');
|
|
156
147
|
this._scrimEl.className = 'adw-alert-dialog-scrim';
|
|
157
148
|
this._scrimEl.addEventListener('click', () => this._dismiss());
|
|
158
149
|
|
|
159
|
-
// The centred dialog box.
|
|
160
150
|
this._dialogEl = document.createElement('div');
|
|
161
151
|
this._dialogEl.className = 'adw-alert-dialog-box';
|
|
162
152
|
this._dialogEl.setAttribute('role', 'alertdialog');
|
|
@@ -164,7 +154,6 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
164
154
|
// Clicks inside the box must not bubble to the scrim's dismiss handler.
|
|
165
155
|
this._dialogEl.addEventListener('click', (event) => event.stopPropagation());
|
|
166
156
|
|
|
167
|
-
// The scrolled message area (heading + body + extra child).
|
|
168
157
|
const messageArea = document.createElement('div');
|
|
169
158
|
messageArea.className = 'adw-alert-dialog-message';
|
|
170
159
|
|
|
@@ -180,15 +169,14 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
180
169
|
|
|
181
170
|
messageArea.append(this._headingEl, this._bodyEl, this._childEl);
|
|
182
171
|
|
|
183
|
-
// The response button area.
|
|
184
172
|
this._responseAreaEl = document.createElement('div');
|
|
185
173
|
this._responseAreaEl.className = 'adw-alert-dialog-responses';
|
|
186
174
|
|
|
187
175
|
this._dialogEl.append(messageArea, this._responseAreaEl);
|
|
188
176
|
this.replaceChildren(this._scrimEl, this._dialogEl);
|
|
189
177
|
|
|
190
|
-
// Escape dismisses the dialog when open (
|
|
191
|
-
//
|
|
178
|
+
// Escape dismisses the dialog when open (the AdwDialog Escape shortcut →
|
|
179
|
+
// close-response).
|
|
192
180
|
this.addEventListener('keydown', (event) => {
|
|
193
181
|
if (event.key === 'Escape' && this.open) {
|
|
194
182
|
event.stopPropagation();
|
|
@@ -196,7 +184,6 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
196
184
|
}
|
|
197
185
|
});
|
|
198
186
|
|
|
199
|
-
// Adopt declared responses now that the response area exists.
|
|
200
187
|
for (const el of declared) {
|
|
201
188
|
const id = el.getAttribute('id');
|
|
202
189
|
if (!id) continue;
|
|
@@ -206,8 +193,8 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
206
193
|
if (el.getAttribute('enabled') === 'false') this.setResponseEnabled(id, false);
|
|
207
194
|
}
|
|
208
195
|
|
|
209
|
-
// Seed the headless model's text from the parsed markup
|
|
210
|
-
//
|
|
196
|
+
// Seed the headless model's text from the parsed markup; the attributes stay the
|
|
197
|
+
// source of truth for what is rendered.
|
|
211
198
|
this._model.heading = this.heading;
|
|
212
199
|
this._model.body = this.body;
|
|
213
200
|
|
|
@@ -219,8 +206,8 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
219
206
|
if (oldValue === newValue) return;
|
|
220
207
|
this._render();
|
|
221
208
|
if (name === 'heading') {
|
|
222
|
-
// Keep the headless model a complete description of the dialog; the
|
|
223
|
-
//
|
|
209
|
+
// Keep the headless model a complete description of the dialog; the attribute
|
|
210
|
+
// stays the source of truth for the rendered heading.
|
|
224
211
|
this._model.heading = this.heading;
|
|
225
212
|
this.dispatchEvent(
|
|
226
213
|
new CustomEvent('notify::heading', { bubbles: true, detail: { heading: this.heading } }),
|
|
@@ -239,10 +226,9 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
239
226
|
this.open = true;
|
|
240
227
|
}
|
|
241
228
|
|
|
242
|
-
/** Adds a response button with the given ID and label. */
|
|
243
229
|
addResponse(id: string, label: string): void {
|
|
244
|
-
// The core registry UPDATES a duplicate id in place; AdwAlertDialog
|
|
245
|
-
//
|
|
230
|
+
// The core registry UPDATES a duplicate id in place; AdwAlertDialog refuses it,
|
|
231
|
+
// so the guard stays here.
|
|
246
232
|
if (this._model.hasResponse(id)) {
|
|
247
233
|
// Mirror AdwAlertDialog's g_critical without throwing.
|
|
248
234
|
console.warn(
|
|
@@ -270,13 +256,12 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
270
256
|
}
|
|
271
257
|
}
|
|
272
258
|
|
|
273
|
-
/** Removes a response. */
|
|
274
259
|
removeResponse(id: string): void {
|
|
275
260
|
if (!this._model.hasResponse(id)) return;
|
|
276
261
|
this._buttons.get(id)?.remove();
|
|
277
262
|
this._buttons.delete(id);
|
|
278
263
|
// `AdwAlertResponses` exposes no removal (a gap against
|
|
279
|
-
// adw_alert_dialog_remove_response), so the registry is rebuilt from the
|
|
264
|
+
// `adw_alert_dialog_remove_response`), so the registry is rebuilt from the
|
|
280
265
|
// survivors — core stays the single owner of the response state.
|
|
281
266
|
const survivors = this._model.responses.filter((response) => response.id !== id);
|
|
282
267
|
const rebuilt = new AdwAlertResponses(this._model.heading, this._model.body);
|
|
@@ -292,17 +277,14 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
292
277
|
this._render();
|
|
293
278
|
}
|
|
294
279
|
|
|
295
|
-
/** Whether a response with the given ID exists. */
|
|
296
280
|
hasResponse(id: string): boolean {
|
|
297
281
|
return this._model.hasResponse(id);
|
|
298
282
|
}
|
|
299
283
|
|
|
300
|
-
/** Gets a response's label. */
|
|
301
284
|
getResponseLabel(id: string): string | null {
|
|
302
285
|
return this._model.responses.find((response) => response.id === id)?.label ?? null;
|
|
303
286
|
}
|
|
304
287
|
|
|
305
|
-
/** Sets a response's label. */
|
|
306
288
|
setResponseLabel(id: string, label: string): void {
|
|
307
289
|
const button = this._buttons.get(id);
|
|
308
290
|
if (!button) return;
|
|
@@ -311,12 +293,10 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
311
293
|
button.textContent = label;
|
|
312
294
|
}
|
|
313
295
|
|
|
314
|
-
/** Gets a response's appearance. */
|
|
315
296
|
getResponseAppearance(id: string): AdwResponseAppearance | null {
|
|
316
297
|
return this._model.hasResponse(id) ? this._model.getResponseAppearance(id) : null;
|
|
317
298
|
}
|
|
318
299
|
|
|
319
|
-
/** Sets a response's appearance (default / suggested / destructive). */
|
|
320
300
|
setResponseAppearance(id: string, appearance: AdwResponseAppearance): void {
|
|
321
301
|
const button = this._buttons.get(id);
|
|
322
302
|
if (!button) return;
|
|
@@ -325,7 +305,6 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
325
305
|
button.classList.toggle('destructive-action', appearance === 'destructive');
|
|
326
306
|
}
|
|
327
307
|
|
|
328
|
-
/** Gets whether a response is enabled. */
|
|
329
308
|
getResponseEnabled(id: string): boolean {
|
|
330
309
|
return this._model.hasResponse(id) ? this._model.getResponseEnabled(id) : false;
|
|
331
310
|
}
|
|
@@ -349,16 +328,13 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
349
328
|
this._model.closeResponse = id;
|
|
350
329
|
}
|
|
351
330
|
|
|
352
|
-
// ── internals ──────────────────────────────────────────────────────────
|
|
353
|
-
|
|
354
|
-
/** A response button was activated: close, then emit `response`. */
|
|
355
331
|
private _activateResponse(id: string): void {
|
|
356
332
|
if (!this._model.getResponseEnabled(id)) return;
|
|
357
333
|
this._blockCloseResponse = true;
|
|
358
334
|
this.open = false;
|
|
359
335
|
this._blockCloseResponse = false;
|
|
360
|
-
// resolveById validates against the registry
|
|
361
|
-
//
|
|
336
|
+
// `resolveById` validates against the registry and falls back to the close
|
|
337
|
+
// response.
|
|
362
338
|
this._emitResponse(this._model.resolveById(id));
|
|
363
339
|
}
|
|
364
340
|
|
|
@@ -398,14 +374,13 @@ export class AdwAlertDialog extends HTMLElement {
|
|
|
398
374
|
|
|
399
375
|
this._childEl.hidden = this._childEl.childElementCount === 0;
|
|
400
376
|
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
// AdwAlertDialog heuristic for keeping wide buttons readable).
|
|
377
|
+
// Horizontal only when prefer-wide-layout is set AND there are at most two
|
|
378
|
+
// responses — the AdwAlertDialog heuristic for keeping wide buttons readable.
|
|
404
379
|
const horizontal = this.preferWideLayout && this._model.responses.length <= 2;
|
|
405
380
|
this._responseAreaEl.classList.toggle('horizontal', horizontal);
|
|
406
381
|
|
|
407
|
-
//
|
|
408
|
-
//
|
|
382
|
+
// The default response gets the suggested highlight unless it already carries an
|
|
383
|
+
// explicit appearance.
|
|
409
384
|
const defaultId = this._model.defaultResponse;
|
|
410
385
|
for (const response of this._model.responses) {
|
|
411
386
|
const isDefault = response.id === defaultId && response.appearance === 'default';
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
// <adw-avatar> — A round avatar showing initials derived from a
|
|
2
|
-
// colour picked from the name), or a symbolic fallback icon.
|
|
1
|
+
// <adw-avatar> — A round avatar showing a custom image, initials derived from a
|
|
2
|
+
// name (with a colour picked from the name), or a symbolic fallback icon.
|
|
3
3
|
// Attributes: text, size (px, default 48), show-initials (boolean),
|
|
4
|
-
// icon (symbolic fallback name, with or without -symbolic)
|
|
4
|
+
// icon (symbolic fallback name, with or without -symbolic),
|
|
5
|
+
// custom-image (an image URL — `Adw.Avatar:custom-image` is a GdkPaintable,
|
|
6
|
+
// and a URL is what a browser draws one from).
|
|
5
7
|
// Reference: refs/adwaita-web/adwaita-web/scss/_avatar.scss
|
|
6
8
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
7
9
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web; the
|
|
8
10
|
// fallback icon node is <adw-icon>.
|
|
9
11
|
//
|
|
10
|
-
// The two derivations — initials from the name, and the palette colour the name
|
|
11
|
-
//
|
|
12
|
-
// {@link
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
12
|
+
// The two derivations — initials from the name, and the palette colour the name hashes to
|
|
13
|
+
// — are HEADLESS and live in `@gjsify/adwaita-core` (ADR 0004) as {@link avatarInitials} /
|
|
14
|
+
// {@link avatarColor}; this element only paints them. The shared vectors in
|
|
15
|
+
// `@gjsify/adwaita-core/conformance` pin it to the C source — see `src/adw-avatar.spec.ts`.
|
|
16
|
+
//
|
|
17
|
+
// `custom-image` was UNPORTED until #1049's follow-up: this element passed
|
|
18
|
+
// `hasCustomImage: false` unconditionally, so the `image` branch of `update_visibility`
|
|
19
|
+
// (adw-avatar.c:117-125) — the branch that wins over both others — could not be reached,
|
|
20
|
+
// and the vector row covering it had nothing to drive.
|
|
18
21
|
|
|
19
22
|
import { avatarColor, avatarFontSize, avatarInitials, avatarMaxFontSize, avatarMode } from '@gjsify/adwaita-core';
|
|
20
23
|
|
|
@@ -23,10 +26,11 @@ import { type AdwIcon, createAdwIcon } from './adw-icon.js';
|
|
|
23
26
|
export class AdwAvatar extends HTMLElement {
|
|
24
27
|
private _textEl!: HTMLSpanElement;
|
|
25
28
|
private _iconEl!: AdwIcon;
|
|
29
|
+
private _imageEl!: HTMLImageElement;
|
|
26
30
|
private _initialized = false;
|
|
27
31
|
|
|
28
32
|
static get observedAttributes() {
|
|
29
|
-
return ['text', 'size', 'show-initials', 'icon'];
|
|
33
|
+
return ['text', 'size', 'show-initials', 'icon', 'custom-image'];
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
connectedCallback() {
|
|
@@ -38,7 +42,14 @@ export class AdwAvatar extends HTMLElement {
|
|
|
38
42
|
|
|
39
43
|
this._iconEl = createAdwIcon(null, 'adw-avatar-icon');
|
|
40
44
|
|
|
41
|
-
this.
|
|
45
|
+
this._imageEl = document.createElement('img');
|
|
46
|
+
this._imageEl.className = 'adw-avatar-custom-image';
|
|
47
|
+
// The image is the avatar's own presentation, not content of its own —
|
|
48
|
+
// the accessible name comes from the surrounding row, as in GTK where
|
|
49
|
+
// the custom image is a plain GtkImage inside the widget.
|
|
50
|
+
this._imageEl.alt = '';
|
|
51
|
+
|
|
52
|
+
this.replaceChildren(this._textEl, this._iconEl, this._imageEl);
|
|
42
53
|
this._render();
|
|
43
54
|
}
|
|
44
55
|
|
|
@@ -52,15 +63,29 @@ export class AdwAvatar extends HTMLElement {
|
|
|
52
63
|
this.style.height = `${size}px`;
|
|
53
64
|
|
|
54
65
|
const text = this.getAttribute('text') ?? '';
|
|
66
|
+
const customImage = this.getAttribute('custom-image') ?? '';
|
|
55
67
|
// The gate is the TEXT, not the derived initials — `update_visibility`
|
|
56
68
|
// keeps a whitespace-only name in initials mode with a blank label.
|
|
57
69
|
const mode = avatarMode({
|
|
58
|
-
hasCustomImage:
|
|
70
|
+
hasCustomImage: customImage.length > 0,
|
|
59
71
|
showInitials: this.hasAttribute('show-initials'),
|
|
60
72
|
text,
|
|
61
73
|
});
|
|
62
74
|
|
|
63
|
-
|
|
75
|
+
this._imageEl.hidden = mode !== 'image';
|
|
76
|
+
if (mode === 'image') {
|
|
77
|
+
// Only touch `src` when it changed: re-assigning the same URL
|
|
78
|
+
// restarts the load and flashes the avatar on every unrelated
|
|
79
|
+
// attribute change.
|
|
80
|
+
if (this._imageEl.getAttribute('src') !== customImage) this._imageEl.src = customImage;
|
|
81
|
+
// No gradient behind an image: `set_class_color` still runs in the C,
|
|
82
|
+
// but nothing of it is visible, and leaving it would show through a
|
|
83
|
+
// transparent PNG.
|
|
84
|
+
this.style.backgroundImage = '';
|
|
85
|
+
this.style.color = '';
|
|
86
|
+
this._textEl.hidden = true;
|
|
87
|
+
this._iconEl.hidden = true;
|
|
88
|
+
} else if (mode === 'initials') {
|
|
64
89
|
const { fg, start, stop } = avatarColor(text);
|
|
65
90
|
this.style.backgroundImage = `linear-gradient(${start}, ${stop})`;
|
|
66
91
|
this.style.color = fg;
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
// <adw-banner> —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// button-style ("default" | "suggested").
|
|
5
|
-
// Events: `button-clicked` (CustomEvent) when the action button is pressed.
|
|
1
|
+
// <adw-banner> — a full-width strip carrying a single in-context message and an optional
|
|
2
|
+
// action button. `button-style` is `"default" | "suggested"`, and `button-clicked`
|
|
3
|
+
// (CustomEvent) fires when the button is pressed.
|
|
6
4
|
//
|
|
7
|
-
// The defaults and the derivations
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// there, and neither renderer had `button-style` (Since 1.7) or stripped the
|
|
11
|
-
// mnemonic marker the template pins onto the button (adw-banner.ui:33).
|
|
5
|
+
// The defaults and the derivations (including stripping the mnemonic marker the upstream
|
|
6
|
+
// template pins onto the button label) come from `@gjsify/adwaita-core` (ADR 0004), so
|
|
7
|
+
// this element and the NativeScript one answer the same markup the same way.
|
|
12
8
|
//
|
|
13
9
|
// Reference: refs/libadwaita/src/adw-banner.c (AdwBanner)
|
|
14
10
|
// Reference: refs/libadwaita/src/adw-banner.ui
|
|
15
|
-
// Reference: refs/libadwaita/src/stylesheet/widgets/_toolbars.scss (banner
|
|
11
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_toolbars.scss (banner)
|
|
16
12
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
17
13
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
18
14
|
|
|
@@ -57,7 +53,7 @@ export class AdwBanner extends HTMLElement {
|
|
|
57
53
|
private _render() {
|
|
58
54
|
const title = this.getAttribute('title') ?? '';
|
|
59
55
|
// DELIBERATE DEPARTURE FROM THE C DEFAULT. `Adw.Banner:use-markup`
|
|
60
|
-
// defaults to TRUE
|
|
56
|
+
// defaults to TRUE and `@gjsify/adwaita-core`
|
|
61
57
|
// carries that as the spec value — but Pango markup is not HTML. Pango
|
|
62
58
|
// has a fixed tag set and no scripting; `innerHTML` will run an
|
|
63
59
|
// `onerror` handler. Adopting the C default here would turn every
|
|
@@ -65,27 +61,27 @@ export class AdwBanner extends HTMLElement {
|
|
|
65
61
|
// in GTK, so on this renderer markup is OPT-IN and the default is text.
|
|
66
62
|
// The opt-in is the developer asserting the title is trusted.
|
|
67
63
|
// The title label carries `use-underline=False` in the template
|
|
68
|
-
|
|
64
|
+
//, so its underscores are literal — no stripping here.
|
|
69
65
|
if (this.hasAttribute('use-markup')) this._titleEl.innerHTML = title;
|
|
70
66
|
else this._titleEl.textContent = title;
|
|
71
67
|
|
|
72
|
-
// `label && label[0]`
|
|
68
|
+
// `label && label[0]` — a first-character test, so a label of
|
|
73
69
|
// spaces still shows a button. The template pins the BUTTON to
|
|
74
|
-
// `use-underline=True
|
|
70
|
+
// `use-underline=True`, so the painted text drops the
|
|
75
71
|
// mnemonic markers the attribute keeps.
|
|
76
72
|
const buttonLabel = this.getAttribute('button-label') ?? '';
|
|
77
73
|
this._button.textContent = bannerButtonText(buttonLabel);
|
|
78
74
|
this._button.hidden = !bannerButtonVisible(buttonLabel);
|
|
79
75
|
|
|
80
|
-
// `button-style` (Since 1.7
|
|
81
|
-
//
|
|
76
|
+
// `button-style` (Since 1.7) adds or removes exactly one class
|
|
77
|
+
// — swap within the managed set so nothing else is touched.
|
|
82
78
|
const style = parseBannerButtonStyle(this.getAttribute('button-style'));
|
|
83
79
|
const applied = bannerButtonStyleClasses(style);
|
|
84
80
|
for (const cls of ADW_BANNER_BUTTON_STYLE_CLASSES) {
|
|
85
81
|
this._button.classList.toggle(cls, applied.includes(cls));
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
// `revealed` (default FALSE
|
|
84
|
+
// `revealed` (default FALSE) slides the strip in/out.
|
|
89
85
|
this.classList.toggle('revealed', this.hasAttribute('revealed'));
|
|
90
86
|
}
|
|
91
87
|
}
|
|
@@ -5,18 +5,14 @@
|
|
|
5
5
|
// layer covers the content and dismisses the sheet on click (unless `can-close`
|
|
6
6
|
// is false, in which case a close attempt is signalled instead).
|
|
7
7
|
//
|
|
8
|
-
// The open/closed state and the dismissal gate are NOT implemented here: they
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// open (adw-bottom-sheet.c:1670 — `open = !!open` on a strict gboolean).
|
|
17
|
-
// - `notify::open` follows the STATE, not the attribute VALUE. Rewriting the
|
|
18
|
-
// attribute to another truthy spelling no longer fires a notification
|
|
19
|
-
// carrying an unchanged payload (adw-bottom-sheet.c:1672-1682).
|
|
8
|
+
// The open/closed state and the dismissal gate are NOT implemented here: they live in
|
|
9
|
+
// `@gjsify/adwaita-core` as `BottomSheetPresentation` + `resolveBottomSheetClose`,
|
|
10
|
+
// ported from the C source and shared with `@gjsify/adwaita-nativescript` (ADR 0004).
|
|
11
|
+
// This element owns only the DOM. Three rules are easy to get wrong:
|
|
12
|
+
// - `open="false"` means CLOSED, like every other boolean here — C's setter is
|
|
13
|
+
// `open = !!open` on a strict gboolean, so `hasAttribute` is the wrong test.
|
|
14
|
+
// - `notify::open` follows the STATE, not the attribute VALUE, so rewriting the
|
|
15
|
+
// attribute to another truthy spelling notifies nobody.
|
|
20
16
|
// - the drag handle is decorative, not a close button — see below.
|
|
21
17
|
//
|
|
22
18
|
// Children are declared as <adw-bottom-sheet-content> (the persistent content)
|
|
@@ -38,10 +34,9 @@
|
|
|
38
34
|
// state changes — mirrors the Adw.BottomSheet `open` GObject property.
|
|
39
35
|
// `close-attempt` (CustomEvent, bubbles) when a dismissal is attempted that the
|
|
40
36
|
// sheet refuses — mirrors the Adw.BottomSheet `close-attempt` signal.
|
|
41
|
-
// `sheet.close` (CustomEvent, bubbles) when the sheet.close action is used on
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// (adw-bottom-sheet.c:382-385).
|
|
37
|
+
// `sheet.close` (CustomEvent, bubbles) when the sheet.close action is used on an
|
|
38
|
+
// ALREADY-closed sheet, so an enclosing sheet/dialog can handle it — the DOM
|
|
39
|
+
// equivalent of `gtk_widget_activate_action (parent, "sheet.close")`.
|
|
45
40
|
//
|
|
46
41
|
// Reference: refs/libadwaita/src/adw-bottom-sheet.c (AdwBottomSheet behaviour)
|
|
47
42
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_bottom-sheet.scss
|
|
@@ -72,7 +67,6 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
72
67
|
return ['open', 'modal', 'can-close', 'show-drag-handle'];
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
/** Whether the sheet is revealed. */
|
|
76
70
|
get open(): boolean {
|
|
77
71
|
return this._state.open;
|
|
78
72
|
}
|
|
@@ -81,7 +75,6 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
81
75
|
this._setOpen(!!value);
|
|
82
76
|
}
|
|
83
77
|
|
|
84
|
-
/** Whether the content is dimmed + blocked while the sheet is open. */
|
|
85
78
|
get modal(): boolean {
|
|
86
79
|
return this._boolAttr('modal', true);
|
|
87
80
|
}
|
|
@@ -99,7 +92,6 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
99
92
|
this._setBoolAttr('can-close', value);
|
|
100
93
|
}
|
|
101
94
|
|
|
102
|
-
/** Whether the overlaid drag handle is shown. */
|
|
103
95
|
get showDragHandle(): boolean {
|
|
104
96
|
return this._boolAttr('show-drag-handle', true);
|
|
105
97
|
}
|
|
@@ -109,14 +101,13 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
109
101
|
}
|
|
110
102
|
|
|
111
103
|
/**
|
|
112
|
-
* Route a dismissal affordance through the shared gate and act on the
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
104
|
+
* Route a dismissal affordance through the shared gate and act on the verdict: close,
|
|
105
|
+
* raise `close-attempt`, forward `sheet.close` to an ancestor, or do nothing. Called
|
|
106
|
+
* for the dimming layer and Escape; a close button inside the sheet passes
|
|
107
|
+
* `'close-button'`.
|
|
116
108
|
*
|
|
117
|
-
* The verdict differs
|
|
118
|
-
*
|
|
119
|
-
* `resolveBottomSheetClose` in `@gjsify/adwaita-core`.
|
|
109
|
+
* The verdict differs PER SOURCE — a locked sheet signals a scrim click but swallows
|
|
110
|
+
* a swipe, and the drag handle never closes anything (`resolveBottomSheetClose`).
|
|
120
111
|
*/
|
|
121
112
|
requestClose(source: BottomSheetCloseSource): BottomSheetCloseOutcome {
|
|
122
113
|
const outcome = this._state.requestClose(source);
|
|
@@ -132,37 +123,30 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
132
123
|
if (this._initialized) return;
|
|
133
124
|
this._initialized = true;
|
|
134
125
|
|
|
135
|
-
//
|
|
136
|
-
// and content are placed by slot; anything unslotted falls back to the
|
|
126
|
+
// The sheet and content are placed by slot; anything unslotted falls back to the
|
|
137
127
|
// content (the AdwBottomSheet GtkBuildable default).
|
|
138
128
|
const claimed = new Set<Node>();
|
|
139
129
|
const sheetChildren = this._collectSlot('adw-bottom-sheet-sheet', 'sheet', claimed);
|
|
140
130
|
const contentChildren = this._collectSlot('adw-bottom-sheet-content', 'content', claimed);
|
|
141
131
|
const unslotted = Array.from(this.childNodes).filter((n) => !claimed.has(n));
|
|
142
132
|
|
|
143
|
-
// Persistent content layer.
|
|
144
133
|
this._contentEl = document.createElement('div');
|
|
145
134
|
this._contentEl.className = 'adw-bottom-sheet-content';
|
|
146
135
|
for (const child of contentChildren) this._contentEl.appendChild(child);
|
|
147
136
|
for (const child of unslotted) this._contentEl.appendChild(child);
|
|
148
137
|
|
|
149
|
-
// Dimming layer — covers the content while the sheet is open (modal).
|
|
150
|
-
// Clicking it dismisses the sheet (or raises close-attempt when locked).
|
|
151
138
|
this._dimmingEl = document.createElement('div');
|
|
152
139
|
this._dimmingEl.className = 'adw-bottom-sheet-dimming';
|
|
153
140
|
this._dimmingEl.addEventListener('click', () => this.requestClose('dimming'));
|
|
154
141
|
|
|
155
|
-
// The slide-up sheet with its overlaid drag handle.
|
|
156
142
|
this._sheetEl = document.createElement('div');
|
|
157
143
|
this._sheetEl.className = 'adw-bottom-sheet-sheet';
|
|
158
144
|
|
|
159
|
-
// libadwaita builds the handle with can_focus = FALSE and
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
// role
|
|
163
|
-
//
|
|
164
|
-
// role="button", tabindex=0 and a click-to-close handler, which is the
|
|
165
|
-
// one behaviour it provably does not have.
|
|
145
|
+
// libadwaita builds the handle with can_focus = FALSE and can_target = FALSE: a
|
|
146
|
+
// decorative pill that cannot be clicked or focused at all. Its only behavioural
|
|
147
|
+
// role is elsewhere — `allow_mouse_drag = show_drag_handle || bottom_bar`. Giving
|
|
148
|
+
// it role="button" and a click-to-close handler is the one behaviour it provably
|
|
149
|
+
// does not have.
|
|
166
150
|
this._dragHandleEl = document.createElement('div');
|
|
167
151
|
this._dragHandleEl.className = 'adw-bottom-sheet-drag-handle';
|
|
168
152
|
this._dragHandleEl.setAttribute('aria-hidden', 'true');
|
|
@@ -175,12 +159,11 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
175
159
|
|
|
176
160
|
this.replaceChildren(this._contentEl, this._dimmingEl, this._sheetEl);
|
|
177
161
|
|
|
178
|
-
// Escape → maybe_close_cb
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
// one only when focus is still inside its (off-screen) body.
|
|
162
|
+
// Escape → `maybe_close_cb`. In GTK the shortcut sits on the sheet itself, so it
|
|
163
|
+
// fires exactly while the sheet has focus — INCLUDING while the sheet is closed,
|
|
164
|
+
// which is the corner that still emits `close-attempt`. This port has no focus
|
|
165
|
+
// transfer into the sheet, so an open sheet counts as the focused surface and a
|
|
166
|
+
// closed one only when focus is still inside its (off-screen) body.
|
|
184
167
|
this.addEventListener('keydown', (event) => {
|
|
185
168
|
if (event.key !== 'Escape') return;
|
|
186
169
|
if (!this.open && !this._sheetEl.contains(event.target as Node)) return;
|
|
@@ -188,9 +171,8 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
188
171
|
this.requestClose('escape');
|
|
189
172
|
});
|
|
190
173
|
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
// silent, as an attribute set during upgrade has always been.
|
|
174
|
+
// Seeding BEFORE the subscription keeps the initial value silent, which is what
|
|
175
|
+
// an attribute set during upgrade must be.
|
|
194
176
|
this._state.setCanClose(this._boolAttr('can-close', true));
|
|
195
177
|
this._state.setOpen(this._boolAttr('open', false));
|
|
196
178
|
this._state.subscribe((open) => {
|
|
@@ -234,13 +216,10 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
234
216
|
this.classList.toggle('modal', this.modal);
|
|
235
217
|
this.classList.toggle('has-drag-handle', this.showDragHandle);
|
|
236
218
|
|
|
237
|
-
// The dimming layer is only meaningful while modal; it fades in with the
|
|
238
|
-
// sheet and is non-interactive when closed.
|
|
239
219
|
this._dimmingEl.classList.toggle('visible', open && this.modal);
|
|
240
220
|
this._dragHandleEl.hidden = !this.showDragHandle;
|
|
241
221
|
}
|
|
242
222
|
|
|
243
|
-
/** Mirror the state onto the `open` attribute (the CSS + markup hook). */
|
|
244
223
|
private _reflectOpen(open: boolean): void {
|
|
245
224
|
this._reflecting = true;
|
|
246
225
|
if (open) this.setAttribute('open', '');
|
|
@@ -265,7 +244,7 @@ export class AdwBottomSheet extends HTMLElement {
|
|
|
265
244
|
*
|
|
266
245
|
* The `<adw-bottom-sheet-sheet>` / `<adw-bottom-sheet-content>` wrappers are
|
|
267
246
|
* markup, not widgets — GtkBuilder's `<child type="sheet">` leaves nothing in
|
|
268
|
-
* the tree
|
|
247
|
+
* the tree — so the wrapper itself is claimed
|
|
269
248
|
* too. It used to fall through to `unslotted` and be appended to the content
|
|
270
249
|
* layer, leaving an empty custom element behind after its children were
|
|
271
250
|
* pulled out.
|