@gjsify/adwaita-web 0.31.0 → 0.32.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 (176) hide show
  1. package/dist/adwaita-web.css +786 -272
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/adw-about-dialog.spec.d.ts +1 -0
  4. package/lib/types/adw-action-rows.spec.d.ts +1 -0
  5. package/lib/types/adw-avatar.spec.d.ts +1 -0
  6. package/lib/types/adw-banner.spec.d.ts +1 -0
  7. package/lib/types/adw-button-content.spec.d.ts +1 -0
  8. package/lib/types/adw-carousel.spec.d.ts +1 -0
  9. package/lib/types/adw-checks.spec.d.ts +1 -0
  10. package/lib/types/adw-header-bar.spec.d.ts +1 -0
  11. package/lib/types/adw-icon.spec.d.ts +1 -0
  12. package/lib/types/adw-progress-bar.spec.d.ts +1 -0
  13. package/lib/types/adw-sidebar.spec.d.ts +1 -0
  14. package/lib/types/adw-switch.spec.d.ts +1 -0
  15. package/lib/types/adw-wrap-box.spec.d.ts +1 -0
  16. package/lib/types/bottom-sheet.spec.d.ts +1 -0
  17. package/lib/types/breakpoints.d.ts +22 -0
  18. package/lib/types/breakpoints.spec.d.ts +1 -0
  19. package/lib/types/chrome.spec.d.ts +1 -0
  20. package/lib/types/elements/adw-about-dialog.d.ts +47 -8
  21. package/lib/types/elements/adw-action-row.d.ts +35 -0
  22. package/lib/types/elements/adw-avatar.d.ts +11 -0
  23. package/lib/types/elements/adw-bottom-sheet.d.ts +33 -4
  24. package/lib/types/elements/adw-button-content.d.ts +12 -0
  25. package/lib/types/elements/adw-button-row.d.ts +10 -1
  26. package/lib/types/elements/adw-carousel.d.ts +111 -8
  27. package/lib/types/elements/adw-checks.d.ts +57 -0
  28. package/lib/types/elements/adw-clamp.d.ts +4 -1
  29. package/lib/types/elements/adw-data-grid.d.ts +11 -31
  30. package/lib/types/elements/adw-drop-down.d.ts +17 -20
  31. package/lib/types/elements/adw-entry-row.d.ts +93 -3
  32. package/lib/types/elements/adw-expander-row.d.ts +7 -1
  33. package/lib/types/elements/adw-header-bar.d.ts +15 -0
  34. package/lib/types/elements/adw-icon.d.ts +25 -0
  35. package/lib/types/elements/adw-inline-view-switcher.d.ts +37 -11
  36. package/lib/types/elements/adw-menu-button.d.ts +6 -6
  37. package/lib/types/elements/adw-navigation-split-view.d.ts +6 -0
  38. package/lib/types/elements/adw-navigation-view.d.ts +63 -20
  39. package/lib/types/elements/adw-overlay-split-view.d.ts +38 -0
  40. package/lib/types/elements/adw-password-entry-row.d.ts +19 -10
  41. package/lib/types/elements/adw-popover.d.ts +58 -0
  42. package/lib/types/elements/adw-preferences-dialog.d.ts +57 -2
  43. package/lib/types/elements/adw-preferences-group.d.ts +31 -0
  44. package/lib/types/elements/adw-progress-bar.d.ts +38 -0
  45. package/lib/types/elements/adw-sidebar.d.ts +37 -5
  46. package/lib/types/elements/adw-split-button.d.ts +53 -7
  47. package/lib/types/elements/adw-switch-row.d.ts +11 -2
  48. package/lib/types/elements/adw-switch.d.ts +15 -0
  49. package/lib/types/elements/adw-tab-view.d.ts +129 -12
  50. package/lib/types/elements/adw-toolbar-view.d.ts +5 -0
  51. package/lib/types/elements/adw-view-stack.d.ts +27 -19
  52. package/lib/types/elements/adw-view-switcher-bar.d.ts +23 -7
  53. package/lib/types/elements/adw-view-switcher.d.ts +22 -11
  54. package/lib/types/elements/adw-window-title.d.ts +3 -1
  55. package/lib/types/elements/view-switcher-dom.d.ts +44 -0
  56. package/lib/types/entry-rows.spec.d.ts +1 -0
  57. package/lib/types/index.d.ts +7 -0
  58. package/lib/types/navigation-view.spec.d.ts +1 -0
  59. package/lib/types/preferences-search.d.ts +20 -0
  60. package/lib/types/preferences.spec.d.ts +1 -0
  61. package/lib/types/split-button.spec.d.ts +1 -0
  62. package/lib/types/split-views.spec.d.ts +1 -0
  63. package/lib/types/styles.generated.d.ts +1 -1
  64. package/lib/types/tab-view.spec.d.ts +1 -0
  65. package/lib/types/view-stack.spec.d.ts +1 -0
  66. package/lib/types/view-switcher.spec.d.ts +1 -0
  67. package/package.json +6 -6
  68. package/scss/_banner.scss +13 -0
  69. package/scss/_bottom_sheet.scss +5 -6
  70. package/scss/_button.scss +3 -26
  71. package/scss/_button_content.scss +28 -0
  72. package/scss/_checks.scss +228 -0
  73. package/scss/_clamp.scss +9 -3
  74. package/scss/_data_grid.scss +15 -7
  75. package/scss/_drop_down.scss +12 -55
  76. package/scss/_expander_row.scss +5 -50
  77. package/scss/_icon.scss +51 -0
  78. package/scss/_icons.generated.scss +6 -0
  79. package/scss/_inline_view_switcher.scss +34 -0
  80. package/scss/_labels.scss +135 -0
  81. package/scss/_menu_button.scss +10 -70
  82. package/scss/_popover.scss +186 -0
  83. package/scss/_preferences.scss +29 -0
  84. package/scss/_preferences_dialog.scss +17 -0
  85. package/scss/_progress_bar.scss +161 -0
  86. package/scss/_reset.scss +18 -7
  87. package/scss/_spinner.scss +7 -3
  88. package/scss/_split_button.scss +6 -50
  89. package/scss/_switch.scss +90 -0
  90. package/scss/_switch_row.scss +6 -52
  91. package/scss/_tab_view.scss +64 -0
  92. package/scss/_theme.scss +10 -0
  93. package/scss/_toolbar_view.scss +74 -0
  94. package/scss/_variables.scss +34 -0
  95. package/scss/_view_switcher.scss +40 -0
  96. package/scss/_view_switcher_bar.scss +37 -0
  97. package/scss/_wrap_box.scss +56 -6
  98. package/scss/adwaita-skin.scss +23 -0
  99. package/src/adw-about-dialog.spec.ts +350 -0
  100. package/src/adw-action-rows.spec.ts +342 -0
  101. package/src/adw-avatar.spec.ts +139 -0
  102. package/src/adw-banner.spec.ts +152 -0
  103. package/src/adw-button-content.spec.ts +183 -0
  104. package/src/adw-button-row.spec.ts +12 -6
  105. package/src/adw-carousel.spec.ts +389 -0
  106. package/src/adw-checks.spec.ts +368 -0
  107. package/src/adw-drop-down.spec.ts +123 -0
  108. package/src/adw-header-bar.spec.ts +83 -0
  109. package/src/adw-icon.spec.ts +167 -0
  110. package/src/adw-progress-bar.spec.ts +173 -0
  111. package/src/adw-sidebar.spec.ts +332 -0
  112. package/src/adw-switch.spec.ts +242 -0
  113. package/src/adw-tab-view.spec.ts +12 -3
  114. package/src/adw-view-switcher-bar.spec.ts +61 -13
  115. package/src/adw-wrap-box.spec.ts +194 -0
  116. package/src/bottom-sheet.spec.ts +325 -0
  117. package/src/breakpoints.spec.ts +147 -0
  118. package/src/breakpoints.ts +67 -0
  119. package/src/chrome.spec.ts +301 -0
  120. package/src/elements/adw-about-dialog.ts +223 -163
  121. package/src/elements/adw-action-row.ts +120 -10
  122. package/src/elements/adw-avatar.ts +48 -54
  123. package/src/elements/adw-banner.ts +46 -10
  124. package/src/elements/adw-bottom-sheet.ts +145 -60
  125. package/src/elements/adw-button-content.ts +107 -33
  126. package/src/elements/adw-button-row.ts +61 -37
  127. package/src/elements/adw-button.ts +5 -7
  128. package/src/elements/adw-carousel.ts +467 -105
  129. package/src/elements/adw-checks.ts +299 -0
  130. package/src/elements/adw-clamp.ts +80 -10
  131. package/src/elements/adw-combo-row.ts +15 -7
  132. package/src/elements/adw-data-grid.ts +43 -91
  133. package/src/elements/adw-drop-down.ts +117 -128
  134. package/src/elements/adw-entry-row.ts +404 -36
  135. package/src/elements/adw-expander-row.ts +61 -29
  136. package/src/elements/adw-header-bar.ts +31 -6
  137. package/src/elements/adw-icon.ts +148 -0
  138. package/src/elements/adw-inline-view-switcher.ts +240 -129
  139. package/src/elements/adw-menu-button.ts +93 -70
  140. package/src/elements/adw-navigation-split-view.ts +48 -7
  141. package/src/elements/adw-navigation-view.ts +286 -148
  142. package/src/elements/adw-overlay-split-view.ts +164 -14
  143. package/src/elements/adw-password-entry-row.ts +111 -89
  144. package/src/elements/adw-popover.ts +283 -0
  145. package/src/elements/adw-preferences-dialog.ts +120 -21
  146. package/src/elements/adw-preferences-group.ts +128 -22
  147. package/src/elements/adw-progress-bar.ts +189 -0
  148. package/src/elements/adw-sidebar.ts +301 -142
  149. package/src/elements/adw-spin-row.ts +11 -5
  150. package/src/elements/adw-spinner.ts +23 -8
  151. package/src/elements/adw-split-button.ts +371 -92
  152. package/src/elements/adw-status-page.ts +9 -9
  153. package/src/elements/adw-switch-row.ts +100 -30
  154. package/src/elements/adw-switch.ts +129 -0
  155. package/src/elements/adw-tab-view.ts +682 -129
  156. package/src/elements/adw-toggle-group.ts +5 -7
  157. package/src/elements/adw-toolbar-view.ts +84 -21
  158. package/src/elements/adw-view-stack.ts +162 -85
  159. package/src/elements/adw-view-switcher-bar.ts +163 -81
  160. package/src/elements/adw-view-switcher.ts +231 -112
  161. package/src/elements/adw-window-title.ts +44 -11
  162. package/src/elements/adw-wrap-box.ts +131 -35
  163. package/src/elements/view-switcher-dom.ts +108 -0
  164. package/src/entry-rows.spec.ts +391 -0
  165. package/src/index.ts +23 -0
  166. package/src/navigation-view.spec.ts +435 -0
  167. package/src/preferences-search.ts +135 -0
  168. package/src/preferences.spec.ts +250 -0
  169. package/src/source-view/adw-source-view.ts +2 -5
  170. package/src/split-button.spec.ts +470 -0
  171. package/src/split-views.spec.ts +309 -0
  172. package/src/styles.generated.ts +1 -1
  173. package/src/tab-view.spec.ts +451 -0
  174. package/src/test.browser.mts +49 -0
  175. package/src/view-stack.spec.ts +272 -0
  176. package/src/view-switcher.spec.ts +446 -0
@@ -1,42 +1,70 @@
1
1
  // <adw-view-switcher-bar> — The narrow BOTTOM bar of icon-over-label buttons
2
2
  // bound to an <adw-view-stack>, the web counterpart of Adw.ViewSwitcherBar. It
3
- // owns no pages of its own: it reads the bound stack's `pages`, renders one
4
- // homogeneous icon-over-label button per page, and is two-way synced to the
5
- // stack's visible child — tapping a button sets the stack's visible page, and a
6
- // change to the stack (from anywhere) re-highlights the matching button. The
7
- // active button's label is accent-coloured (via the `.active` CSS class).
3
+ // owns no pages: it reads the bound stack's `pages`, renders one homogeneous
4
+ // button per page, and is two-way synced to the stack's visible child.
8
5
  //
9
- // A `revealed` flag lets an adaptive layout flip the bar on/off: shown on narrow
10
- // screens, hidden on wide ones where a header-bar <adw-view-switcher> takes over
11
- // (mirrors Adw.ViewSwitcherBar `reveal` driven by an Adw.Breakpoint).
6
+ // The derivations are HEADLESS and live in `@gjsify/adwaita-core` (ADR 0004):
7
+ // the per-button model (`buildViewSwitcherButtons`, so this bar and the header
8
+ // switcher cannot disagree about what a page looks like) and — the rule this
9
+ // port was missing entirely — `ViewSwitcherBarState`, which is `reveal` AND
10
+ // MORE THAN ONE VISIBLE PAGE (`update_bar_revealed`,
11
+ // adw-view-switcher-bar.c:104-126). A bar bound to a one-page stack used to show
12
+ // an empty strip that libadwaita keeps collapsed.
13
+ //
14
+ // Also fixed here, from the same C: `disconnectedCallback` removed the stack
15
+ // listener and `connectedCallback` only re-added it when `ref && !this._stack`,
16
+ // so a bar that was detached and re-inserted never tracked its stack again. In
17
+ // C the handlers are bound in `set_stack` and released only in `dispose`
18
+ // (:330-341, :218-221) — realize/unrealize never touch them.
12
19
  //
13
20
  // Attributes:
14
- // stack — the id of the <adw-view-stack> to drive (like <label for>).
15
- // revealed — boolean; when absent the bar is hidden (matches `reveal`).
16
- // Properties (mirroring Adw.ViewSwitcherBar):
17
- // stack — the bound AdwViewStack element (get/set; set also accepts an id).
18
- // revealed — whether the bar is shown (get/set).
21
+ // stack — the id of the <adw-view-stack> to drive (like <label for>).
22
+ // reveal — boolean; the REQUEST (`Adw.ViewSwitcherBar:reveal`, default off).
23
+ // revealed — the legacy spelling of `reveal`, still accepted as the request.
24
+ // Properties:
25
+ // stack — the bound AdwViewStack element (get/set; set also accepts an id).
26
+ // reveal — the request (get/set).
27
+ // revealed — READ-ONLY: whether the bar is actually shown, i.e. the request
28
+ // AND more than one visible page.
19
29
  // Methods:
20
30
  // setStack(stack) — bind to a stack element (or null); rebuilds the buttons.
21
- // refresh() — rebuild the buttons after late page adds to the stack.
31
+ // refresh() — rebuild after pages were added to the bound stack without
32
+ // changing its selection (see the note in `_rebuild`).
22
33
  // Reference: refs/libadwaita/src/adw-view-switcher-bar.c (AdwViewSwitcherBar)
23
- // Reference: refs/libadwaita/src/stylesheet/widgets/_view-switcher.scss (.narrow policy)
34
+ // Reference: refs/libadwaita/src/adw-view-switcher.c (the switcher it embeds)
24
35
  // Reference: packages/nativescript-bridge/adwaita/src/widgets/adw-view-switcher-bar.ts (NS twin)
25
36
  // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
26
37
  // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
27
38
 
39
+ import { ViewSwitcherBarState, buildViewSwitcherButtons, viewSwitcherPagesFromStack } from '@gjsify/adwaita-core';
40
+
28
41
  import type { AdwViewStack } from './adw-view-stack.js';
42
+ import { applyDescription, applyIndicator, applySwitcherIcon } from './view-switcher-dom.js';
43
+ import { type AdwIcon, createAdwIcon } from './adw-icon.js';
44
+
45
+ /** The DOM nodes one bar button owns — the bar paints the derived model onto them. */
46
+ interface BarButtonNodes {
47
+ button: HTMLButtonElement;
48
+ icon: AdwIcon;
49
+ label: HTMLSpanElement;
50
+ indicator: HTMLSpanElement;
51
+ }
29
52
 
30
53
  export class AdwViewSwitcherBar extends HTMLElement {
54
+ private readonly _barState = new ViewSwitcherBarState();
31
55
  private _barEl!: HTMLDivElement;
32
- private _buttons: HTMLButtonElement[] = [];
56
+ private _nodes: BarButtonNodes[] = [];
33
57
  private _stack: AdwViewStack | null = null;
34
- private _revealed = false;
35
58
  private _initialized = false;
36
- private _onStackNotify = (): void => this._syncActive();
59
+ private readonly _onStackNotify = (): void => this._rebuild();
37
60
 
38
61
  static get observedAttributes() {
39
- return ['stack', 'revealed'];
62
+ return ['stack', 'reveal', 'revealed'];
63
+ }
64
+
65
+ constructor() {
66
+ super();
67
+ this._barState.subscribe(() => this._applyRevealed());
40
68
  }
41
69
 
42
70
  /** The bound stack element, or null when unbound. */
@@ -48,14 +76,26 @@ export class AdwViewSwitcherBar extends HTMLElement {
48
76
  this.setStack(this._resolveStack(value));
49
77
  }
50
78
 
51
- /** Whether the bar is revealed (shown). Hidden when false. */
52
- get revealed(): boolean {
53
- return this._revealed;
79
+ /** Whether the layout asked for the bar — `Adw.ViewSwitcherBar:reveal`. */
80
+ get reveal(): boolean {
81
+ return this._barState.reveal;
54
82
  }
55
83
 
56
- set revealed(value: boolean) {
57
- if (value) this.setAttribute('revealed', '');
58
- else this.removeAttribute('revealed');
84
+ set reveal(value: boolean) {
85
+ if (value) this.setAttribute('reveal', '');
86
+ else {
87
+ this.removeAttribute('reveal');
88
+ this.removeAttribute('revealed');
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Whether the bar is actually shown. READ-ONLY: a one-page stack keeps it
94
+ * collapsed however loudly the layout asks (adw-view-switcher-bar.c:125).
95
+ * Write {@link reveal} to make the request.
96
+ */
97
+ get revealed(): boolean {
98
+ return this._barState.revealed;
59
99
  }
60
100
 
61
101
  connectedCallback() {
@@ -66,25 +106,31 @@ export class AdwViewSwitcherBar extends HTMLElement {
66
106
  this._barEl.setAttribute('role', 'tablist');
67
107
  this.replaceChildren(this._barEl);
68
108
  }
69
- this._revealed = this.hasAttribute('revealed');
70
- this._applyRevealed();
71
- // Resolve the `stack` id ref now that the element is in the document — the
72
- // referenced stack is normally earlier in the DOM (content above a bottom
73
- // bar), so its pages are ready.
109
+ this._barState.setReveal(this._readReveal());
110
+
111
+ // Resolve the `stack` id ref now that the element is in the document —
112
+ // the referenced stack is normally earlier in the DOM (content above a
113
+ // bottom bar), so its pages are ready.
74
114
  const ref = this.getAttribute('stack');
75
- if (ref && !this._stack) this.setStack(this._resolveStack(ref));
76
- else this._syncActive();
115
+ if (ref && !this._stack) {
116
+ this.setStack(this._resolveStack(ref));
117
+ return;
118
+ }
119
+ // Re-attaching must re-bind: `disconnectedCallback` released the
120
+ // listener, and the old `ref && !this._stack` guard could never take it
121
+ // back. In C the binding survives unrealize entirely.
122
+ this._bindStack();
123
+ this._rebuild();
77
124
  }
78
125
 
79
126
  disconnectedCallback() {
80
- if (this._stack) this._stack.removeEventListener('notify::visible-child', this._onStackNotify);
127
+ this._unbindStack();
81
128
  }
82
129
 
83
130
  attributeChangedCallback(name: string, _old: string | null, value: string | null) {
84
131
  if (!this._initialized) return;
85
- if (name === 'revealed') {
86
- this._revealed = this.hasAttribute('revealed');
87
- this._applyRevealed();
132
+ if (name === 'reveal' || name === 'revealed') {
133
+ this._barState.setReveal(this._readReveal());
88
134
  return;
89
135
  }
90
136
  if (name === 'stack') {
@@ -94,74 +140,110 @@ export class AdwViewSwitcherBar extends HTMLElement {
94
140
 
95
141
  /** Bind to a stack element (or null). Unbinds the previous one and rebuilds. */
96
142
  setStack(stack: AdwViewStack | null): void {
97
- if (this._stack) this._stack.removeEventListener('notify::visible-child', this._onStackNotify);
143
+ this._unbindStack();
98
144
  this._stack = stack;
99
- if (stack) stack.addEventListener('notify::visible-child', this._onStackNotify);
145
+ this._bindStack();
100
146
  this._rebuild();
101
147
  }
102
148
 
103
- /** Rebuild the buttons — call after adding pages to the bound stack. */
149
+ /**
150
+ * Rebuild the buttons — needed after pages were added to the bound stack
151
+ * WITHOUT changing its selection. A page add that does move the selection
152
+ * (the first one) notifies and rebuilds on its own. libadwaita rebuilds on
153
+ * the pages model's `items-changed` (adw-view-switcher.c:258-263), which the
154
+ * DOM stack has no counterpart for yet.
155
+ */
104
156
  refresh(): void {
105
157
  this._rebuild();
106
158
  }
107
159
 
160
+ private _readReveal(): boolean {
161
+ return this.hasAttribute('reveal') || this.hasAttribute('revealed');
162
+ }
163
+
164
+ private _bindStack(): void {
165
+ this._stack?.addEventListener('notify::visible-child', this._onStackNotify);
166
+ }
167
+
168
+ private _unbindStack(): void {
169
+ this._stack?.removeEventListener('notify::visible-child', this._onStackNotify);
170
+ }
171
+
108
172
  private _resolveStack(value: AdwViewStack | string | null): AdwViewStack | null {
109
173
  if (value === null) return null;
110
- if (typeof value === 'string') {
111
- return (document.getElementById(value) as AdwViewStack | null) ?? null;
112
- }
174
+ if (typeof value === 'string') return (document.getElementById(value) as AdwViewStack | null) ?? null;
113
175
  return value;
114
176
  }
115
177
 
116
178
  private _rebuild(): void {
117
179
  if (!this._initialized) return;
118
- this._barEl.replaceChildren();
119
- this._buttons = [];
120
- const pages = this._stack?.pages ?? [];
121
-
122
- pages.forEach((page, index) => {
123
- const button = document.createElement('button');
124
- button.type = 'button';
125
- button.className = 'adw-view-switcher-bar-button';
126
- button.setAttribute('role', 'tab');
127
-
128
- const iconEl = document.createElement('span');
129
- iconEl.className = `adw-icon adw-view-switcher-bar-icon${page.icon ? ` adw-icon--${page.icon}` : ''}`;
130
- iconEl.setAttribute('aria-hidden', 'true'); // decorative (mask-image only)
131
- iconEl.hidden = page.icon.length === 0;
132
-
133
- const labelEl = document.createElement('span');
134
- labelEl.className = 'adw-view-switcher-bar-label';
135
- labelEl.textContent = page.title;
136
- labelEl.hidden = page.title.length === 0;
137
-
138
- button.append(iconEl, labelEl);
139
- // An icon-only button (no title) needs an accessible name.
140
- if (page.icon && !page.title) button.setAttribute('aria-label', page.icon);
141
-
142
- button.addEventListener('click', () => {
143
- if (this._stack) this._stack.visibleChildIndex = index;
144
- });
145
-
146
- this._barEl.appendChild(button);
147
- this._buttons.push(button);
180
+
181
+ const stackPages = this._stack?.pages ?? [];
182
+ // The page count is half the reveal derivation, so it is fed on every
183
+ // rebuild — `update_bar_revealed` is re-run from set_stack and from
184
+ // items-changed for exactly this reason (:340-343, :277).
185
+ this._barState.setPages(stackPages);
186
+
187
+ const pages = viewSwitcherPagesFromStack(stackPages);
188
+ const models = buildViewSwitcherButtons(pages, this._stack?.visibleChildIndex ?? -1, 'narrow');
189
+
190
+ // Nodes are recreated only when the page COUNT moves. A selection change
191
+ // must not replace the button the user is standing on — `selection_changed_cb`
192
+ // only re-marks the existing buttons (adw-view-switcher.c:265-295), and a
193
+ // wholesale replace would drop keyboard focus mid-interaction.
194
+ if (models.length !== this._nodes.length) {
195
+ this._barEl.replaceChildren();
196
+ this._nodes = models.map((model) => this._buildButton(model.pageIndex));
197
+ this._barEl.append(...this._nodes.map((nodes) => nodes.button));
198
+ }
199
+
200
+ models.forEach((model, index) => {
201
+ const nodes = this._nodes[index];
202
+ if (!nodes) return;
203
+
204
+ nodes.button.hidden = !model.visible;
205
+ nodes.button.classList.toggle('active', model.selected);
206
+ nodes.button.setAttribute('aria-selected', String(model.selected));
207
+ nodes.button.tabIndex = model.selected ? 0 : -1;
208
+
209
+ applySwitcherIcon(nodes.icon, model.iconName);
210
+ nodes.label.textContent = model.label;
211
+ nodes.label.hidden = model.label.length === 0;
212
+ applyIndicator(nodes.indicator, model.badgeLabel, model.needsAttention);
213
+ applyDescription(nodes.button, model.description);
214
+
215
+ if (model.label) nodes.button.removeAttribute('aria-label');
216
+ else nodes.button.setAttribute('aria-label', model.iconName);
148
217
  });
149
218
 
150
- this._syncActive();
219
+ this._applyRevealed();
151
220
  }
152
221
 
153
- private _syncActive(): void {
154
- const selected = this._stack?.visibleChildIndex ?? -1;
155
- this._buttons.forEach((button, index) => {
156
- const isActive = index === selected;
157
- button.classList.toggle('active', isActive);
158
- button.setAttribute('aria-selected', String(isActive));
159
- button.tabIndex = isActive ? 0 : -1;
222
+ private _buildButton(pageIndex: number): BarButtonNodes {
223
+ const button = document.createElement('button');
224
+ button.type = 'button';
225
+ button.className = 'adw-view-switcher-bar-button';
226
+ button.setAttribute('role', 'tab');
227
+
228
+ const icon = createAdwIcon(null, 'adw-view-switcher-bar-icon');
229
+
230
+ const label = document.createElement('span');
231
+ label.className = 'adw-view-switcher-bar-label';
232
+
233
+ const indicator = document.createElement('span');
234
+ indicator.className = 'adw-view-switcher-bar-indicator';
235
+ indicator.setAttribute('aria-hidden', 'true'); // announced via aria-description
236
+
237
+ button.append(icon, label, indicator);
238
+ button.addEventListener('click', () => {
239
+ if (this._stack) this._stack.visibleChildIndex = pageIndex;
160
240
  });
241
+
242
+ return { button, icon, label, indicator };
161
243
  }
162
244
 
163
245
  private _applyRevealed(): void {
164
- this.hidden = !this._revealed;
246
+ this.hidden = !this._barState.revealed;
165
247
  }
166
248
  }
167
249