@gjsify/adwaita-web 0.35.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
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
// <adw-popover> — the elevated surface that pops up next to an anchor, the web
|
|
2
|
-
// counterpart of GtkPopover as libadwaita styles it. It is the ONE popover in
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// and one invented two-layer one) and so did the behaviour — the split button
|
|
7
|
-
// had neither Escape dismissal nor arrow-key navigation, while the other two
|
|
8
|
-
// spelled the same wrap arithmetic out twice.
|
|
2
|
+
// counterpart of GtkPopover as libadwaita styles it. It is the ONE popover in this
|
|
3
|
+
// package: `<adw-menu-button>`, `<adw-drop-down>` and `<adw-split-button>` all use it
|
|
4
|
+
// rather than building their own, which is what keeps the 15px radius, the
|
|
5
|
+
// three-layer shadow, Escape dismissal and the wrap arithmetic in one place.
|
|
9
6
|
//
|
|
10
7
|
// The dismissal + keyboard state machine lives in `@gjsify/adwaita-core`
|
|
11
8
|
// ({@link PopoverState}, {@link resolvePopoverKey}, ADR 0004); this element owns
|
|
@@ -19,23 +16,20 @@
|
|
|
19
16
|
//
|
|
20
17
|
// Attributes:
|
|
21
18
|
// open — boolean; reflects (and drives) the visible state.
|
|
22
|
-
// position — bottom | top | start | end (default bottom) — which side of the
|
|
23
|
-
//
|
|
24
|
-
// autohide/has-arrow semantics
|
|
25
|
-
//
|
|
26
|
-
// the placement axis is modelled.
|
|
19
|
+
// position — bottom | top | start | end (default bottom) — which side of the anchor
|
|
20
|
+
// the surface sits on. Only the placement axis is modelled:
|
|
21
|
+
// `GtkPopover:position`'s autohide/has-arrow semantics belong to GTK,
|
|
22
|
+
// which libadwaita does not vendor, so they are not guessed at.
|
|
27
23
|
// align — start | end (default start) — which edge it lines up with.
|
|
28
24
|
// role — menu | listbox (default menu) — the ARIA role of the surface.
|
|
29
25
|
// An a11y fact ONLY; it does not pick the surface variant, see
|
|
30
26
|
// `menu` below.
|
|
31
|
-
// menu — boolean; libadwaita's `.menu` STYLE CLASS on the popover node
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
// `
|
|
36
|
-
//
|
|
37
|
-
// `menuitem`s. Tying the surface to the role gets that backwards
|
|
38
|
-
// and pads the drop-down like a bare content popover.
|
|
27
|
+
// menu — boolean; libadwaita's `.menu` STYLE CLASS on the popover node (0
|
|
28
|
+
// padding on the contents, `$menu_margin` on the item box), opt-in
|
|
29
|
+
// exactly as it is in GTK. DO NOT infer it from `role`: `GtkDropDown`'s
|
|
30
|
+
// popover is a `popover.menu` too (`dropdown { popover.menu { … } }`)
|
|
31
|
+
// while its rows are `option`s, not `menuitem`s, so tying the surface to
|
|
32
|
+
// the role pads the drop-down like a bare content popover.
|
|
39
33
|
// Properties:
|
|
40
34
|
// open — whether the popover is showing (get/set).
|
|
41
35
|
// anchor — the element the surface is positioned against and returns focus
|
|
@@ -49,8 +43,8 @@
|
|
|
49
43
|
// `popover-item-activated` (CustomEvent, bubbles, detail = { index }) — a row
|
|
50
44
|
// was chosen by keyboard. Click activation stays the row's own listener.
|
|
51
45
|
//
|
|
52
|
-
// Reference: refs/libadwaita/src/stylesheet/widgets/_popovers.scss
|
|
53
|
-
// Reference: refs/libadwaita/src/stylesheet/widgets/_menus.scss
|
|
46
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_popovers.scss (popover > contents)
|
|
47
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_menus.scss (popover.menu, modelbutton)
|
|
54
48
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
55
49
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
56
50
|
|
|
@@ -82,16 +76,16 @@ export class AdwPopover extends HTMLElement {
|
|
|
82
76
|
private _onDocumentPointerDown = (event: Event): void => {
|
|
83
77
|
const target = event.target as Node;
|
|
84
78
|
if (this.contains(target)) return;
|
|
85
|
-
// A click on the anchor is the anchor's own toggle — closing here too
|
|
86
|
-
//
|
|
79
|
+
// A click on the anchor is the anchor's own toggle — closing here too would close
|
|
80
|
+
// and immediately reopen (or reopen and immediately close).
|
|
87
81
|
if (this.anchor?.contains(target) === true) return;
|
|
88
82
|
this._state.dismiss();
|
|
89
83
|
};
|
|
90
84
|
|
|
91
85
|
private _onDocumentKeyDown = (event: KeyboardEvent): void => {
|
|
92
86
|
if (event.key !== 'Escape') return;
|
|
93
|
-
// Captured at the document, so a nested popover's Escape does not also
|
|
94
|
-
//
|
|
87
|
+
// Captured at the document, so a nested popover's Escape does not also close its
|
|
88
|
+
// parent.
|
|
95
89
|
event.stopPropagation();
|
|
96
90
|
this._state.dismiss();
|
|
97
91
|
this.anchor?.focus();
|
|
@@ -101,7 +95,6 @@ export class AdwPopover extends HTMLElement {
|
|
|
101
95
|
return ['open', 'position', 'align', 'role', 'menu'];
|
|
102
96
|
}
|
|
103
97
|
|
|
104
|
-
/** Whether the popover is showing. */
|
|
105
98
|
get open(): boolean {
|
|
106
99
|
return this._state.open;
|
|
107
100
|
}
|
|
@@ -112,9 +105,8 @@ export class AdwPopover extends HTMLElement {
|
|
|
112
105
|
}
|
|
113
106
|
|
|
114
107
|
/**
|
|
115
|
-
* The element the surface is positioned against and hands focus back to.
|
|
116
|
-
*
|
|
117
|
-
* last child of the widget that owns it.
|
|
108
|
+
* The element the surface is positioned against and hands focus back to. Defaults to
|
|
109
|
+
* `parentElement`: the popover is normally the last child of the widget that owns it.
|
|
118
110
|
*/
|
|
119
111
|
get anchor(): HTMLElement | null {
|
|
120
112
|
return this._anchor ?? this.parentElement;
|
|
@@ -124,7 +116,6 @@ export class AdwPopover extends HTMLElement {
|
|
|
124
116
|
this._anchor = value;
|
|
125
117
|
}
|
|
126
118
|
|
|
127
|
-
/** Which side of the anchor the surface sits on. */
|
|
128
119
|
get position(): AdwPopoverPosition {
|
|
129
120
|
const value = this.getAttribute('position');
|
|
130
121
|
return POSITIONS.includes(value as AdwPopoverPosition) ? (value as AdwPopoverPosition) : 'bottom';
|
|
@@ -134,7 +125,6 @@ export class AdwPopover extends HTMLElement {
|
|
|
134
125
|
this.setAttribute('position', value);
|
|
135
126
|
}
|
|
136
127
|
|
|
137
|
-
/** Which edge of the anchor the surface lines up with. */
|
|
138
128
|
get align(): AdwPopoverAlign {
|
|
139
129
|
const value = this.getAttribute('align');
|
|
140
130
|
return ALIGNS.includes(value as AdwPopoverAlign) ? (value as AdwPopoverAlign) : 'start';
|
|
@@ -144,7 +134,6 @@ export class AdwPopover extends HTMLElement {
|
|
|
144
134
|
this.setAttribute('align', value);
|
|
145
135
|
}
|
|
146
136
|
|
|
147
|
-
/** The surface's ARIA role. */
|
|
148
137
|
get popoverRole(): AdwPopoverRole {
|
|
149
138
|
const value = this.getAttribute('role');
|
|
150
139
|
return ROLES.includes(value as AdwPopoverRole) ? (value as AdwPopoverRole) : 'menu';
|
|
@@ -176,10 +165,8 @@ export class AdwPopover extends HTMLElement {
|
|
|
176
165
|
|
|
177
166
|
this.classList.add('adw-popover');
|
|
178
167
|
if (!this.hasAttribute('role')) this.setAttribute('role', 'menu');
|
|
179
|
-
// The `menu` variant is NOT defaulted or inferred here — it is GTK's
|
|
180
|
-
//
|
|
181
|
-
// it, as in the C. See the attribute docs above for why role is the
|
|
182
|
-
// wrong thing to key it off.
|
|
168
|
+
// The `menu` variant is NOT defaulted or inferred here — it is GTK's `.menu`
|
|
169
|
+
// style class, added by the widget that owns the popover, as in the C.
|
|
183
170
|
|
|
184
171
|
this.addEventListener('keydown', (event) => this._onKeyDown(event));
|
|
185
172
|
this._state.subscribe((change) => this._onStateChange(change.open));
|
|
@@ -202,16 +189,14 @@ export class AdwPopover extends HTMLElement {
|
|
|
202
189
|
return;
|
|
203
190
|
}
|
|
204
191
|
if (name === 'role' && value === null) {
|
|
205
|
-
// A removed role falls back to the default rather than leaving the
|
|
206
|
-
//
|
|
192
|
+
// A removed role falls back to the default rather than leaving the surface
|
|
193
|
+
// unlabelled to assistive tech.
|
|
207
194
|
this.setAttribute('role', 'menu');
|
|
208
195
|
return;
|
|
209
196
|
}
|
|
210
197
|
this._render();
|
|
211
198
|
}
|
|
212
199
|
|
|
213
|
-
// ── internals ──────────────────────────────────────────────────────────
|
|
214
|
-
|
|
215
200
|
private _onStateChange(open: boolean): void {
|
|
216
201
|
if (open) this._bindDocument();
|
|
217
202
|
else this._unbindDocument();
|
|
@@ -269,9 +254,8 @@ export class AdwPopover extends HTMLElement {
|
|
|
269
254
|
return;
|
|
270
255
|
}
|
|
271
256
|
if (action === 'activate') {
|
|
272
|
-
// preventDefault BEFORE the synthetic click: a focused <button>
|
|
273
|
-
//
|
|
274
|
-
// without it the row would fire twice.
|
|
257
|
+
// preventDefault BEFORE the synthetic click: a focused <button> activates
|
|
258
|
+
// natively on Enter (keydown) and Space (keyup), so the row would fire twice.
|
|
275
259
|
event.preventDefault();
|
|
276
260
|
items[index]?.click();
|
|
277
261
|
this.dispatchEvent(new CustomEvent('popover-item-activated', { bubbles: true, detail: { index } }));
|
|
@@ -4,22 +4,18 @@
|
|
|
4
4
|
// clamp of <adw-preferences-group> boxed lists — under a flat header bar that
|
|
5
5
|
// shows the dialog title and a trailing close button.
|
|
6
6
|
//
|
|
7
|
-
// Pages are declared as <adw-preferences-page> children (each with optional
|
|
8
|
-
// `
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// adw_preferences_dialog_add.
|
|
12
|
-
// (Adw shows a view-switcher when there is more than one page and the window is
|
|
13
|
-
// wide, and shows exactly one page at a time; the web port renders the pages
|
|
14
|
-
// stacked, matching the single-page common case the storybook exercises. The
|
|
15
|
-
// page COLLECTION model — visible-page, visible-page-name, the auto-pick —
|
|
16
|
-
// belongs to Adw.ViewStack and is available as `ViewStackState` in
|
|
17
|
-
// @gjsify/adwaita-core when this port grows a switcher.)
|
|
7
|
+
// Pages are declared as <adw-preferences-page> children (each with optional `title` /
|
|
8
|
+
// `icon-name` / `name` / `use-underline` attributes + arbitrary group content) and are
|
|
9
|
+
// moved — as ELEMENTS, keeping their identity — into the scrolled body.
|
|
10
|
+
// `add(page)` accepts one at any later time, mirroring `adw_preferences_dialog_add`.
|
|
18
11
|
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// @gjsify/adwaita-core
|
|
22
|
-
//
|
|
12
|
+
// DEVIATION: Adw shows one page at a time behind a view switcher; this port renders
|
|
13
|
+
// them stacked. The page COLLECTION model belongs to Adw.ViewStack and is available as
|
|
14
|
+
// `ViewStackState` in @gjsify/adwaita-core when this port grows a switcher.
|
|
15
|
+
//
|
|
16
|
+
// `search(query)` searches those pages: corpus, filter and `page → group` result
|
|
17
|
+
// subtitles all come from @gjsify/adwaita-core, so the browser and NativeScript dialogs
|
|
18
|
+
// answer identically for the same tree.
|
|
23
19
|
//
|
|
24
20
|
// Like Adw.Dialog the dialog starts hidden; `present()` / setting the `open`
|
|
25
21
|
// attribute reveals it. It is dismissed by Escape, by clicking the scrim, or by
|
|
@@ -58,15 +54,12 @@ import { searchPreferencesDom } from '../preferences-search.js';
|
|
|
58
54
|
const DEFAULT_CONTENT_WIDTH = 640;
|
|
59
55
|
|
|
60
56
|
/**
|
|
61
|
-
* A single preferences page.
|
|
62
|
-
* `title` / `icon-name` / `name` / `use-underline` attributes and any
|
|
63
|
-
* <adw-preferences-group> content — mirrors Adw.PreferencesPage.
|
|
57
|
+
* A single preferences page (Adw.PreferencesPage), child of <adw-preferences-dialog>.
|
|
64
58
|
*
|
|
65
|
-
* The dialog keeps the page ELEMENT rather than hoisting its children out,
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* page
|
|
69
|
-
* needs the title back the moment a second page is visible.
|
|
59
|
+
* The dialog keeps the page ELEMENT rather than hoisting its children out, because the
|
|
60
|
+
* page's identity is load-bearing: `adw_preferences_dialog_add` binds title / icon-name
|
|
61
|
+
* / name / use-underline / visible onto the view-stack page, and
|
|
62
|
+
* `create_search_row_subtitle` needs the title back the moment a second page is visible.
|
|
70
63
|
*/
|
|
71
64
|
export class AdwPreferencesPage extends HTMLElement {
|
|
72
65
|
static get observedAttributes() {
|
|
@@ -100,7 +93,6 @@ export class AdwPreferencesPage extends HTMLElement {
|
|
|
100
93
|
this.toggleAttribute('use-underline', value);
|
|
101
94
|
}
|
|
102
95
|
|
|
103
|
-
/** The page's groups, in document order. */
|
|
104
96
|
get groups(): HTMLElement[] {
|
|
105
97
|
return Array.from(this.querySelectorAll('adw-preferences-group'));
|
|
106
98
|
}
|
|
@@ -119,7 +111,6 @@ export class AdwPreferencesDialog extends HTMLElement {
|
|
|
119
111
|
return ['title', 'open', 'can-close', 'content-width'];
|
|
120
112
|
}
|
|
121
113
|
|
|
122
|
-
/** Whether the dialog is revealed. */
|
|
123
114
|
get open(): boolean {
|
|
124
115
|
return this.hasAttribute('open');
|
|
125
116
|
}
|
|
@@ -169,27 +160,23 @@ export class AdwPreferencesDialog extends HTMLElement {
|
|
|
169
160
|
}
|
|
170
161
|
|
|
171
162
|
/**
|
|
172
|
-
* `adw_preferences_dialog_add`
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* page appended later stayed a sibling of the scrim and rendered OUTSIDE
|
|
177
|
-
* the dialog card. GTK and the NativeScript port both accept a page
|
|
178
|
-
* whenever it arrives.
|
|
163
|
+
* `adw_preferences_dialog_add` — add a page at ANY time. A page that is only
|
|
164
|
+
* snapshotted in `connectedCallback` stays a sibling of the scrim and renders
|
|
165
|
+
* OUTSIDE the dialog card; GTK and the NativeScript port both accept one whenever
|
|
166
|
+
* it arrives.
|
|
179
167
|
*/
|
|
180
168
|
add(page: AdwPreferencesPage): void {
|
|
181
169
|
this._pagesEl.appendChild(page);
|
|
182
170
|
}
|
|
183
171
|
|
|
184
172
|
/**
|
|
185
|
-
* `adw_preferences_dialog_remove
|
|
186
|
-
*
|
|
187
|
-
*
|
|
173
|
+
* `adw_preferences_dialog_remove`. C logs `ADW_CRITICAL_CANNOT_REMOVE_CHILD` and
|
|
174
|
+
* no-ops for a page it does not own; returning `false` is the same contract without
|
|
175
|
+
* a console write.
|
|
188
176
|
*
|
|
189
|
-
* NOT named `remove`: `ChildNode.remove()`
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* requires.
|
|
177
|
+
* NOT named `remove`: `ChildNode.remove()` exists on every element and takes no
|
|
178
|
+
* arguments, so overriding it with a different signature makes the class
|
|
179
|
+
* un-assignable to `HTMLElement` — which `customElements.define` requires.
|
|
193
180
|
*/
|
|
194
181
|
removePage(page: AdwPreferencesPage): boolean {
|
|
195
182
|
if (page.parentNode !== this._pagesEl) return false;
|
|
@@ -197,22 +184,18 @@ export class AdwPreferencesDialog extends HTMLElement {
|
|
|
197
184
|
return true;
|
|
198
185
|
}
|
|
199
186
|
|
|
200
|
-
/** The dialog's pages, in add order. */
|
|
201
187
|
get pages(): AdwPreferencesPage[] {
|
|
202
188
|
return Array.from(this._pagesEl.querySelectorAll('adw-preferences-page')) as AdwPreferencesPage[];
|
|
203
189
|
}
|
|
204
190
|
|
|
205
191
|
/**
|
|
206
|
-
* The preferences search (`filter_search_results` +
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* NativeScript one answer identically for the same tree — including the
|
|
211
|
-
* three corpus filters (visible page → visible group → titled visible row)
|
|
212
|
-
* and the case fold that makes `strasse` find `Straße`.
|
|
192
|
+
* The preferences search (`filter_search_results` + `create_search_row_subtitle`),
|
|
193
|
+
* over this dialog's live page subtree. The pipeline is `@gjsify/adwaita-core`'s,
|
|
194
|
+
* including the three corpus filters (visible page → visible group → titled visible
|
|
195
|
+
* row) and the case fold that makes `strasse` find `Straße`.
|
|
213
196
|
*
|
|
214
|
-
* An empty query returns the WHOLE corpus, which is what
|
|
215
|
-
*
|
|
197
|
+
* An empty query returns the WHOLE corpus, as C does, which is what makes the
|
|
198
|
+
* results list non-empty before the user types.
|
|
216
199
|
*/
|
|
217
200
|
search(query: string, options?: SearchPreferencesOptions): PreferencesSearchResult<Element>[] {
|
|
218
201
|
return searchPreferencesDom(this._pagesEl, query, options);
|
|
@@ -222,21 +205,17 @@ export class AdwPreferencesDialog extends HTMLElement {
|
|
|
222
205
|
if (this._initialized) return;
|
|
223
206
|
this._initialized = true;
|
|
224
207
|
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
// buildable default adds a child as a page).
|
|
208
|
+
// The page ELEMENTS move into the body intact — their title / name / icon-name
|
|
209
|
+
// are what a search result subtitle and a view switcher read — and any unwrapped
|
|
210
|
+
// children follow them (Adw.PreferencesDialog's buildable default adds a child
|
|
211
|
+
// as a page).
|
|
230
212
|
const declared = Array.from(this.childNodes);
|
|
231
213
|
|
|
232
|
-
//
|
|
233
|
-
// close-attempt when locked).
|
|
214
|
+
// Clicking the scrim dismisses the dialog, or raises close-attempt when locked.
|
|
234
215
|
this._scrimEl = document.createElement('div');
|
|
235
216
|
this._scrimEl.className = 'adw-preferences-dialog-scrim';
|
|
236
217
|
this._scrimEl.addEventListener('click', () => this._attemptClose());
|
|
237
218
|
|
|
238
|
-
// The centred dialog box: a flat header bar (title + close button) above
|
|
239
|
-
// a scrolled preferences body.
|
|
240
219
|
this._dialogEl = document.createElement('div');
|
|
241
220
|
this._dialogEl.className = 'adw-preferences-dialog-box';
|
|
242
221
|
this._dialogEl.setAttribute('role', 'dialog');
|
|
@@ -262,14 +241,13 @@ export class AdwPreferencesDialog extends HTMLElement {
|
|
|
262
241
|
this._closeBtn.type = 'button';
|
|
263
242
|
this._closeBtn.className = 'adw-preferences-dialog-close';
|
|
264
243
|
this._closeBtn.setAttribute('aria-label', 'Close');
|
|
265
|
-
//
|
|
266
|
-
//
|
|
244
|
+
// The "×" is a CSS glyph: `window-close` has no mask class, because it is not in
|
|
245
|
+
// the ICONS map in `scripts/build-scss.mjs` that generates them.
|
|
267
246
|
this._closeBtn.addEventListener('click', () => this._attemptClose());
|
|
268
247
|
trailing.appendChild(this._closeBtn);
|
|
269
248
|
|
|
270
249
|
header.append(leading, this._titleEl, trailing);
|
|
271
250
|
|
|
272
|
-
// Scrolled body — a clamped column of the declared pages.
|
|
273
251
|
this._bodyEl = document.createElement('div');
|
|
274
252
|
this._bodyEl.className = 'adw-preferences-dialog-body';
|
|
275
253
|
|
|
@@ -282,9 +260,8 @@ export class AdwPreferencesDialog extends HTMLElement {
|
|
|
282
260
|
|
|
283
261
|
this.replaceChildren(this._scrimEl, this._dialogEl);
|
|
284
262
|
|
|
285
|
-
// Escape closes the dialog while open (
|
|
286
|
-
//
|
|
287
|
-
// the key event when the dialog has focus.
|
|
263
|
+
// Escape closes the dialog while open (Adw.Dialog's Escape shortcut →
|
|
264
|
+
// `maybe_close_cb`). The element is focusable so it can receive the key event.
|
|
288
265
|
if (!this.hasAttribute('tabindex')) this.tabIndex = -1;
|
|
289
266
|
this.addEventListener('keydown', (event) => {
|
|
290
267
|
if (event.key === 'Escape' && this.open) {
|
|
@@ -324,8 +301,6 @@ export class AdwPreferencesDialog extends HTMLElement {
|
|
|
324
301
|
|
|
325
302
|
this._dialogEl.style.setProperty('--adw-dialog-content-width', `${this.contentWidth}px`);
|
|
326
303
|
|
|
327
|
-
// While locked the close button has no effect; reflect that visually +
|
|
328
|
-
// for assistive tech.
|
|
329
304
|
const locked = !this.canClose;
|
|
330
305
|
this._closeBtn.disabled = locked;
|
|
331
306
|
this._closeBtn.classList.toggle('locked', locked);
|
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
// <adw-preferences-group> —
|
|
2
|
-
//
|
|
3
|
-
// widget (e.g. a flat "Sign out" button at the trailing edge of the header).
|
|
4
|
-
// Attributes: title, description.
|
|
5
|
-
// Property: description (get/set, proxies the attribute).
|
|
6
|
-
// Header-suffix: any child carrying `slot="header-suffix"` is hoisted into the
|
|
7
|
-
// trailing edge of the header instead of the boxed list — mirroring
|
|
8
|
-
// Adw.PreferencesGroup's `header-suffix` property / buildable type.
|
|
1
|
+
// <adw-preferences-group> — groups child rows in a boxed list with a title, an
|
|
2
|
+
// optional dimmed description line and an optional trailing header-suffix widget.
|
|
9
3
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
4
|
+
// Any child carrying `slot="header-suffix"` is hoisted into the trailing edge of the
|
|
5
|
+
// header instead of the boxed list, mirroring Adw.PreferencesGroup's `header-suffix`
|
|
6
|
+
// property / buildable type.
|
|
7
|
+
//
|
|
8
|
+
// The five visibility states (title, description, header, `single-line`, listbox) are
|
|
9
|
+
// NOT derived here: they come from `derivePreferencesGroupHeader` in
|
|
10
|
+
// `@gjsify/adwaita-core`, which this element and the NativeScript group both delegate
|
|
11
|
+
// to (ADR 0004).
|
|
17
12
|
//
|
|
18
13
|
// Reference: refs/libadwaita/src/adw-preferences-group.c
|
|
19
14
|
// (update_title_visibility, update_listbox_visibility, is_single_line,
|
|
@@ -57,14 +52,13 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
57
52
|
}
|
|
58
53
|
|
|
59
54
|
/**
|
|
60
|
-
* `adw_preferences_group_add
|
|
61
|
-
*
|
|
55
|
+
* `adw_preferences_group_add`: a row goes into the boxed list, a `header-suffix`
|
|
56
|
+
* child into the header.
|
|
62
57
|
*
|
|
63
|
-
* Exposed as a method AND wired to a `MutationObserver` on the host, because
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* row count that decides whether the card is painted at all.
|
|
58
|
+
* Exposed as a method AND wired to a `MutationObserver` on the host, because both
|
|
59
|
+
* spellings occur — `group.addRow(row)` in code, `group.append(row)` from a
|
|
60
|
+
* framework's rendering — and a bare child of the host would sit outside the card,
|
|
61
|
+
* invisible to the row count that decides whether the card is painted at all.
|
|
68
62
|
*/
|
|
69
63
|
addRow(child: Node): void {
|
|
70
64
|
if (isHeaderSuffix(child)) this._suffixEl.appendChild(child);
|
|
@@ -73,9 +67,9 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
73
67
|
}
|
|
74
68
|
|
|
75
69
|
/**
|
|
76
|
-
* `adw_preferences_group_remove
|
|
77
|
-
*
|
|
78
|
-
*
|
|
70
|
+
* `adw_preferences_group_remove`. C logs `ADW_CRITICAL_CANNOT_REMOVE_CHILD` and
|
|
71
|
+
* no-ops for a child it does not own; returning `false` is the same contract
|
|
72
|
+
* without a console write.
|
|
79
73
|
*/
|
|
80
74
|
removeRow(child: Node): boolean {
|
|
81
75
|
const parent = child.parentNode;
|
|
@@ -93,13 +87,9 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
93
87
|
this._initialized = true;
|
|
94
88
|
|
|
95
89
|
const children = Array.from(this.childNodes);
|
|
96
|
-
// Children opting into the header-suffix slot are hoisted to the
|
|
97
|
-
// trailing edge of the header; everything else goes into the boxed list.
|
|
98
90
|
const suffixChildren = children.filter(isHeaderSuffix);
|
|
99
91
|
const rowChildren = children.filter((child) => !suffixChildren.includes(child));
|
|
100
92
|
|
|
101
|
-
// Header: a flex row with a title/description label column on the left
|
|
102
|
-
// and the optional header-suffix on the trailing edge.
|
|
103
93
|
this._headerEl = document.createElement('div');
|
|
104
94
|
this._headerEl.className = 'adw-preferences-group-header';
|
|
105
95
|
|
|
@@ -120,7 +110,6 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
120
110
|
|
|
121
111
|
this._headerEl.append(labels, this._suffixEl);
|
|
122
112
|
|
|
123
|
-
// Boxed list container — the remaining children move into it.
|
|
124
113
|
this._listboxEl = document.createElement('div');
|
|
125
114
|
this._listboxEl.className = 'adw-preferences-group-listbox';
|
|
126
115
|
for (const child of rowChildren) this._listboxEl.appendChild(child);
|
|
@@ -146,18 +135,17 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
146
135
|
* Adopt children appended to the HOST after the subtree was built, so
|
|
147
136
|
* `group.append(row)` behaves like `adw_preferences_group_add`.
|
|
148
137
|
*
|
|
149
|
-
* C gets this for free
|
|
150
|
-
* `update_listbox_visibility` through `items-changed
|
|
151
|
-
*
|
|
152
|
-
* to have the same rule hold over time rather than only at connect.
|
|
138
|
+
* C gets this for free: `gtk_widget_observe_children` on the listbox drives
|
|
139
|
+
* `update_listbox_visibility` through `items-changed`. A DOM element needs the
|
|
140
|
+
* observer for the same rule to hold over time rather than only at connect.
|
|
153
141
|
*/
|
|
154
142
|
private _observeHost(): void {
|
|
155
143
|
if (this._observer) return;
|
|
156
144
|
this._observer = new MutationObserver((records) => {
|
|
157
145
|
for (const record of records) {
|
|
158
|
-
// Only the HOST's own children need re-homing. Records from the
|
|
159
|
-
//
|
|
160
|
-
//
|
|
146
|
+
// Only the HOST's own children need re-homing. Records from the listbox are
|
|
147
|
+
// the CONSEQUENCE of that move; re-adopting them would append each row a
|
|
148
|
+
// second time and reorder the list.
|
|
161
149
|
if (record.target !== this) continue;
|
|
162
150
|
for (const node of record.addedNodes) {
|
|
163
151
|
if (!(node instanceof Element)) continue;
|
|
@@ -183,22 +171,21 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
183
171
|
title,
|
|
184
172
|
description,
|
|
185
173
|
hasHeaderSuffix: this._suffixEl.childElementCount > 0,
|
|
186
|
-
// The RAW child count, matching `gtk_widget_observe_children
|
|
187
|
-
//
|
|
174
|
+
// The RAW child count, matching `gtk_widget_observe_children`: a row with an
|
|
175
|
+
// empty title still keeps the card painted.
|
|
188
176
|
rowCount: this._listboxEl.childElementCount,
|
|
189
|
-
// The labels are `textContent`, not markup
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
// here would hide a title this element visibly renders.
|
|
177
|
+
// The labels are `textContent`, not markup, so visibility must be judged on
|
|
178
|
+
// the raw string. libadwaita's labels are `use-markup=True`, but rendering
|
|
179
|
+
// Pango markup in the browser is a separate open gap — half-closing it here
|
|
180
|
+
// would hide a title this element visibly renders.
|
|
194
181
|
useMarkup: false,
|
|
195
182
|
});
|
|
196
183
|
|
|
197
184
|
this._titleEl.hidden = !state.titleVisible;
|
|
198
185
|
this._descriptionEl.hidden = !state.descriptionVisible;
|
|
199
186
|
this._headerEl.hidden = !state.headerVisible;
|
|
200
|
-
// `single-line` is not cosmetic: the stylesheet keys the header's
|
|
201
|
-
//
|
|
187
|
+
// `single-line` is not cosmetic: the stylesheet keys the header's min-height and
|
|
188
|
+
// margin-bottom off it.
|
|
202
189
|
this._headerEl.classList.toggle('single-line', state.singleLine);
|
|
203
190
|
this._listboxEl.hidden = !state.listboxVisible;
|
|
204
191
|
}
|
|
@@ -1,43 +1,25 @@
|
|
|
1
1
|
// <adw-progress-bar> — the DETERMINATE progress indicator, and the partner to
|
|
2
2
|
// <adw-spinner>, which only ever covers "busy, no idea how long".
|
|
3
3
|
//
|
|
4
|
-
// NO CORE STATE MACHINE
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// `@gjsify/adwaita-core`. Nothing was added there for this element.
|
|
4
|
+
// NO CORE STATE MACHINE: the whole behaviour is `CLAMP(fraction, 0, 1)` plus a pulsing
|
|
5
|
+
// flag, and ADR 0004 is explicit that trivial behaviour gets no core class. The clamp
|
|
6
|
+
// is still shared — `glibClamp` (GLib's CLAMP, high bound tested FIRST) comes from
|
|
7
|
+
// `@gjsify/adwaita-core`.
|
|
9
8
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// `
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// {@link AdwProgressBar.pulse} ENTERS that state and is idempotent afterwards
|
|
18
|
-
// rather than advancing an offset nothing here could check. The
|
|
19
|
-
// default `show-text` label — `NN%` — is likewise this port's, not a
|
|
20
|
-
// reproduction of GTK's format string.
|
|
9
|
+
// `gtk_progress_bar_pulse()`'s step semantics (the `pulse-step` default, how the block
|
|
10
|
+
// reflects at the ends, whether it resets `fraction`) are NOT reproduced: GtkProgressBar
|
|
11
|
+
// is a GTK widget, `refs/gtk` is an uninitialized submodule here and libadwaita vendors
|
|
12
|
+
// no `adw-progress-bar.c`, so none of it is verifiable. What ships instead is the
|
|
13
|
+
// vendored port's web idiom — a CSS keyframe animation that runs while `pulsing` is set
|
|
14
|
+
// — so {@link AdwProgressBar.pulse} ENTERS that state and is idempotent afterwards. The
|
|
15
|
+
// default `show-text` label `NN%` is likewise this port's, not GTK's format string.
|
|
21
16
|
//
|
|
22
|
-
//
|
|
23
|
-
// fraction — 0…1, clamped. How much of the bar is filled.
|
|
24
|
-
// pulsing — boolean; indeterminate. The block bounces instead of filling.
|
|
25
|
-
// show-text — boolean; reveal the text node under the bar.
|
|
26
|
-
// text — the text to show. Defaults to the fraction as a percentage.
|
|
27
|
-
// inverted — boolean; fill from the far end.
|
|
28
|
-
// osd — boolean; libadwaita's `.osd` STYLE CLASS — a 2px troughless bar
|
|
29
|
-
// for under a header bar (_progress-bar.scss:59-83).
|
|
30
|
-
// disabled — boolean; dims the widget.
|
|
31
|
-
// Properties:
|
|
32
|
-
// fraction / pulsing / showText / text / inverted / osd / disabled (get/set).
|
|
33
|
-
// Methods:
|
|
34
|
-
// pulse() — switch to indeterminate. GtkProgressBar's own vocabulary; see
|
|
35
|
-
// the caveat above for how far the resemblance goes.
|
|
17
|
+
// `osd` is libadwaita's `.osd` STYLE CLASS: a 2px troughless bar for under a header bar.
|
|
36
18
|
//
|
|
37
|
-
// A11Y: `role="progressbar"
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
19
|
+
// A11Y: `role="progressbar"`, `aria-valuemin`/`aria-valuemax` fixed at 0/1,
|
|
20
|
+
// `aria-valuenow` carrying the clamped fraction. While pulsing `aria-valuenow` is
|
|
21
|
+
// REMOVED — that is how ARIA spells "indeterminate", and a stale value there would be
|
|
22
|
+
// announced as real progress.
|
|
41
23
|
//
|
|
42
24
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_progress-bar.scss
|
|
43
25
|
// Reference: refs/adwaita-web/adwaita-web/scss/_progressbar.scss (the indeterminate animation only)
|
|
@@ -60,8 +42,8 @@ export class AdwProgressBar extends HTMLElement {
|
|
|
60
42
|
/** How much of the bar is filled, 0…1. Always the CLAMPED value. */
|
|
61
43
|
get fraction(): number {
|
|
62
44
|
const raw = Number.parseFloat(this.getAttribute('fraction') ?? '');
|
|
63
|
-
// A missing or unparseable attribute is 0, GtkProgressBar's default —
|
|
64
|
-
//
|
|
45
|
+
// A missing or unparseable attribute is 0, GtkProgressBar's default — not NaN,
|
|
46
|
+
// which would reach the width and the a11y value.
|
|
65
47
|
if (!Number.isFinite(raw)) return 0;
|
|
66
48
|
return glibClamp(raw, 0, 1);
|
|
67
49
|
}
|
|
@@ -127,10 +109,9 @@ export class AdwProgressBar extends HTMLElement {
|
|
|
127
109
|
}
|
|
128
110
|
|
|
129
111
|
/**
|
|
130
|
-
* Switch the bar to indeterminate — GtkProgressBar's method name for the
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* `pulse-step` arithmetic is not verifiable in this tree and is not faked.
|
|
112
|
+
* Switch the bar to indeterminate — GtkProgressBar's method name for the same
|
|
113
|
+
* intent. The bouncing block is a CSS animation here, so this ENTERS the state and
|
|
114
|
+
* repeat calls are no-ops (see the module header).
|
|
134
115
|
*/
|
|
135
116
|
pulse(): void {
|
|
136
117
|
this.pulsing = true;
|
|
@@ -167,15 +148,14 @@ export class AdwProgressBar extends HTMLElement {
|
|
|
167
148
|
const fraction = this.fraction;
|
|
168
149
|
const pulsing = this.pulsing;
|
|
169
150
|
|
|
170
|
-
// `> trough.empty > progress { all: unset }
|
|
171
|
-
//
|
|
172
|
-
//
|
|
151
|
+
// `> trough.empty > progress { all: unset }`: the indicator has to VANISH at 0,
|
|
152
|
+
// not draw a rounded stub. A pulsing bar is never empty — its block is what says
|
|
153
|
+
// "working".
|
|
173
154
|
const empty = !pulsing && fraction === 0;
|
|
174
155
|
this._trough.classList.toggle('empty', empty);
|
|
175
|
-
// The inline width is dropped in
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
// against (inline styles beat any author rule, `all: unset` included).
|
|
156
|
+
// The inline width is dropped in the pulsing and the empty case so the rules that
|
|
157
|
+
// own the indicator there (the keyframes, `all: unset`) are not fighting an inline
|
|
158
|
+
// declaration they cannot win against — inline styles beat any author rule.
|
|
179
159
|
this._progress.style.width = pulsing || empty ? '' : `${fraction * 100}%`;
|
|
180
160
|
|
|
181
161
|
this._text.textContent = this.text;
|