@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.
Files changed (146) hide show
  1. package/dist/adwaita-web.css +433 -33
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
  4. package/lib/types/breakpoints.d.ts +10 -12
  5. package/lib/types/elements/adw-about-dialog.d.ts +19 -36
  6. package/lib/types/elements/adw-action-row.d.ts +9 -12
  7. package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
  8. package/lib/types/elements/adw-avatar.d.ts +1 -0
  9. package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
  10. package/lib/types/elements/adw-button-content.d.ts +3 -4
  11. package/lib/types/elements/adw-button-row.d.ts +1 -1
  12. package/lib/types/elements/adw-carousel.d.ts +28 -49
  13. package/lib/types/elements/adw-checks.d.ts +2 -8
  14. package/lib/types/elements/adw-combo-row.d.ts +6 -6
  15. package/lib/types/elements/adw-data-grid.d.ts +5 -10
  16. package/lib/types/elements/adw-drop-down.d.ts +3 -11
  17. package/lib/types/elements/adw-entry-row.d.ts +20 -23
  18. package/lib/types/elements/adw-icon.d.ts +2 -4
  19. package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
  20. package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
  21. package/lib/types/elements/adw-navigation-view.d.ts +2 -10
  22. package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
  23. package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
  24. package/lib/types/elements/adw-popover.d.ts +2 -7
  25. package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
  26. package/lib/types/elements/adw-preferences-group.d.ts +12 -14
  27. package/lib/types/elements/adw-progress-bar.d.ts +3 -4
  28. package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
  29. package/lib/types/elements/adw-sidebar.d.ts +5 -10
  30. package/lib/types/elements/adw-spinner.d.ts +3 -5
  31. package/lib/types/elements/adw-split-button.d.ts +6 -16
  32. package/lib/types/elements/adw-switch-row.d.ts +3 -3
  33. package/lib/types/elements/adw-tab-view.d.ts +16 -26
  34. package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
  35. package/lib/types/elements/adw-view-stack.d.ts +7 -17
  36. package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
  37. package/lib/types/elements/adw-view-switcher.d.ts +18 -38
  38. package/lib/types/elements/adw-wrap-box.d.ts +7 -17
  39. package/lib/types/elements/view-switcher-dom.d.ts +10 -13
  40. package/lib/types/index.d.ts +1 -0
  41. package/lib/types/source-view/hex.d.ts +7 -13
  42. package/lib/types/style-classes.spec.d.ts +11 -0
  43. package/lib/types/styles.generated.d.ts +1 -1
  44. package/package.json +6 -6
  45. package/scss/_about_dialog.scss +3 -2
  46. package/scss/_action_row.scss +33 -0
  47. package/scss/_avatar.scss +12 -0
  48. package/scss/_button.scss +44 -22
  49. package/scss/_card.scss +11 -8
  50. package/scss/_checks.scss +2 -4
  51. package/scss/_dialog.scss +3 -2
  52. package/scss/_headerbar.scss +23 -0
  53. package/scss/_popover.scss +8 -4
  54. package/scss/_preferences.scss +7 -7
  55. package/scss/_progress_bar.scss +9 -6
  56. package/scss/_reset.scss +1 -1
  57. package/scss/_shortcut_label.scss +80 -0
  58. package/scss/_sidebar.scss +57 -0
  59. package/scss/_status_page.scss +20 -0
  60. package/scss/_style_classes.scss +407 -0
  61. package/scss/_switch.scss +4 -3
  62. package/scss/_variables.scss +25 -0
  63. package/scss/adwaita-skin.scss +12 -0
  64. package/src/adw-about-dialog.spec.ts +34 -49
  65. package/src/adw-action-rows.spec.ts +25 -33
  66. package/src/adw-avatar.spec.ts +51 -20
  67. package/src/adw-banner.spec.ts +5 -8
  68. package/src/adw-button-content.spec.ts +9 -12
  69. package/src/adw-button-row.spec.ts +2 -4
  70. package/src/adw-carousel.spec.ts +40 -53
  71. package/src/adw-checks.spec.ts +36 -47
  72. package/src/adw-drop-down.spec.ts +25 -32
  73. package/src/adw-header-bar.spec.ts +5 -7
  74. package/src/adw-icon.spec.ts +23 -32
  75. package/src/adw-progress-bar.spec.ts +10 -12
  76. package/src/adw-row-state.spec.ts +9 -10
  77. package/src/adw-shortcut-label.spec.ts +112 -0
  78. package/src/adw-sidebar.spec.ts +17 -24
  79. package/src/adw-switch.spec.ts +11 -17
  80. package/src/adw-tab-view.spec.ts +7 -10
  81. package/src/adw-toast-overlay.spec.ts +7 -11
  82. package/src/adw-view-switcher-bar.spec.ts +10 -14
  83. package/src/adw-wrap-box.spec.ts +25 -36
  84. package/src/bottom-sheet.spec.ts +30 -36
  85. package/src/breakpoints.ts +15 -18
  86. package/src/chrome.spec.ts +13 -21
  87. package/src/elements/adw-about-dialog.ts +71 -114
  88. package/src/elements/adw-action-row.ts +22 -37
  89. package/src/elements/adw-alert-dialog.ts +25 -50
  90. package/src/elements/adw-avatar.ts +40 -15
  91. package/src/elements/adw-banner.ts +14 -18
  92. package/src/elements/adw-bottom-sheet.ts +31 -52
  93. package/src/elements/adw-button-content.ts +30 -37
  94. package/src/elements/adw-button-row.ts +14 -24
  95. package/src/elements/adw-carousel.ts +87 -148
  96. package/src/elements/adw-checks.ts +43 -56
  97. package/src/elements/adw-clamp.ts +8 -9
  98. package/src/elements/adw-combo-row.ts +17 -25
  99. package/src/elements/adw-data-grid.ts +26 -38
  100. package/src/elements/adw-dialog.ts +25 -56
  101. package/src/elements/adw-drop-down.ts +31 -51
  102. package/src/elements/adw-entry-row.ts +39 -46
  103. package/src/elements/adw-expander-row.ts +30 -53
  104. package/src/elements/adw-icon.ts +30 -53
  105. package/src/elements/adw-inline-view-switcher.ts +38 -67
  106. package/src/elements/adw-menu-button.ts +28 -58
  107. package/src/elements/adw-navigation-split-view.ts +35 -47
  108. package/src/elements/adw-navigation-view.ts +12 -28
  109. package/src/elements/adw-overlay-split-view.ts +100 -175
  110. package/src/elements/adw-password-entry-row.ts +18 -28
  111. package/src/elements/adw-popover.ts +28 -44
  112. package/src/elements/adw-preferences-dialog.ts +41 -66
  113. package/src/elements/adw-preferences-group.ts +33 -46
  114. package/src/elements/adw-progress-bar.ts +27 -47
  115. package/src/elements/adw-shortcut-label.ts +138 -0
  116. package/src/elements/adw-sidebar.ts +31 -39
  117. package/src/elements/adw-spin-row.ts +7 -14
  118. package/src/elements/adw-spinner.ts +31 -43
  119. package/src/elements/adw-split-button.ts +45 -75
  120. package/src/elements/adw-switch-row.ts +29 -46
  121. package/src/elements/adw-switch.ts +28 -47
  122. package/src/elements/adw-tab-view.ts +62 -103
  123. package/src/elements/adw-toast-overlay.ts +28 -35
  124. package/src/elements/adw-toggle-group.ts +9 -16
  125. package/src/elements/adw-view-stack.ts +32 -51
  126. package/src/elements/adw-view-switcher-bar.ts +26 -50
  127. package/src/elements/adw-view-switcher.ts +60 -110
  128. package/src/elements/adw-window-title.ts +4 -4
  129. package/src/elements/adw-wrap-box.ts +33 -59
  130. package/src/elements/view-switcher-dom.ts +15 -22
  131. package/src/entry-rows.spec.ts +18 -24
  132. package/src/index.ts +13 -15
  133. package/src/navigation-view.spec.ts +7 -10
  134. package/src/preferences-search.ts +11 -15
  135. package/src/preferences.spec.ts +13 -15
  136. package/src/source-view/adw-source-view.ts +6 -19
  137. package/src/source-view/hex.ts +7 -13
  138. package/src/split-button.spec.ts +32 -44
  139. package/src/split-views.spec.ts +83 -149
  140. package/src/style-classes.spec.ts +181 -0
  141. package/src/style-isolation.spec.ts +3 -6
  142. package/src/styles.generated.ts +1 -1
  143. package/src/tab-view.spec.ts +26 -35
  144. package/src/test.browser.mts +4 -0
  145. package/src/view-stack.spec.ts +23 -34
  146. 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
- // this package: `<adw-menu-button>`, `<adw-drop-down>` and `<adw-split-button>`
4
- // each used to build their own, and no two agreed. The surface disagreed (12px /
5
- // 12px / 9px radius against libadwaita's 15px; two correct three-layer shadows
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
- // anchor the surface sits on. `GtkPopover:position`'s own
24
- // autohide/has-arrow semantics are NOT verifiable in this tree
25
- // (refs/gtk is empty, libadwaita vendors no adw-popover.c), so only
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
- // (0 padding on the contents, `$menu_margin` on the item box —
33
- // _menus.scss:58-66), opt-in exactly as it is in GTK.
34
- // DO NOT infer it from `role`: `GtkDropDown`'s popover is a
35
- // `popover.menu` too (`dropdown { popover.menu { … } }`,
36
- // _dropdowns.scss:22) while its rows are `option`s, not
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:7-28 (popover > contents)
53
- // Reference: refs/libadwaita/src/stylesheet/widgets/_menus.scss:58-66, :134-156 (popover.menu, modelbutton)
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
- // would close and immediately reopen (or reopen and immediately close).
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
- // close its parent.
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
- * Defaults to `parentElement`, which is the common case: the popover is the
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
- // `.menu` style class, and the widget that owns the popover is what adds
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
- // surface unlabelled to assistive tech.
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
- // activates natively on Enter (keydown) and Space (keyup), so
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
- // `title` / `icon-name` / `name` / `use-underline` attributes + arbitrary group
9
- // content) and are moved — as ELEMENTS, keeping their identity — into the
10
- // scrolled body. `add(page)` accepts one at any later time, mirroring
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
- // `search(query)` implements the dialog's search over those pages: the corpus,
20
- // the filter and the `page → group` result subtitles all come from
21
- // @gjsify/adwaita-core, so the browser and NativeScript dialogs return the same
22
- // results for the same tree.
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. Child of <adw-preferences-dialog>. Carries optional
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
- * because the page's identity is load-bearing: `adw_preferences_dialog_add`
67
- * binds title / icon-name / name / use-underline / visible onto the view-stack
68
- * page (adw-preferences-dialog.c:707-711), and `create_search_row_subtitle`
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` (adw-preferences-dialog.c:693-716) — add a
173
- * page at ANY time.
174
- *
175
- * Before this the pages were snapshotted once, in `connectedCallback`; a
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` (:723-745). C logs
186
- * `ADW_CRITICAL_CANNOT_REMOVE_CHILD` and no-ops for a page it does not own;
187
- * returning `false` is the same contract without a console write.
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()` already exists on every element
190
- * and takes no arguments, so overriding it with a different signature makes
191
- * the class un-assignable to `HTMLElement` — which `customElements.define`
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
- * `create_search_row_subtitle`), over this dialog's live page subtree.
208
- *
209
- * The whole pipeline is `@gjsify/adwaita-core`'s, so this dialog and the
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 C does and what
215
- * makes the results list non-empty before the user types.
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
- // Snapshot the declared children before taking over the subtree. The
226
- // page ELEMENTS move into the body intact — their title / name /
227
- // icon-name are what a search result subtitle and a view switcher read
228
- // — and any unwrapped children follow them (Adw.PreferencesDialog's
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
- // Full-cover scrim — clicking it dismisses the dialog (or raises
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
- // No symbolic close icon ships in @gjsify/adwaita-icons; draw the "×"
266
- // glyph in CSS (same approach as <adw-tab-view>'s close affordance).
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 (mirrors Adw.Dialog's Escape
286
- // shortcut → maybe_close_cb). The element is focusable so it can receive
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> — Groups child rows in a boxed list with a title,
2
- // an optional dimmed description line and an optional trailing header-suffix
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
- // The five visibility states (title, description, header, `single-line`,
11
- // listbox) are NOT derived here: they come from
12
- // `derivePreferencesGroupHeader` in `@gjsify/adwaita-core`, which both this
13
- // element and the NativeScript group delegate to (ADR 0004). This element used
14
- // to compute two of the five by hand and was missing the other three — no
15
- // `single-line` class, and a `.boxed-list` card that stayed painted (with its
16
- // full-width `box-shadow` hairline) over an empty group.
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` (adw-preferences-group.c:396-411): a row goes
61
- * into the boxed list, a `header-suffix` child into the header.
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
- * both spellings occur: `group.addRow(row)` in code, `group.append(row)` in
65
- * a framework's rendering. Before this, a child appended after connect
66
- * stayed a bare child of the host — outside the card, and invisible to the
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` (:421-440). C logs
77
- * `ADW_CRITICAL_CANNOT_REMOVE_CHILD` and no-ops for a child it does not
78
- * own; returning `false` is the same contract without a console write.
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 — `gtk_widget_observe_children` on the listbox drives
150
- * `update_listbox_visibility` through `items-changed`
151
- * (adw-preferences-group.c:335-339) — and a DOM element needs the observer
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
- // listbox are the CONSEQUENCE of that move; re-adopting them
160
- // would append each row a second time and reorder the list.
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` — a
187
- // row with an empty title still keeps the card painted.
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: what is displayed IS the
190
- // attribute value, so visibility must be judged on the raw string.
191
- // libadwaita's labels are `use-markup=True`; rendering Pango markup
192
- // in the browser is a separate, still-open gap, and half-closing it
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
- // min-height / margin-bottom off it.
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. The whole behaviour is `CLAMP(fraction, 0, 1)` plus a
5
- // pulsing flag — ADR 0004 is explicit that a widget with genuinely trivial
6
- // behaviour does not get a core class, and the clamp itself is already shared:
7
- // `glibClamp` (GLib's CLAMP, high bound tested FIRST) comes from
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
- // WHAT IS NOT VERIFIABLE IN THIS TREE. `GtkProgressBar` is a GTK widget:
11
- // `refs/gtk` is EMPTY here and libadwaita vendors no `adw-progress-bar.c`, so
12
- // `gtk_progress_bar_pulse()`'s step semantics — the `pulse-step` default, how
13
- // the block reflects at the ends, whether it resets `fraction` — are NOT
14
- // verifiable and are NOT reproduced. What ships instead is the web idiom the
15
- // vendored port uses: a CSS keyframe animation that runs while `pulsing` is set
16
- // (scss/_progress_bar.scss, which cites which half came from which source), so
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
- // Attributes:
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"` with `aria-valuemin`/`aria-valuemax` fixed at 0/1
38
- // and `aria-valuenow` carrying the clamped fraction. While pulsing,
39
- // `aria-valuenow` is REMOVED — that is how ARIA spells "indeterminate", and a
40
- // stale value there would be announced as real progress.
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
- // NOT NaN, which would reach the width and the a11y value.
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
- * same intent. See the module header: the bouncing block is a CSS animation
132
- * here, so this ENTERS the state and repeat calls are no-ops; GTK's
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 }` (_progress-bar.scss:85) —
171
- // the indicator has to VANISH at 0, not draw a rounded stub. A pulsing
172
- // bar is never empty: its block is what says "working".
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 both the pulsing and the empty case, so
176
- // the rules that own the indicator there — the keyframes, and
177
- // `all: unset` — are not fighting an inline declaration they cannot win
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;