@gjsify/adwaita-web 0.36.0 → 0.38.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 (151) hide show
  1. package/dist/adwaita-web.css +504 -34
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/accent.d.ts +28 -0
  4. package/lib/types/adw-accent.spec.d.ts +2 -0
  5. package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
  6. package/lib/types/breakpoints.d.ts +10 -12
  7. package/lib/types/elements/adw-about-dialog.d.ts +19 -36
  8. package/lib/types/elements/adw-action-row.d.ts +9 -12
  9. package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
  10. package/lib/types/elements/adw-avatar.d.ts +1 -0
  11. package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
  12. package/lib/types/elements/adw-button-content.d.ts +3 -4
  13. package/lib/types/elements/adw-button-row.d.ts +1 -1
  14. package/lib/types/elements/adw-carousel.d.ts +28 -49
  15. package/lib/types/elements/adw-checks.d.ts +2 -8
  16. package/lib/types/elements/adw-combo-row.d.ts +6 -6
  17. package/lib/types/elements/adw-data-grid.d.ts +5 -10
  18. package/lib/types/elements/adw-drop-down.d.ts +3 -11
  19. package/lib/types/elements/adw-entry-row.d.ts +20 -23
  20. package/lib/types/elements/adw-icon.d.ts +2 -4
  21. package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
  22. package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
  23. package/lib/types/elements/adw-navigation-view.d.ts +2 -10
  24. package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
  25. package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
  26. package/lib/types/elements/adw-popover.d.ts +2 -7
  27. package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
  28. package/lib/types/elements/adw-preferences-group.d.ts +12 -14
  29. package/lib/types/elements/adw-progress-bar.d.ts +3 -4
  30. package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
  31. package/lib/types/elements/adw-sidebar.d.ts +5 -10
  32. package/lib/types/elements/adw-spinner.d.ts +3 -5
  33. package/lib/types/elements/adw-split-button.d.ts +6 -16
  34. package/lib/types/elements/adw-switch-row.d.ts +3 -3
  35. package/lib/types/elements/adw-tab-view.d.ts +16 -26
  36. package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
  37. package/lib/types/elements/adw-view-stack.d.ts +7 -17
  38. package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
  39. package/lib/types/elements/adw-view-switcher.d.ts +18 -38
  40. package/lib/types/elements/adw-wrap-box.d.ts +7 -17
  41. package/lib/types/elements/view-switcher-dom.d.ts +10 -13
  42. package/lib/types/index.d.ts +3 -0
  43. package/lib/types/source-view/hex.d.ts +7 -13
  44. package/lib/types/style-classes.spec.d.ts +11 -0
  45. package/lib/types/styles.generated.d.ts +1 -1
  46. package/package.json +18 -7
  47. package/scss/_about_dialog.scss +3 -2
  48. package/scss/_action_row.scss +33 -0
  49. package/scss/_avatar.scss +12 -0
  50. package/scss/_button.scss +44 -22
  51. package/scss/_card.scss +11 -8
  52. package/scss/_checks.scss +2 -4
  53. package/scss/_dialog.scss +3 -2
  54. package/scss/_headerbar.scss +23 -0
  55. package/scss/_popover.scss +8 -4
  56. package/scss/_preferences.scss +7 -7
  57. package/scss/_progress_bar.scss +9 -6
  58. package/scss/_reset.scss +1 -1
  59. package/scss/_shortcut_label.scss +80 -0
  60. package/scss/_sidebar.scss +57 -0
  61. package/scss/_status_page.scss +20 -0
  62. package/scss/_style_classes.scss +407 -0
  63. package/scss/_switch.scss +4 -3
  64. package/scss/_theme.scss +20 -2
  65. package/scss/_variables.scss +34 -1
  66. package/scss/adwaita-skin.scss +12 -0
  67. package/src/accent.ts +74 -0
  68. package/src/adw-about-dialog.spec.ts +34 -49
  69. package/src/adw-accent.spec.ts +105 -0
  70. package/src/adw-action-rows.spec.ts +25 -33
  71. package/src/adw-avatar.spec.ts +51 -20
  72. package/src/adw-banner.spec.ts +5 -8
  73. package/src/adw-button-content.spec.ts +9 -12
  74. package/src/adw-button-row.spec.ts +2 -4
  75. package/src/adw-carousel.spec.ts +40 -53
  76. package/src/adw-checks.spec.ts +36 -47
  77. package/src/adw-drop-down.spec.ts +25 -32
  78. package/src/adw-header-bar.spec.ts +5 -7
  79. package/src/adw-icon.spec.ts +23 -32
  80. package/src/adw-progress-bar.spec.ts +10 -12
  81. package/src/adw-row-state.spec.ts +9 -10
  82. package/src/adw-shortcut-label.spec.ts +112 -0
  83. package/src/adw-sidebar.spec.ts +17 -24
  84. package/src/adw-switch.spec.ts +11 -17
  85. package/src/adw-tab-view.spec.ts +7 -10
  86. package/src/adw-toast-overlay.spec.ts +7 -11
  87. package/src/adw-view-switcher-bar.spec.ts +10 -14
  88. package/src/adw-wrap-box.spec.ts +25 -36
  89. package/src/bottom-sheet.spec.ts +30 -36
  90. package/src/breakpoints.ts +15 -18
  91. package/src/chrome.spec.ts +13 -21
  92. package/src/elements/adw-about-dialog.ts +71 -114
  93. package/src/elements/adw-action-row.ts +22 -37
  94. package/src/elements/adw-alert-dialog.ts +25 -50
  95. package/src/elements/adw-avatar.ts +40 -15
  96. package/src/elements/adw-banner.ts +14 -18
  97. package/src/elements/adw-bottom-sheet.ts +31 -52
  98. package/src/elements/adw-button-content.ts +30 -37
  99. package/src/elements/adw-button-row.ts +14 -24
  100. package/src/elements/adw-carousel.ts +87 -148
  101. package/src/elements/adw-checks.ts +43 -56
  102. package/src/elements/adw-clamp.ts +8 -9
  103. package/src/elements/adw-combo-row.ts +17 -25
  104. package/src/elements/adw-data-grid.ts +26 -38
  105. package/src/elements/adw-dialog.ts +25 -56
  106. package/src/elements/adw-drop-down.ts +31 -51
  107. package/src/elements/adw-entry-row.ts +39 -46
  108. package/src/elements/adw-expander-row.ts +30 -53
  109. package/src/elements/adw-icon.ts +30 -53
  110. package/src/elements/adw-inline-view-switcher.ts +38 -67
  111. package/src/elements/adw-menu-button.ts +28 -58
  112. package/src/elements/adw-navigation-split-view.ts +35 -47
  113. package/src/elements/adw-navigation-view.ts +12 -28
  114. package/src/elements/adw-overlay-split-view.ts +100 -175
  115. package/src/elements/adw-password-entry-row.ts +18 -28
  116. package/src/elements/adw-popover.ts +28 -44
  117. package/src/elements/adw-preferences-dialog.ts +41 -66
  118. package/src/elements/adw-preferences-group.ts +33 -46
  119. package/src/elements/adw-progress-bar.ts +27 -47
  120. package/src/elements/adw-shortcut-label.ts +138 -0
  121. package/src/elements/adw-sidebar.ts +31 -39
  122. package/src/elements/adw-spin-row.ts +7 -14
  123. package/src/elements/adw-spinner.ts +31 -43
  124. package/src/elements/adw-split-button.ts +45 -75
  125. package/src/elements/adw-switch-row.ts +29 -46
  126. package/src/elements/adw-switch.ts +28 -47
  127. package/src/elements/adw-tab-view.ts +62 -103
  128. package/src/elements/adw-toast-overlay.ts +28 -35
  129. package/src/elements/adw-toggle-group.ts +9 -16
  130. package/src/elements/adw-view-stack.ts +32 -51
  131. package/src/elements/adw-view-switcher-bar.ts +26 -50
  132. package/src/elements/adw-view-switcher.ts +60 -110
  133. package/src/elements/adw-window-title.ts +4 -4
  134. package/src/elements/adw-wrap-box.ts +33 -59
  135. package/src/elements/view-switcher-dom.ts +15 -22
  136. package/src/entry-rows.spec.ts +18 -24
  137. package/src/index.ts +24 -15
  138. package/src/navigation-view.spec.ts +7 -10
  139. package/src/preferences-search.ts +11 -15
  140. package/src/preferences.spec.ts +13 -15
  141. package/src/source-view/adw-source-view.ts +6 -19
  142. package/src/source-view/hex.ts +7 -13
  143. package/src/split-button.spec.ts +32 -44
  144. package/src/split-views.spec.ts +83 -149
  145. package/src/style-classes.spec.ts +181 -0
  146. package/src/style-isolation.spec.ts +3 -6
  147. package/src/styles.generated.ts +1 -1
  148. package/src/tab-view.spec.ts +26 -35
  149. package/src/test.browser.mts +6 -0
  150. package/src/view-stack.spec.ts +23 -34
  151. package/src/view-switcher.spec.ts +56 -84
@@ -13,19 +13,15 @@
13
13
  // type-ahead (typing jumps to the next matching option).
14
14
  //
15
15
  // The popover is `<adw-popover>` and the dismissal/keyboard machine is
16
- // `@gjsify/adwaita-core`'s (ADR 0004). So is the SELECTION: the options list,
17
- // the index↔value mapping, the bounds guards and the
18
- // programmatic-vs-interactive split are {@link ComboState}, the same state
19
- // machine `<adw-combo-row>` composes — this element imported NOTHING from core
20
- // and re-derived all of it, which is how the two selectors ended up accepting
21
- // different option vocabularies (this one took `{value,label}` descriptors, the
22
- // row only bare strings). What stays here is what is genuinely a DROP-DOWN: the
23
- // search filter and the type-ahead buffer.
16
+ // `@gjsify/adwaita-core`'s (ADR 0004). So is the SELECTION: the options list, the
17
+ // index↔value mapping, the bounds guards and the programmatic-vs-interactive split are
18
+ // {@link ComboState}, the same state machine `<adw-combo-row>` composes — so both
19
+ // selectors accept one option vocabulary. What stays here is what is genuinely a
20
+ // DROP-DOWN: the search filter and the type-ahead buffer.
24
21
  //
25
- // The arrow/Home/End wrap arithmetic left earlier, with the popover lift — it
26
- // was a second copy of `<adw-menu-button>`'s, and both had the same bug (ArrowUp
27
- // from an unfocused list landed on the SECOND-TO-LAST option, which
28
- // `enable-search` hit on every single open because it focuses the entry).
22
+ // The arrow/Home/End wrap arithmetic belongs to the popover, not here: a per-element
23
+ // copy is how ArrowUp from an unfocused list lands on the SECOND-TO-LAST option, which
24
+ // `enable-search` hits on every open because it focuses the entry.
29
25
  //
30
26
  // Attributes:
31
27
  // options / items — JSON array: `["a","b"]` or `[{"value":"a","label":"A"}]`.
@@ -66,11 +62,9 @@ import type { AdwComboOption, AdwComboOptionInput } from '@gjsify/adwaita-core';
66
62
  import { createAdwIcon } from './adw-icon.js';
67
63
 
68
64
  /**
69
- * A dropdown option — `value` is the stable id, `label` the shown text.
70
- *
71
- * An alias of core's {@link AdwComboOption}: the drop-down and the combo row are
72
- * the same selection model behind two chromes, and they had two names for one
73
- * descriptor. Kept exported so the published type name does not break.
65
+ * A dropdown option — `value` is the stable id, `label` the shown text. An alias of
66
+ * core's {@link AdwComboOption}, since the drop-down and the combo row are the same
67
+ * selection model behind two chromes; still exported so the published name keeps working.
74
68
  */
75
69
  export type AdwDropDownOption = AdwComboOption;
76
70
 
@@ -103,7 +97,6 @@ export class AdwDropDown extends HTMLElement {
103
97
  if (this._initialized) this._renderAll();
104
98
  }
105
99
 
106
- /** Alias of `options` (Gtk.DropDown's model is often called "items"). */
107
100
  get items(): AdwDropDownOption[] {
108
101
  return this._state.options;
109
102
  }
@@ -131,12 +124,10 @@ export class AdwDropDown extends HTMLElement {
131
124
  if (index >= 0) this._selectIndex(index);
132
125
  }
133
126
 
134
- /** The selected option descriptor, or null when empty. */
135
127
  get selectedItem(): AdwDropDownOption | null {
136
128
  return this._state.options[this._state.selectedIndex] ?? null;
137
129
  }
138
130
 
139
- /** Whether the search entry is shown in the popover. */
140
131
  get enableSearch(): boolean {
141
132
  return this.hasAttribute('enable-search');
142
133
  }
@@ -146,7 +137,6 @@ export class AdwDropDown extends HTMLElement {
146
137
  else this.removeAttribute('enable-search');
147
138
  }
148
139
 
149
- /** Whether the popover is currently open. */
150
140
  get active(): boolean {
151
141
  return this._popoverEl?.open ?? false;
152
142
  }
@@ -177,19 +167,17 @@ export class AdwDropDown extends HTMLElement {
177
167
  this._popoverEl = document.createElement('adw-popover') as AdwPopover;
178
168
  this._popoverEl.classList.add('adw-drop-down-popover');
179
169
  this._popoverEl.setAttribute('role', 'listbox');
180
- // A dropdown's popover is a `popover.menu` too — `dropdown { popover.menu
181
- // { … } }` (_dropdowns.scss:22) — even though its rows are ARIA `option`s
182
- // rather than `menuitem`s. The style class and the role answer different
183
- // questions, so neither is derived from the other.
170
+ // A dropdown's popover is a `popover.menu` too — `dropdown { popover.menu { … } }`
171
+ // — even though its rows are ARIA `option`s rather than `menuitem`s. The style
172
+ // class and the role answer different questions, so neither derives the other.
184
173
  this._popoverEl.setAttribute('menu', '');
185
174
 
186
175
  this._listEl = document.createElement('div');
187
176
  this._listEl.className = 'adw-drop-down-list';
188
177
  this._popoverEl.appendChild(this._listEl);
189
178
 
190
- // Seed options from the property (already set) or the JSON attribute,
191
- // BEFORE anything subscribes, so building the initial DOM below is not
192
- // driven by a change notification.
179
+ // Seed options BEFORE anything subscribes, so building the initial DOM below is
180
+ // not driven by a change notification.
193
181
  if (this._state.count === 0) this._state.setOptions(this._parseOptionsAttr());
194
182
  const attrSelected = Number.parseInt(this.getAttribute('selected') ?? '', 10);
195
183
  if (this._state.hasIndex(attrSelected)) this._state.setSelectedIndex(attrSelected);
@@ -197,9 +185,9 @@ export class AdwDropDown extends HTMLElement {
197
185
  this.replaceChildren(this._buttonEl, this._popoverEl);
198
186
  this._popoverEl.anchor = this._buttonEl;
199
187
  this._popoverEl.subscribe((open) => this._onPopoverToggled(open));
200
- // Type-ahead is the ONE key class the shared popover deliberately leaves
201
- // to the renderer (`resolvePopoverKey` returns 'none' for printables), so
202
- // it needs a listener of its own alongside the popover's.
188
+ // Type-ahead is the ONE key class the shared popover deliberately leaves to the
189
+ // renderer (`resolvePopoverKey` returns 'none' for printables), so it needs a
190
+ // listener of its own alongside the popover's.
203
191
  this._popoverEl.addEventListener('keydown', (event) => this._onPopoverTypeAhead(event));
204
192
  this._renderAll();
205
193
  }
@@ -220,14 +208,12 @@ export class AdwDropDown extends HTMLElement {
220
208
  }
221
209
  }
222
210
 
223
- // ── internals ──────────────────────────────────────────────────────────
224
-
225
211
  private _parseOptionsAttr(): AdwDropDownOption[] {
226
212
  const raw = this.getAttribute('options') ?? this.getAttribute('items');
227
213
  if (!raw) return [];
228
214
  try {
229
- // `normalizeComboOptions` already guards a non-array, so a JSON
230
- // object or scalar yields the empty model rather than a throw.
215
+ // `normalizeComboOptions` already guards a non-array, so a JSON object or
216
+ // scalar yields the empty model rather than a throw.
231
217
  return normalizeComboOptions(JSON.parse(raw) as AdwComboOptionInput[]);
232
218
  } catch {
233
219
  return [];
@@ -237,12 +223,11 @@ export class AdwDropDown extends HTMLElement {
237
223
  private _selectIndex(index: number, opts: { fromAttr?: boolean; fromUser?: boolean } = {}): void {
238
224
  // The bounds arithmetic is `ComboState`'s; the POLICY of rejecting an
239
225
  // out-of-range set is this element's published `selected` contract, and
240
- // `<adw-combo-row>` deliberately answers it the other way — see
241
- // `ComboState.hasIndex`.
226
+ // `<adw-combo-row>` deliberately answers it the other way (`ComboState.hasIndex`).
242
227
  if (!this._state.hasIndex(index)) return;
243
- // Both core setters return "did it change", which is what gates the
244
- // notify — no second copy of the current index is kept here to compare
245
- // against, and `setOptions`' own label re-sync cannot masquerade as one.
228
+ // Both core setters return "did it change", which is what gates the notify — no
229
+ // second copy of the current index is kept here, so `setOptions`' own label
230
+ // re-sync cannot masquerade as one.
246
231
  const changed = opts.fromUser ? this._state.select(index) : this._state.setSelectedIndex(index);
247
232
  this._updateLabel();
248
233
  this._updateSelectedStates();
@@ -252,13 +237,12 @@ export class AdwDropDown extends HTMLElement {
252
237
  }
253
238
  if (changed) {
254
239
  const option = this._state.options[index];
255
- // `notify::selected` mirrors GObject property-notify: it fires on EVERY change,
240
+ // `notify::selected` mirrors GObject property-notify: EVERY change,
256
241
  // programmatic included.
257
242
  this.dispatchEvent(new CustomEvent('notify::selected', { bubbles: true, detail: { selected: index } }));
258
- // `change` mirrors the DOM <select> contract: it fires ONLY on a user-initiated change.
259
- // A programmatic `.selected`/`.selectedValue`/`selected`-attribute assignment stays silent
260
- // (just as native `select.value = x` fires no `change`), so consumers can set the value
261
- // without guarding against a spurious user-style event.
243
+ // `change` mirrors the DOM <select> contract: ONLY a user-initiated change. A
244
+ // programmatic assignment stays silent, just as native `select.value = x` fires
245
+ // no `change`, so consumers can set the value without guarding against it.
262
246
  if (opts.fromUser) {
263
247
  this.dispatchEvent(
264
248
  new CustomEvent('change', {
@@ -302,7 +286,6 @@ export class AdwDropDown extends HTMLElement {
302
286
  this._typeAhead(event.key);
303
287
  }
304
288
 
305
- /** Type-ahead: accumulate keys, focus/select the next matching option. */
306
289
  private _typeAhead(char: string): void {
307
290
  this._typeAheadBuffer += char.toLowerCase();
308
291
  if (this._typeAheadTimer) clearTimeout(this._typeAheadTimer);
@@ -313,7 +296,6 @@ export class AdwDropDown extends HTMLElement {
313
296
  else this._selectIndex(match, { fromUser: true });
314
297
  }
315
298
 
316
- /** Filter the visible items by a search query (case-insensitive substring). */
317
299
  private _filter(query: string): void {
318
300
  const q = query.trim().toLowerCase();
319
301
  this._itemButtons.forEach((button, index) => {
@@ -334,8 +316,8 @@ export class AdwDropDown extends HTMLElement {
334
316
  }
335
317
 
336
318
  private _updateLabel(): void {
337
- // The setters (`options`/`selected`) may run before connectedCallback
338
- // builds the DOM — stay a no-op until the label element exists.
319
+ // The setters may run before connectedCallback builds the DOM — a no-op until the
320
+ // label element exists.
339
321
  if (!this._labelEl) return;
340
322
  this._labelEl.textContent = this._state.selectedLabel;
341
323
  }
@@ -360,7 +342,6 @@ export class AdwDropDown extends HTMLElement {
360
342
  this._searchEl.placeholder = 'Search…';
361
343
  this._searchEl.setAttribute('aria-label', 'Search');
362
344
  this._searchEl.addEventListener('input', () => this._filter(this._searchEl?.value ?? ''));
363
- // Prepend before the list so it sits atop the options.
364
345
  this._popoverEl.insertBefore(this._searchEl, this._listEl);
365
346
  } else {
366
347
  const existing = this._popoverEl.querySelector('.adw-drop-down-search');
@@ -378,7 +359,6 @@ export class AdwDropDown extends HTMLElement {
378
359
  label.className = 'adw-drop-down-item-label';
379
360
  label.textContent = option.label;
380
361
 
381
- // A check icon marks the selected option (Gtk.DropDown's selected row).
382
362
  const check = document.createElement('span');
383
363
  check.className = 'adw-drop-down-item-check';
384
364
  check.setAttribute('aria-hidden', 'true');
@@ -16,12 +16,11 @@
16
16
  // activatesDefault, editing.
17
17
  // Events:
18
18
  // `changed` + `notify::text` — every buffer change. libadwaita implements
19
- // GtkEditable through a delegate (adw-entry-row.c:87-93, :821-834) so it
20
- // emits BOTH spellings; the two ports had each picked one and drifted, so
21
- // this emits both and code written against either runs against the other.
22
- // `notify::text-length` — on_length_changed (C:268-273).
23
- // `apply` — the apply button, or Enter while it shows (C:240).
24
- // `entry-activated` — Enter otherwise (C:256), AFTER the default was activated.
19
+ // GtkEditable through a delegate, so it emits BOTH spellings and so does this:
20
+ // code written against either runs against the other.
21
+ // `notify::text-length` — `on_length_changed`.
22
+ // `apply` — the apply button, or Enter while it shows.
23
+ // `entry-activated` — Enter otherwise, AFTER the default was activated.
25
24
  //
26
25
  // Reference: refs/libadwaita/src/adw-entry-row.c, adw-entry-row.ui
27
26
  // Reference: refs/adwaita-web/adwaita-web/docs/widgets/entryrow.md
@@ -41,13 +40,10 @@ import {
41
40
  import { type AdwIcon, createAdwIcon } from './adw-icon.js';
42
41
 
43
42
  /**
44
- * Show/hide a rendered part.
45
- *
46
- * `hidden` carries the semantics and is what the conformance suite reads, but
47
- * author CSS beats the UA `[hidden]` rule: `.adw-icon` already ships its own
48
- * `&[hidden] { display: none }` override in `_icon.scss` and `.adw-button`
49
- * does not, so a hidden apply button would still paint. The inline `display`
50
- * rides along until the entry-row SCSS pass lands.
43
+ * Show/hide a rendered part. `hidden` carries the semantics and is what the conformance
44
+ * suite reads, but author CSS beats the UA `[hidden]` rule: `.adw-icon` ships its own
45
+ * `&[hidden]` override in `_icon.scss` and `.adw-button` does not, so a hidden apply
46
+ * button would still paint without the inline `display`.
51
47
  */
52
48
  function setPartVisible(part: HTMLElement, visible: boolean): void {
53
49
  part.hidden = !visible;
@@ -93,7 +89,6 @@ export class AdwEntryRow extends HTMLElement {
93
89
  return 'adw-entry-row';
94
90
  }
95
91
 
96
- /** The editable text. */
97
92
  get text(): string {
98
93
  return this._state.text;
99
94
  }
@@ -143,7 +138,6 @@ export class AdwEntryRow extends HTMLElement {
143
138
  this._state.setActivatesDefault(value);
144
139
  }
145
140
 
146
- /** Whether the embedded entry currently has focus. */
147
141
  get editing(): boolean {
148
142
  return this._state.editing;
149
143
  }
@@ -166,7 +160,7 @@ export class AdwEntryRow extends HTMLElement {
166
160
  this._renderTitle();
167
161
 
168
162
  // Subclass parts first, THEN the author's children: `AdwPasswordEntryRow`
169
- // installs its peek toggle from its own `init` (C:152), so a consumer
163
+ // installs its peek toggle from its own `init`, so a consumer
170
164
  // suffix lands after it and both remain.
171
165
  this._onConnected();
172
166
  this._adoptAuthored();
@@ -202,9 +196,9 @@ export class AdwEntryRow extends HTMLElement {
202
196
  }
203
197
 
204
198
  /**
205
- * Add a widget after the editable area — `adw_entry_row_add_suffix`
206
- * (adw-entry-row.c:885-899), which APPENDS into a box, so several suffixes
207
- * coexist and the password row's own peek toggle survives a consumer's.
199
+ * Add a widget after the editable area — `adw_entry_row_add_suffix`, which APPENDS
200
+ * into a box, so several suffixes coexist and the password row's own peek toggle
201
+ * survives a consumer's.
208
202
  */
209
203
  addSuffix(node: Node): void {
210
204
  this._suffixes.append(node);
@@ -212,9 +206,9 @@ export class AdwEntryRow extends HTMLElement {
212
206
  }
213
207
 
214
208
  /**
215
- * Add a widget before the editable area — `adw_entry_row_add_prefix`
216
- * (C:864-880). PREPENDS, like the `gtk_box_prepend` it mirrors, so several
217
- * prefixes stack in reverse call order exactly as they do in GTK.
209
+ * Add a widget before the editable area — `adw_entry_row_add_prefix`. PREPENDS, like
210
+ * the `gtk_box_prepend` it mirrors, so several prefixes stack in reverse call order
211
+ * exactly as they do in GTK.
218
212
  */
219
213
  addPrefix(node: Node): void {
220
214
  this._prefixes.prepend(node);
@@ -223,32 +217,32 @@ export class AdwEntryRow extends HTMLElement {
223
217
 
224
218
  /**
225
219
  * Drive the trailing indicator — `adw_entry_row_set_show_indicator`
226
- * (adw-entry-row-private.h, C:1281-1296). Private-to-the-library upstream,
227
- * public here because `<adw-password-entry-row>` is a separate custom element
228
- * rather than a C subclass with access to the private header.
220
+ * (adw-entry-row-private.h). Private-to-the-library upstream, public here because
221
+ * `<adw-password-entry-row>` is a separate custom element rather than a C subclass
222
+ * with access to the private header.
229
223
  */
230
224
  setShowIndicator(show: boolean): void {
231
225
  this._state.setShowIndicator(show);
232
226
  }
233
227
 
234
- /** `adw_entry_row_set_indicator_icon_name` (C:1263-1273). */
228
+ /** `adw_entry_row_set_indicator_icon_name`. */
235
229
  setIndicatorIconName(iconName: string): void {
236
230
  this._indicator.dataset.iconName = iconName;
237
231
  }
238
232
 
239
- /** `adw_entry_row_set_indicator_tooltip` (C:1275-1279). */
233
+ /** `adw_entry_row_set_indicator_tooltip`. */
240
234
  setIndicatorTooltip(tooltip: string): void {
241
235
  this._indicator.title = tooltip;
242
236
  this._indicator.setAttribute('aria-label', tooltip);
243
237
  }
244
238
 
245
- /** Apply the pending edit — the apply button click (C:229-241). */
239
+ /** Apply the pending edit — the apply button click. */
246
240
  apply(): void {
247
241
  this._state.apply();
248
242
  this.dispatchEvent(new CustomEvent('apply', { bubbles: true }));
249
243
  }
250
244
 
251
- /** Focus the entry without selecting — `adw_entry_row_grab_focus_without_selecting` (C:1379-1389). */
245
+ /** Focus the entry without selecting — `adw_entry_row_grab_focus_without_selecting`. */
252
246
  grabFocusWithoutSelecting(): boolean {
253
247
  this._input.focus();
254
248
  return this._state.editing;
@@ -271,11 +265,11 @@ export class AdwEntryRow extends HTMLElement {
271
265
  this._suffixes = document.createElement('div');
272
266
  this._suffixes.className = `${prefix}-suffixes`;
273
267
 
274
- // TWO title labels, exactly like adw-entry-row.ui: `empty_title` is the
275
- // large placeholder shown while the row is empty, `title` the small label
276
- // above the value. GTK cross-fades them over EMPTY_ANIMATION_DURATION_MS
277
- // (C:433-439); until the SCSS pass lands this is a hard swap at the two
278
- // animation endpoints, which is why the render reads `emptyTarget`.
268
+ // TWO title labels, exactly like adw-entry-row.ui: `empty_title` is the large
269
+ // placeholder shown while the row is empty, `title` the small label above the
270
+ // value. GTK cross-fades them over EMPTY_ANIMATION_DURATION_MS; here it is a hard
271
+ // swap at the two animation endpoints, which is why the render reads
272
+ // `emptyTarget`.
279
273
  this._emptyTitle = document.createElement('span');
280
274
  this._emptyTitle.className = `adw-row-title ${prefix}-empty-title`;
281
275
  this._title = document.createElement('span');
@@ -328,8 +322,8 @@ export class AdwEntryRow extends HTMLElement {
328
322
  this._editIcon = createAdwIcon('document-edit', 'adw-row-edit', `${prefix}-edit`);
329
323
  this._editIcon.dataset.iconName = ENTRY_ROW_EDIT_ICON_NAME;
330
324
 
331
- // Snapshot order: indicator → apply → edit icon inside the editable area
332
- // (C:429-431), with the prefixes/suffixes boxes outside it.
325
+ // Snapshot order: indicator → apply → edit icon inside the editable area, with
326
+ // the prefixes/suffixes boxes outside it.
333
327
  this.replaceChildren(
334
328
  this._prefixes,
335
329
  this._area,
@@ -345,9 +339,8 @@ export class AdwEntryRow extends HTMLElement {
345
339
  }
346
340
 
347
341
  /**
348
- * Re-home the children the author wrote. libadwaita's buildable maps an
349
- * untyped `<child>` to a suffix (C:796-812); `slot="prefix"` is the web
350
- * spelling of its `type="prefix"`.
342
+ * Re-home the children the author wrote. libadwaita's buildable maps an untyped
343
+ * `<child>` to a suffix; `slot="prefix"` is the web spelling of `type="prefix"`.
351
344
  */
352
345
  private _adoptAuthored(): void {
353
346
  for (const node of this._authored) {
@@ -358,10 +351,10 @@ export class AdwEntryRow extends HTMLElement {
358
351
  }
359
352
 
360
353
  /**
361
- * `pressed_cb` (C:201-227): a click landing on the row, the editable area,
362
- * the indicator or either affordance box grabs focus; anything else (a suffix
363
- * control, the apply button) is left alone. The pencil is `can-target=False`
364
- * in the .ui, so a click on it picks the editable area — hence it focuses too.
354
+ * `pressed_cb`: a click landing on the row, the editable area, the indicator or
355
+ * either affordance box grabs focus; anything else (a suffix control, the apply
356
+ * button) is left alone. The pencil is `can-target=False` in the .ui, so a click on
357
+ * it picks the editable area — hence it focuses too.
365
358
  */
366
359
  private _maybeFocus(event: MouseEvent): void {
367
360
  const target = event.target;
@@ -379,15 +372,15 @@ export class AdwEntryRow extends HTMLElement {
379
372
  }
380
373
  }
381
374
 
382
- /** `text_activated_cb` (C:243-266) — exactly one of the two signals. */
375
+ /** `text_activated_cb` — exactly one of the two signals. */
383
376
  private _activate(): void {
384
377
  const activation = this._state.activate();
385
378
  if (activation.signal === 'apply') {
386
379
  this.dispatchEvent(new CustomEvent('apply', { bubbles: true }));
387
380
  return;
388
381
  }
389
- // C:253-256 — the default widget is activated BEFORE `entry-activated`.
390
- // Implicit form submission is the web's default-widget activation.
382
+ // The default widget is activated BEFORE `entry-activated`; implicit form
383
+ // submission is the web's default-widget activation.
391
384
  if (activation.activateDefault) this.closest('form')?.requestSubmit();
392
385
  this.dispatchEvent(new CustomEvent('entry-activated', { bubbles: true }));
393
386
  }
@@ -1,28 +1,16 @@
1
- // <adw-expander-row> — A boxed-list row that discloses nested rows when expanded,
1
+ // <adw-expander-row> — a boxed-list row that discloses nested rows when expanded,
2
2
  // with an optional enable switch. Mirrors Adw.ExpanderRow.
3
- // Attributes: title, subtitle, expanded (boolean), enable-expansion (boolean),
4
- // show-enable-switch (boolean).
5
- // Slots: slot="prefix" / slot="suffix" widgets sit in the header row beside the
6
- // title (before the enable switch + disclosure chevron), like Adw.ExpanderRow's
7
- // add_prefix / add_suffix; any other child <adw-*-row> is moved into the
8
- // disclosed content listbox.
9
- // Properties: expanded, enableExpansion (get/set, reflect to attributes).
10
- // Events: notify::expanded (CustomEvent), notify::enable-expansion (CustomEvent)
11
- // — mirror Adw.ExpanderRow's GObject signal names so the same code path drives
12
- // both the web and GTK renderers.
3
+ //
4
+ // Slots: `slot="prefix"` / `slot="suffix"` children sit in the HEADER row beside the
5
+ // title (before the enable switch and disclosure chevron), like Adw.ExpanderRow's
6
+ // add_prefix/add_suffix; every other child is moved into the disclosed content
7
+ // listbox. The `notify::expanded` / `notify::enable-expansion` events are named after
8
+ // Adw.ExpanderRow's GObject signals so one code path drives the web and GTK renderers.
13
9
  //
14
10
  // The DISCLOSURE state (the expanded flag and its idempotent, notify-on-change
15
11
  // transitions) is HEADLESS and lives in `@gjsify/adwaita-core` (ADR 0004) as
16
- // {@link ExpanderState}; this element composes it and keeps only the DOM render
17
- // half — the header/content markup, the `expanded` attribute reflection and the
18
- // `notify::expanded` event. `@gjsify/adwaita-nativescript` composes the same
19
- // state machine, so both ports share one behaviour.
20
- //
21
- // The optional enable switch is `<adw-switch>`. Both the markup and the
22
- // stylesheet block behind it used to be hand-built copies of
23
- // `<adw-switch-row>`'s — `_expander_row.scss:73-121` was character-for-character
24
- // `_switch_row.scss:15-63` — because `.adw-switch` was nested inside
25
- // `adw-switch-row {` and unusable from here.
12
+ // {@link ExpanderState}; this element keeps only the DOM half — header/content markup,
13
+ // the `expanded` attribute reflection and the event.
26
14
  //
27
15
  // Reference: refs/adwaita-web/adwaita-web/scss/_expander_row.scss
28
16
  // Reference: refs/libadwaita/src/stylesheet/widgets/_expander-row.scss
@@ -33,13 +21,12 @@
33
21
 
34
22
  import { ExpanderState, deriveRowLabels } from '@gjsify/adwaita-core';
35
23
 
36
- // SIDE-EFFECT imports, deliberately separate from the type import below: they
37
- // are what guarantee `adw-switch` / `adw-icon` are defined before this module's
38
- // own `customElements.define` can upgrade a server-rendered `<adw-expander-row>`
39
- // and build them. A combined `import { AdwSwitch }` would NOT do it — the
40
- // binding is only ever used in type position here, and this package compiles
41
- // without `verbatimModuleSyntax`, so TypeScript would elide the whole statement
42
- // and take the registration with it.
24
+ // SIDE-EFFECT import, deliberately separate from the type import below: it guarantees
25
+ // `adw-switch` is defined before this module's `customElements.define` can upgrade a
26
+ // server-rendered `<adw-expander-row>` and build one. A combined
27
+ // `import { AdwSwitch }` would NOT do it — the binding is only used in type position,
28
+ // and this package compiles without `verbatimModuleSyntax`, so TypeScript would elide
29
+ // the statement and take the registration with it.
43
30
  import './adw-switch.js';
44
31
  import type { AdwSwitch } from './adw-switch.js';
45
32
  import { type AdwIcon, createAdwIcon } from './adw-icon.js';
@@ -65,10 +52,10 @@ export class AdwExpanderRow extends HTMLElement {
65
52
 
66
53
  constructor() {
67
54
  super();
68
- // Subscribed in the constructor (NOT connectedCallback) so a disclosure
69
- // set before the element is connected still reflects to the attribute.
70
- // The custom-element constructor rules allow this — nothing touches the
71
- // DOM until the state actually changes.
55
+ // Subscribed in the constructor, NOT connectedCallback, so a disclosure set
56
+ // before the element is connected still reflects to the attribute. The
57
+ // custom-element constructor rules allow it: nothing touches the DOM until the
58
+ // state actually changes.
72
59
  this._state.subscribe((expanded) => {
73
60
  if (expanded) this.setAttribute('expanded', '');
74
61
  else this.removeAttribute('expanded');
@@ -115,15 +102,11 @@ export class AdwExpanderRow extends HTMLElement {
115
102
  if (this._initialized) return;
116
103
  this._initialized = true;
117
104
 
118
- // Slotted prefix/suffix widgets go in the header; everything else is a
119
- // nested row to disclose.
120
105
  const prefixChildren = Array.from(this.querySelectorAll(':scope > [slot="prefix"]'));
121
106
  const suffixChildren = Array.from(this.querySelectorAll(':scope > [slot="suffix"]'));
122
107
  const claimed = new Set<Node>([...prefixChildren, ...suffixChildren]);
123
108
  const rows = Array.from(this.childNodes).filter((node) => !claimed.has(node));
124
109
 
125
- // Header row — looks like an action row: a text column, an optional
126
- // enable switch, and the disclosure chevron.
127
110
  this._headerEl = document.createElement('div');
128
111
  this._headerEl.className = 'adw-expander-row-header';
129
112
 
@@ -135,15 +118,13 @@ export class AdwExpanderRow extends HTMLElement {
135
118
  this._subtitleEl.className = 'adw-row-subtitle';
136
119
  textEl.append(this._titleEl, this._subtitleEl);
137
120
 
138
- // Optional enable switch (the same toggle Adw.ExpanderRow shows when
139
- // show-enable-switch is set). Clicking it does NOT toggle the disclosure.
121
+ // Clicking the enable switch does NOT toggle the disclosure.
140
122
  this._enableSwitch = document.createElement('adw-switch') as AdwSwitch;
141
123
  this._enableSwitch.classList.add('adw-expander-row-enable-switch');
142
124
  this._enableSwitch.active = this.enableExpansion;
143
125
 
144
126
  this._chevronEl = createAdwIcon('go-down', 'adw-expander-row-chevron');
145
127
 
146
- // Header prefix / suffix widget slots (Adw.ExpanderRow add_prefix/add_suffix).
147
128
  this._prefixEl = document.createElement('div');
148
129
  this._prefixEl.className = 'adw-expander-row-prefix';
149
130
  for (const child of prefixChildren) this._prefixEl.appendChild(child);
@@ -154,7 +135,6 @@ export class AdwExpanderRow extends HTMLElement {
154
135
 
155
136
  this._headerEl.append(this._prefixEl, textEl, this._suffixEl, this._enableSwitch, this._chevronEl);
156
137
 
157
- // Disclosed content — a nested listbox holding the rows.
158
138
  this._contentEl = document.createElement('div');
159
139
  this._contentEl.className = 'adw-expander-row-content';
160
140
  for (const row of rows) this._contentEl.appendChild(row);
@@ -164,9 +144,9 @@ export class AdwExpanderRow extends HTMLElement {
164
144
  this._headerEl.addEventListener('click', (event) => {
165
145
  // Clicks on the enable switch toggle expansion-enable, not disclosure.
166
146
  if (this._enableSwitch.contains(event.target as Node)) return;
167
- // The core state machine flips + reflects + re-renders; the event is
168
- // emitted here so only a USER disclosure notifies (a programmatic
169
- // `expanded = …` stays silent, as it always has on this port).
147
+ // The core state machine flips, reflects and re-renders; the event is emitted
148
+ // here so only a USER disclosure notifies — a programmatic `expanded = …`
149
+ // stays silent.
170
150
  if (this._state.toggle()) {
171
151
  this.dispatchEvent(
172
152
  new CustomEvent('notify::expanded', { bubbles: true, detail: { expanded: this.expanded } }),
@@ -175,10 +155,9 @@ export class AdwExpanderRow extends HTMLElement {
175
155
  });
176
156
 
177
157
  this._enableSwitch.addEventListener('notify::active', (event) => {
178
- // The switch's own notify is stopped at the row: the row publishes
179
- // `notify::enable-expansion`, and letting an identically-shaped
180
- // `notify::active` past would put a second, differently-named event
181
- // for the same user action on every ancestor listener.
158
+ // The switch's own notify stops at the row: the row publishes
159
+ // `notify::enable-expansion`, and letting `notify::active` past would give
160
+ // every ancestor listener a second, differently-named event for one action.
182
161
  event.stopPropagation();
183
162
  if (this._reflectingEnable) return;
184
163
  const enabled = this._enableSwitch.active;
@@ -191,23 +170,21 @@ export class AdwExpanderRow extends HTMLElement {
191
170
  );
192
171
  });
193
172
 
194
- // Seed the disclosure from the parsed markup — attributeChangedCallback
195
- // is guarded until the element is initialized, so a declarative
196
- // `expanded` attribute has to be adopted here.
173
+ // Seed the disclosure from the parsed markup: attributeChangedCallback is guarded
174
+ // until the element is initialized, so a declarative `expanded` lands here.
197
175
  this._state.setExpanded(this.hasAttribute('expanded'));
198
176
  this._render();
199
177
  }
200
178
 
201
179
  attributeChangedCallback(name: string) {
202
180
  if (!this._initialized) return;
203
- // The `expanded` attribute is the declarative face of the core state.
204
181
  if (name === 'expanded') this._state.setExpanded(this.hasAttribute('expanded'));
205
182
  this._render();
206
183
  }
207
184
 
208
185
  private _render() {
209
- // The `string_is_not_empty` label rule, from core — this block was one of
210
- // six hand-rolled copies, all of which omitted the TITLE half.
186
+ // The `string_is_not_empty` label rule, from core — it hides the TITLE too, not
187
+ // just the subtitle.
211
188
  const labels = deriveRowLabels({
212
189
  title: this.getAttribute('title'),
213
190
  subtitle: this.getAttribute('subtitle'),