@gjsify/adwaita-web 0.10.0 → 0.12.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 (120) hide show
  1. package/README.md +5 -5
  2. package/dist/adwaita-web.css +2742 -263
  3. package/dist/adwaita-web.css.map +1 -1
  4. package/lib/types/elements/adw-about-dialog.d.ts +77 -0
  5. package/lib/types/elements/adw-action-row.d.ts +15 -0
  6. package/lib/types/elements/adw-alert-dialog.d.ts +80 -0
  7. package/lib/types/elements/adw-avatar.d.ts +9 -0
  8. package/lib/types/elements/adw-banner.d.ts +9 -0
  9. package/lib/types/elements/adw-bottom-sheet.d.ts +35 -0
  10. package/lib/types/elements/adw-button-content.d.ts +10 -0
  11. package/lib/types/elements/adw-button-row.d.ts +11 -0
  12. package/lib/types/elements/adw-button.d.ts +11 -0
  13. package/lib/types/elements/adw-carousel.d.ts +64 -0
  14. package/lib/types/elements/adw-clamp.d.ts +6 -0
  15. package/lib/types/elements/adw-combo-row.d.ts +3 -0
  16. package/lib/types/elements/adw-entry-row.d.ts +10 -0
  17. package/lib/types/elements/adw-entry.d.ts +11 -0
  18. package/lib/types/elements/adw-expander-row.d.ts +26 -0
  19. package/lib/types/elements/adw-header-bar.d.ts +4 -0
  20. package/lib/types/elements/adw-inline-view-switcher.d.ts +30 -0
  21. package/lib/types/elements/adw-navigation-split-view.d.ts +15 -0
  22. package/lib/types/elements/adw-navigation-view.d.ts +66 -0
  23. package/lib/types/elements/adw-password-entry-row.d.ts +16 -0
  24. package/lib/types/elements/adw-preferences-dialog.d.ts +36 -0
  25. package/lib/types/elements/adw-preferences-group.d.ts +8 -0
  26. package/lib/types/elements/adw-sidebar.d.ts +29 -0
  27. package/lib/types/elements/adw-spin-row.d.ts +3 -0
  28. package/lib/types/elements/adw-spinner.d.ts +6 -0
  29. package/lib/types/elements/adw-split-button.d.ts +18 -0
  30. package/lib/types/elements/adw-status-page.d.ts +11 -0
  31. package/lib/types/elements/adw-switch-row.d.ts +3 -0
  32. package/lib/types/elements/adw-tab-view.d.ts +24 -0
  33. package/lib/types/elements/adw-toast-overlay.d.ts +19 -4
  34. package/lib/types/elements/adw-toggle-group.d.ts +19 -0
  35. package/lib/types/elements/adw-toolbar-view.d.ts +13 -0
  36. package/lib/types/elements/adw-view-switcher.d.ts +29 -0
  37. package/lib/types/elements/adw-window-title.d.ts +9 -0
  38. package/lib/types/elements/adw-wrap-box.d.ts +13 -0
  39. package/lib/types/index.d.ts +29 -0
  40. package/lib/types/styles.generated.d.ts +1 -0
  41. package/package.json +5 -5
  42. package/scss/_about_dialog.scss +257 -0
  43. package/scss/_action_row.scss +65 -0
  44. package/scss/_alert_dialog.scss +151 -0
  45. package/scss/_avatar.scss +31 -0
  46. package/scss/_banner.scss +46 -0
  47. package/scss/_bottom_sheet.scss +113 -0
  48. package/scss/_button.scss +168 -0
  49. package/scss/_button_content.scss +48 -0
  50. package/scss/_button_row.scss +100 -0
  51. package/scss/_carousel.scss +130 -0
  52. package/scss/_clamp.scss +11 -0
  53. package/scss/_entry.scss +49 -0
  54. package/scss/_entry_row.scss +54 -0
  55. package/scss/_expander_row.scss +150 -0
  56. package/scss/_headerbar.scss +1 -1
  57. package/scss/_inline_view_switcher.scss +174 -0
  58. package/scss/_navigation_split_view.scss +48 -0
  59. package/scss/_navigation_view.scss +80 -0
  60. package/scss/_overlay_split_view.scss +19 -0
  61. package/scss/_password_entry_row.scss +67 -0
  62. package/scss/_preferences.scss +26 -3
  63. package/scss/_preferences_dialog.scss +154 -0
  64. package/scss/_row.scss +31 -0
  65. package/scss/_sidebar.scss +213 -0
  66. package/scss/_spin_row.scss +0 -1
  67. package/scss/_spinner.scss +28 -0
  68. package/scss/_split_button.scss +222 -0
  69. package/scss/_status_page.scss +40 -0
  70. package/scss/_switch_row.scss +0 -1
  71. package/scss/_tab_view.scss +193 -0
  72. package/scss/_theme.scss +12 -0
  73. package/scss/_toast.scss +122 -12
  74. package/scss/_toggle_group.scss +124 -0
  75. package/scss/_toolbar_view.scss +31 -0
  76. package/scss/_variables.scss +19 -0
  77. package/scss/_view_switcher.scss +133 -0
  78. package/scss/_window_title.scss +33 -0
  79. package/scss/_wrap_box.scss +20 -0
  80. package/scss/adwaita-skin.scss +38 -0
  81. package/src/elements/adw-about-dialog.ts +629 -0
  82. package/src/elements/adw-action-row.ts +82 -0
  83. package/src/elements/adw-alert-dialog.ts +385 -0
  84. package/src/elements/adw-avatar.ts +108 -0
  85. package/src/elements/adw-banner.ts +57 -0
  86. package/src/elements/adw-bottom-sheet.ts +205 -0
  87. package/src/elements/adw-button-content.ts +69 -0
  88. package/src/elements/adw-button-row.ts +75 -0
  89. package/src/elements/adw-button.ts +81 -0
  90. package/src/elements/adw-carousel.ts +321 -0
  91. package/src/elements/adw-clamp.ts +28 -0
  92. package/src/elements/adw-combo-row.ts +32 -20
  93. package/src/elements/adw-entry-row.ts +69 -0
  94. package/src/elements/adw-entry.ts +61 -0
  95. package/src/elements/adw-expander-row.ts +167 -0
  96. package/src/elements/adw-header-bar.ts +21 -8
  97. package/src/elements/adw-inline-view-switcher.ts +222 -0
  98. package/src/elements/adw-navigation-split-view.ts +89 -0
  99. package/src/elements/adw-navigation-view.ts +344 -0
  100. package/src/elements/adw-password-entry-row.ts +121 -0
  101. package/src/elements/adw-preferences-dialog.ts +240 -0
  102. package/src/elements/adw-preferences-group.ts +79 -12
  103. package/src/elements/adw-sidebar.ts +238 -0
  104. package/src/elements/adw-spin-row.ts +28 -14
  105. package/src/elements/adw-spinner.ts +31 -0
  106. package/src/elements/adw-split-button.ts +206 -0
  107. package/src/elements/adw-status-page.ts +66 -0
  108. package/src/elements/adw-switch-row.ts +29 -23
  109. package/src/elements/adw-tab-view.ts +184 -0
  110. package/src/elements/adw-toast-overlay.ts +98 -19
  111. package/src/elements/adw-toggle-group.ts +126 -0
  112. package/src/elements/adw-toolbar-view.ts +60 -0
  113. package/src/elements/adw-view-switcher.ts +200 -0
  114. package/src/elements/adw-window-title.ts +48 -0
  115. package/src/elements/adw-wrap-box.ts +139 -0
  116. package/src/index.js +1 -1
  117. package/src/index.ts +46 -3
  118. package/src/styles.generated.js +2976 -0
  119. package/src/styles2.generated.js +2976 -0
  120. package/style.css.d.ts +17 -4
@@ -0,0 +1,184 @@
1
+ // <adw-tab-view> — A dynamic tabbed container with a tab bar header (the web
2
+ // counterpart of Adw.TabView + Adw.TabBar). One page is shown at a time; the
3
+ // tab bar renders one chip per page (title + close affordance) over a
4
+ // headerbar-colored strip, with the selected chip raised onto the view
5
+ // background. Pages are declared as <adw-tab-page> children (each with a
6
+ // `title` attribute and arbitrary content); the view snapshots them at connect
7
+ // time, builds the bar, and toggles the active page's visibility.
8
+ // Attributes:
9
+ // selected (zero-based index of the visible page, default 0)
10
+ // autohide (boolean — hide the tab bar when only one page remains, mirroring
11
+ // the Adw.TabBar `autohide` property)
12
+ // Events:
13
+ // `notify::selected-page` (CustomEvent, bubbles, `detail = { selected }`) when
14
+ // the visible page changes — mirrors the Adw.TabView `selected-page` property.
15
+ // `close-page` (CustomEvent, bubbles, `detail = { index }`) when a page's close
16
+ // affordance is activated — mirrors the Adw.TabView `close-page` signal.
17
+ // Reference: refs/libadwaita/src/adw-tab-view.c (AdwTabView behaviour)
18
+ // Reference: refs/libadwaita/src/adw-tab-bar.c (AdwTabBar autohide)
19
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_tab-view.scss
20
+ // Reference: refs/adwaita-web/adwaita-web/scss/_tabs.scss
21
+ // Copyright (c) 2020-2022 Purism SPC / GNOME contributors (libadwaita). LGPLv2.1+.
22
+ // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
23
+
24
+ /** A single page. Children of <adw-tab-view>; consumed at connect time. */
25
+ export class AdwTabPage extends HTMLElement {
26
+ static get observedAttributes() {
27
+ return ['title'];
28
+ }
29
+ }
30
+
31
+ export class AdwTabView extends HTMLElement {
32
+ private _barEl!: HTMLDivElement;
33
+ private _pagesEl!: HTMLDivElement;
34
+ private _tabs: HTMLButtonElement[] = [];
35
+ private _pages: HTMLDivElement[] = [];
36
+ private _selected = 0;
37
+ private _initialized = false;
38
+
39
+ static get observedAttributes() {
40
+ return ['selected', 'autohide'];
41
+ }
42
+
43
+ /** Zero-based index of the visible page. */
44
+ get selected(): number {
45
+ return this._selected;
46
+ }
47
+
48
+ set selected(value: number) {
49
+ this.setAttribute('selected', String(value));
50
+ }
51
+
52
+ /** Whether the tab bar hides itself when only one page remains. */
53
+ get autohide(): boolean {
54
+ return this.hasAttribute('autohide');
55
+ }
56
+
57
+ set autohide(value: boolean) {
58
+ if (value) this.setAttribute('autohide', '');
59
+ else this.removeAttribute('autohide');
60
+ }
61
+
62
+ connectedCallback() {
63
+ if (this._initialized) return;
64
+ this._initialized = true;
65
+
66
+ // Snapshot the declared <adw-tab-page> children before we take over the
67
+ // subtree — their title attribute + content become the rendered pages.
68
+ const pages = Array.from(this.querySelectorAll('adw-tab-page')) as AdwTabPage[];
69
+
70
+ // The tab bar (headerbar-colored strip of chips), built first so it sits
71
+ // above the pages.
72
+ this._barEl = document.createElement('div');
73
+ this._barEl.className = 'adw-tab-bar';
74
+ this._barEl.setAttribute('role', 'tablist');
75
+
76
+ const tabBox = document.createElement('div');
77
+ tabBox.className = 'adw-tab-box';
78
+ this._barEl.appendChild(tabBox);
79
+
80
+ this._pagesEl = document.createElement('div');
81
+ this._pagesEl.className = 'adw-tab-view-pages';
82
+
83
+ this._tabs = [];
84
+ this._pages = [];
85
+
86
+ pages.forEach((page, index) => {
87
+ const title = page.getAttribute('title') ?? '';
88
+
89
+ const tab = document.createElement('button');
90
+ tab.type = 'button';
91
+ tab.className = 'adw-tab';
92
+ tab.setAttribute('role', 'tab');
93
+
94
+ const label = document.createElement('span');
95
+ label.className = 'adw-tab-title';
96
+ label.textContent = title;
97
+ tab.appendChild(label);
98
+
99
+ // Close affordance — a small flat button drawn with a CSS "×" glyph
100
+ // (no symbolic close icon ships in @gjsify/adwaita-icons).
101
+ const close = document.createElement('button');
102
+ close.type = 'button';
103
+ close.className = 'adw-tab-close';
104
+ close.setAttribute('aria-label', 'Close tab');
105
+ close.addEventListener('click', (event) => {
106
+ event.stopPropagation();
107
+ this.dispatchEvent(new CustomEvent('close-page', { bubbles: true, detail: { index } }));
108
+ });
109
+ tab.appendChild(close);
110
+
111
+ tab.addEventListener('click', () => this._selectIndex(index));
112
+ tabBox.appendChild(tab);
113
+ this._tabs.push(tab);
114
+
115
+ // The page content area — the declared element's children are moved
116
+ // into a panel that the view toggles visible.
117
+ const panel = document.createElement('div');
118
+ panel.className = 'adw-tab-page';
119
+ panel.setAttribute('role', 'tabpanel');
120
+ for (const child of Array.from(page.childNodes)) panel.appendChild(child);
121
+ this._pagesEl.appendChild(panel);
122
+ this._pages.push(panel);
123
+ });
124
+
125
+ this.replaceChildren(this._barEl, this._pagesEl);
126
+
127
+ this._selected = this._readSelectedAttr();
128
+ this._render();
129
+ }
130
+
131
+ attributeChangedCallback(name: string) {
132
+ if (!this._initialized) return;
133
+ if (name === 'selected') {
134
+ const next = this._readSelectedAttr();
135
+ if (next !== this._selected) {
136
+ this._selected = next;
137
+ this._render();
138
+ }
139
+ return;
140
+ }
141
+ // autohide is styling/visibility-only.
142
+ this._render();
143
+ }
144
+
145
+ private _readSelectedAttr(): number {
146
+ const raw = Number.parseInt(this.getAttribute('selected') ?? '0', 10);
147
+ if (Number.isNaN(raw)) return 0;
148
+ const max = Math.max(0, this._pages.length - 1);
149
+ return Math.min(Math.max(raw, 0), max);
150
+ }
151
+
152
+ private _selectIndex(index: number): void {
153
+ if (index === this._selected) return;
154
+ this._selected = index;
155
+ // Keep the attribute in sync without re-entering via the guarded
156
+ // attributeChangedCallback (value already matches, so it no-ops).
157
+ this.setAttribute('selected', String(index));
158
+ this._render();
159
+ this.dispatchEvent(new CustomEvent('notify::selected-page', { bubbles: true, detail: { selected: index } }));
160
+ }
161
+
162
+ private _render(): void {
163
+ // Autohide: collapse the bar when only one page (or none) remains.
164
+ const hidden = this.autohide && this._pages.length <= 1;
165
+ this._barEl.hidden = hidden;
166
+ this._barEl.classList.toggle('single-tab', this._pages.length <= 1);
167
+
168
+ this._tabs.forEach((tab, index) => {
169
+ const isActive = index === this._selected;
170
+ tab.classList.toggle('active', isActive);
171
+ tab.setAttribute('aria-selected', String(isActive));
172
+ tab.tabIndex = isActive ? 0 : -1;
173
+ });
174
+
175
+ this._pages.forEach((panel, index) => {
176
+ const isActive = index === this._selected;
177
+ panel.classList.toggle('active-page', isActive);
178
+ panel.hidden = !isActive;
179
+ });
180
+ }
181
+ }
182
+
183
+ customElements.define('adw-tab-page', AdwTabPage);
184
+ customElements.define('adw-tab-view', AdwTabView);
@@ -1,44 +1,123 @@
1
1
  // <adw-toast-overlay> — Adwaita toast notification overlay.
2
- // Displays temporary feedback messages at the bottom of the viewport.
2
+ // The web counterpart of Adw.ToastOverlay: it wraps arbitrary content and shows
3
+ // transient `.adw-toast` banners (the Adw.Toast equivalent — a title, an
4
+ // optional action button and a close affordance) layered over the bottom of its
5
+ // own bounds, each auto-dismissing after a timeout.
3
6
  // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
4
7
  // Reference: refs/adwaita-web/adwaita-web/scss/_toast.scss, _toast_overlay.scss
5
- // Reference: refs/libadwaita/src/adw-toast-overlay.c
8
+ // Reference: refs/libadwaita/src/adw-toast-overlay.c, adw-toast.c (Adw.Toast.add_toast)
6
9
  // Copyright (c) 2025 csm (adwaita-web). MIT License.
7
10
  // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
8
- // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web.
11
+ // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web;
12
+ // extended addToast() with action button + close button + options API.
13
+
14
+ /** Options for {@link AdwToastOverlay.addToast}, mirroring Adw.Toast's properties. */
15
+ export interface AdwToastOptions {
16
+ /** Seconds before the toast auto-dismisses; `0` keeps it until dismissed. Mirrors Adw.Toast:timeout (default 5). */
17
+ timeout?: number;
18
+ /** Label for the action button; omitted/empty means no button. Mirrors Adw.Toast:button-label. */
19
+ buttonLabel?: string;
20
+ /** Invoked when the action button is pressed. Mirrors the activation of Adw.Toast:action-name. */
21
+ onAction?: () => void;
22
+ }
9
23
 
10
24
  export class AdwToastOverlay extends HTMLElement {
25
+ private _toasts!: HTMLDivElement;
26
+ private _initialized = false;
27
+
28
+ connectedCallback() {
29
+ if (this._initialized) return;
30
+ this._initialized = true;
31
+
32
+ // Adopt the declared children as the wrapped content (the Adw.ToastOverlay
33
+ // :child) and layer a dedicated toast container over the bottom edge.
34
+ const content = document.createElement('div');
35
+ content.className = 'adw-toast-overlay-content';
36
+ for (const child of Array.from(this.childNodes)) content.appendChild(child);
37
+
38
+ this._toasts = document.createElement('div');
39
+ this._toasts.className = 'adw-toast-overlay-toasts';
40
+
41
+ this.replaceChildren(content, this._toasts);
42
+ }
43
+
11
44
  /**
12
- * Show a toast notification.
13
- * @param title - The text to display.
14
- * @param timeout - Time in ms before the toast auto-dismisses (default 2000).
45
+ * Show a toast notification — the web equivalent of Adw.ToastOverlay.add_toast()
46
+ * with a freshly-built Adw.Toast.
47
+ *
48
+ * @param title The text to display.
49
+ * @param options Either a timeout in seconds (legacy shorthand) or an
50
+ * {@link AdwToastOptions} bag (title text already supplied here).
15
51
  */
16
- addToast(title: string, timeout = 2000): void {
52
+ addToast(title: string, options: number | AdwToastOptions = {}): void {
53
+ // Ensure the toast container exists even if addToast() races a not-yet
54
+ // connected element (mirrors Adw.ToastOverlay queuing before realize).
55
+ if (!this._toasts) this.connectedCallback();
56
+
57
+ // Back-compat: the original signature was addToast(title, timeoutMs).
58
+ const opts: AdwToastOptions =
59
+ typeof options === 'number' ? { timeout: options / 1000 } : options;
60
+ const timeoutSeconds = opts.timeout ?? 5;
61
+
17
62
  const toast = document.createElement('div');
18
63
  toast.className = 'adw-toast';
19
64
 
65
+ const wrapper = document.createElement('div');
66
+ wrapper.className = 'adw-toast-content-wrapper';
67
+
20
68
  const titleEl = document.createElement('span');
21
69
  titleEl.className = 'adw-toast-title';
22
70
  titleEl.textContent = title;
23
- toast.appendChild(titleEl);
24
-
25
- this.appendChild(toast);
71
+ wrapper.appendChild(titleEl);
72
+ toast.appendChild(wrapper);
26
73
 
27
- // Trigger enter animation on next frame
28
- requestAnimationFrame(() => {
29
- toast.classList.add('visible');
30
- });
31
-
32
- // Auto-dismiss after timeout
33
- setTimeout(() => {
74
+ let dismissTimer = 0;
75
+ const dismiss = (): void => {
76
+ if (dismissTimer) {
77
+ clearTimeout(dismissTimer);
78
+ dismissTimer = 0;
79
+ }
34
80
  toast.classList.remove('visible');
35
81
  toast.classList.add('hiding');
36
82
  toast.addEventListener('transitionend', () => toast.remove(), { once: true });
37
- // Fallback if transitionend doesn't fire
83
+ // Fallback if transitionend doesn't fire (e.g. reduced motion).
38
84
  setTimeout(() => {
39
85
  if (toast.parentNode) toast.remove();
40
86
  }, 300);
41
- }, timeout);
87
+ };
88
+
89
+ if (opts.buttonLabel) {
90
+ const button = document.createElement('button');
91
+ button.type = 'button';
92
+ button.className = 'adw-toast-action-button';
93
+ button.textContent = opts.buttonLabel;
94
+ button.addEventListener('click', () => {
95
+ opts.onAction?.();
96
+ dismiss();
97
+ });
98
+ toast.appendChild(button);
99
+ }
100
+
101
+ // Close affordance — a CSS-drawn "×" (no symbolic close icon ships in the
102
+ // curated @gjsify/adwaita-icons set), mirroring adw-tab-view's .adw-tab-close.
103
+ const close = document.createElement('button');
104
+ close.type = 'button';
105
+ close.className = 'adw-toast-close-button';
106
+ close.setAttribute('aria-label', 'Close');
107
+ close.addEventListener('click', dismiss);
108
+ toast.appendChild(close);
109
+
110
+ this._toasts.appendChild(toast);
111
+
112
+ // Trigger the enter animation on the next frame.
113
+ requestAnimationFrame(() => {
114
+ toast.classList.add('visible');
115
+ });
116
+
117
+ // Auto-dismiss after the timeout (0 = stay until manually dismissed).
118
+ if (timeoutSeconds > 0) {
119
+ dismissTimer = setTimeout(dismiss, timeoutSeconds * 1000) as unknown as number;
120
+ }
42
121
  }
43
122
  }
44
123
 
@@ -0,0 +1,126 @@
1
+ // <adw-toggle-group> — A linked set of toggle buttons where exactly one is
2
+ // active at a time (the web counterpart of Adw.ToggleGroup). Toggles are
3
+ // declared as <adw-toggle> children with `label` and/or `icon-name` attributes;
4
+ // the group renders them inside an inner container, tracks the active index, and
5
+ // recolors the active toggle with the Adwaita active-toggle accent.
6
+ // Attributes: active (zero-based index of the active toggle, default 0), and the
7
+ // style flags flat / round (mirroring the Adw.ToggleGroup `.flat` / `.round`
8
+ // style classes).
9
+ // Events: `notify::active` (CustomEvent, bubbles, `detail = { active }`) when the
10
+ // active toggle changes — mirrors the Adw.ToggleGroup `active` GObject property.
11
+ // Reference: refs/libadwaita/src/adw-toggle-group.c (AdwToggleGroup behaviour)
12
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_toggle-group.scss
13
+ // Reference: refs/adwaita-web/adwaita-web/scss/_toggle_group.scss
14
+ // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
15
+ // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
16
+
17
+ /** A single toggle. Children of <adw-toggle-group>; consumed at connect time. */
18
+ export class AdwToggle extends HTMLElement {
19
+ static get observedAttributes() {
20
+ return ['label', 'icon-name'];
21
+ }
22
+ }
23
+
24
+ export class AdwToggleGroup extends HTMLElement {
25
+ private _innerEl!: HTMLDivElement;
26
+ private _buttons: HTMLButtonElement[] = [];
27
+ private _active = 0;
28
+ private _initialized = false;
29
+
30
+ static get observedAttributes() {
31
+ return ['active', 'flat', 'round'];
32
+ }
33
+
34
+ /** Zero-based index of the active toggle. */
35
+ get active(): number {
36
+ return this._active;
37
+ }
38
+
39
+ set active(value: number) {
40
+ this.setAttribute('active', String(value));
41
+ }
42
+
43
+ connectedCallback() {
44
+ if (this._initialized) return;
45
+ this._initialized = true;
46
+
47
+ // Snapshot the declared <adw-toggle> children before we take over the
48
+ // subtree — their label / icon-name become the rendered buttons.
49
+ const toggles = Array.from(this.querySelectorAll('adw-toggle')) as AdwToggle[];
50
+
51
+ this._innerEl = document.createElement('div');
52
+ this._innerEl.className = 'adw-toggle-group-inner';
53
+
54
+ this._buttons = toggles.map((toggle, index) => {
55
+ const btn = document.createElement('button');
56
+ btn.type = 'button';
57
+ btn.className = 'adw-toggle';
58
+
59
+ const label = toggle.getAttribute('label') ?? '';
60
+ const icon = toggle.getAttribute('icon-name') ?? '';
61
+ if (icon) {
62
+ const span = document.createElement('span');
63
+ span.className = `adw-icon adw-icon--${icon}`;
64
+ span.setAttribute('aria-hidden', 'true'); // decorative (mask-image only)
65
+ btn.appendChild(span);
66
+ }
67
+ if (label) btn.appendChild(document.createTextNode(label));
68
+
69
+ // An icon-only toggle has no text — give it an accessible name.
70
+ if (icon && !label) btn.setAttribute('aria-label', icon);
71
+
72
+ btn.addEventListener('click', () => this._selectIndex(index));
73
+ return btn;
74
+ });
75
+
76
+ this._innerEl.append(...this._buttons);
77
+ this.replaceChildren(this._innerEl);
78
+
79
+ this._active = this._readActiveAttr();
80
+ this._render();
81
+ }
82
+
83
+ attributeChangedCallback(name: string) {
84
+ if (!this._initialized) return;
85
+ if (name === 'active') {
86
+ const next = this._readActiveAttr();
87
+ if (next !== this._active) {
88
+ this._active = next;
89
+ this._render();
90
+ }
91
+ return;
92
+ }
93
+ // flat / round are styling-only.
94
+ this._render();
95
+ }
96
+
97
+ private _readActiveAttr(): number {
98
+ const raw = Number.parseInt(this.getAttribute('active') ?? '0', 10);
99
+ if (Number.isNaN(raw)) return 0;
100
+ const max = Math.max(0, this._buttons.length - 1);
101
+ return Math.min(Math.max(raw, 0), max);
102
+ }
103
+
104
+ private _selectIndex(index: number): void {
105
+ if (index === this._active) return;
106
+ this._active = index;
107
+ // Keep the attribute in sync without re-entering via the guarded
108
+ // attributeChangedCallback (value already matches, so it no-ops).
109
+ this.setAttribute('active', String(index));
110
+ this._render();
111
+ this.dispatchEvent(new CustomEvent('notify::active', { bubbles: true, detail: { active: index } }));
112
+ }
113
+
114
+ private _render(): void {
115
+ this.classList.toggle('flat', this.hasAttribute('flat'));
116
+ this.classList.toggle('round', this.hasAttribute('round'));
117
+ this._buttons.forEach((btn, index) => {
118
+ const isActive = index === this._active;
119
+ btn.classList.toggle('active', isActive);
120
+ btn.setAttribute('aria-pressed', String(isActive));
121
+ });
122
+ }
123
+ }
124
+
125
+ customElements.define('adw-toggle', AdwToggle);
126
+ customElements.define('adw-toggle-group', AdwToggleGroup);
@@ -0,0 +1,60 @@
1
+ // <adw-toolbar-view> — A content area framed by flat top and/or bottom bars
2
+ // (typically header bars). Mirrors Adw.ToolbarView.
3
+ // Slots: slot="top" (top bars, stacked), slot="bottom" (bottom bars), and any
4
+ // remaining children become the scrollable content.
5
+ // Reference: refs/adwaita-web/adwaita-web/docs/widgets/toolbarview.md
6
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_toolbars.scss
7
+ // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
8
+ // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
9
+
10
+ export class AdwToolbarView extends HTMLElement {
11
+ private _topEl!: HTMLDivElement;
12
+ private _contentEl!: HTMLDivElement;
13
+ private _bottomEl!: HTMLDivElement;
14
+ private _initialized = false;
15
+
16
+ /** The top-bar container — append header bars here imperatively. */
17
+ get topBar(): HTMLDivElement {
18
+ return this._topEl;
19
+ }
20
+
21
+ /** The scrollable content container. */
22
+ get contentArea(): HTMLDivElement {
23
+ return this._contentEl;
24
+ }
25
+
26
+ /** The bottom-bar container. */
27
+ get bottomBar(): HTMLDivElement {
28
+ return this._bottomEl;
29
+ }
30
+
31
+ connectedCallback() {
32
+ if (this._initialized) return;
33
+ this._initialized = true;
34
+
35
+ const topChildren = Array.from(this.querySelectorAll(':scope > [slot="top"]'));
36
+ const bottomChildren = Array.from(this.querySelectorAll(':scope > [slot="bottom"]'));
37
+ const contentChildren = Array.from(this.childNodes).filter(
38
+ (n) => !topChildren.includes(n as Element) && !bottomChildren.includes(n as Element),
39
+ );
40
+
41
+ this._topEl = document.createElement('div');
42
+ this._topEl.className = 'adw-toolbar-view-top';
43
+ for (const child of topChildren) this._topEl.appendChild(child);
44
+
45
+ this._contentEl = document.createElement('div');
46
+ this._contentEl.className = 'adw-toolbar-view-content';
47
+ for (const child of contentChildren) this._contentEl.appendChild(child);
48
+
49
+ this._bottomEl = document.createElement('div');
50
+ this._bottomEl.className = 'adw-toolbar-view-bottom';
51
+ for (const child of bottomChildren) this._bottomEl.appendChild(child);
52
+
53
+ this._topEl.hidden = this._topEl.childElementCount === 0;
54
+ this._bottomEl.hidden = this._bottomEl.childElementCount === 0;
55
+
56
+ this.replaceChildren(this._topEl, this._contentEl, this._bottomEl);
57
+ }
58
+ }
59
+
60
+ customElements.define('adw-toolbar-view', AdwToolbarView);