@gjsify/adwaita-web 0.64.2-next.252 → 0.64.2-next.254

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 (293) hide show
  1. package/README.md +10 -80
  2. package/dist/adwaita-web.css +52 -3
  3. package/dist/adwaita-web.css.map +1 -1
  4. package/lib/types/accessible.d.ts +8 -0
  5. package/lib/types/compute-expand.d.ts +5 -0
  6. package/lib/types/compute-expand.spec.d.ts +1 -0
  7. package/lib/types/elements/adw-about-dialog.d.ts +28 -1
  8. package/lib/types/elements/adw-action-row.d.ts +2 -1
  9. package/lib/types/elements/adw-alert-dialog.d.ts +3 -2
  10. package/lib/types/elements/adw-avatar.d.ts +2 -1
  11. package/lib/types/elements/adw-banner.d.ts +2 -1
  12. package/lib/types/elements/adw-bin.d.ts +2 -1
  13. package/lib/types/elements/adw-bottom-sheet.d.ts +5 -4
  14. package/lib/types/elements/adw-breakpoint-bin.d.ts +2 -1
  15. package/lib/types/elements/adw-button-content.d.ts +2 -1
  16. package/lib/types/elements/adw-button-row.d.ts +2 -1
  17. package/lib/types/elements/adw-card.d.ts +2 -1
  18. package/lib/types/elements/adw-carousel.d.ts +20 -3
  19. package/lib/types/elements/adw-clamp-scrollable.d.ts +2 -1
  20. package/lib/types/elements/adw-clamp.d.ts +2 -1
  21. package/lib/types/elements/adw-combo-row.d.ts +6 -1
  22. package/lib/types/elements/adw-data-grid.d.ts +2 -1
  23. package/lib/types/elements/adw-dialog.d.ts +2 -1
  24. package/lib/types/elements/adw-entry-row.d.ts +2 -1
  25. package/lib/types/elements/adw-expander-row.d.ts +2 -1
  26. package/lib/types/elements/adw-header-bar.d.ts +2 -1
  27. package/lib/types/elements/adw-inline-view-switcher.d.ts +3 -2
  28. package/lib/types/elements/adw-layout-slot.d.ts +2 -1
  29. package/lib/types/elements/adw-multi-layout-view.d.ts +2 -1
  30. package/lib/types/elements/adw-navigation-split-view.d.ts +2 -1
  31. package/lib/types/elements/adw-navigation-view.d.ts +3 -2
  32. package/lib/types/elements/adw-overlay-split-view.d.ts +2 -1
  33. package/lib/types/elements/adw-preferences-dialog.d.ts +5 -4
  34. package/lib/types/elements/adw-preferences-group.d.ts +2 -1
  35. package/lib/types/elements/adw-preferences-row.d.ts +2 -1
  36. package/lib/types/elements/adw-shortcut-label.d.ts +2 -1
  37. package/lib/types/elements/adw-shortcuts-item.d.ts +2 -1
  38. package/lib/types/elements/adw-shortcuts-section.d.ts +2 -1
  39. package/lib/types/elements/adw-sidebar.d.ts +4 -3
  40. package/lib/types/elements/adw-spin-row.d.ts +2 -1
  41. package/lib/types/elements/adw-spinner.d.ts +2 -1
  42. package/lib/types/elements/adw-split-button.d.ts +2 -1
  43. package/lib/types/elements/adw-status-page.d.ts +2 -1
  44. package/lib/types/elements/adw-switch-row.d.ts +4 -1
  45. package/lib/types/elements/adw-tab-bar.d.ts +2 -1
  46. package/lib/types/elements/adw-tab-button.d.ts +2 -1
  47. package/lib/types/elements/adw-tab-overview.d.ts +2 -1
  48. package/lib/types/elements/adw-tab-view.d.ts +3 -2
  49. package/lib/types/elements/adw-toast-overlay.d.ts +2 -1
  50. package/lib/types/elements/adw-toggle-group.d.ts +3 -2
  51. package/lib/types/elements/adw-toolbar-view.d.ts +2 -1
  52. package/lib/types/elements/adw-view-stack.d.ts +3 -1
  53. package/lib/types/elements/adw-view-switcher-bar.d.ts +2 -1
  54. package/lib/types/elements/adw-view-switcher-sidebar.d.ts +2 -1
  55. package/lib/types/elements/adw-view-switcher.d.ts +3 -2
  56. package/lib/types/elements/adw-window-title.d.ts +2 -1
  57. package/lib/types/elements/adw-wrap-box.d.ts +2 -1
  58. package/lib/types/elements/checks.d.ts +16 -1
  59. package/lib/types/elements/gtk-action-bar.d.ts +2 -1
  60. package/lib/types/elements/gtk-aspect-frame.d.ts +2 -1
  61. package/lib/types/elements/gtk-box.d.ts +5 -1
  62. package/lib/types/elements/gtk-button.d.ts +5 -7
  63. package/lib/types/elements/gtk-calendar.d.ts +2 -1
  64. package/lib/types/elements/gtk-center-box.d.ts +2 -1
  65. package/lib/types/elements/gtk-color-dialog-button.d.ts +2 -1
  66. package/lib/types/elements/gtk-drag-icon.d.ts +2 -1
  67. package/lib/types/elements/gtk-drawing-area.d.ts +2 -1
  68. package/lib/types/elements/gtk-drop-down.d.ts +2 -1
  69. package/lib/types/elements/gtk-editable-label.d.ts +2 -1
  70. package/lib/types/elements/gtk-entry.d.ts +2 -1
  71. package/lib/types/elements/gtk-expander.d.ts +2 -1
  72. package/lib/types/elements/gtk-fixed.d.ts +2 -1
  73. package/lib/types/elements/gtk-flow-box-child.d.ts +2 -1
  74. package/lib/types/elements/gtk-flow-box.d.ts +2 -1
  75. package/lib/types/elements/gtk-font-dialog-button.d.ts +2 -1
  76. package/lib/types/elements/gtk-frame.d.ts +2 -1
  77. package/lib/types/elements/gtk-gl-area.d.ts +2 -1
  78. package/lib/types/elements/gtk-graphics-offload.d.ts +2 -1
  79. package/lib/types/elements/gtk-grid.d.ts +2 -1
  80. package/lib/types/elements/gtk-header-bar.d.ts +2 -1
  81. package/lib/types/elements/gtk-image.d.ts +5 -6
  82. package/lib/types/elements/gtk-inscription.d.ts +2 -1
  83. package/lib/types/elements/gtk-label.d.ts +10 -3
  84. package/lib/types/elements/gtk-level-bar.d.ts +2 -1
  85. package/lib/types/elements/gtk-list-box-row.d.ts +2 -1
  86. package/lib/types/elements/gtk-list-box.d.ts +2 -1
  87. package/lib/types/elements/gtk-list-view.d.ts +2 -1
  88. package/lib/types/elements/gtk-media-controls.d.ts +2 -1
  89. package/lib/types/elements/gtk-menu-button.d.ts +2 -1
  90. package/lib/types/elements/gtk-notebook.d.ts +2 -1
  91. package/lib/types/elements/gtk-overlay.d.ts +2 -1
  92. package/lib/types/elements/gtk-paned.d.ts +2 -1
  93. package/lib/types/elements/gtk-picture.d.ts +2 -1
  94. package/lib/types/elements/gtk-popover-bin.d.ts +2 -1
  95. package/lib/types/elements/gtk-popover-menu-bar.d.ts +2 -1
  96. package/lib/types/elements/gtk-popover.d.ts +2 -1
  97. package/lib/types/elements/gtk-progress-bar.d.ts +2 -1
  98. package/lib/types/elements/gtk-revealer.d.ts +2 -1
  99. package/lib/types/elements/gtk-scale-button.d.ts +2 -1
  100. package/lib/types/elements/gtk-scale.d.ts +2 -1
  101. package/lib/types/elements/gtk-scrollbar.d.ts +2 -1
  102. package/lib/types/elements/gtk-scrolled-window.d.ts +2 -1
  103. package/lib/types/elements/gtk-search-bar.d.ts +2 -1
  104. package/lib/types/elements/gtk-separator.d.ts +2 -1
  105. package/lib/types/elements/gtk-spin-button.d.ts +2 -1
  106. package/lib/types/elements/gtk-spinner.d.ts +2 -1
  107. package/lib/types/elements/gtk-stack-page.d.ts +2 -1
  108. package/lib/types/elements/gtk-stack-sidebar.d.ts +2 -1
  109. package/lib/types/elements/gtk-stack-switcher.d.ts +2 -1
  110. package/lib/types/elements/gtk-stack.d.ts +2 -1
  111. package/lib/types/elements/gtk-switch.d.ts +2 -1
  112. package/lib/types/elements/gtk-text-view.d.ts +2 -1
  113. package/lib/types/elements/gtk-text.d.ts +2 -1
  114. package/lib/types/elements/gtk-tree-expander.d.ts +2 -1
  115. package/lib/types/elements/gtk-video.d.ts +2 -1
  116. package/lib/types/elements/gtk-viewport.d.ts +2 -1
  117. package/lib/types/elements/gtk-window-controls.d.ts +2 -1
  118. package/lib/types/elements/gtk-window-handle.d.ts +2 -1
  119. package/lib/types/elements/gtk-window.d.ts +2 -1
  120. package/lib/types/gobject-elements.d.ts +7 -1
  121. package/lib/types/gtksource/gtk-source-view.d.ts +2 -1
  122. package/lib/types/{icon-registry.spec.d.ts → icon-masks.spec.d.ts} +1 -1
  123. package/lib/types/icon-theme.d.ts +15 -35
  124. package/lib/types/index.d.ts +1 -2
  125. package/lib/types/namespace/gdk.d.ts +1 -0
  126. package/lib/types/namespace/gio.d.ts +1 -0
  127. package/lib/types/namespace/glib.d.ts +2 -1
  128. package/lib/types/signals.d.ts +5 -1
  129. package/lib/types/source-view/adw-source-view.d.ts +2 -1
  130. package/lib/types/styles.generated.d.ts +1 -1
  131. package/lib/types/widget-element.d.ts +25 -0
  132. package/lib/types/widget-map.spec.d.ts +1 -0
  133. package/lib/types/widget-mapping.d.ts +7 -0
  134. package/package.json +12 -12
  135. package/scss/_about_dialog.scss +46 -1
  136. package/scss/_checks.scss +5 -2
  137. package/scss/_icon.scss +3 -4
  138. package/scss/_labels.scss +2 -2
  139. package/scss/_overlay_split_view.scss +10 -0
  140. package/scss/_scrolled_window.scss +15 -0
  141. package/scss/_widget.scss +5 -1
  142. package/src/accessible.ts +29 -0
  143. package/src/adw-about-dialog.spec.ts +81 -12
  144. package/src/adw-action-rows.spec.ts +2 -2
  145. package/src/adw-button-content.spec.ts +1 -1
  146. package/src/adw-carousel.spec.ts +49 -1
  147. package/src/capabilities.mjs +1 -0
  148. package/src/checks.spec.ts +51 -1
  149. package/src/compute-expand.spec.ts +186 -0
  150. package/src/compute-expand.ts +134 -0
  151. package/src/construct-vectors.spec.ts +46 -1
  152. package/src/elements/adw-about-dialog.ts +152 -9
  153. package/src/elements/adw-action-row.ts +2 -1
  154. package/src/elements/adw-alert-dialog.ts +3 -2
  155. package/src/elements/adw-avatar.ts +2 -1
  156. package/src/elements/adw-banner.ts +2 -1
  157. package/src/elements/adw-bin.ts +2 -1
  158. package/src/elements/adw-bottom-sheet.ts +5 -4
  159. package/src/elements/adw-breakpoint-bin.ts +2 -1
  160. package/src/elements/adw-button-content.ts +2 -1
  161. package/src/elements/adw-button-row.ts +2 -1
  162. package/src/elements/adw-card.ts +2 -1
  163. package/src/elements/adw-carousel.ts +62 -3
  164. package/src/elements/adw-clamp-scrollable.ts +2 -1
  165. package/src/elements/adw-clamp.ts +2 -1
  166. package/src/elements/adw-combo-row.ts +15 -1
  167. package/src/elements/adw-data-grid.ts +2 -1
  168. package/src/elements/adw-dialog.ts +2 -1
  169. package/src/elements/adw-entry-row.ts +2 -1
  170. package/src/elements/adw-expander-row.ts +2 -1
  171. package/src/elements/adw-header-bar.ts +2 -1
  172. package/src/elements/adw-inline-view-switcher.ts +3 -2
  173. package/src/elements/adw-layout-slot.ts +2 -1
  174. package/src/elements/adw-multi-layout-view.ts +2 -1
  175. package/src/elements/adw-navigation-split-view.ts +2 -1
  176. package/src/elements/adw-navigation-view.ts +3 -2
  177. package/src/elements/adw-overlay-split-view.ts +2 -1
  178. package/src/elements/adw-preferences-dialog.ts +6 -4
  179. package/src/elements/adw-preferences-group.ts +2 -1
  180. package/src/elements/adw-preferences-row.ts +2 -1
  181. package/src/elements/adw-shortcut-label.ts +2 -1
  182. package/src/elements/adw-shortcuts-item.ts +2 -1
  183. package/src/elements/adw-shortcuts-section.ts +2 -1
  184. package/src/elements/adw-sidebar.ts +4 -3
  185. package/src/elements/adw-spin-row.ts +2 -1
  186. package/src/elements/adw-spinner.ts +2 -1
  187. package/src/elements/adw-split-button.ts +2 -1
  188. package/src/elements/adw-status-page.ts +2 -1
  189. package/src/elements/adw-switch-row.ts +10 -1
  190. package/src/elements/adw-tab-bar.ts +2 -1
  191. package/src/elements/adw-tab-button.ts +2 -1
  192. package/src/elements/adw-tab-overview.ts +2 -1
  193. package/src/elements/adw-tab-view.ts +3 -2
  194. package/src/elements/adw-toast-overlay.ts +2 -1
  195. package/src/elements/adw-toggle-group.ts +3 -2
  196. package/src/elements/adw-toolbar-view.ts +2 -1
  197. package/src/elements/adw-view-stack.ts +39 -1
  198. package/src/elements/adw-view-switcher-bar.ts +2 -1
  199. package/src/elements/adw-view-switcher-sidebar.ts +2 -1
  200. package/src/elements/adw-view-switcher.ts +3 -2
  201. package/src/elements/adw-window-title.ts +2 -1
  202. package/src/elements/adw-wrap-box.ts +2 -1
  203. package/src/elements/checks.ts +85 -6
  204. package/src/elements/gtk-action-bar.ts +2 -1
  205. package/src/elements/gtk-aspect-frame.ts +2 -1
  206. package/src/elements/gtk-box.ts +56 -1
  207. package/src/elements/gtk-button.ts +10 -22
  208. package/src/elements/gtk-calendar.ts +2 -1
  209. package/src/elements/gtk-center-box.ts +2 -1
  210. package/src/elements/gtk-color-dialog-button.ts +2 -1
  211. package/src/elements/gtk-drag-icon.ts +2 -1
  212. package/src/elements/gtk-drawing-area.ts +2 -1
  213. package/src/elements/gtk-drop-down.ts +2 -1
  214. package/src/elements/gtk-editable-label.ts +2 -1
  215. package/src/elements/gtk-entry.ts +2 -1
  216. package/src/elements/gtk-expander.ts +2 -1
  217. package/src/elements/gtk-fixed.ts +2 -1
  218. package/src/elements/gtk-flow-box-child.ts +2 -1
  219. package/src/elements/gtk-flow-box.ts +2 -1
  220. package/src/elements/gtk-font-dialog-button.ts +2 -1
  221. package/src/elements/gtk-frame.ts +2 -1
  222. package/src/elements/gtk-gl-area.ts +2 -1
  223. package/src/elements/gtk-graphics-offload.ts +2 -1
  224. package/src/elements/gtk-grid.ts +2 -1
  225. package/src/elements/gtk-header-bar.ts +2 -1
  226. package/src/elements/gtk-image.ts +14 -15
  227. package/src/elements/gtk-inscription.ts +2 -1
  228. package/src/elements/gtk-label.ts +12 -5
  229. package/src/elements/gtk-level-bar.ts +2 -1
  230. package/src/elements/gtk-list-box-row.ts +2 -1
  231. package/src/elements/gtk-list-box.ts +2 -1
  232. package/src/elements/gtk-list-view.ts +2 -1
  233. package/src/elements/gtk-media-controls.ts +2 -1
  234. package/src/elements/gtk-menu-button.ts +2 -1
  235. package/src/elements/gtk-notebook.ts +2 -1
  236. package/src/elements/gtk-overlay.ts +2 -1
  237. package/src/elements/gtk-paned.ts +2 -1
  238. package/src/elements/gtk-picture.ts +2 -1
  239. package/src/elements/gtk-popover-bin.ts +2 -1
  240. package/src/elements/gtk-popover-menu-bar.ts +2 -1
  241. package/src/elements/gtk-popover.ts +2 -1
  242. package/src/elements/gtk-progress-bar.ts +2 -1
  243. package/src/elements/gtk-revealer.ts +2 -1
  244. package/src/elements/gtk-scale-button.ts +2 -1
  245. package/src/elements/gtk-scale.ts +2 -1
  246. package/src/elements/gtk-scrollbar.ts +2 -1
  247. package/src/elements/gtk-scrolled-window.ts +8 -5
  248. package/src/elements/gtk-search-bar.ts +2 -1
  249. package/src/elements/gtk-separator.ts +2 -1
  250. package/src/elements/gtk-spin-button.ts +2 -1
  251. package/src/elements/gtk-spinner.ts +2 -1
  252. package/src/elements/gtk-stack-page.ts +2 -1
  253. package/src/elements/gtk-stack-sidebar.ts +2 -1
  254. package/src/elements/gtk-stack-switcher.ts +2 -1
  255. package/src/elements/gtk-stack.ts +2 -1
  256. package/src/elements/gtk-switch.ts +2 -1
  257. package/src/elements/gtk-text-view.ts +2 -1
  258. package/src/elements/gtk-text.ts +2 -1
  259. package/src/elements/gtk-tree-expander.ts +2 -1
  260. package/src/elements/gtk-video.ts +2 -1
  261. package/src/elements/gtk-viewport.ts +2 -1
  262. package/src/elements/gtk-window-controls.ts +2 -1
  263. package/src/elements/gtk-window-handle.ts +2 -1
  264. package/src/elements/gtk-window.ts +2 -1
  265. package/src/gio.spec.ts +13 -3
  266. package/src/glib.spec.ts +9 -0
  267. package/src/gobject-elements.ts +18 -4
  268. package/src/gtk-label.spec.ts +39 -0
  269. package/src/gtk-scrolled-window.spec.ts +6 -6
  270. package/src/gtk-value-doors.spec.ts +19 -0
  271. package/src/gtk-window.spec.ts +2 -2
  272. package/src/gtksource/gtk-source-view.ts +2 -1
  273. package/src/{icon-registry.spec.ts → icon-masks.spec.ts} +4 -100
  274. package/src/icon-theme.spec.ts +69 -124
  275. package/src/icon-theme.ts +29 -119
  276. package/src/index.ts +3 -7
  277. package/src/namespace/gdk.ts +1 -0
  278. package/src/namespace/gio.ts +8 -0
  279. package/src/namespace/glib.ts +4 -0
  280. package/src/shared-tree-builder.ts +26 -3
  281. package/src/signals.ts +11 -2
  282. package/src/source-view/adw-source-view.ts +2 -1
  283. package/src/split-button.spec.ts +1 -1
  284. package/src/style-manager.spec.ts +13 -0
  285. package/src/style-manager.ts +6 -0
  286. package/src/styles.generated.ts +1 -1
  287. package/src/test.browser.mts +6 -2
  288. package/src/widget-element.ts +75 -0
  289. package/src/widget-map.spec.ts +116 -0
  290. package/src/widget-mapping.ts +100 -0
  291. package/src/widget-visible.spec.ts +44 -0
  292. package/lib/types/icon-registry.d.ts +0 -37
  293. package/src/icon-registry.ts +0 -163
@@ -0,0 +1,186 @@
1
+ // `gtk_widget_compute_expand` in a box (gtkwidget.c:8387-8423, gtkboxlayout.c:793): a widget
2
+ // that authored no `hexpand` grows when a descendant does, an explicit value stops the walk, and
3
+ // the answer follows the tree as it changes. Plus the two size facts the same app layout broke
4
+ // on: a scrolled window requests 0 on a scrolling axis (gtkscrolledwindow.c:1821-1960), and a
5
+ // page leaving a view stack takes none of the stack's marks with it (adw-view-stack.c:1184-1205).
6
+ import { describe, expect, it } from '@gjsify/unit';
7
+
8
+ import type { AdwViewStack } from './elements/adw-view-stack.js';
9
+
10
+ function mount(html: string, width = 600, height = 600): { host: HTMLElement; box: (sel: string) => DOMRect } {
11
+ const host = document.createElement('div');
12
+ host.style.cssText = `width:${width}px;height:${height}px;display:flex;flex-direction:column`;
13
+ host.innerHTML = html;
14
+ document.body.appendChild(host);
15
+ const box = (sel: string) => (host.querySelector(sel) as HTMLElement).getBoundingClientRect();
16
+ return { host, box };
17
+ }
18
+
19
+ /** The box writes its computed values from a MutationObserver: one microtask, then a frame. */
20
+ const settle = () => new Promise((resolve) => requestAnimationFrame(resolve));
21
+
22
+ export const ComputeExpandTest = async () => {
23
+ await describe('computed expand in a <gtk-box>', async () => {
24
+ await it('a bin with an hexpand descendant fills a horizontal box', async () => {
25
+ const { host, box } = mount(
26
+ `<gtk-box id="row" style="width:600px">
27
+ <div id="fixed" style="width:100px"></div>
28
+ <adw-bin id="bin"><gtk-box><div id="leaf" hexpand></div></gtk-box></adw-bin>
29
+ </gtk-box>`,
30
+ );
31
+ await settle();
32
+ expect(host.querySelector('#bin')!.hasAttribute('data-computed-hexpand')).toBe(true);
33
+ expect(box('#bin').width).toBe(500);
34
+ host.remove();
35
+ });
36
+
37
+ await it('an explicit hexpand=false on an intermediate stops propagation', async () => {
38
+ const { host, box } = mount(
39
+ `<gtk-box style="width:600px">
40
+ <adw-bin id="bin"><gtk-box hexpand="false"><div style="width:10px" hexpand></div></gtk-box></adw-bin>
41
+ </gtk-box>`,
42
+ );
43
+ await settle();
44
+ expect(host.querySelector('#bin')!.hasAttribute('data-computed-hexpand')).toBe(false);
45
+ expect(box('#bin').width).toBe(10);
46
+ host.remove();
47
+ });
48
+
49
+ await it('a leaf widget does not propagate, and a hidden one never expands', async () => {
50
+ const { host } = mount(
51
+ `<gtk-box>
52
+ <gtk-label id="label"><div hexpand></div></gtk-label>
53
+ <adw-bin id="bin"><gtk-box hidden><div hexpand></div></gtk-box></adw-bin>
54
+ </gtk-box>`,
55
+ );
56
+ await settle();
57
+ expect(host.querySelector('#label')!.hasAttribute('data-computed-hexpand')).toBe(false);
58
+ expect(host.querySelector('#bin')!.hasAttribute('data-computed-hexpand')).toBe(false);
59
+ host.remove();
60
+ });
61
+
62
+ await it('propagation updates after a runtime reparent', async () => {
63
+ const { host, box } = mount(
64
+ `<gtk-box style="width:600px">
65
+ <div style="width:100px"></div>
66
+ <adw-bin id="bin"><div style="width:20px"></div></adw-bin>
67
+ </gtk-box>
68
+ <div id="parking"><div id="editor" hexpand></div></div>`,
69
+ );
70
+ await settle();
71
+ expect(box('#bin').width).toBe(20);
72
+ const bin = host.querySelector('#bin')!;
73
+ bin.replaceChildren(host.querySelector('#editor')!);
74
+ await settle();
75
+ expect(bin.hasAttribute('data-computed-hexpand')).toBe(true);
76
+ expect(box('#bin').width).toBe(500);
77
+ host.querySelector('#parking')!.append(host.querySelector('#editor')!);
78
+ await settle();
79
+ expect(bin.hasAttribute('data-computed-hexpand')).toBe(false);
80
+ host.remove();
81
+ });
82
+
83
+ await it('rewriting a label text does not recompute, adding a widget does', async () => {
84
+ const { host } = mount(
85
+ `<gtk-box>
86
+ <adw-bin id="bin"><gtk-box id="inner"><gtk-label id="label">0000</gtk-label><div hexpand></div></gtk-box></adw-bin>
87
+ </gtk-box>`,
88
+ );
89
+ await settle();
90
+ const bin = host.querySelector('#bin')!;
91
+ expect(bin.hasAttribute('data-computed-hexpand')).toBe(true);
92
+ // Drop the mark by hand: only a recompute would write it back.
93
+ bin.removeAttribute('data-computed-hexpand');
94
+ host.querySelector('#label')!.textContent = 'FFFF';
95
+ await settle();
96
+ expect(bin.hasAttribute('data-computed-hexpand')).toBe(false);
97
+ host.querySelector('#inner')!.append(document.createElement('gtk-label'));
98
+ await settle();
99
+ expect(bin.hasAttribute('data-computed-hexpand')).toBe(true);
100
+ host.remove();
101
+ });
102
+
103
+ await it('a registered subclass of a bin propagates as its base does', async () => {
104
+ const Bin = customElements.get('adw-bin')!;
105
+ const tag = 'compute-expand-test-bin';
106
+ if (!customElements.get(tag)) customElements.define(tag, class extends Bin {});
107
+ const { host, box } = mount(
108
+ `<gtk-box style="width:600px">
109
+ <div style="width:100px"></div>
110
+ <${tag} id="sub"><div hexpand></div></${tag}>
111
+ </gtk-box>`,
112
+ );
113
+ await settle();
114
+ expect(box('#sub').width).toBe(500);
115
+ host.remove();
116
+ });
117
+ });
118
+
119
+ await describe('<gtk-scrolled-window> requests no content size on a scrolling axis', async () => {
120
+ await it('in a horizontal box inside a 600px-high box it stays 600 and scrolls', async () => {
121
+ const { host, box } = mount(
122
+ `<gtk-box orientation="vertical" style="height:600px">
123
+ <gtk-box id="row" vexpand>
124
+ <gtk-scrolled-window id="sw" hexpand>
125
+ <div style="height:2000px;width:50px"></div>
126
+ </gtk-scrolled-window>
127
+ </gtk-box>
128
+ </gtk-box>`,
129
+ );
130
+ await settle();
131
+ expect(box('#row').height).toBe(600);
132
+ expect(box('#sw').height).toBe(600);
133
+ const viewport = host.querySelector('#sw gtk-viewport') as HTMLElement;
134
+ expect(viewport.getBoundingClientRect().height).toBe(600);
135
+ expect(viewport.scrollHeight > 600).toBe(true);
136
+ host.remove();
137
+ });
138
+
139
+ await it('sizes the measured chain: split-view content fills, the editor bin gets width', async () => {
140
+ const { host, box } = mount(
141
+ `<adw-overlay-split-view id="osv" style="height:600px">
142
+ <div slot="sidebar" style="width:100px"></div>
143
+ <gtk-box id="centerRight">
144
+ <adw-bin id="editor"><div hexpand style="height:10px"></div></adw-bin>
145
+ <gtk-scrolled-window id="right" hscrollbar-policy="never" style="flex:0 1 auto">
146
+ <div style="width:150px;height:3000px"></div>
147
+ </gtk-scrolled-window>
148
+ </gtk-box>
149
+ </adw-overlay-split-view>`,
150
+ 800,
151
+ 600,
152
+ );
153
+ await settle();
154
+ expect(box('#centerRight').height).toBe(box('#osv').height);
155
+ expect(box('#editor').width > 0).toBe(true);
156
+ expect(box('#right').height).toBe(box('#centerRight').height);
157
+ host.remove();
158
+ });
159
+ });
160
+
161
+ await describe('<adw-view-stack> page removal', async () => {
162
+ await it('a child removed from a view stack loses the stack classes', async () => {
163
+ const { host } = mount('<adw-view-stack id="s"></adw-view-stack><div id="elsewhere"></div>');
164
+ const stack = host.querySelector('#s') as AdwViewStack;
165
+ const a = document.createElement('div');
166
+ const b = document.createElement('div');
167
+ stack.add(a, 'a');
168
+ stack.add(b, 'b');
169
+ expect(a.classList.contains('active-view')).toBe(true);
170
+ expect(b.hidden).toBe(true);
171
+
172
+ stack.removePage('a');
173
+ expect(a.classList.contains('adw-view-stack-child')).toBe(false);
174
+ expect(a.classList.contains('active-view')).toBe(false);
175
+ expect(a.dataset.name).toBe(undefined);
176
+
177
+ // A plain DOM reparent is `gtk_widget_unparent` reaching the stack too.
178
+ host.querySelector('#elsewhere')!.append(b);
179
+ await settle();
180
+ expect(b.classList.contains('adw-view-stack-child')).toBe(false);
181
+ expect(b.hidden).toBe(false);
182
+ expect(stack.pages.length).toBe(0);
183
+ host.remove();
184
+ });
185
+ });
186
+ };
@@ -0,0 +1,134 @@
1
+ // compute-expand.ts — `gtk_widget_compute_expand`: does a widget tree rooted here want spare space?
2
+ //
3
+ // GTK does not only read a widget's OWN `hexpand`: a widget that set none expands when one of
4
+ // its children does (gtkwidget.c:8387-8423, `gtk_widget_update_computed_expand`). An AdwBin
5
+ // whose child is an hexpand editor therefore grows in a horizontal box, where the `[hexpand]`
6
+ // rules in `_widget.scss` gave it nothing and the editor came out 0px wide.
7
+ //
8
+ // THREE RULES, all in the C:
9
+ //
10
+ // · a widget that is not visible never expands (gtkwidget.c:8508-8509);
11
+ // · an EXPLICIT value wins and stops the walk — `hexpand_set` is checked before the vfunc
12
+ // runs, so an intermediate `hexpand: false` hides every expanding descendant;
13
+ // · otherwise only a class with a `compute_expand` vfunc looks at its children at all. The
14
+ // base GtkWidget has none, so a leaf (a label, a button's icon) never propagates.
15
+ //
16
+ // WHY JS AND NOT `:has()`: a selector cannot say "and no widget between here and there set
17
+ // hexpand" at arbitrary depth, it cannot tell a container with the vfunc from a leaf without
18
+ // one, and a registered subclass (`GObject.registerClass` over AdwBin) is a NEW tag that only
19
+ // the prototype chain links back to its base. So the value is computed here, and the box that
20
+ // needs it writes it as `data-computed-hexpand` / `data-computed-vexpand` on its children
21
+ // (gtk-box.ts) — internal state, never an authored attribute.
22
+ //
23
+ // The DOM has wrapper elements GTK has no widget for (`.adw-overlay-child`, a view stack's
24
+ // page `div`): those are transparent, the walk goes through them to the widgets inside.
25
+ //
26
+ // Reference: refs/gtk/gtk/gtkwidget.c (gtk_widget_compute_expand, update_computed_expand)
27
+ // Reference: refs/gtk/gtk/gtkbox.c:181-194 (every child, OR-ed)
28
+ // Reference: refs/gtk/gtk/gtkstack.c:2342-2373 (every page, visible or not)
29
+ // Reference: refs/gtk/gtk/gtkoverlay.c:292-301 (the main child only)
30
+ // Reference: refs/gtk/gtk/gtkscrolledwindow.c:544-554, gtkviewport.c:211-220 (the child only)
31
+ // Reference: refs/libadwaita/src/adw-widget-utils.c:501-536 (adw_widget_compute_expand)
32
+ // Copyright (c) The GTK Team. LGPLv2.1+. Copyright (c) The libadwaita authors. LGPLv2.1+.
33
+
34
+ export type ExpandOrientation = 'horizontal' | 'vertical';
35
+
36
+ /**
37
+ * Per tag that has a `compute_expand` vfunc: the subtrees of its own that are NOT the children
38
+ * the vfunc reads, and whether it answers on one axis only. A tag missing here behaves as the
39
+ * base GtkWidget — it does not propagate.
40
+ */
41
+ interface ExpandVfunc {
42
+ readonly skip?: string;
43
+ readonly horizontalOnly?: boolean;
44
+ }
45
+
46
+ /** `adw_widget_compute_expand` (adw-widget-utils.c:501), by the class_init that installs it. */
47
+ const ADW_WIDGET: ExpandVfunc = {};
48
+
49
+ const VFUNCS: ReadonlyMap<string, ExpandVfunc> = new Map<string, ExpandVfunc>([
50
+ ['gtk-box', {}],
51
+ ['gtk-stack', {}],
52
+ // The overlay children are its children too, but `gtk_overlay_compute_expand` reads only `child`.
53
+ ['gtk-overlay', { skip: '.adw-overlay-layer' }],
54
+ // The scrollbars are children the vfunc skips; they are leaves, so skipping them is exact.
55
+ ['gtk-scrolled-window', { skip: 'gtk-scrollbar' }],
56
+ ['gtk-viewport', {}],
57
+ ['adw-bin', ADW_WIDGET], // adw-bin.c:108
58
+ ['adw-bottom-sheet', ADW_WIDGET], // adw-bottom-sheet.c:795
59
+ ['adw-breakpoint-bin', ADW_WIDGET], // adw-breakpoint-bin.c:623
60
+ ['adw-clamp', ADW_WIDGET], // adw-clamp.c:174
61
+ ['adw-clamp-scrollable', ADW_WIDGET], // adw-clamp-scrollable.c:244
62
+ ['adw-layout-slot', ADW_WIDGET], // adw-layout-slot.c:128
63
+ ['adw-multi-layout-view', ADW_WIDGET], // adw-multi-layout-view.c:354
64
+ ['adw-navigation-view', ADW_WIDGET], // adw-navigation-view.c:1900
65
+ ['adw-overlay-split-view', ADW_WIDGET], // adw-overlay-split-view.c:909
66
+ ['adw-status-page', ADW_WIDGET], // adw-status-page.c:213
67
+ ['adw-toast-overlay', ADW_WIDGET], // adw-toast-overlay.c:543
68
+ ['adw-toolbar-view', ADW_WIDGET], // adw-toolbar-view.c:525
69
+ ['adw-view-stack', ADW_WIDGET], // adw-view-stack.c:1520
70
+ ['adw-header-bar', { horizontalOnly: true }], // adw-header-bar.c:853
71
+ ]);
72
+
73
+ const ATTRIBUTE: Record<ExpandOrientation, string> = { horizontal: 'hexpand', vertical: 'vexpand' };
74
+
75
+ /** The base tag whose vfunc `el` inherits — its own tag, or the one a registered subclass extends. */
76
+ function vfuncOf(el: Element): ExpandVfunc | undefined {
77
+ const own = VFUNCS.get(el.localName);
78
+ if (own !== undefined) return own;
79
+ if (typeof customElements.getName !== 'function') return undefined;
80
+ for (let proto = Object.getPrototypeOf(el); proto && proto !== HTMLElement.prototype;) {
81
+ const tag = customElements.getName(proto.constructor as CustomElementConstructor);
82
+ const found = tag === null ? undefined : VFUNCS.get(tag);
83
+ if (found !== undefined) return found;
84
+ proto = Object.getPrototypeOf(proto);
85
+ }
86
+ return undefined;
87
+ }
88
+
89
+ /**
90
+ * A GTK widget, as opposed to a wrapper this renderer added: every element is a custom element,
91
+ * and a plain element an author gave an expand value stands in for one.
92
+ */
93
+ function isWidget(el: Element): boolean {
94
+ return el.localName.includes('-') || el.hasAttribute('hexpand') || el.hasAttribute('vexpand');
95
+ }
96
+
97
+ /**
98
+ * A view stack writes `hidden` on every page but the selected one. That is
99
+ * `gtk_widget_set_child_visible` (adw-view-stack.c:1016-1023), not `GtkWidget:visible`, and
100
+ * `gtk_widget_compute_expand` reads only the latter — so a background page still counts.
101
+ */
102
+ function isStackPage(el: Element): boolean {
103
+ return el.classList.contains('adw-view-stack-child') && el.parentElement?.localName === 'adw-view-stack';
104
+ }
105
+
106
+ /** The value of an authored `hexpand` / `vexpand`: `""` is true, `"false"` false, absent unset. */
107
+ export function explicitExpand(el: Element, orientation: ExpandOrientation): boolean | undefined {
108
+ const raw = el.getAttribute(ATTRIBUTE[orientation]);
109
+ return raw === null ? undefined : raw !== 'false';
110
+ }
111
+
112
+ /** `gtk_widget_compute_expand (el, orientation)`. */
113
+ export function computeExpand(el: Element, orientation: ExpandOrientation): boolean {
114
+ if (el.hasAttribute('hidden') && !isStackPage(el)) return false;
115
+ const explicit = explicitExpand(el, orientation);
116
+ if (explicit !== undefined) return explicit;
117
+ const vfunc = vfuncOf(el);
118
+ if (vfunc === undefined) return false;
119
+ if (vfunc.horizontalOnly && orientation === 'vertical') return false;
120
+ return anyChildExpands(el, orientation, vfunc.skip);
121
+ }
122
+
123
+ /** The OR over the widget children of `parent`, reached through any wrapper that is not one. */
124
+ function anyChildExpands(parent: Element, orientation: ExpandOrientation, skip: string | undefined): boolean {
125
+ for (const child of parent.children) {
126
+ if (skip !== undefined && child.matches(skip)) continue;
127
+ if (isWidget(child)) {
128
+ if (computeExpand(child, orientation)) return true;
129
+ } else if (anyChildExpands(child, orientation, undefined)) {
130
+ return true;
131
+ }
132
+ }
133
+ return false;
134
+ }
@@ -106,6 +106,13 @@ function observe(vector: ConstructVector): unknown {
106
106
  }
107
107
  const { root, unmount } = mountSharedTree(vector.tree);
108
108
  try {
109
+ if (vector.kind === 'accessibility') {
110
+ // Read off the inner `<button>`, the one that carries the role and is announced.
111
+ const control = (root.querySelector('#described') as unknown as { button: HTMLButtonElement }).button;
112
+ return [
113
+ { label: control.getAttribute('aria-label'), description: control.getAttribute('aria-description') },
114
+ ];
115
+ }
109
116
  if (vector.kind === 'strings') {
110
117
  return [...root.querySelectorAll('option, .adw-drop-down-item-label')].map((node) => node.textContent);
111
118
  }
@@ -152,7 +159,9 @@ export const AdwConstructVectorsTest = async () => {
152
159
  children: [{ tag: 'GtkButton', signals: [signal] }],
153
160
  });
154
161
  const scope = { onX: () => {} };
155
- expect(() => mountSharedTree(on({ name: 'map', handler: 'onX' }), { scope })).toThrow("no signal 'map'");
162
+ expect(() => mountSharedTree(on({ name: 'toggled', handler: 'onX' }), { scope })).toThrow(
163
+ "no signal 'toggled'",
164
+ );
156
165
  expect(() => mountSharedTree(on({ name: 'clicked', handler: 'onX', flags: ['after'] }), { scope })).toThrow(
157
166
  'plain handlers only',
158
167
  );
@@ -376,6 +385,42 @@ export const AdwConstructVectorsTest = async () => {
376
385
  await it('refuses a breakpoint setter that names a sibling the tree does not carry', () => {
377
386
  expect(() => buildWithSiblings({ ...vector('sibling-object'), siblings: [] })).toThrow("id 'beside'");
378
387
  });
388
+ await it('names a check button on its input, and lets an authored name beat the icon fallback', () => {
389
+ const { root, unmount } = mountSharedTree({
390
+ tag: 'GtkBox',
391
+ children: [
392
+ { tag: 'GtkCheckButton', id: 'check', accessibility: { label: { value: 'Dark' } } },
393
+ {
394
+ tag: 'GtkButton',
395
+ id: 'icon',
396
+ props: { 'icon-name': 'edit-copy-symbolic', 'tooltip-text': 'Copy' },
397
+ accessibility: { label: { value: 'Copy the link' } },
398
+ },
399
+ ],
400
+ });
401
+ try {
402
+ const check = root.querySelector('#check')!;
403
+ const input = check.querySelector('.adw-check-input')!;
404
+ expect(input.getAttribute('aria-label')).toBe('Dark');
405
+ expect(input.hasAttribute('aria-description')).toBe(false);
406
+ check.removeAttribute('aria-label');
407
+ expect(input.hasAttribute('aria-label')).toBe(false);
408
+ const icon = root.querySelector('#icon') as unknown as HTMLElement & { button: HTMLButtonElement };
409
+ expect(icon.button.getAttribute('aria-label')).toBe('Copy the link');
410
+ icon.removeAttribute('aria-label');
411
+ expect(icon.button.getAttribute('aria-label')).toBe('Copy');
412
+ } finally {
413
+ unmount();
414
+ }
415
+ });
416
+ await it('refuses an accessibility name it has no door for, naming it', () => {
417
+ const tree: SharedTreeNode = {
418
+ tag: 'GtkBox',
419
+ children: [{ tag: 'GtkCheckButton', accessibility: { checked: { value: true } } }],
420
+ };
421
+ expect(() => buildSharedTree(tree)).toThrow("'checked'");
422
+ expect(() => mountSharedTree(tree)).toThrow('cannot be rendered by adwaita-web');
423
+ });
379
424
  await it('does not read a registered name for a node that is not extern', () => {
380
425
  const el = buildSharedTree({ tag: 'GtkBox', children: [{ tag: 'GtkLabel' }] });
381
426
  expect(el.children[0]!.localName).toBe('gtk-label');