@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.
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,32 +1,28 @@
1
- // <adw-checkbox> and <adw-radio> — the two form-control primitives, in ONE
2
- // module, for the same reason `scss/_checks.scss` is one partial: upstream
3
- // merges them (`refs/libadwaita/src/stylesheet/widgets/_checks.scss` has no
4
- // `_radio.scss` beside it), and everything but the corner radius, the glyph and
5
- // the group is shared. `refs/adwaita-web` split them into two files and the two
6
- // promptly drifted apart.
1
+ // <adw-checkbox> and <adw-radio> — the two form-control primitives, in ONE module,
2
+ // for the same reason `scss/_checks.scss` is one partial: upstream merges them
3
+ // (`refs/libadwaita/src/stylesheet/widgets/_checks.scss` has no `_radio.scss` beside
4
+ // it), and everything but the corner radius, the glyph and the group is shared.
5
+ // Splitting them, as `refs/adwaita-web` does, is what lets the two drift apart.
7
6
  //
8
- // WHAT LIVES IN `@gjsify/adwaita-core`, AND WHAT DOES NOT
9
- //
10
- // The checkbox has no core class: it is one boolean plus a third state, which
11
- // ADR 0004 calls out as too trivial to model. What IS core is the pair of
12
- // pieces a second renderer would otherwise re-invent:
7
+ // The checkbox has no core class: it is one boolean plus a third state, which ADR 0004
8
+ // calls out as too trivial to model. What IS core is the pair a second renderer would
9
+ // otherwise re-invent:
13
10
  //
14
11
  // - {@link RadioGroupState} — group exclusivity. The browser gives the inner
15
12
  // `<input type="radio" name="…">` exclusivity for free, and STOPS THERE: it
16
- // unchecks the sibling INPUT and leaves the sibling `<adw-radio>` host's
17
- // `checked` attribute — the published state, and the selector
18
- // `_checks.scss` paints from — stale, so a group left to the browser draws
19
- // two selected radios. The shared state is what repaints the loser.
20
- // - {@link resolveCheckState} — indeterminate outranks checked, which is read
21
- // off the cascade in `_checks.scss:79-81` and is what `aria-checked` needs.
13
+ // unchecks the sibling INPUT and leaves the sibling `<adw-radio>` host's `checked`
14
+ // attribute — the published state, and the selector `_checks.scss` paints from —
15
+ // stale, so a group left to the browser draws two selected radios. The shared
16
+ // state is what repaints the loser.
17
+ // - {@link resolveCheckState} — indeterminate outranks checked, read off the cascade
18
+ // in libadwaita's `_checks.scss`, and what `aria-checked` needs.
22
19
  //
23
- // WHAT IS NOT VERIFIABLE IN THIS TREE. `GtkCheckButton` is a GTK widget:
24
- // `refs/gtk` is EMPTY here and libadwaita vendors no `adw-checkbox.c`, so its
25
- // group semantics (`gtk_check_button_set_group`, whether a fresh group starts
26
- // with a member active) and the exact meaning of `GtkCheckButton:inconsistent`
27
- // are NOT verifiable and are not guessed at. Where a rule was needed the HTML
28
- // one is used and cited as such — clearing `indeterminate` on activation is the
29
- // HTML pre-click activation step, not a reading of GTK.
20
+ // NOT DERIVED FROM GTK. `GtkCheckButton` is a GTK widget and libadwaita has no
21
+ // `adw-checkbox.c`, so its group semantics (`gtk_check_button_set_group`, whether a
22
+ // fresh group starts with a member active) and the exact meaning of
23
+ // `GtkCheckButton:inconsistent` are not guessed at. Where a rule was needed the HTML
24
+ // one is used and cited as such — clearing `indeterminate` on activation is the HTML
25
+ // pre-click activation step, not a reading of GTK.
30
26
  //
31
27
  // NAMING. The state attribute is `checked`, not GtkCheckButton's `active`:
32
28
  // these elements wrap a real `<input>`, `checked` is the spelling every author
@@ -67,8 +63,8 @@ import { RadioGroupState, resolveCheckState } from '@gjsify/adwaita-core';
67
63
 
68
64
  /**
69
65
  * The document's radio groups. ONE registry, as global as a bare
70
- * `<input type="radio" name="x">` outside a form is — two independent groups
71
- * therefore need two names.
66
+ * `<input type="radio" name="x">` outside a form is — two independent groups therefore
67
+ * need two names.
72
68
  */
73
69
  const radioGroups = new RadioGroupState();
74
70
 
@@ -85,15 +81,13 @@ abstract class AdwCheckBase extends HTMLElement {
85
81
  /** What a click / Space / arrow key on the control means for this kind. */
86
82
  protected abstract activate(): void;
87
83
 
88
- /** Whether the control is checked. */
89
84
  get checked(): boolean {
90
85
  return this.hasAttribute('checked');
91
86
  }
92
87
 
93
88
  set checked(value: boolean) {
94
- // `toggleAttribute` with a force flag is a no-op when the attribute
95
- // already holds that state, so re-setting the current value mutates
96
- // nothing and the notify never runs.
89
+ // `toggleAttribute` with a force flag is a no-op when the attribute already
90
+ // holds that state, so re-setting the current value never notifies.
97
91
  this.toggleAttribute('checked', !!value);
98
92
  }
99
93
 
@@ -106,7 +100,6 @@ abstract class AdwCheckBase extends HTMLElement {
106
100
  this.toggleAttribute('indeterminate', !!value);
107
101
  }
108
102
 
109
- /** Whether the control is inert. */
110
103
  get disabled(): boolean {
111
104
  return this.hasAttribute('disabled');
112
105
  }
@@ -115,7 +108,6 @@ abstract class AdwCheckBase extends HTMLElement {
115
108
  this.toggleAttribute('disabled', !!value);
116
109
  }
117
110
 
118
- /** The label text beside the indicator. */
119
111
  get label(): string {
120
112
  return this.getAttribute('label') ?? this._label?.textContent ?? '';
121
113
  }
@@ -131,8 +123,8 @@ abstract class AdwCheckBase extends HTMLElement {
131
123
  }
132
124
  this._initialized = true;
133
125
 
134
- // Captured BEFORE the element takes over its own children: a
135
- // server-rendered `<adw-checkbox>Enable</adw-checkbox>` keeps its text.
126
+ // Captured BEFORE the element takes over its own children, so a server-rendered
127
+ // `<adw-checkbox>Enable</adw-checkbox>` keeps its text.
136
128
  const authored = [...this.childNodes];
137
129
 
138
130
  this._input = document.createElement('input');
@@ -154,9 +146,9 @@ abstract class AdwCheckBase extends HTMLElement {
154
146
  // and a direct programmatic `input.click()` both surface as `change`.
155
147
  this._input.addEventListener('change', () => this.activate());
156
148
 
157
- // The indicator and the label cover the control; the input is 0x0, so
158
- // every pointer click lands on one of them. A click that DID reach the
159
- // input is already handled above — activating again would undo it.
149
+ // The indicator and the label cover the control; the input is 0x0, so every
150
+ // pointer click lands on one of them. A click that DID reach the input is already
151
+ // handled above — activating again would undo it.
160
152
  this.addEventListener('click', (event) => {
161
153
  if (this.disabled) return;
162
154
  if (event.target === this._input) return;
@@ -184,19 +176,16 @@ abstract class AdwCheckBase extends HTMLElement {
184
176
  }
185
177
  }
186
178
 
187
- /** Extra wiring for a subclass on connect. */
188
179
  protected onConnected(): void {}
189
180
 
190
- /** Extra teardown for a subclass on disconnect. */
191
181
  protected onDisconnected(): void {}
192
182
 
193
183
  protected render(): void {
194
184
  const state = resolveCheckState(this.checked, this.indeterminate);
195
185
  this._input.checked = state === 'checked';
196
186
  this._input.indeterminate = state === 'indeterminate';
197
- // Explicit, because HTML has a native mixed CHECKBOX (the property
198
- // above) but no mixed radio — this is the one spelling that announces
199
- // the third state for both.
187
+ // Explicit, because HTML has a native mixed CHECKBOX (the property above) but no
188
+ // mixed radio — this is the one spelling that announces the third state for both.
200
189
  this._input.setAttribute('aria-checked', state === 'indeterminate' ? 'mixed' : String(state === 'checked'));
201
190
  this._input.disabled = this.disabled;
202
191
  this._label.hidden = this._label.childNodes.length === 0;
@@ -214,10 +203,9 @@ export class AdwCheckbox extends AdwCheckBase {
214
203
  }
215
204
 
216
205
  protected activate(): void {
217
- // HTML's pre-click activation steps clear `indeterminate` before the
218
- // toggle, so a user can always reach a definite state. That rule is the
219
- // HTML spec's, not a reading of GtkCheckButton:inconsistent, whose
220
- // behaviour is not verifiable in this tree.
206
+ // HTML's pre-click activation steps clear `indeterminate` before the toggle, so a
207
+ // user can always reach a definite state. That rule is the HTML spec's, not a
208
+ // reading of `GtkCheckButton:inconsistent`.
221
209
  this.indeterminate = false;
222
210
  this.checked = !this.checked;
223
211
  }
@@ -235,7 +223,6 @@ export class AdwRadio extends AdwCheckBase {
235
223
  return 'radio';
236
224
  }
237
225
 
238
- /** The group this member belongs to. */
239
226
  get name(): string {
240
227
  return this.getAttribute('name') ?? '';
241
228
  }
@@ -245,8 +232,8 @@ export class AdwRadio extends AdwCheckBase {
245
232
  }
246
233
 
247
234
  /**
248
- * This member's value. Falls back to the label text so a group authored
249
- * without values still has members the group state can tell apart.
235
+ * This member's value. Falls back to the label text so a group authored without
236
+ * values still has members the group state can tell apart.
250
237
  */
251
238
  get value(): string {
252
239
  return this.getAttribute('value') ?? this.label.trim();
@@ -258,17 +245,17 @@ export class AdwRadio extends AdwCheckBase {
258
245
 
259
246
  protected activate(): void {
260
247
  this.indeterminate = false;
261
- // An UNNAMED radio is not in a group — HTML forms a radio button group
262
- // only from a non-empty `name`, so a nameless one checks itself and
263
- // never unchecks anything. Without this guard every nameless radio in
264
- // the document would join one `''` group and fight over it.
248
+ // An UNNAMED radio is not in a group — HTML forms a radio button group only from
249
+ // a non-empty `name`, so a nameless one checks itself and never unchecks anything.
250
+ // Without this guard every nameless radio in the document would join one `''`
251
+ // group and fight over it.
265
252
  if (this.name === '') {
266
253
  this.checked = true;
267
254
  return;
268
255
  }
269
- // The pick goes through the shared state, never straight onto this
270
- // element: the member that LOSES the selection is a different element
271
- // and only the state knows which one it is.
256
+ // The pick goes through the shared state, never straight onto this element: the
257
+ // member that LOSES the selection is a different element and only the state knows
258
+ // which one it is.
272
259
  radioGroups.select(this.name, this.value);
273
260
  }
274
261
 
@@ -1,16 +1,15 @@
1
1
  // <adw-clamp> — Constrains its children to a maximum width and centers them.
2
2
  // Attributes: maximum-size (px, default 600), tightening-threshold (px, default 400).
3
3
  //
4
- // The clamp is NOT a `max-width`, which is what this element used to be. Adwaita
5
- // eases the child from "all the available width" into the cap across a tightening
6
- // region, and stamps a `small`/`medium`/`large` class on it — the arithmetic lives
7
- // in `@gjsify/adwaita-core` (`clampAllocate`) so this element and the NativeScript
8
- // widget produce the same width for the same viewport.
4
+ // The clamp is NOT a `max-width`: Adwaita eases the child from "all the available width"
5
+ // into the cap across a tightening region and stamps a `small`/`medium`/`large` class on
6
+ // it. The arithmetic lives in `@gjsify/adwaita-core` (`clampAllocate`), so this element and
7
+ // the NativeScript widget produce the same width for the same viewport.
9
8
  //
10
- // The cap now sits on the CHILDREN, not on this element. The element has to keep
11
- // reporting the AVAILABLE width — it is what the curve is evaluated against — and
12
- // an element that caps itself reports the capped width back to its own
13
- // ResizeObserver, which is a feedback loop, not a measurement.
9
+ // The cap sits on the CHILDREN, not on this element, which has to keep reporting the
10
+ // AVAILABLE width — that is what the curve is evaluated against. An element that caps
11
+ // itself reports the capped width back to its own ResizeObserver, which is a feedback
12
+ // loop, not a measurement.
14
13
  //
15
14
  // `childMin` is passed as 0 rather than measured. libadwaita's
16
15
  // `lower = MAX (…, child_min)` says a child is never squeezed below its own
@@ -1,15 +1,11 @@
1
- // <adw-combo-row> — Row with a title/subtitle and a dropdown select.
2
- // Attributes: title, subtitle, items (JSON string[]), selected (index number)
3
- // Events: notify::selected (CustomEvent, mirrors GJS GObject signal naming)
4
- // The native <select> is stretched invisibly over the row so clicking anywhere opens it.
1
+ // <adw-combo-row> — row with a title/subtitle and a dropdown select. `items` is a JSON
2
+ // `string[]`, `selected` an index, and the native <select> is stretched invisibly over the
3
+ // row so clicking anywhere opens it.
5
4
  //
6
- // The SELECTION state machine (the options list, the two-way index↔value mapping,
7
- // the empty/out-of-range guards and the programmatic-vs-interactive notify split)
8
- // is HEADLESS and lives in `@gjsify/adwaita-core` (ADR 0004) as {@link ComboState};
9
- // this element composes it and keeps only the DOM render half — the <select>, the
10
- // inline value label and the `notify::selected` event.
11
- // `@gjsify/adwaita-nativescript` composes the same state machine, so both ports
12
- // share one behaviour.
5
+ // The SELECTION state machine (the options list, the two-way index↔value mapping, the
6
+ // empty/out-of-range guards and the programmatic-vs-interactive notify split) is HEADLESS
7
+ // and lives in `@gjsify/adwaita-core` (ADR 0004) as {@link ComboState}; this element keeps
8
+ // only the DOM half — the <select>, the inline value label and `notify::selected`.
13
9
  //
14
10
  // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
15
11
  // Copyright (c) 2025 csm. MIT License.
@@ -45,10 +41,9 @@ export class AdwComboRow extends HTMLElement {
45
41
  this._initialized = true;
46
42
 
47
43
  const items: string[] = JSON.parse(this.getAttribute('items') || '[]');
48
- // Seed the headless state BEFORE subscribing, so building the initial DOM
49
- // below is not driven by a change notification. The string→descriptor
50
- // mapping is core's: `<adw-drop-down>` had its own, richer copy, so the
51
- // two selectors accepted different option vocabularies.
44
+ // Seed the headless state BEFORE subscribing, so building the initial DOM below is
45
+ // not driven by a change notification. The string→descriptor mapping is core's, so
46
+ // this row and `<adw-drop-down>` accept one option vocabulary.
52
47
  this._state.setOptions(normalizeComboOptions(items));
53
48
  this._state.setSelectedIndex(parseInt(this.getAttribute('selected') || '0', 10));
54
49
  const selectedIdx = this._state.selectedIndex;
@@ -61,12 +56,10 @@ export class AdwComboRow extends HTMLElement {
61
56
  this._subtitleEl.className = 'adw-row-subtitle';
62
57
  text.append(this._titleEl, this._subtitleEl);
63
58
 
64
- // Visible selected value display
65
59
  this._valueEl = document.createElement('span');
66
60
  this._valueEl.className = 'adw-row-value';
67
61
  this._valueEl.textContent = this._state.selectedLabel;
68
62
 
69
- // Hidden select overlaying the entire row
70
63
  const select = document.createElement('select');
71
64
  items.forEach((item, i) => {
72
65
  const option = document.createElement('option');
@@ -118,13 +111,13 @@ export class AdwComboRow extends HTMLElement {
118
111
  }
119
112
 
120
113
  /**
121
- * `model_changed` (adw-combo-row.c:187-195) — one option or none is not a
122
- * choice, so the arrow goes and the row stops being activatable.
114
+ * `model_changed`: one option or none is not a choice, so the arrow goes and the row
115
+ * stops being activatable.
123
116
  *
124
- * Both halves have to be expressed: `data-presents-chooser` gates the
125
- * `.adw-row-value::after` mask in the stylesheet, and DISABLING the overlaid
126
- * `<select>` is what makes the row inert — hiding the arrow alone would leave
127
- * a row that still opens a one-entry popup on click.
117
+ * Both halves have to be expressed — `data-presents-chooser` gates the
118
+ * `.adw-row-value::after` mask in the stylesheet, and DISABLING the overlaid `<select>`
119
+ * is what makes the row inert. Hiding the arrow alone leaves a row that still opens a
120
+ * one-entry popup on click.
128
121
  */
129
122
  private _syncChooser() {
130
123
  const presents = this._state.presentsChooser;
@@ -133,8 +126,7 @@ export class AdwComboRow extends HTMLElement {
133
126
  }
134
127
 
135
128
  private _renderText() {
136
- // The `string_is_not_empty` label rule, from core — this block was one of
137
- // six hand-rolled copies, all of which omitted the TITLE half.
129
+ // The `string_is_not_empty` label rule, from core — it hides the TITLE too.
138
130
  const labels = deriveRowLabels({
139
131
  title: this.getAttribute('title'),
140
132
  subtitle: this.getAttribute('subtitle'),
@@ -1,18 +1,15 @@
1
- // <adw-data-grid> — A slim, Adwaita-styled ALIGNED data grid: the web mirror of
2
- // the native Gtk.Grid an accounting app (Buchhaltung) uses for its financial
3
- // statements (the BWA / P&L, invoice line-items). It is DELIBERATELY NOT a
4
- // heavyweight sortable Gtk.ColumnView / TreeView — boxed lists (<adw-action-row>
5
- // inside <adw-preferences-group>) stay the default for record lists. Reach for
6
- // this ONLY for genuinely tabular numeric data where columns of figures must
7
- // line up (labels left, amounts right-aligned with tabular figures), with the
8
- // accounting row emphasis a statement needs: section headers, subtotals and a
9
- // bold final total.
1
+ // <adw-data-grid> — A slim, Adwaita-styled ALIGNED data grid: the web mirror of the
2
+ // native Gtk.Grid an accounting app uses for its financial statements (BWA / P&L,
3
+ // invoice line-items). DELIBERATELY NOT a heavyweight sortable Gtk.ColumnView /
4
+ // TreeView — boxed lists (<adw-action-row> inside <adw-preferences-group>) stay the
5
+ // default for record lists. Reach for this ONLY for genuinely tabular numeric data
6
+ // where columns of figures must line up, with the accounting row emphasis a statement
7
+ // needs: section headers, subtotals and a bold final total.
10
8
  //
11
- // It is a PLAIN presentational element — it does layout, alignment and emphasis;
12
- // it never formats or sorts. Cell values are PRE-FORMATTED strings (locale
13
- // currency formatting stays in the app, e.g. `1.234,50 €`); the grid renders the
14
- // strings and aligns them. Sorting is a data op driven externally (e.g. an
15
- // <adw-drop-down>) — there are no clickable-sortable column headers here.
9
+ // PRESENTATIONAL ONLY: layout, alignment and emphasis, never formatting or sorting.
10
+ // Cell values are PRE-FORMATTED strings (locale currency formatting stays in the app,
11
+ // e.g. `1.234,50 €`); sorting is a data op driven externally, so there are no
12
+ // clickable-sortable column headers here.
16
13
  //
17
14
  // Columns (the `columns` property, or a JSON `columns` attribute):
18
15
  // { key, label?, align?, width?, flex?, monospace?, numeric? }
@@ -51,13 +48,12 @@
51
48
  // Responsive: the grid scrolls horizontally within its OWN container on narrow
52
49
  // viewports (it never overflows the page); it sits inside an <adw-card>/boxed
53
50
  // surface and follows light + dark via the adwaita-web CSS variables.
54
- // The derivations are HEADLESS and live in `@gjsify/adwaita-core` (ADR 0004):
55
- // the column tracks, the cell classes, the two normalisers, the cell text and
56
- // the activation rule. `@gjsify/adwaita-nativescript` renders the same widget
57
- // over `GridLayout` `ItemSpec`s — NativeScript has no subgrid — so the TRACK
58
- // rule is emitted as a renderer-neutral descriptor and only the mapping onto
59
- // `grid-template-columns` stays here. `@gjsify/adwaita-core/conformance`'s
60
- // `DATA_GRID_*_VECTORS` is the table both ports are held to.
51
+ // The derivations are HEADLESS and live in `@gjsify/adwaita-core` (ADR 0004): the
52
+ // column tracks, the cell classes, the two normalisers, the cell text and the activation
53
+ // rule. `@gjsify/adwaita-nativescript` renders the same widget over `GridLayout`
54
+ // `ItemSpec`s — NativeScript has no subgrid — so the TRACK rule is a renderer-neutral
55
+ // descriptor and only the mapping onto `grid-template-columns` stays here. Both ports
56
+ // are held to `@gjsify/adwaita-core/conformance`'s `DATA_GRID_*_VECTORS`.
61
57
  // Reference: Gtk.Grid usage in the Buchhaltung BWA view (native financial grid).
62
58
  // Reference: refs/libadwaita/src/stylesheet/_colors.scss (separator / card tokens).
63
59
  // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
@@ -94,7 +90,6 @@ export class AdwDataGrid extends HTMLElement {
94
90
  return ['columns', 'rows', 'caption', 'interactive'];
95
91
  }
96
92
 
97
- /** The column descriptors. Setting rebuilds the grid. */
98
93
  get columns(): AdwDataGridColumn[] {
99
94
  return this._columns;
100
95
  }
@@ -104,7 +99,6 @@ export class AdwDataGrid extends HTMLElement {
104
99
  if (this._initialized) this._render();
105
100
  }
106
101
 
107
- /** The row objects. Setting rebuilds the body. */
108
102
  get rows(): AdwDataGridRow[] {
109
103
  return this._rows;
110
104
  }
@@ -124,7 +118,6 @@ export class AdwDataGrid extends HTMLElement {
124
118
  else this.removeAttribute('caption');
125
119
  }
126
120
 
127
- /** Whether every `normal` row is clickable (element-level flag). */
128
121
  get interactive(): boolean {
129
122
  return this.hasAttribute('interactive');
130
123
  }
@@ -176,8 +169,6 @@ export class AdwDataGrid extends HTMLElement {
176
169
  void value;
177
170
  }
178
171
 
179
- // ── internals ──────────────────────────────────────────────────────────
180
-
181
172
  private _parseJsonAttr<T>(name: string, coerce: (v: unknown) => T): T {
182
173
  const raw = this.getAttribute(name);
183
174
  if (!raw) return coerce(undefined);
@@ -195,13 +186,11 @@ export class AdwDataGrid extends HTMLElement {
195
186
  }
196
187
 
197
188
  /**
198
- * The CSS `grid-template-columns` value derived from the column descriptors.
199
- *
200
- * The DERIVATION is core's (`dataGridTracks`), because NativeScript needs the
201
- * same one for its `ItemSpec`s and can do nothing with a CSS string; the
202
- * mapping onto CSS text — including the `minmax(0px, 1fr)` slack track whose
203
- * `0px` must keep its unit to round-trip through the CSSOM — is
204
- * `dataGridTrackTemplate`.
189
+ * The CSS `grid-template-columns` value derived from the column descriptors. The
190
+ * DERIVATION is core's (`dataGridTracks`), because NativeScript needs the same one for
191
+ * its `ItemSpec`s and can do nothing with a CSS string; the mapping onto CSS text —
192
+ * including the `minmax(0px, 1fr)` slack track whose `0px` must keep its unit to
193
+ * round-trip through the CSSOM — is `dataGridTrackTemplate`.
205
194
  */
206
195
  private _trackTemplate(): string {
207
196
  return dataGridTrackTemplate(dataGridTracks(this._columns));
@@ -220,7 +209,6 @@ export class AdwDataGrid extends HTMLElement {
220
209
  const columnCount = this._columns.length;
221
210
  if (columnCount === 0) return;
222
211
 
223
- // Header row (dim, small-caps) — column labels, aligned per column.
224
212
  const header = document.createElement('div');
225
213
  header.className = 'adw-data-grid-row header';
226
214
  header.setAttribute('role', 'row');
@@ -242,8 +230,8 @@ export class AdwDataGrid extends HTMLElement {
242
230
  row.setAttribute('role', 'row');
243
231
 
244
232
  if (variant === 'section') {
245
- // A section header spans all columns; its text is the first
246
- // column's value (the section title).
233
+ // A section header spans all columns; its text is the first column's
234
+ // value (the section title).
247
235
  const firstKey = this._columns[0]?.key ?? '';
248
236
  const cell = document.createElement('div');
249
237
  cell.className = 'adw-data-grid-cell align-start section-cell';
@@ -261,8 +249,8 @@ export class AdwDataGrid extends HTMLElement {
261
249
  }
262
250
  }
263
251
 
264
- // A row is clickable only when it is a plain data row AND either the
265
- // element opts everything in or the row opts itself in.
252
+ // A row is clickable only when it is a plain data row AND either the element
253
+ // opts everything in or the row opts itself in.
266
254
  if (dataGridRowInteractive(variant, rowData.interactive, elementInteractive)) {
267
255
  row.classList.add('interactive');
268
256
  row.tabIndex = 0;
@@ -1,44 +1,23 @@
1
- // <adw-dialog> — A generic adaptive dialog: the web counterpart of Adw.Dialog.
2
- // It wraps arbitrary content in a floating card centred over a full-cover scrim
3
- // on wide viewports, and re-anchors it as a bottom sheet (full-width, rounded
4
- // top corners) on narrow ones — the adaptive presentation Adw.Dialog gets for
5
- // free from AdwBreakpoint on GTK. There is no Adw.Breakpoint primitive on the
6
- // web, so the switch is a CSS media query keyed on the `presentation-mode`.
1
+ // <adw-dialog> — the web counterpart of Adw.Dialog: arbitrary content in a floating
2
+ // card over a full-cover scrim on wide viewports, re-anchored as a bottom sheet
3
+ // (full-width, rounded top corners) on narrow ones. The adaptive switch is a CSS
4
+ // media query keyed on `presentation-mode`, not an AdwBreakpoint object.
7
5
  //
8
6
  // Unlike the specialised dialogs (<adw-alert-dialog>, <adw-preferences-dialog>,
9
- // <adw-about-dialog>) this one imposes no content model of its own: its default
10
- // slot holds any markup, and it renders an OPTIONAL flat header bar (a title +
11
- // a trailing close button) only when a `title` is set or `show-header` is used.
12
- // It is the generic base those specialised dialogs could later be built on.
7
+ // <adw-about-dialog>) this one imposes no content model of its own: its default slot
8
+ // holds any markup, and the flat header bar (title + trailing close button) renders
9
+ // only when a `title` is set or `show-header` is used.
13
10
  //
14
- // Like Adw.Dialog it starts hidden; `present()` (or the `open` attribute)
15
- // reveals it and `close()` dismisses it — gated by `can-close` (Escape /
16
- // scrim-click / close-button raise `close-attempt` instead of closing while
17
- // locked). Presenting traps focus inside the dialog and returns it to the
18
- // previously-focused element on close. A disconnected element is attached to
19
- // `document.body` on present (the Adw.Dialog.present(parent) idiom); a
20
- // declaratively-authored dialog already in the DOM is simply revealed in place
21
- // (and stays reusable — close hides, it does not detach).
11
+ // Starts hidden; `present()` (or the `open` attribute) reveals it and `close()`
12
+ // dismisses it — gated by `can-close`, so while locked Escape / scrim-click /
13
+ // close-button raise `close-attempt` instead. Presenting traps focus inside the
14
+ // dialog and returns it to the previously-focused element on close. A DISCONNECTED
15
+ // element is attached to `document.body` on present (the Adw.Dialog.present(parent)
16
+ // idiom); one already in the DOM is revealed in place and stays reusable — close
17
+ // hides, it does not detach.
22
18
  //
23
- // Attributes:
24
- // title (the header title — Adw.Dialog:title; presence renders the header)
25
- // open (boolean — whether the dialog is revealed; default hidden)
26
- // can-close (boolean — user may dismiss; default on. `="false"` locks it
27
- // so a dismissal raises `close-attempt` — Adw.Dialog:can-close)
28
- // content-width (max content width in px — Adw.Dialog:content-width; default 360)
29
- // content-height (max content height in px — Adw.Dialog:content-height; default unset)
30
- // presentation-mode (`auto` | `floating` | `bottom-sheet`; default `auto` —
31
- // Adw.Dialog:presentation-mode. `auto` = floating when wide,
32
- // bottom sheet when narrow)
33
- // show-header (boolean — render the header bar even without a title)
34
- //
35
- // Properties: open, title, canClose, contentWidth, contentHeight, presentationMode.
36
- // Methods: present(), close(), forceClose().
37
- // Events:
38
- // `notify::open` (CustomEvent, bubbles, detail = { open }) — revealed state changed.
39
- // `closed` (CustomEvent, bubbles) — the dialog finished closing (Adw.Dialog::closed).
40
- // `close-attempt` (CustomEvent, bubbles) — a dismissal was attempted while locked
41
- // (Adw.Dialog::close-attempt).
19
+ // Events, all bubbling CustomEvents: `notify::open` (detail `{ open }`), `closed`
20
+ // (Adw.Dialog::closed), `close-attempt` (Adw.Dialog::close-attempt).
42
21
  //
43
22
  // Reference: refs/libadwaita/src/adw-dialog.c (present/close/can-close, Escape, focus)
44
23
  // Reference: refs/libadwaita/src/stylesheet/widgets/_dialogs.scss (floating sheet + bottom sheet)
@@ -51,7 +30,6 @@ export type AdwDialogPresentationMode = 'auto' | 'floating' | 'bottom-sheet';
51
30
  const DEFAULT_CONTENT_WIDTH = 360;
52
31
  const PRESENTATION_MODES: readonly AdwDialogPresentationMode[] = ['auto', 'floating', 'bottom-sheet'];
53
32
 
54
- // Selector matching the tabbable elements a focus trap must cycle through.
55
33
  const FOCUSABLE_SELECTOR = [
56
34
  'a[href]',
57
35
  'button:not([disabled])',
@@ -163,25 +141,24 @@ export class AdwDialog extends HTMLElement {
163
141
 
164
142
  connectedCallback() {
165
143
  if (this._initialized) {
166
- // A re-attach (e.g. present() on a previously-connected dialog) must
167
- // not rebuild the subtree — the DOM already exists.
144
+ // A re-attach (present() on a previously-connected dialog) must not rebuild
145
+ // the subtree — the DOM already exists.
168
146
  return;
169
147
  }
170
148
  this._initialized = true;
171
149
 
172
150
  this.classList.add('adw-dialog');
173
151
 
174
- // Everything the author placed inside becomes the dialog content.
152
+ // Snapshot before the subtree is replaced: everything the author placed inside
153
+ // becomes the dialog content.
175
154
  const contentChildren = Array.from(this.childNodes);
176
155
 
177
- // Full-cover scrim — a flex container that centres the box; clicking it
178
- // (outside the box) attempts to dismiss the dialog.
179
156
  this._scrimEl = document.createElement('div');
180
157
  this._scrimEl.className = 'adw-dialog-scrim';
181
158
  this._scrimEl.addEventListener('click', () => this._attemptClose());
182
159
 
183
- // The dialog box (the "sheet"). Focusable so it can receive keys even
184
- // when the content has none; clicks inside must not reach the scrim.
160
+ // Focusable so the box receives keys even when the content has none, and clicks
161
+ // inside must not reach the scrim's dismiss handler.
185
162
  this._boxEl = document.createElement('div');
186
163
  this._boxEl.className = 'adw-dialog-box';
187
164
  this._boxEl.setAttribute('role', 'dialog');
@@ -190,8 +167,6 @@ export class AdwDialog extends HTMLElement {
190
167
  this._boxEl.addEventListener('click', (event) => event.stopPropagation());
191
168
  this._boxEl.addEventListener('keydown', (event) => this._onBoxKeyDown(event));
192
169
 
193
- // Optional flat header bar (title + trailing close button), strictly
194
- // centred like an AdwHeaderBar.
195
170
  this._headerEl = document.createElement('div');
196
171
  this._headerEl.className = 'adw-dialog-header';
197
172
 
@@ -208,14 +183,12 @@ export class AdwDialog extends HTMLElement {
208
183
  this._closeBtn.type = 'button';
209
184
  this._closeBtn.className = 'adw-dialog-close';
210
185
  this._closeBtn.setAttribute('aria-label', 'Close');
211
- // No `window-close` symbolic ships in @gjsify/adwaita-icons; the "×"
212
- // glyph is drawn in CSS (same approach as <adw-preferences-dialog>).
186
+ // The "×" glyph is drawn in CSS (same approach as <adw-preferences-dialog>).
213
187
  this._closeBtn.addEventListener('click', () => this._attemptClose());
214
188
  trailing.appendChild(this._closeBtn);
215
189
 
216
190
  this._headerEl.append(leading, this._titleEl, trailing);
217
191
 
218
- // The content area — the author's markup.
219
192
  this._contentEl = document.createElement('div');
220
193
  this._contentEl.className = 'adw-dialog-content';
221
194
  for (const child of contentChildren) this._contentEl.appendChild(child);
@@ -238,8 +211,6 @@ export class AdwDialog extends HTMLElement {
238
211
  }
239
212
  }
240
213
 
241
- // ── internals ──────────────────────────────────────────────────────────
242
-
243
214
  /** A dismissal was requested: honour `can-close`, else raise close-attempt. */
244
215
  private _attemptClose(): void {
245
216
  if (!this.open) return;
@@ -306,20 +277,18 @@ export class AdwDialog extends HTMLElement {
306
277
  const title = this.title;
307
278
  this._titleEl.textContent = title;
308
279
 
309
- // The header shows when there is a title or the author forces it.
310
280
  const hasHeader = title.length > 0 || this.hasAttribute('show-header');
311
281
  this._headerEl.hidden = !hasHeader;
312
282
 
313
- // Presentation mode drives the CSS layout (floating vs bottom sheet).
283
+ // The CSS reads both of these: the dataset picks floating vs bottom sheet, the
284
+ // custom properties carry the size constraints.
314
285
  this.dataset.presentation = this.presentationMode;
315
286
 
316
- // Size constraints as CSS custom properties consumed by the box.
317
287
  this._boxEl.style.setProperty('--adw-dialog-content-width', `${this.contentWidth}px`);
318
288
  const height = this.contentHeight;
319
289
  if (height > 0) this._boxEl.style.setProperty('--adw-dialog-content-height', `${height}px`);
320
290
  else this._boxEl.style.removeProperty('--adw-dialog-content-height');
321
291
 
322
- // A locked dialog's close button has no effect; reflect that.
323
292
  const locked = !this.canClose;
324
293
  this._closeBtn.disabled = locked;
325
294
  this._closeBtn.classList.toggle('locked', locked);