@gjsify/adwaita-web 0.31.0 → 0.33.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 (183) hide show
  1. package/dist/adwaita-web.css +835 -299
  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-combo-row.d.ts +10 -0
  30. package/lib/types/elements/adw-data-grid.d.ts +11 -31
  31. package/lib/types/elements/adw-drop-down.d.ts +17 -20
  32. package/lib/types/elements/adw-entry-row.d.ts +93 -3
  33. package/lib/types/elements/adw-expander-row.d.ts +7 -1
  34. package/lib/types/elements/adw-header-bar.d.ts +15 -0
  35. package/lib/types/elements/adw-icon.d.ts +25 -0
  36. package/lib/types/elements/adw-inline-view-switcher.d.ts +73 -11
  37. package/lib/types/elements/adw-menu-button.d.ts +6 -6
  38. package/lib/types/elements/adw-navigation-split-view.d.ts +48 -0
  39. package/lib/types/elements/adw-navigation-view.d.ts +63 -20
  40. package/lib/types/elements/adw-overlay-split-view.d.ts +94 -0
  41. package/lib/types/elements/adw-password-entry-row.d.ts +19 -10
  42. package/lib/types/elements/adw-popover.d.ts +58 -0
  43. package/lib/types/elements/adw-preferences-dialog.d.ts +57 -2
  44. package/lib/types/elements/adw-preferences-group.d.ts +31 -0
  45. package/lib/types/elements/adw-progress-bar.d.ts +38 -0
  46. package/lib/types/elements/adw-sidebar.d.ts +37 -5
  47. package/lib/types/elements/adw-spinner.d.ts +23 -0
  48. package/lib/types/elements/adw-split-button.d.ts +53 -7
  49. package/lib/types/elements/adw-switch-row.d.ts +11 -2
  50. package/lib/types/elements/adw-switch.d.ts +15 -0
  51. package/lib/types/elements/adw-tab-view.d.ts +129 -12
  52. package/lib/types/elements/adw-toolbar-view.d.ts +5 -0
  53. package/lib/types/elements/adw-view-stack.d.ts +39 -19
  54. package/lib/types/elements/adw-view-switcher-bar.d.ts +25 -7
  55. package/lib/types/elements/adw-view-switcher.d.ts +76 -13
  56. package/lib/types/elements/adw-window-title.d.ts +3 -1
  57. package/lib/types/elements/adw-wrap-box.d.ts +58 -3
  58. package/lib/types/elements/view-switcher-dom.d.ts +44 -0
  59. package/lib/types/entry-rows.spec.d.ts +1 -0
  60. package/lib/types/index.d.ts +7 -0
  61. package/lib/types/navigation-view.spec.d.ts +1 -0
  62. package/lib/types/preferences-search.d.ts +20 -0
  63. package/lib/types/preferences.spec.d.ts +1 -0
  64. package/lib/types/split-button.spec.d.ts +1 -0
  65. package/lib/types/split-views.spec.d.ts +1 -0
  66. package/lib/types/styles.generated.d.ts +1 -1
  67. package/lib/types/tab-view.spec.d.ts +1 -0
  68. package/lib/types/view-stack.spec.d.ts +1 -0
  69. package/lib/types/view-switcher.spec.d.ts +1 -0
  70. package/package.json +6 -6
  71. package/scss/_banner.scss +13 -0
  72. package/scss/_bottom_sheet.scss +5 -6
  73. package/scss/_button.scss +3 -26
  74. package/scss/_button_content.scss +28 -0
  75. package/scss/_checks.scss +228 -0
  76. package/scss/_clamp.scss +9 -3
  77. package/scss/_combo_row.scss +19 -0
  78. package/scss/_data_grid.scss +15 -7
  79. package/scss/_drop_down.scss +12 -55
  80. package/scss/_expander_row.scss +5 -50
  81. package/scss/_icon.scss +51 -0
  82. package/scss/_icons.generated.scss +6 -0
  83. package/scss/_inline_view_switcher.scss +34 -0
  84. package/scss/_labels.scss +135 -0
  85. package/scss/_menu_button.scss +10 -70
  86. package/scss/_navigation_split_view.scss +47 -8
  87. package/scss/_overlay_split_view.scss +117 -32
  88. package/scss/_popover.scss +186 -0
  89. package/scss/_preferences.scss +29 -0
  90. package/scss/_preferences_dialog.scss +17 -0
  91. package/scss/_progress_bar.scss +161 -0
  92. package/scss/_reset.scss +18 -7
  93. package/scss/_spinner.scss +37 -19
  94. package/scss/_split_button.scss +6 -50
  95. package/scss/_switch.scss +90 -0
  96. package/scss/_switch_row.scss +6 -52
  97. package/scss/_tab_view.scss +63 -0
  98. package/scss/_theme.scss +10 -0
  99. package/scss/_toolbar_view.scss +74 -0
  100. package/scss/_variables.scss +34 -0
  101. package/scss/_view_switcher.scss +40 -0
  102. package/scss/_view_switcher_bar.scss +37 -0
  103. package/scss/_wrap_box.scss +65 -6
  104. package/scss/adwaita-skin.scss +23 -0
  105. package/src/adw-about-dialog.spec.ts +350 -0
  106. package/src/adw-action-rows.spec.ts +342 -0
  107. package/src/adw-avatar.spec.ts +176 -0
  108. package/src/adw-banner.spec.ts +152 -0
  109. package/src/adw-button-content.spec.ts +207 -0
  110. package/src/adw-button-row.spec.ts +12 -6
  111. package/src/adw-carousel.spec.ts +389 -0
  112. package/src/adw-checks.spec.ts +368 -0
  113. package/src/adw-drop-down.spec.ts +123 -0
  114. package/src/adw-header-bar.spec.ts +83 -0
  115. package/src/adw-icon.spec.ts +167 -0
  116. package/src/adw-progress-bar.spec.ts +173 -0
  117. package/src/adw-row-state.spec.ts +49 -0
  118. package/src/adw-sidebar.spec.ts +332 -0
  119. package/src/adw-switch.spec.ts +242 -0
  120. package/src/adw-tab-view.spec.ts +12 -3
  121. package/src/adw-view-switcher-bar.spec.ts +61 -13
  122. package/src/adw-wrap-box.spec.ts +341 -0
  123. package/src/bottom-sheet.spec.ts +325 -0
  124. package/src/breakpoints.spec.ts +147 -0
  125. package/src/breakpoints.ts +67 -0
  126. package/src/chrome.spec.ts +400 -0
  127. package/src/elements/adw-about-dialog.ts +223 -163
  128. package/src/elements/adw-action-row.ts +131 -11
  129. package/src/elements/adw-avatar.ts +48 -54
  130. package/src/elements/adw-banner.ts +46 -10
  131. package/src/elements/adw-bottom-sheet.ts +145 -60
  132. package/src/elements/adw-button-content.ts +107 -33
  133. package/src/elements/adw-button-row.ts +61 -37
  134. package/src/elements/adw-button.ts +5 -7
  135. package/src/elements/adw-carousel.ts +467 -105
  136. package/src/elements/adw-checks.ts +299 -0
  137. package/src/elements/adw-clamp.ts +80 -10
  138. package/src/elements/adw-combo-row.ts +32 -7
  139. package/src/elements/adw-data-grid.ts +43 -91
  140. package/src/elements/adw-drop-down.ts +117 -128
  141. package/src/elements/adw-entry-row.ts +404 -36
  142. package/src/elements/adw-expander-row.ts +61 -29
  143. package/src/elements/adw-header-bar.ts +31 -6
  144. package/src/elements/adw-icon.ts +148 -0
  145. package/src/elements/adw-inline-view-switcher.ts +325 -131
  146. package/src/elements/adw-menu-button.ts +93 -70
  147. package/src/elements/adw-navigation-split-view.ts +220 -14
  148. package/src/elements/adw-navigation-view.ts +286 -148
  149. package/src/elements/adw-overlay-split-view.ts +536 -28
  150. package/src/elements/adw-password-entry-row.ts +111 -89
  151. package/src/elements/adw-popover.ts +283 -0
  152. package/src/elements/adw-preferences-dialog.ts +120 -21
  153. package/src/elements/adw-preferences-group.ts +128 -22
  154. package/src/elements/adw-progress-bar.ts +189 -0
  155. package/src/elements/adw-sidebar.ts +301 -142
  156. package/src/elements/adw-spin-row.ts +11 -5
  157. package/src/elements/adw-spinner.ts +168 -9
  158. package/src/elements/adw-split-button.ts +371 -92
  159. package/src/elements/adw-status-page.ts +9 -9
  160. package/src/elements/adw-switch-row.ts +100 -30
  161. package/src/elements/adw-switch.ts +129 -0
  162. package/src/elements/adw-tab-view.ts +700 -129
  163. package/src/elements/adw-toggle-group.ts +5 -7
  164. package/src/elements/adw-toolbar-view.ts +84 -21
  165. package/src/elements/adw-view-stack.ts +183 -85
  166. package/src/elements/adw-view-switcher-bar.ts +171 -81
  167. package/src/elements/adw-view-switcher.ts +328 -117
  168. package/src/elements/adw-window-title.ts +44 -11
  169. package/src/elements/adw-wrap-box.ts +329 -78
  170. package/src/elements/view-switcher-dom.ts +108 -0
  171. package/src/entry-rows.spec.ts +391 -0
  172. package/src/index.ts +23 -0
  173. package/src/navigation-view.spec.ts +435 -0
  174. package/src/preferences-search.ts +135 -0
  175. package/src/preferences.spec.ts +250 -0
  176. package/src/source-view/adw-source-view.ts +2 -5
  177. package/src/split-button.spec.ts +470 -0
  178. package/src/split-views.spec.ts +893 -0
  179. package/src/styles.generated.ts +1 -1
  180. package/src/tab-view.spec.ts +451 -0
  181. package/src/test.browser.mts +49 -0
  182. package/src/view-stack.spec.ts +272 -0
  183. package/src/view-switcher.spec.ts +665 -0
@@ -1,15 +1,51 @@
1
1
  // <adw-switch-row> — Row with a title/subtitle and a trailing toggle switch.
2
2
  // Attributes: title, subtitle, active (boolean)
3
3
  // Events: notify::active (CustomEvent, mirrors GJS GObject signal naming)
4
+ //
5
+ // The ACTIVE flag, its notify rule and the toggle-on-row-activation are HEADLESS
6
+ // and live in `@gjsify/adwaita-core` (ADR 0004) as {@link SwitchRowState}; this
7
+ // element composes it and keeps only the DOM render half — the checkbox, the
8
+ // label column and the `notify::active` event.
9
+ // `@gjsify/adwaita-nativescript` composes the same state, so both ports share one
10
+ // behaviour. Two things this fixes, both from adw-switch-row.c:
11
+ // - a PROGRAMMATIC `row.active = true` now notifies. The DOM does not fire
12
+ // `change` for `.checked =`, so keying the event off the checkbox meant the
13
+ // row was silent — while the NativeScript port, whose `checkedChange` DOES
14
+ // fire on a programmatic write, notified. libadwaita has exactly one notify
15
+ // path (`slider_notify_active_cb`, C:66-77) and it cannot see the origin.
16
+ // - clicking the ROW toggles. `adw_switch_row_init` points the
17
+ // activatable-widget at the slider (C:160-162), so the title is part of the
18
+ // control; here only the handle itself worked.
19
+ //
20
+ // The TOGGLE itself is `<adw-switch>`. It used to be built here by hand —
21
+ // `label.adw-switch > input[type=checkbox] + span.adw-switch-slider` — and
22
+ // `<adw-expander-row>` built the identical thing, over a stylesheet block that
23
+ // was a character-for-character copy of this row's.
24
+ //
4
25
  // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
5
26
  // Copyright (c) 2025 csm. MIT License.
6
27
  // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web;
7
- // title/subtitle text column added to match Adw.SwitchRow.
28
+ // title/subtitle text column added to match Adw.SwitchRow; the active state
29
+ // composed from @gjsify/adwaita-core; the toggle markup from <adw-switch>.
30
+
31
+ import { SwitchRowState, deriveRowLabels } from '@gjsify/adwaita-core';
32
+
33
+ // SIDE-EFFECT import, deliberately separate from the type import below: it is
34
+ // what guarantees `adw-switch` is defined before this module's own
35
+ // `customElements.define` can upgrade a server-rendered `<adw-switch-row>` and
36
+ // build one. A combined `import { AdwSwitch }` would NOT do it — the binding is
37
+ // only ever used in type position here, and this package compiles without
38
+ // `verbatimModuleSyntax`, so TypeScript would elide the whole statement and take
39
+ // the registration with it.
40
+ import './adw-switch.js';
41
+ import type { AdwSwitch } from './adw-switch.js';
8
42
 
9
43
  export class AdwSwitchRow extends HTMLElement {
10
- private _checkbox!: HTMLInputElement;
44
+ private _switchEl!: AdwSwitch;
11
45
  private _titleEl!: HTMLSpanElement;
12
46
  private _subtitleEl!: HTMLSpanElement;
47
+ /** The headless active flag + its notify rule (ADR 0004). */
48
+ private readonly _state = new SwitchRowState();
13
49
  private _initialized = false;
14
50
 
15
51
  static get observedAttributes() {
@@ -17,12 +53,13 @@ export class AdwSwitchRow extends HTMLElement {
17
53
  }
18
54
 
19
55
  get active(): boolean {
20
- return this.hasAttribute('active');
56
+ return this._initialized ? this._state.active : this.hasAttribute('active');
21
57
  }
22
58
 
23
59
  set active(value: boolean) {
24
- if (value) this.setAttribute('active', '');
25
- else this.removeAttribute('active');
60
+ // Routed through the attribute so the declarative and imperative faces
61
+ // stay one code path; `attributeChangedCallback` drives the state.
62
+ this.toggleAttribute('active', !!value);
26
63
  }
27
64
 
28
65
  connectedCallback() {
@@ -37,42 +74,75 @@ export class AdwSwitchRow extends HTMLElement {
37
74
  this._subtitleEl.className = 'adw-row-subtitle';
38
75
  text.append(this._titleEl, this._subtitleEl);
39
76
 
40
- const label = document.createElement('label');
41
- label.className = 'adw-switch';
42
- const input = document.createElement('input');
43
- input.type = 'checkbox';
44
- input.checked = this.hasAttribute('active');
45
- const slider = document.createElement('span');
46
- slider.className = 'adw-switch-slider';
47
- label.append(input, slider);
48
-
49
- this.replaceChildren(text, label);
50
-
51
- this._checkbox = input;
52
- this._checkbox.addEventListener('change', () => {
53
- this.toggleAttribute('active', this._checkbox.checked);
54
- this.dispatchEvent(
55
- new CustomEvent('notify::active', {
56
- bubbles: true,
57
- detail: { active: this._checkbox.checked },
58
- }),
59
- );
77
+ this._switchEl = document.createElement('adw-switch') as AdwSwitch;
78
+
79
+ this.replaceChildren(text, this._switchEl);
80
+
81
+ this._state.setActive(this.hasAttribute('active'));
82
+ this._switchEl.active = this._state.active;
83
+ // `gtk_widget_class_set_accessible_role (…, GTK_ACCESSIBLE_ROLE_SWITCH)`
84
+ // plus the initial CHECKED state (adw-switch-row.c:147, :155-158).
85
+ this.setAttribute('role', 'switch');
86
+ this.setAttribute('aria-checked', String(this._state.active));
87
+
88
+ this._switchEl.addEventListener('notify::active', (event) => {
89
+ // The ROW is the public event surface — libadwaita has exactly one
90
+ // notify path for this property (`slider_notify_active_cb`, C:66-77).
91
+ // Letting the composed switch's own identically-named event escape
92
+ // would make every listener on the row see each toggle twice.
93
+ event.stopPropagation();
94
+ this._apply(this._state.setActive(this._switchEl.active));
95
+ });
96
+
97
+ // Activating the row inverts the state (adw-switch-row.c:23-27). A click
98
+ // that landed on the switch itself is already handled by the switch —
99
+ // toggling again here would immediately undo it.
100
+ this.addEventListener('click', (event) => {
101
+ if (event.target instanceof Node && this._switchEl.contains(event.target)) return;
102
+ this._apply(this._state.activate());
60
103
  });
61
104
 
62
105
  this._renderText();
63
106
  }
64
107
 
65
- attributeChangedCallback(name: string, _old: string | null, value: string | null) {
108
+ attributeChangedCallback(name: string) {
66
109
  if (!this._initialized) return;
67
- if (name === 'active') this._checkbox.checked = value !== null;
110
+ if (name === 'active') this._apply(this._state.setActive(this.hasAttribute('active')));
68
111
  else this._renderText();
69
112
  }
70
113
 
114
+ /**
115
+ * Push a state transition out to the DOM and emit `notify::active` — but
116
+ * only when something actually changed, which is the `g_object_notify`
117
+ * gate libadwaita puts on the same transition (C:224-225 → :76).
118
+ */
119
+ private _apply(changed: boolean) {
120
+ if (!changed) return;
121
+ // Idempotent on the switch's side too, so this write does not bounce
122
+ // back through the `notify::active` listener as a second transition.
123
+ this._switchEl.active = this._state.active;
124
+ this.toggleAttribute('active', this._state.active);
125
+ // The a11y state libadwaita updates alongside the notify (C:71-74).
126
+ this.setAttribute('aria-checked', String(this._state.active));
127
+ this.dispatchEvent(
128
+ new CustomEvent('notify::active', {
129
+ bubbles: true,
130
+ detail: { active: this._state.active },
131
+ }),
132
+ );
133
+ }
134
+
71
135
  private _renderText() {
72
- this._titleEl.textContent = this.getAttribute('title') ?? '';
73
- const subtitle = this.getAttribute('subtitle') ?? '';
136
+ const { title, titleVisible, subtitle, subtitleVisible } = deriveRowLabels({
137
+ title: this.getAttribute('title'),
138
+ subtitle: this.getAttribute('subtitle'),
139
+ });
140
+ this._titleEl.textContent = title;
141
+ // `string_is_not_empty` applies to the TITLE too (adw-action-row.ui:49-53,
142
+ // inherited — AdwSwitchRow derives from AdwActionRow, adw-switch-row.c:50).
143
+ this._titleEl.hidden = !titleVisible;
74
144
  this._subtitleEl.textContent = subtitle;
75
- this._subtitleEl.hidden = subtitle.length === 0;
145
+ this._subtitleEl.hidden = !subtitleVisible;
76
146
  }
77
147
  }
78
148
 
@@ -0,0 +1,129 @@
1
+ // <adw-switch> — the Adwaita toggle, as an element. A 44×24 track with a hidden
2
+ // checkbox behind it and a knob that slides on `active`.
3
+ //
4
+ // WHY IT EXISTS: `<adw-switch-row>` and `<adw-expander-row>` each built
5
+ // `label.adw-switch > input[type=checkbox] + span.adw-switch-slider` by hand, and
6
+ // `_expander_row.scss:73-121` was a character-for-character copy of
7
+ // `_switch_row.scss:15-63`. The cause was structural — `_switch_row.scss:9`
8
+ // opened `adw-switch-row {` and nested everything inside it, so `.adw-switch` did
9
+ // not exist outside a switch row and the expander could not reuse it. The styles
10
+ // are now the unscoped `scss/_switch.scss` and the markup is here.
11
+ //
12
+ // NO CORE STATE MACHINE. The state is one boolean with no derivation, no
13
+ // ordering and no notify subtlety beyond "on change" — ADR 0004 is explicit that
14
+ // a widget with genuinely trivial behaviour does not get a core class, and the
15
+ // `active` ATTRIBUTE is the state (`toggleAttribute` is idempotent by
16
+ // definition, so "notify only on a real change" needs no guard of its own).
17
+ // `GtkSwitch` does carry a two-phase `active`/`state` pair for async toggles,
18
+ // but `refs/gtk` is EMPTY in this tree so that is unverifiable here, and neither
19
+ // this port nor `@gjsify/adwaita-nativescript` models it — see the ROW's state
20
+ // (`SwitchRowState`), which is where the notify rule that IS derived from C
21
+ // lives.
22
+ //
23
+ // Attributes:
24
+ // active — boolean; reflects (and drives) the toggled state.
25
+ // disabled — boolean; the control does not respond and is dimmed.
26
+ // Properties:
27
+ // active — the toggled state (get/set).
28
+ // disabled — whether the control is inert (get/set).
29
+ // Events:
30
+ // `notify::active` (CustomEvent, bubbles, detail = { active }) — mirrors the
31
+ // GObject property-notify; fires on every change, programmatic included.
32
+ // A widget that COMPOSES this one and publishes its own `notify::active`
33
+ // (`<adw-switch-row>`) stops this one at the switch, so the row stays the
34
+ // single public event surface.
35
+ //
36
+ // A11Y: the checkbox is left bare — no `role="switch"` — because the widgets
37
+ // that host one claim that role themselves (`adw_switch_row_init` sets
38
+ // `GTK_ACCESSIBLE_ROLE_SWITCH` on the ROW, adw-switch-row.c:147), and a nested
39
+ // second switch role would be announced twice. This is exactly the markup that
40
+ // shipped inside the two rows before the extraction.
41
+ //
42
+ // Reference: refs/adwaita-web/adwaita-web/scss/_switch.scss
43
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_switch.scss
44
+ // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
45
+ // Copyright (c) 2025 csm (adwaita-web). MIT License.
46
+ // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
47
+
48
+ export class AdwSwitch extends HTMLElement {
49
+ private _input!: HTMLInputElement;
50
+ private _slider!: HTMLSpanElement;
51
+ private _initialized = false;
52
+
53
+ static get observedAttributes() {
54
+ return ['active', 'disabled'];
55
+ }
56
+
57
+ /** Whether the switch is on. */
58
+ get active(): boolean {
59
+ return this.hasAttribute('active');
60
+ }
61
+
62
+ set active(value: boolean) {
63
+ // `toggleAttribute` with a force flag is a no-op when the attribute is
64
+ // already in the wanted state, so re-setting the current value mutates
65
+ // nothing and `attributeChangedCallback` — and therefore the notify —
66
+ // never runs. That idempotence is what a state class would otherwise be
67
+ // holding.
68
+ this.toggleAttribute('active', !!value);
69
+ }
70
+
71
+ /** Whether the switch is inert. */
72
+ get disabled(): boolean {
73
+ return this.hasAttribute('disabled');
74
+ }
75
+
76
+ set disabled(value: boolean) {
77
+ this.toggleAttribute('disabled', !!value);
78
+ }
79
+
80
+ connectedCallback() {
81
+ if (this._initialized) return;
82
+ this._initialized = true;
83
+
84
+ this.classList.add('adw-switch');
85
+
86
+ this._input = document.createElement('input');
87
+ this._input.type = 'checkbox';
88
+ this._slider = document.createElement('span');
89
+ this._slider.className = 'adw-switch-slider';
90
+ this.replaceChildren(this._input, this._slider);
91
+
92
+ // Keyboard (Space on the focused checkbox) and a direct programmatic
93
+ // `input.click()` both arrive here; the attribute is the state, so this
94
+ // just writes it and lets attributeChangedCallback do the rest.
95
+ this._input.addEventListener('change', () => {
96
+ this.active = this._input.checked;
97
+ });
98
+
99
+ // The slider covers the whole track (`inset: 0`), so every pointer click
100
+ // lands on it rather than on the 0×0 checkbox — this is the toggle the
101
+ // wrapping `<label>` used to provide for free. A click that DID reach the
102
+ // checkbox is already handled by the listener above; toggling again here
103
+ // would immediately undo it.
104
+ this.addEventListener('click', (event) => {
105
+ if (this.disabled) return;
106
+ if (event.target === this._input) return;
107
+ this.active = !this.active;
108
+ });
109
+
110
+ this._render();
111
+ }
112
+
113
+ attributeChangedCallback(name: string) {
114
+ if (!this._initialized) return;
115
+ this._render();
116
+ if (name === 'active') {
117
+ this.dispatchEvent(new CustomEvent('notify::active', { bubbles: true, detail: { active: this.active } }));
118
+ }
119
+ }
120
+
121
+ private _render(): void {
122
+ const disabled = this.disabled;
123
+ this._input.checked = this.active;
124
+ this._input.disabled = disabled;
125
+ this.classList.toggle('disabled', disabled);
126
+ }
127
+ }
128
+
129
+ customElements.define('adw-switch', AdwSwitch);