@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
@@ -1,43 +1,27 @@
1
- // <adw-icon> — the symbolic icon node, as an element instead of a shape you
2
- // retype. Every icon in this package is a CSS-masked box whose glyph comes from
3
- // a generated `.adw-icon--<name>` class and whose colour is `currentColor`, so
4
- // it themes with whatever contains it.
5
- //
6
- // WHY IT EXISTS: `<span class="adw-icon adw-icon--<name>" aria-hidden="true">`
7
- // was built by hand at TWENTY-THREE sites across SEVENTEEN files, NINE of which
8
- // re-derived the name (`name.replace(/-symbolic$/, '')`) on the spot — one of
9
- // them unanchored. Exactly ONE site (`<adw-split-button>`) also checked the
10
- // result was a single CSS token. The others did not, so `icon-name="a b"`
11
- // interpolated into `class="adw-icon adw-icon--a b"` and shipped a stray `b`
12
- // class; that was live on `<adw-menu-button>`, for its own icon and for every
13
- // JSON menu entry's. The derivation now happens once, in
14
- // `@gjsify/adwaita-core`'s {@link normalizeIconName} (guard included), and the
15
- // markup happens once, here.
1
+ // <adw-icon> — the symbolic icon node as an element. Every icon in this package is a
2
+ // CSS-masked box whose glyph comes from a generated `.adw-icon--<name>` class and
3
+ // whose colour is `currentColor`, so it themes with whatever contains it. The name
4
+ // derivation and its guard live once in `@gjsify/adwaita-core`'s
5
+ // {@link normalizeIconName}, the markup once here.
16
6
  //
17
7
  // Attributes:
18
- // icon-name — symbolic name, with or without the `-symbolic` suffix. A name
19
- // that is not a single CSS token (a space, a quote, a
20
- // reverse-DNS application id) resolves to NO icon rather than to
21
- // injected markup — see `normalizeIconName`.
22
- // size — the rendered edge length in px. Absent leaves the stylesheet's
23
- // 16px in charge; a context that sizes its own icons through CSS
24
- // (`.adw-about-dialog-icon`, the split button's 14px arrows) keeps
25
- // doing exactly that and should NOT set this.
26
- // Properties:
27
- // iconName — the declared name (get/set; reflects to the attribute).
28
- // resolvedIconName — what actually reached the mask class ('' = no icon).
29
- // size — the px size, or null when the stylesheet decides (get/set).
8
+ // icon-name — symbolic name, with or without the `-symbolic` suffix. A name that is
9
+ // not a single CSS token (a space, a quote, a reverse-DNS application
10
+ // id) resolves to NO icon rather than to injected markup.
11
+ // size — rendered edge length in px. Absent leaves the stylesheet's 16px in
12
+ // charge; a context that sizes its own icons through CSS
13
+ // (`.adw-about-dialog-icon`, the split button's 14px arrows) keeps doing
14
+ // exactly that and should NOT set this.
30
15
  //
31
- // THE ELEMENT *IS* THE ICON BOX — it carries `.adw-icon` itself rather than
32
- // wrapping a span. A wrapper would have inserted a layout node into flex rows,
33
- // button interiors and absolutely-positioned arrows that were all measured
34
- // against the icon box directly; carrying the class keeps every existing
35
- // class-scoped rule (`.adw-drop-down-arrow`, `.adw-tab-icon`, `.adw-row-edit`, …)
36
- // matching the same node it always matched.
16
+ // THE ELEMENT *IS* THE ICON BOX — it carries `.adw-icon` itself rather than wrapping a
17
+ // span, because a wrapper would insert a layout node into flex rows, button interiors
18
+ // and absolutely-positioned arrows that are all measured against the icon box, and
19
+ // would stop every class-scoped rule (`.adw-drop-down-arrow`, `.adw-tab-icon`, …)
20
+ // matching the node it matches today.
37
21
  //
38
- // Reference: refs/libadwaita/src/stylesheet/_common.scss:34-41 — `.normal-icons`
39
- // / `.large-icons` set `-gtk-icon-size`; libadwaita has NO `_icon.scss`, the
40
- // icon is a GtkImage and its size is a GTK property, not a stylesheet rule.
22
+ // Reference: refs/libadwaita/src/stylesheet/_common.scss — `.normal-icons` /
23
+ // `.large-icons` set `-gtk-icon-size`; libadwaita has NO `_icon.scss`, the icon is a
24
+ // GtkImage and its size is a GTK property, not a stylesheet rule.
41
25
  // Reference: refs/adwaita-web/adwaita-web/scss/_icon.scss — the vendored web
42
26
  // port's `.adw-icon` (an inline-flex SVG holder; this package masks instead).
43
27
  // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
@@ -93,25 +77,20 @@ export class AdwIcon extends HTMLElement {
93
77
  this._render();
94
78
  }
95
79
 
96
- // ── internals ──────────────────────────────────────────────────────────
97
-
98
80
  private _render(): void {
99
81
  this.classList.add('adw-icon');
100
82
 
101
- // Swap ONLY the mask class: the callers put their own positioning class
102
- // on the same node (`adw-drop-down-arrow`, `adw-sidebar-item-arrow`, …),
103
- // and a `className =` assignment here would take those with it. The list
104
- // is collected BEFORE removing — `classList` is live, so removing while
105
- // iterating it would skip the entry after each hit.
83
+ // Swap ONLY the mask class: callers put their own positioning class on the same
84
+ // node, and a `className =` assignment would take those with it. The list is
85
+ // collected BEFORE removing — `classList` is live, so removing while iterating it
86
+ // would skip the entry after each hit.
106
87
  const stale = [...this.classList].filter((existing) => existing.startsWith(MASK_CLASS_PREFIX));
107
88
  this.classList.remove(...stale);
108
89
  const name = this.resolvedIconName;
109
90
  if (name !== '') this.classList.add(`${MASK_CLASS_PREFIX}${name}`);
110
91
 
111
- // A masked box has no text, so assistive tech has nothing to announce —
112
- // the accessible name belongs to the control that HOSTS the icon. Every
113
- // hand-rolled copy that thought about it said the same thing; the ones
114
- // that did not were the inconsistency, not the intent.
92
+ // A masked box has no text, so assistive tech has nothing to announce — the
93
+ // accessible name belongs to the control that HOSTS the icon.
115
94
  this.setAttribute('aria-hidden', 'true');
116
95
 
117
96
  const size = this.size;
@@ -124,8 +103,8 @@ export class AdwIcon extends HTMLElement {
124
103
  const px = `${size}px`;
125
104
  this.style.width = px;
126
105
  this.style.height = px;
127
- // The box and the mask scale together — sizing only the box leaves a
128
- // 16px glyph floating in the middle of it.
106
+ // The box and the mask scale together: sizing only the box leaves a 16px glyph
107
+ // floating in the middle of it.
129
108
  this.style.setProperty('mask-size', px);
130
109
  this.style.setProperty('-webkit-mask-size', px);
131
110
  }
@@ -133,10 +112,8 @@ export class AdwIcon extends HTMLElement {
133
112
  }
134
113
 
135
114
  /**
136
- * Build one icon node: the three lines every caller would otherwise write.
137
- *
138
- * `extraClasses` are the caller's own positioning/context classes, added
139
- * alongside the managed `.adw-icon` + mask class rather than replacing them.
115
+ * Build one icon node. `extraClasses` are the caller's own positioning/context
116
+ * classes, added alongside the managed `.adw-icon` + mask class, not replacing them.
140
117
  */
141
118
  export function createAdwIcon(iconName: string | null, ...extraClasses: string[]): AdwIcon {
142
119
  const icon = document.createElement('adw-icon') as AdwIcon;
@@ -4,28 +4,16 @@
4
4
  // and exactly one page is visible at a time.
5
5
  //
6
6
  // Everything that decides WHAT to show is HEADLESS and lives in
7
- // `@gjsify/adwaita-core` (ADR 0004) — including the TWO INDEX SPACES this widget
8
- // is built on: a hidden page produces NO toggle, so the compacted toggle index
9
- // and the page index diverge, and libadwaita reconciles them through the
10
- // `child-index` it stashes on every toggle (adw-inline-view-switcher.c:346,
11
- // :114-129, :434-453). `buildInlineToggles` carries both indices, so this file
12
- // never has to reconcile them itself.
7
+ // `@gjsify/adwaita-core` (ADR 0004) — including the TWO INDEX SPACES this widget is
8
+ // built on: a hidden page produces NO toggle, so the compacted toggle index and the
9
+ // page index diverge, and libadwaita reconciles them through the `child-index` it
10
+ // stashes on every toggle. `buildInlineToggles` carries both indices, so this file
11
+ // never reconciles them itself.
13
12
  //
14
- // What the lift fixed here, all of it C-derived:
15
- // - hidden pages were not filtered at all, so the two index spaces collapsed
16
- // into one and `AdwViewStackPage:visible` had no spelling (:370-378).
17
- // - the display-mode default was 'both'; libadwaita's is 'labels' (:657, :715).
18
- // - an unrecognised display-mode value REPLACED the current mode with 'both';
19
- // `g_return_if_fail (mode <= …_BOTH)` rejects it and keeps the current one
20
- // (:819).
21
- // - the icons-mode tooltip used the RAW title, so `title="_Files"` showed the
22
- // underscore; C strips the mnemonic when use-underline is set (:180-188).
23
- // - a page with no icon rendered no icon; C substitutes `image-missing`
24
- // (:137-142) in every mode that builds an image at all.
25
- // - `active` was CLAMPED into range instead of refused, `notify::display-mode`
26
- // was documented but never dispatched, the page query was unscoped (so a
27
- // nested switcher lost its pages to the outer one), and the 500 ms
28
- // drag-hover auto-switch did not exist (:80, :194-236).
13
+ // Two of the core's derivations are easy to get wrong by hand: the icons-mode tooltip
14
+ // STRIPS the mnemonic when use-underline is set (`title="_Files"` must not show the
15
+ // underscore), and a page with no icon gets `image-missing` substituted in every mode
16
+ // that builds an image at all.
29
17
  //
30
18
  // Attributes:
31
19
  // active — zero-based index of the visible PAGE (not of the toggle). Refused,
@@ -77,14 +65,10 @@ export class AdwViewStackPage extends HTMLElement {
77
65
 
78
66
  export class AdwInlineViewSwitcher extends HTMLElement {
79
67
  /**
80
- * The dwell timer, injectable.
81
- *
82
- * It used to be `new ViewSwitcherState({ scheduler: domViewSwitcherScheduler })`
83
- * inline, which left NOWHERE to hand a clock in — so
84
- * `VIEW_SWITCHER_DRAG_VECTORS` was driven by the core suite alone, and the
85
- * conformance header claimed all three suites drove it. `scheduler` is a
86
- * property so a test can replace it before the element connects; the DOM one
87
- * is the default, exactly as before.
68
+ * The dwell timer, injectable: a property rather than an inline
69
+ * `new ViewSwitcherState({ scheduler: domViewSwitcherScheduler })`, so a test can
70
+ * replace the clock before the element connects and drive
71
+ * `VIEW_SWITCHER_DRAG_VECTORS` here too. The DOM scheduler is the default.
88
72
  */
89
73
  scheduler: ViewSwitcherScheduler = domViewSwitcherScheduler;
90
74
  private _stateInstance: ViewSwitcherState | null = null;
@@ -127,14 +111,12 @@ export class AdwInlineViewSwitcher extends HTMLElement {
127
111
 
128
112
  /**
129
113
  * Index of the ACTIVE TOGGLE, `-1` when the visible page has none because it
130
- * is hidden — the port-safe `GTK_INVALID_LIST_POSITION`
131
- * (adw-inline-view-switcher.c:441-446).
114
+ * is hidden — the port-safe `GTK_INVALID_LIST_POSITION`.
132
115
  */
133
116
  get activeToggle(): number {
134
117
  return toggleIndexForPage(this._state.pages, this._state.selected);
135
118
  }
136
119
 
137
- /** What the toggles display: 'labels', 'icons' or 'both'. */
138
120
  get displayMode(): AdwInlineViewSwitcherDisplayMode {
139
121
  return this._displayMode;
140
122
  }
@@ -152,7 +134,6 @@ export class AdwInlineViewSwitcher extends HTMLElement {
152
134
  this._state.selectName(name);
153
135
  }
154
136
 
155
- /** The resolved page records. */
156
137
  get pages(): readonly AdwViewSwitcherPage[] {
157
138
  return this._state.pages;
158
139
  }
@@ -160,8 +141,7 @@ export class AdwInlineViewSwitcher extends HTMLElement {
160
141
  /**
161
142
  * Show or hide a page. Returns whether the SELECTION moved. Rebuilds the
162
143
  * toggle row, because a hidden page has no toggle at all — C reacts to a
163
- * page's `notify::visible` with `recreate_toggles`
164
- * (adw-inline-view-switcher.c:425, :471-477).
144
+ * page's `notify::visible` with `recreate_toggles`.
165
145
  */
166
146
  setPageVisible(name: string, visible: boolean): boolean {
167
147
  const moved = this._state.setPageVisible(name, visible);
@@ -222,11 +202,9 @@ export class AdwInlineViewSwitcher extends HTMLElement {
222
202
  }
223
203
 
224
204
  /**
225
- * Re-read every declared page and push it into the state.
226
- *
227
- * Driven by {@link _watchPages}, which is what makes a runtime
228
- * `badge-number` or `icon-name` change reach the toggle — C rebinds on every
229
- * page `notify` (adw-view-switcher.c:184-193) and this element did not.
205
+ * Re-read every declared page and push it into the state. Driven by
206
+ * {@link _watchPages}, which is what makes a runtime `badge-number` or `icon-name`
207
+ * change reach the toggle, as C's per-page `notify` bindings do.
230
208
  */
231
209
  refreshPages(): void {
232
210
  if (!this._initialized) return;
@@ -236,13 +214,11 @@ export class AdwInlineViewSwitcher extends HTMLElement {
236
214
 
237
215
  /**
238
216
  * Watch the declared pages for attribute changes — the DOM's `notify::` on
239
- * `AdwViewStackPage`, which C binds per page (adw-view-switcher.c:184-193).
240
- *
241
- * A `MutationObserver` rather than an `attributeChangedCallback`: this
242
- * element consumes the page elements at connect and `replaceChildren`s them
243
- * out of the tree, so a detached page cannot find its owner. It delivers on
244
- * a MICROTASK where GTK's notify is synchronous — invisible to a user, and
245
- * nothing here reads the toggle back inside the setter.
217
+ * `AdwViewStackPage`. A `MutationObserver` rather than an
218
+ * `attributeChangedCallback`, because this element consumes the page elements at
219
+ * connect and `replaceChildren`s them out of the tree, so a detached page cannot
220
+ * find its owner. It delivers on a MICROTASK where GTK's notify is synchronous —
221
+ * invisible to a user, and nothing here reads the toggle back inside the setter.
246
222
  */
247
223
  private _watchPages(): void {
248
224
  this._pageObserver?.disconnect();
@@ -264,14 +240,13 @@ export class AdwInlineViewSwitcher extends HTMLElement {
264
240
  if (next === this._displayMode) return;
265
241
  this._displayMode = next;
266
242
  // C rebuilds every toggle's child on a mode change rather than
267
- // toggling visibility on the existing ones (:852-854).
243
+ // toggling visibility on the existing ones.
268
244
  this._rebuildToggles();
269
245
  this.dispatchEvent(
270
246
  new CustomEvent('notify::display-mode', { bubbles: true, detail: { displayMode: next } }),
271
247
  );
272
248
  return;
273
249
  }
274
- // flat / round are styling-only.
275
250
  this._applyStyleClasses();
276
251
  }
277
252
 
@@ -282,11 +257,10 @@ export class AdwInlineViewSwitcher extends HTMLElement {
282
257
  }
283
258
 
284
259
  /**
285
- * Rebuild the toggle row from the derived models. One node per VISIBLE page,
286
- * in compacted order — the structural half of `recreate_toggles`. Selection
287
- * alone never lands here: `selection_changed_cb` only sets the active toggle
288
- * (adw-inline-view-switcher.c:434-453), and rebuilding mid-drag would drop
289
- * the dwell the drag has already armed.
260
+ * Rebuild the toggle row from the derived models: one node per VISIBLE page, in
261
+ * compacted order — the structural half of `recreate_toggles`. Selection alone never
262
+ * lands here (`selection_changed_cb` only sets the active toggle), because
263
+ * rebuilding mid-drag would drop the dwell the drag has already armed.
290
264
  */
291
265
  private _rebuildToggles(): void {
292
266
  if (!this._initialized) return;
@@ -327,13 +301,12 @@ export class AdwInlineViewSwitcher extends HTMLElement {
327
301
  toggle.addEventListener('click', () => {
328
302
  this._state.setSelected(model.pageIndex);
329
303
  });
330
- // The `relatedTarget` test is load-bearing: a toggle holds an icon,
331
- // a label and an indicator, and the DOM fires `dragleave` +
332
- // `dragenter` when the pointer crosses BETWEEN them — which cleared
333
- // the dwell timer and re-armed a fresh 500ms, so a drag that merely
334
- // slid across the toggle never switched the page. C has one
335
- // `GtkDropControllerMotion` per BUTTON widget
336
- // (adw-view-switcher-button.c:79-96), which has no internal edges.
304
+ // The `relatedTarget` test is load-bearing: a toggle holds an icon, a label
305
+ // and an indicator, and the DOM fires `dragleave` + `dragenter` when the
306
+ // pointer crosses BETWEEN them, which clears the dwell timer and re-arms a
307
+ // fresh 500 ms — so a drag that merely slid across the toggle would never
308
+ // switch the page. C has one `GtkDropControllerMotion` per BUTTON widget,
309
+ // which has no internal edges.
337
310
  toggle.addEventListener('dragenter', (event) => {
338
311
  if (toggle.contains((event as DragEvent).relatedTarget as Node | null)) return;
339
312
  this._state.dragEnter(model.pageIndex);
@@ -389,18 +362,16 @@ export class AdwInlineViewSwitcher extends HTMLElement {
389
362
  }
390
363
 
391
364
  private _applyStyleClasses(): void {
392
- // `css_classes_changed_cb` (adw-inline-view-switcher.c:528-545) forwards
393
- // THREE classes onto the toggle group, not two: `flat`, `round` and
394
- // `osd`. This element carried the first two and only onto the host, so an
395
- // `osd` inline switcher — the one in a floating overlay, where the class
396
- // exists to make it legible — styled nothing at all.
365
+ // `css_classes_changed_cb` forwards THREE classes onto the toggle group, not
366
+ // two: `flat`, `round` AND `osd` — the last is what makes an inline switcher in a
367
+ // floating overlay legible, so dropping it styles nothing.
397
368
  for (const name of ['flat', 'round', 'osd'] as const) {
398
369
  const present = this.hasAttribute(name);
399
370
  this.classList.toggle(name, present);
400
371
  this._groupEl?.classList.toggle(name, present);
401
372
  }
402
373
  // The group carries the display-mode class, mirroring libadwaita's
403
- // inline-view-switcher > toggle-group.{labels,icons,both} (:826-850).
374
+ // `inline-view-switcher > toggle-group.{labels,icons,both}`.
404
375
  for (const mode of INLINE_VIEW_SWITCHER_DISPLAY_MODES) {
405
376
  this._groupEl.classList.toggle(mode, mode === this._displayMode);
406
377
  }
@@ -1,43 +1,19 @@
1
- // <adw-menu-button> — An icon button that opens a popover menu, the web
2
- // counterpart of Adw.MenuButton / a Gtk.MenuButton. By default it is the flat
3
- // `open-menu-symbolic` primary/app-menu button used at the end of a header bar
4
- // (About / Preferences / Keyboard Shortcuts / Quit …). Clicking it toggles a
5
- // real Adwaita-styled popover positioned under the button; the popover is
6
- // dismissed on an outside click or Escape and supports arrow-key navigation.
1
+ // <adw-menu-button> — the web counterpart of Adw.MenuButton / Gtk.MenuButton: by
2
+ // default the flat `open-menu-symbolic` app-menu button used at the end of a header
3
+ // bar. Clicking it toggles an Adwaita-styled popover under the button, dismissed on
4
+ // an outside click or Escape, with arrow-key navigation.
7
5
  //
8
6
  // The popover is `<adw-popover>` and the dismissal/keyboard machine is
9
- // `@gjsify/adwaita-core`'s (ADR 0004). This element used to build both by hand,
10
- // as did `<adw-drop-down>` and `<adw-split-button>` — three surfaces that
11
- // disagreed with libadwaita and with each other on radius, shadow and padding,
12
- // and two copies of the same `(current ± 1 + n) % n` arithmetic. This element
13
- // keeps only what is a menu button: the icon, the item model, the title.
7
+ // `@gjsify/adwaita-core`'s (ADR 0004); this element keeps only what is a menu button:
8
+ // the icon, the item model, the title. Icons go through `<adw-icon>`, whose
9
+ // `normalizeIconName` guard is what keeps a multi-token `icon-name="a b"` from
10
+ // shipping a stray CSS class — for the button icon and for every menu entry's.
14
11
  //
15
- // THE ICON WENT THE SAME WAY, and it is where the drift actually bit: this
16
- // element interpolated `class="adw-icon adw-menu-button-icon adw-icon--<name>"`
17
- // while `<adw-split-button>` — alone among six copies — first checked the name
18
- // was one CSS token. So `icon-name="a b"` shipped a stray `b` class here, and so
19
- // did every JSON menu entry's `icon`. Both are `<adw-icon>` now, and the guard
20
- // is `normalizeIconName`'s.
12
+ // The `menu` attribute is a JSON array of `{ "id"?, "label", "icon"? }`. Choosing an
13
+ // item closes the popover and fires `menu-item-activated` (CustomEvent, bubbles,
14
+ // detail `{ id, label, index }`) with `id` falling back to `label`, matching the NS
15
+ // twin.
21
16
  //
22
- // Attributes:
23
- // icon-name — symbolic icon (no `-symbolic` suffix), default `open-menu`.
24
- // menu-title — optional heading shown atop the popover.
25
- // menu — JSON array of items, each `{ "id"?, "label", "icon"? }`.
26
- // disabled — boolean; a disabled button does not open the menu.
27
- // flat — boolean style class (default: flat, like the header app menu).
28
- // circular — boolean style class (round 34×34 button).
29
- // direction — none | up | down | left | right (default down); where the
30
- // popover opens. `none` behaves as `down`, which is
31
- // Gtk.MenuButton's own rule, not ours — see
32
- // {@link menuButtonPopupDirection}.
33
- // Properties (mirroring Adw.MenuButton / the NS twin):
34
- // menuItems — the menu entries ({ id?, label, icon? }[]) (get/set).
35
- // menuTitle — the popover heading (get/set).
36
- // active — whether the popover is open (get).
37
- // Events:
38
- // `menu-item-activated` (CustomEvent, bubbles, detail = { id, label, index })
39
- // when an item is chosen — `id` falls back to `label` when omitted (matches
40
- // the NS twin). The popover closes on activation.
41
17
  // Reference: refs/libadwaita/src/adw-menu-button.c (AdwMenuButton)
42
18
  // Reference: refs/libadwaita/src/stylesheet/widgets/_menus.scss (popover.menu / modelbutton)
43
19
  // Reference: packages/nativescript-bridge/adwaita/src/widgets/adw-menu-button.ts (NS twin)
@@ -47,13 +23,12 @@
47
23
  import { isSplitButtonDirection, menuButtonPopupDirection } from '@gjsify/adwaita-core';
48
24
  import type { SplitButtonDirection } from '@gjsify/adwaita-core';
49
25
 
50
- // SIDE-EFFECT import, deliberately separate from the type import below: it is
51
- // what guarantees `adw-popover` is defined before this module's own
52
- // `customElements.define` can upgrade a server-rendered `<adw-menu-button>` and
53
- // build one. A combined `import { AdwPopover }` would NOT do it — the binding is
54
- // only ever used in type position here, and this package compiles without
55
- // `verbatimModuleSyntax`, so TypeScript would elide the whole statement and take
56
- // the registration with it.
26
+ // SIDE-EFFECT import, deliberately separate from the type import below: it guarantees
27
+ // `adw-popover` is defined before this module's `customElements.define` can upgrade a
28
+ // server-rendered `<adw-menu-button>` and build one. A combined
29
+ // `import { AdwPopover }` would NOT do it — the binding is only used in type position,
30
+ // and this package compiles without `verbatimModuleSyntax`, so TypeScript would elide
31
+ // the statement and take the registration with it.
57
32
  import './adw-popover.js';
58
33
  import type { AdwPopover } from './adw-popover.js';
59
34
 
@@ -67,14 +42,12 @@ export interface AdwMenuItem {
67
42
  }
68
43
 
69
44
  /**
70
- * Where the popover sits, per `GtkArrowType`. `menuButtonPopupDirection` folds
71
- * `none` onto `down` (adw-split-button.c:415 — a pass-through to
72
- * `gtk_menu_button_set_direction`, so it is GtkMenuButton's rule); this table is
73
- * only the direction→CSS-axis mapping, which is a renderer fact.
45
+ * Where the popover sits, per `GtkArrowType`. `menuButtonPopupDirection` folds `none`
46
+ * onto `down` — GtkMenuButton's rule, not ours; this table is only the
47
+ * direction→CSS-axis mapping, which is a renderer fact.
74
48
  *
75
- * `left`/`right` become `start`/`end` because the surface is placed with
76
- * logical properties, so it follows the writing direction the way GTK's
77
- * `:dir(rtl)` popovers do.
49
+ * `left`/`right` become `start`/`end` because the surface is placed with logical
50
+ * properties, so it follows the writing direction as GTK's `:dir(rtl)` popovers do.
78
51
  */
79
52
  const POPOVER_POSITIONS = {
80
53
  down: 'bottom',
@@ -163,9 +136,8 @@ export class AdwMenuButton extends HTMLElement {
163
136
 
164
137
  // Seed items from the `menu` attribute if the property was not set.
165
138
  if (this._items.length === 0) this._items = this._parseMenuAttr();
166
- // Default to the flat header idiom unless another variant is requested.
167
- // Set only now the DOM exists, so the observed-attribute callback (which
168
- // renders) never runs before `_buttonEl` is created.
139
+ // The flat default is set only now the DOM exists, so the observed-attribute
140
+ // callback (which renders) never runs before `_buttonEl` is created.
169
141
  if (!this.hasAttribute('flat') && !this.hasAttribute('circular')) this.setAttribute('flat', '');
170
142
  this._render();
171
143
  }
@@ -197,7 +169,6 @@ export class AdwMenuButton extends HTMLElement {
197
169
  private _onPopoverToggled(open: boolean): void {
198
170
  this.classList.toggle('active', open);
199
171
  this._buttonEl.setAttribute('aria-expanded', String(open));
200
- // Move focus into the menu for keyboard navigation.
201
172
  if (open) this._itemButtons[0]?.focus();
202
173
  }
203
174
 
@@ -238,9 +209,9 @@ export class AdwMenuButton extends HTMLElement {
238
209
  item.setAttribute('role', 'menuitem');
239
210
  item.tabIndex = -1;
240
211
 
241
- // An entry with no USABLE icon gets no icon node at all, rather than
242
- // an empty 16px box — the element itself answers whether the name
243
- // resolved, so the `-symbolic`-only and bad-token cases agree.
212
+ // An entry with no USABLE icon gets no icon node at all rather than an empty
213
+ // 16px box; `<adw-icon>` itself answers whether the name resolved, so the
214
+ // `-symbolic`-only and bad-token cases agree.
244
215
  const entryIcon = createAdwIcon(entry.icon ?? null, 'adw-menu-button-item-icon');
245
216
  if (entryIcon.resolvedIconName !== '') item.appendChild(entryIcon);
246
217
  const labelEl = document.createElement('span');
@@ -262,7 +233,6 @@ export class AdwMenuButton extends HTMLElement {
262
233
  this._itemButtons.push(item);
263
234
  }
264
235
 
265
- // A menu-button with no entries can never open.
266
236
  if (this._items.length === 0) this._popoverEl.popdown();
267
237
  }
268
238
  }