@gjsify/adwaita-web 0.64.1 → 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 (335) hide show
  1. package/README.md +22 -88
  2. package/dist/adwaita-web.css +91 -12
  3. package/dist/adwaita-web.css.map +1 -1
  4. package/lib/types/accessible.d.ts +8 -0
  5. package/lib/types/application.d.ts +1 -1
  6. package/lib/types/child-slot.spec.d.ts +1 -0
  7. package/lib/types/compute-expand.d.ts +5 -0
  8. package/lib/types/compute-expand.spec.d.ts +1 -0
  9. package/lib/types/container-fill.spec.d.ts +1 -0
  10. package/lib/types/elements/adw-about-dialog.d.ts +28 -1
  11. package/lib/types/elements/adw-action-row.d.ts +2 -1
  12. package/lib/types/elements/adw-alert-dialog.d.ts +80 -19
  13. package/lib/types/elements/adw-avatar.d.ts +2 -1
  14. package/lib/types/elements/adw-banner.d.ts +2 -1
  15. package/lib/types/elements/adw-bin.d.ts +2 -1
  16. package/lib/types/elements/adw-bottom-sheet.d.ts +5 -4
  17. package/lib/types/elements/adw-breakpoint-bin.d.ts +2 -1
  18. package/lib/types/elements/adw-button-content.d.ts +2 -1
  19. package/lib/types/elements/adw-button-row.d.ts +2 -1
  20. package/lib/types/elements/adw-card.d.ts +2 -1
  21. package/lib/types/elements/adw-carousel.d.ts +20 -3
  22. package/lib/types/elements/adw-clamp-scrollable.d.ts +2 -1
  23. package/lib/types/elements/adw-clamp.d.ts +2 -1
  24. package/lib/types/elements/adw-combo-row.d.ts +6 -1
  25. package/lib/types/elements/adw-data-grid.d.ts +2 -1
  26. package/lib/types/elements/adw-dialog.d.ts +2 -1
  27. package/lib/types/elements/adw-entry-row.d.ts +2 -1
  28. package/lib/types/elements/adw-expander-row.d.ts +2 -1
  29. package/lib/types/elements/adw-header-bar.d.ts +2 -1
  30. package/lib/types/elements/adw-inline-view-switcher.d.ts +3 -2
  31. package/lib/types/elements/adw-layout-slot.d.ts +2 -1
  32. package/lib/types/elements/adw-multi-layout-view.d.ts +2 -1
  33. package/lib/types/elements/adw-navigation-split-view.d.ts +2 -1
  34. package/lib/types/elements/adw-navigation-view.d.ts +3 -2
  35. package/lib/types/elements/adw-overlay-split-view.d.ts +2 -1
  36. package/lib/types/elements/adw-preferences-dialog.d.ts +5 -4
  37. package/lib/types/elements/adw-preferences-group.d.ts +2 -1
  38. package/lib/types/elements/adw-preferences-row.d.ts +2 -1
  39. package/lib/types/elements/adw-shortcut-label.d.ts +2 -1
  40. package/lib/types/elements/adw-shortcuts-item.d.ts +2 -1
  41. package/lib/types/elements/adw-shortcuts-section.d.ts +2 -1
  42. package/lib/types/elements/adw-sidebar.d.ts +4 -3
  43. package/lib/types/elements/adw-spin-row.d.ts +2 -1
  44. package/lib/types/elements/adw-spinner.d.ts +2 -1
  45. package/lib/types/elements/adw-split-button.d.ts +2 -1
  46. package/lib/types/elements/adw-status-page.d.ts +2 -1
  47. package/lib/types/elements/adw-switch-row.d.ts +4 -1
  48. package/lib/types/elements/adw-tab-bar.d.ts +2 -1
  49. package/lib/types/elements/adw-tab-button.d.ts +2 -1
  50. package/lib/types/elements/adw-tab-overview.d.ts +2 -1
  51. package/lib/types/elements/adw-tab-view.d.ts +3 -2
  52. package/lib/types/elements/adw-toast-overlay.d.ts +10 -30
  53. package/lib/types/elements/adw-toggle-group.d.ts +3 -2
  54. package/lib/types/elements/adw-toolbar-view.d.ts +2 -1
  55. package/lib/types/elements/adw-view-stack.d.ts +3 -1
  56. package/lib/types/elements/adw-view-switcher-bar.d.ts +2 -1
  57. package/lib/types/elements/adw-view-switcher-sidebar.d.ts +2 -1
  58. package/lib/types/elements/adw-view-switcher.d.ts +3 -2
  59. package/lib/types/elements/adw-window-features.d.ts +8 -0
  60. package/lib/types/elements/adw-window-title.d.ts +2 -1
  61. package/lib/types/elements/adw-wrap-box.d.ts +2 -1
  62. package/lib/types/elements/checks.d.ts +16 -1
  63. package/lib/types/elements/gtk-action-bar.d.ts +2 -1
  64. package/lib/types/elements/gtk-application-window.d.ts +1 -4
  65. package/lib/types/elements/gtk-aspect-frame.d.ts +2 -1
  66. package/lib/types/elements/gtk-box.d.ts +5 -1
  67. package/lib/types/elements/gtk-button.d.ts +5 -7
  68. package/lib/types/elements/gtk-calendar.d.ts +2 -1
  69. package/lib/types/elements/gtk-center-box.d.ts +2 -1
  70. package/lib/types/elements/gtk-color-dialog-button.d.ts +2 -1
  71. package/lib/types/elements/gtk-drag-icon.d.ts +2 -1
  72. package/lib/types/elements/gtk-drawing-area.d.ts +2 -1
  73. package/lib/types/elements/gtk-drop-down.d.ts +2 -1
  74. package/lib/types/elements/gtk-editable-label.d.ts +2 -1
  75. package/lib/types/elements/gtk-emoji-chooser.d.ts +2 -0
  76. package/lib/types/elements/gtk-entry.d.ts +2 -1
  77. package/lib/types/elements/gtk-expander.d.ts +2 -1
  78. package/lib/types/elements/gtk-fixed.d.ts +2 -1
  79. package/lib/types/elements/gtk-flow-box-child.d.ts +2 -1
  80. package/lib/types/elements/gtk-flow-box.d.ts +2 -1
  81. package/lib/types/elements/gtk-font-dialog-button.d.ts +2 -1
  82. package/lib/types/elements/gtk-frame.d.ts +2 -1
  83. package/lib/types/elements/gtk-gl-area.d.ts +2 -1
  84. package/lib/types/elements/gtk-graphics-offload.d.ts +2 -1
  85. package/lib/types/elements/gtk-grid.d.ts +2 -1
  86. package/lib/types/elements/gtk-header-bar.d.ts +2 -1
  87. package/lib/types/elements/gtk-image.d.ts +5 -6
  88. package/lib/types/elements/gtk-inscription.d.ts +2 -1
  89. package/lib/types/elements/gtk-label.d.ts +10 -3
  90. package/lib/types/elements/gtk-level-bar.d.ts +2 -1
  91. package/lib/types/elements/gtk-list-box-row.d.ts +2 -1
  92. package/lib/types/elements/gtk-list-box.d.ts +2 -1
  93. package/lib/types/elements/gtk-list-view.d.ts +2 -1
  94. package/lib/types/elements/gtk-media-controls.d.ts +2 -1
  95. package/lib/types/elements/gtk-menu-button.d.ts +2 -1
  96. package/lib/types/elements/gtk-notebook.d.ts +2 -1
  97. package/lib/types/elements/gtk-overlay.d.ts +2 -1
  98. package/lib/types/elements/gtk-paned.d.ts +2 -1
  99. package/lib/types/elements/gtk-picture.d.ts +2 -1
  100. package/lib/types/elements/gtk-popover-bin.d.ts +2 -1
  101. package/lib/types/elements/gtk-popover-menu-bar.d.ts +2 -1
  102. package/lib/types/elements/gtk-popover-menu.d.ts +2 -0
  103. package/lib/types/elements/gtk-popover.d.ts +7 -1
  104. package/lib/types/elements/gtk-progress-bar.d.ts +2 -1
  105. package/lib/types/elements/gtk-revealer.d.ts +2 -1
  106. package/lib/types/elements/gtk-scale-button.d.ts +2 -1
  107. package/lib/types/elements/gtk-scale.d.ts +2 -1
  108. package/lib/types/elements/gtk-scrollbar.d.ts +2 -1
  109. package/lib/types/elements/gtk-scrolled-window.d.ts +2 -1
  110. package/lib/types/elements/gtk-search-bar.d.ts +2 -1
  111. package/lib/types/elements/gtk-separator.d.ts +2 -1
  112. package/lib/types/elements/gtk-spin-button.d.ts +2 -1
  113. package/lib/types/elements/gtk-spinner.d.ts +2 -1
  114. package/lib/types/elements/gtk-stack-page.d.ts +29 -0
  115. package/lib/types/elements/gtk-stack-sidebar.d.ts +2 -1
  116. package/lib/types/elements/gtk-stack-switcher.d.ts +2 -1
  117. package/lib/types/elements/gtk-stack.d.ts +13 -1
  118. package/lib/types/elements/gtk-switch.d.ts +2 -1
  119. package/lib/types/elements/gtk-text-view.d.ts +2 -1
  120. package/lib/types/elements/gtk-text.d.ts +2 -1
  121. package/lib/types/elements/gtk-tree-expander.d.ts +2 -1
  122. package/lib/types/elements/gtk-video.d.ts +2 -1
  123. package/lib/types/elements/gtk-viewport.d.ts +2 -1
  124. package/lib/types/elements/gtk-window-controls.d.ts +2 -1
  125. package/lib/types/elements/gtk-window-handle.d.ts +2 -1
  126. package/lib/types/elements/gtk-window.d.ts +18 -2
  127. package/lib/types/gobject-elements.d.ts +15 -0
  128. package/lib/types/gtksource/gtk-source-view.d.ts +2 -1
  129. package/lib/types/{icon-registry.spec.d.ts → icon-masks.spec.d.ts} +1 -1
  130. package/lib/types/icon-theme.d.ts +15 -35
  131. package/lib/types/index.d.ts +3 -4
  132. package/lib/types/learn6502-window.spec.d.ts +1 -0
  133. package/lib/types/namespace/adw.d.ts +1 -1
  134. package/lib/types/namespace/gdk.d.ts +1 -0
  135. package/lib/types/namespace/gio.d.ts +1 -0
  136. package/lib/types/namespace/glib.d.ts +2 -1
  137. package/lib/types/namespace/gtk.d.ts +2 -0
  138. package/lib/types/shared-tree-builder.d.ts +31 -0
  139. package/lib/types/signals.d.ts +5 -1
  140. package/lib/types/source-view/adw-source-view.d.ts +2 -1
  141. package/lib/types/styles.generated.d.ts +1 -1
  142. package/lib/types/widget-element.d.ts +25 -0
  143. package/lib/types/widget-map.spec.d.ts +1 -0
  144. package/lib/types/widget-mapping.d.ts +7 -0
  145. package/lib/types/widget-visible.spec.d.ts +1 -0
  146. package/lib/types/window-maximize.spec.d.ts +1 -0
  147. package/package.json +13 -12
  148. package/scss/_about_dialog.scss +46 -1
  149. package/scss/_bin.scss +6 -3
  150. package/scss/_checks.scss +5 -2
  151. package/scss/_icon.scss +3 -4
  152. package/scss/_labels.scss +2 -2
  153. package/scss/_overlay.scss +11 -1
  154. package/scss/_overlay_split_view.scss +10 -0
  155. package/scss/_reset.scss +1 -1
  156. package/scss/_scrolled_window.scss +15 -0
  157. package/scss/_stack.scss +8 -2
  158. package/scss/_toast.scss +13 -3
  159. package/scss/_widget.scss +5 -1
  160. package/scss/_window.scss +16 -0
  161. package/src/accessible.ts +29 -0
  162. package/src/adw-about-dialog.spec.ts +81 -12
  163. package/src/adw-action-rows.spec.ts +2 -2
  164. package/src/adw-alert-dialog.spec.ts +71 -5
  165. package/src/adw-bin.spec.ts +4 -3
  166. package/src/adw-button-content.spec.ts +1 -1
  167. package/src/adw-carousel.spec.ts +49 -1
  168. package/src/adw-header-bar.spec.ts +18 -0
  169. package/src/adw-toast-overlay.spec.ts +68 -24
  170. package/src/application.spec.ts +44 -0
  171. package/src/application.ts +13 -18
  172. package/src/capabilities.mjs +2 -3
  173. package/src/checks.spec.ts +51 -1
  174. package/src/child-slot.spec.ts +71 -0
  175. package/src/compute-expand.spec.ts +186 -0
  176. package/src/compute-expand.ts +134 -0
  177. package/src/construct-vectors.spec.ts +97 -7
  178. package/src/container-fill.spec.ts +70 -0
  179. package/src/elements/adw-about-dialog.ts +152 -9
  180. package/src/elements/adw-action-row.ts +2 -1
  181. package/src/elements/adw-alert-dialog.ts +177 -46
  182. package/src/elements/adw-application-window.ts +0 -3
  183. package/src/elements/adw-avatar.ts +2 -1
  184. package/src/elements/adw-banner.ts +2 -1
  185. package/src/elements/adw-bin.ts +2 -1
  186. package/src/elements/adw-bottom-sheet.ts +5 -4
  187. package/src/elements/adw-breakpoint-bin.ts +5 -1
  188. package/src/elements/adw-button-content.ts +2 -1
  189. package/src/elements/adw-button-row.ts +2 -1
  190. package/src/elements/adw-card.ts +2 -1
  191. package/src/elements/adw-carousel.ts +62 -3
  192. package/src/elements/adw-clamp-scrollable.ts +2 -1
  193. package/src/elements/adw-clamp.ts +2 -1
  194. package/src/elements/adw-combo-row.ts +15 -1
  195. package/src/elements/adw-data-grid.ts +2 -1
  196. package/src/elements/adw-dialog.ts +5 -2
  197. package/src/elements/adw-entry-row.ts +2 -1
  198. package/src/elements/adw-expander-row.ts +2 -1
  199. package/src/elements/adw-header-bar.ts +6 -1
  200. package/src/elements/adw-inline-view-switcher.ts +3 -2
  201. package/src/elements/adw-layout-slot.ts +2 -1
  202. package/src/elements/adw-multi-layout-view.ts +2 -1
  203. package/src/elements/adw-navigation-split-view.ts +2 -1
  204. package/src/elements/adw-navigation-view.ts +3 -2
  205. package/src/elements/adw-overlay-split-view.ts +2 -1
  206. package/src/elements/adw-preferences-dialog.ts +6 -4
  207. package/src/elements/adw-preferences-group.ts +2 -1
  208. package/src/elements/adw-preferences-row.ts +2 -1
  209. package/src/elements/adw-shortcut-label.ts +2 -1
  210. package/src/elements/adw-shortcuts-item.ts +2 -1
  211. package/src/elements/adw-shortcuts-section.ts +2 -1
  212. package/src/elements/adw-sidebar.ts +4 -3
  213. package/src/elements/adw-spin-row.ts +2 -1
  214. package/src/elements/adw-spinner.ts +2 -1
  215. package/src/elements/adw-split-button.ts +2 -1
  216. package/src/elements/adw-status-page.ts +2 -1
  217. package/src/elements/adw-switch-row.ts +10 -1
  218. package/src/elements/adw-tab-bar.ts +2 -1
  219. package/src/elements/adw-tab-button.ts +2 -1
  220. package/src/elements/adw-tab-overview.ts +2 -1
  221. package/src/elements/adw-tab-view.ts +3 -2
  222. package/src/elements/adw-toast-overlay.ts +37 -70
  223. package/src/elements/adw-toggle-group.ts +3 -2
  224. package/src/elements/adw-toolbar-view.ts +2 -1
  225. package/src/elements/adw-view-stack.ts +39 -1
  226. package/src/elements/adw-view-switcher-bar.ts +2 -1
  227. package/src/elements/adw-view-switcher-sidebar.ts +2 -1
  228. package/src/elements/adw-view-switcher.ts +3 -2
  229. package/src/elements/adw-window-features.ts +14 -2
  230. package/src/elements/adw-window-title.ts +2 -1
  231. package/src/elements/adw-wrap-box.ts +2 -1
  232. package/src/elements/checks.ts +85 -6
  233. package/src/elements/gtk-action-bar.ts +2 -1
  234. package/src/elements/gtk-application-window.ts +3 -8
  235. package/src/elements/gtk-aspect-frame.ts +2 -1
  236. package/src/elements/gtk-box.ts +56 -1
  237. package/src/elements/gtk-button.ts +10 -22
  238. package/src/elements/gtk-calendar.ts +2 -1
  239. package/src/elements/gtk-center-box.ts +2 -1
  240. package/src/elements/gtk-color-dialog-button.ts +2 -1
  241. package/src/elements/gtk-drag-icon.ts +6 -1
  242. package/src/elements/gtk-drawing-area.ts +2 -1
  243. package/src/elements/gtk-drop-down.ts +2 -1
  244. package/src/elements/gtk-editable-label.ts +2 -1
  245. package/src/elements/gtk-emoji-chooser.ts +5 -0
  246. package/src/elements/gtk-entry.ts +2 -1
  247. package/src/elements/gtk-expander.ts +2 -1
  248. package/src/elements/gtk-fixed.ts +2 -1
  249. package/src/elements/gtk-flow-box-child.ts +5 -2
  250. package/src/elements/gtk-flow-box.ts +2 -1
  251. package/src/elements/gtk-font-dialog-button.ts +2 -1
  252. package/src/elements/gtk-frame.ts +2 -1
  253. package/src/elements/gtk-gl-area.ts +2 -1
  254. package/src/elements/gtk-graphics-offload.ts +5 -2
  255. package/src/elements/gtk-grid.ts +2 -1
  256. package/src/elements/gtk-header-bar.ts +2 -1
  257. package/src/elements/gtk-image.ts +14 -15
  258. package/src/elements/gtk-inscription.ts +2 -1
  259. package/src/elements/gtk-label.ts +12 -5
  260. package/src/elements/gtk-level-bar.ts +2 -1
  261. package/src/elements/gtk-list-box-row.ts +5 -2
  262. package/src/elements/gtk-list-box.ts +2 -1
  263. package/src/elements/gtk-list-view.ts +2 -1
  264. package/src/elements/gtk-media-controls.ts +2 -1
  265. package/src/elements/gtk-menu-button.ts +2 -1
  266. package/src/elements/gtk-notebook.ts +2 -1
  267. package/src/elements/gtk-overlay.ts +7 -5
  268. package/src/elements/gtk-paned.ts +9 -2
  269. package/src/elements/gtk-picture.ts +2 -1
  270. package/src/elements/gtk-popover-bin.ts +5 -1
  271. package/src/elements/gtk-popover-menu-bar.ts +2 -1
  272. package/src/elements/gtk-popover-menu.ts +5 -0
  273. package/src/elements/gtk-popover.ts +14 -1
  274. package/src/elements/gtk-progress-bar.ts +2 -1
  275. package/src/elements/gtk-revealer.ts +2 -1
  276. package/src/elements/gtk-scale-button.ts +2 -1
  277. package/src/elements/gtk-scale.ts +2 -1
  278. package/src/elements/gtk-scrollbar.ts +2 -1
  279. package/src/elements/gtk-scrolled-window.ts +8 -5
  280. package/src/elements/gtk-search-bar.ts +7 -6
  281. package/src/elements/gtk-separator.ts +2 -1
  282. package/src/elements/gtk-spin-button.ts +2 -1
  283. package/src/elements/gtk-spinner.ts +2 -1
  284. package/src/elements/gtk-stack-page.ts +112 -0
  285. package/src/elements/gtk-stack-sidebar.ts +2 -1
  286. package/src/elements/gtk-stack-switcher.ts +2 -1
  287. package/src/elements/gtk-stack.ts +52 -6
  288. package/src/elements/gtk-switch.ts +2 -1
  289. package/src/elements/gtk-text-view.ts +2 -1
  290. package/src/elements/gtk-text.ts +2 -1
  291. package/src/elements/gtk-tree-expander.ts +5 -1
  292. package/src/elements/gtk-video.ts +2 -1
  293. package/src/elements/gtk-viewport.ts +6 -1
  294. package/src/elements/gtk-window-controls.ts +2 -1
  295. package/src/elements/gtk-window-handle.ts +6 -1
  296. package/src/elements/gtk-window.ts +57 -7
  297. package/src/gio.spec.ts +13 -3
  298. package/src/glib.spec.ts +9 -0
  299. package/src/gobject-elements.ts +44 -2
  300. package/src/gtk-label.spec.ts +39 -0
  301. package/src/gtk-scrolled-window.spec.ts +6 -6
  302. package/src/gtk-stack.spec.ts +170 -0
  303. package/src/gtk-value-doors.spec.ts +19 -0
  304. package/src/gtk-window.spec.ts +2 -2
  305. package/src/gtksource/gtk-source-view.ts +2 -1
  306. package/src/{icon-registry.spec.ts → icon-masks.spec.ts} +4 -100
  307. package/src/icon-theme.spec.ts +69 -124
  308. package/src/icon-theme.ts +29 -119
  309. package/src/index.ts +5 -9
  310. package/src/keyboard-operable.spec.ts +3 -3
  311. package/src/learn6502-window.spec.ts +113 -0
  312. package/src/namespace/adw.ts +1 -1
  313. package/src/namespace/gdk.ts +1 -0
  314. package/src/namespace/gio.ts +8 -0
  315. package/src/namespace/glib.ts +4 -0
  316. package/src/namespace/gtk.ts +2 -0
  317. package/src/shared-tree-builder.ts +131 -13
  318. package/src/shared-trees.spec.ts +15 -1
  319. package/src/signals.ts +11 -2
  320. package/src/slotted-children.spec.ts +24 -0
  321. package/src/slotted-children.ts +18 -2
  322. package/src/source-view/adw-source-view.ts +2 -1
  323. package/src/split-button.spec.ts +1 -1
  324. package/src/style-manager.spec.ts +13 -0
  325. package/src/style-manager.ts +6 -0
  326. package/src/styles.generated.ts +1 -1
  327. package/src/test.browser.mts +16 -2
  328. package/src/value-lists.spec.ts +3 -3
  329. package/src/widget-element.ts +75 -0
  330. package/src/widget-map.spec.ts +116 -0
  331. package/src/widget-mapping.ts +100 -0
  332. package/src/widget-visible.spec.ts +148 -0
  333. package/src/window-maximize.spec.ts +93 -0
  334. package/lib/types/icon-registry.d.ts +0 -37
  335. package/src/icon-registry.ts +0 -163
@@ -124,8 +124,8 @@ export const AdwAlertDialogTest = async () => {
124
124
  // directly would assert a contract this element does not have.
125
125
  const { el, host } = mount({ 'close-response': 'cancel', open: '' });
126
126
  let seen: string | null = null;
127
- el.addEventListener('response', (event) => {
128
- seen = (event as CustomEvent<{ response: string }>).detail.response;
127
+ el.connect('response', (_dialog, response: string) => {
128
+ seen = response;
129
129
  });
130
130
  (el.querySelector('.adw-alert-dialog-scrim') as HTMLElement).click();
131
131
  expect(seen).toBe('cancel');
@@ -147,9 +147,9 @@ export const AdwAlertDialogTest = async () => {
147
147
  await describe('adw-alert-dialog responses before the first connect', async () => {
148
148
  await it('buttons added while detached appear once connected', () => {
149
149
  const el = document.createElement('adw-alert-dialog') as AdwAlertDialog;
150
- el.addResponse('cancel', 'Cancel');
151
- el.addResponse('ok', 'OK');
152
- expect(el.getResponseLabel('ok')).toBe('OK');
150
+ el.add_response('cancel', 'Cancel');
151
+ el.add_response('ok', 'OK');
152
+ expect(el.get_response_label('ok')).toBe('OK');
153
153
  const host = document.createElement('div');
154
154
  document.body.appendChild(host);
155
155
  host.appendChild(el);
@@ -158,4 +158,70 @@ export const AdwAlertDialogTest = async () => {
158
158
  host.remove();
159
159
  });
160
160
  });
161
+
162
+ // The calls a GJS app makes (Learn6502's `notification.service.ts`), run as written.
163
+ await describe('adw-alert-dialog in the GJS construct-props form', async () => {
164
+ const Dialog = customElements.get('adw-alert-dialog') as unknown as new (
165
+ props?: Record<string, unknown>,
166
+ ) => AdwAlertDialog;
167
+
168
+ const confirmDialog = () => {
169
+ const dialog = new Dialog({
170
+ heading: 'Reset?',
171
+ body: 'This clears the memory.',
172
+ close_response: 'cancel',
173
+ default_response: 'confirm',
174
+ });
175
+ dialog.add_response('cancel', 'Cancel');
176
+ dialog.add_response('confirm', 'Confirm');
177
+ return dialog;
178
+ };
179
+
180
+ await it('takes the snake_case bag', () => {
181
+ const dialog = confirmDialog();
182
+ expect(dialog.heading).toBe('Reset?');
183
+ expect(dialog.body).toBe('This clears the memory.');
184
+ expect(dialog.get_close_response()).toBe('cancel');
185
+ expect(dialog.get_default_response()).toBe('confirm');
186
+ });
187
+
188
+ await it('refuses a key that is no Adw.AlertDialog property', () => {
189
+ expect(() => new Dialog({ no_such_prop: 1 })).toThrow();
190
+ });
191
+
192
+ await it('present() attaches it, a click emits response with the id, and closing detaches it', () => {
193
+ const dialog = confirmDialog();
194
+ const seen: unknown[][] = [];
195
+ dialog.connect('response', (emitter, response: string) => seen.push([emitter, response]));
196
+ dialog.present(null);
197
+ expect(dialog.isConnected).toBe(true);
198
+ expect(dialog.open).toBe(true);
199
+ const buttons = [...dialog.querySelectorAll('.adw-alert-dialog-response')] as HTMLButtonElement[];
200
+ buttons.find((b) => b.textContent === 'Confirm')!.click();
201
+ expect(seen.length).toBe(1);
202
+ expect(seen[0]![0]).toBe(dialog);
203
+ expect(seen[0]![1]).toBe('confirm');
204
+ expect(dialog.open).toBe(false);
205
+ expect(dialog.isConnected).toBe(false);
206
+ });
207
+
208
+ await it('a dismissal emits the close_response once, not the clicked one too', () => {
209
+ const dialog = confirmDialog();
210
+ const seen: string[] = [];
211
+ dialog.connect('response', (_dialog, response: string) => seen.push(response));
212
+ dialog.present(null);
213
+ (dialog.querySelector('.adw-alert-dialog-scrim') as HTMLElement).click();
214
+ expect(seen.join(',')).toBe('cancel');
215
+ expect(dialog.isConnected).toBe(false);
216
+ });
217
+
218
+ await it('remove_response drops the response and its button', () => {
219
+ const dialog = confirmDialog();
220
+ dialog.present(null);
221
+ dialog.remove_response('confirm');
222
+ expect(dialog.has_response('confirm')).toBe(false);
223
+ expect(dialog.querySelectorAll('.adw-alert-dialog-response').length).toBe(1);
224
+ dialog.remove();
225
+ });
226
+ });
161
227
  };
@@ -61,12 +61,13 @@ export const AdwBinTest = async () => {
61
61
  other.remove();
62
62
  });
63
63
 
64
- await it('an empty bin is a block box, and its only child fills it', () => {
64
+ await it('a bin is a column flex box, and its only child fills it', () => {
65
65
  const { el, host } = mount([document.createElement('div')]);
66
66
  // A bin takes the size it is GIVEN and hands all of it to the child, which
67
- // `GtkBinLayout` does on both axes (gtkbinlayout.c:81).
67
+ // `GtkBinLayout` does on both axes (gtkbinlayout.c:81). Flex stretch, not a
68
+ // percentage, so it also holds when the bin is itself a flex item.
68
69
  el.style.height = '120px';
69
- expect(getComputedStyle(el).display).toBe('block');
70
+ expect(getComputedStyle(el).display).toBe('flex');
70
71
  const child = el.firstElementChild as HTMLElement;
71
72
  expect(child.getBoundingClientRect().height).toBe(120);
72
73
  host.remove();
@@ -20,7 +20,7 @@ import {
20
20
  BUTTON_CONTENT_TEXT_VECTORS,
21
21
  } from '@gjsify/adwaita-core/conformance';
22
22
  import type { AdwButtonContent } from './elements/adw-button-content.js';
23
- import { fallbackMask, maskOf } from './icon-registry.spec.js';
23
+ import { fallbackMask, maskOf } from './icon-masks.spec.js';
24
24
 
25
25
  /** Mount a button content inside a plain Adwaita button, the way the stories do. */
26
26
  function mount(attributes: Record<string, string> = {}): {
@@ -11,11 +11,13 @@ import { describe, expect, it } from '@gjsify/unit';
11
11
 
12
12
  import type { CarouselPageMeasurement } from '@gjsify/adwaita-core';
13
13
  import {
14
+ CAROUSEL_DOOR_VECTORS,
14
15
  CAROUSEL_PAGE_ALLOCATION_VECTORS,
15
16
  CAROUSEL_PAGE_AT_POSITION_VECTORS,
16
17
  CAROUSEL_PAGE_LIST_VECTORS,
18
+ driveCarouselDoorVectors,
17
19
  } from '@gjsify/adwaita-core/conformance';
18
- import type { CarouselPageOp } from '@gjsify/adwaita-core/conformance';
20
+ import type { CarouselDoorLike, CarouselPageOp } from '@gjsify/adwaita-core/conformance';
19
21
 
20
22
  /** The carousel surface this suite drives — the element's own public API. */
21
23
  interface CarouselElement extends HTMLElement {
@@ -513,4 +515,50 @@ export const AdwCarouselTest = async () => {
513
515
  host.remove();
514
516
  });
515
517
  });
518
+
519
+ // The GIR doors, on the vectors real `gi://Adw` passes. `mount` makes the animated
520
+ // scroll instant too, so `settle` waits for the rAF-throttled read-back only.
521
+ const hosts: HTMLElement[] = [];
522
+ await driveCarouselDoorVectors(
523
+ {
524
+ name: 'adw-carousel',
525
+ isOracle: false,
526
+ makeCarousel(count) {
527
+ const { carousel, host } = mount(Array.from({ length: count }, (_unused, i) => `p${i}`));
528
+ hosts.push(host);
529
+ const pages = Array.from(carousel.querySelectorAll('.adw-carousel-page')).map(
530
+ (slot) => slot.firstElementChild as HTMLElement,
531
+ );
532
+ return { carousel: carousel as unknown as CarouselDoorLike, pages };
533
+ },
534
+ makeStranger: () => card('stranger'),
535
+ async settle() {
536
+ await flush();
537
+ await flush();
538
+ },
539
+ dispose() {
540
+ for (const host of hosts.splice(0)) host.remove();
541
+ },
542
+ },
543
+ { describe, it, expect },
544
+ CAROUSEL_DOOR_VECTORS,
545
+ );
546
+
547
+ await describe('adw-carousel scroll_to before the pages are adopted', async () => {
548
+ await it('lands where a declared position would, as upstream defers it to an idle', async () => {
549
+ const carousel = document.createElement('adw-carousel') as CarouselElement & CarouselDoorLike;
550
+ carousel.style.cssText = 'display:block;width:200px;height:80px;';
551
+ const pages = ['a', 'b', 'c'].map((label) => card(label));
552
+ for (const page of pages) carousel.appendChild(page);
553
+ expect(carousel.get_n_pages()).toBe(3);
554
+ expect(carousel.get_nth_page(2)).toBe(pages[2]);
555
+ carousel.scroll_to(pages[2], false);
556
+
557
+ document.body.appendChild(carousel);
558
+ await flush();
559
+ await flush();
560
+ expect(carousel.get_position()).toBe(2);
561
+ carousel.remove();
562
+ });
563
+ });
516
564
  };
@@ -233,6 +233,24 @@ export const AdwHeaderBarTest = async () => {
233
233
 
234
234
  // `windowtitle .title, .subtitle { padding: 0 12px }` (_header-bar.scss:214-227).
235
235
  // Measured beside a GTK render of the gallery's window-title `.blp`: 27px narrower here.
236
+ await describe('<adw-header-bar> packing', async () => {
237
+ await it('packs an untyped child from the start, declared or appended', async () => {
238
+ // adw-header-bar.c:1040-1041 — `type == NULL` is pack_start. A Blueprint `[start]`
239
+ // tags only the next child, so the buttons after it arrive untyped.
240
+ const tagged = document.createElement('button');
241
+ tagged.setAttribute('slot', 'start');
242
+ const declared = document.createElement('button');
243
+ const bar = mountHeaderBar({ title: 'T' }, [tagged, declared]);
244
+ const late = document.createElement('button');
245
+ bar.appendChild(late);
246
+ await Promise.resolve();
247
+ const start = bar.querySelector('.adw-header-bar-start');
248
+ const report = [tagged, declared, late].map((el) => el.parentElement === start);
249
+ unmountAll();
250
+ expect(report).toStrictEqual([true, true, true]);
251
+ });
252
+ });
253
+
236
254
  await describe('adw-window-title label sides', async () => {
237
255
  await it('pads its title and subtitle 12px on each side', () => {
238
256
  const title = document.createElement('adw-window-title');
@@ -1,12 +1,14 @@
1
1
  // DOM-level behaviour tests for <adw-toast-overlay>, in a real browser via the
2
2
  // @gjsify/adwaita-web browser test axis.
3
3
  //
4
- // `Adw.ToastOverlay` shows exactly ONE toast at a time and queues the rest FIFO,
4
+ // `Adw.ToastOverlay` shows exactly ONE toast at a time and queues the rest,
5
5
  // advancing only when the visible one is dismissed (manually or by its timeout) — the
6
- // policy `AdwToastQueue` owns in `@gjsify/adwaita-core` and this element composes.
6
+ // policy `ToastOverlayQueue` owns in `@gjsify/adwaita-core` and this element composes.
7
+ // A toast is an `Adw.Toast`, added with `add_toast(toast)` as on GTK.
7
8
  //
8
9
  // Timing runs through core's injected `ToastScheduler` seam (a virtual clock), so the
9
10
  // auto-dismiss lifecycle is deterministic with no real timers.
11
+ import { Toast } from '@gjsify/adwaita-core';
10
12
  import { describe, expect, it } from '@gjsify/unit';
11
13
 
12
14
  import type { AdwToastOverlay, ToastScheduler, ToastTimerHandle } from './elements/adw-toast-overlay.js';
@@ -65,12 +67,10 @@ export const AdwToastOverlayTest = async () => {
65
67
  await it('shows exactly one toast at a time, in FIFO order', async () => {
66
68
  const { overlay, clock, host } = mountOverlay();
67
69
 
68
- overlay.addToast('First', { timeout: 3 });
69
- overlay.addToast('Second', { timeout: 3 });
70
- overlay.addToast('Third', { timeout: 3 });
70
+ overlay.add_toast(new Toast({ title: 'First', timeout: 3 }));
71
+ overlay.add_toast(new Toast({ title: 'Second', timeout: 3 }));
72
+ overlay.add_toast(new Toast({ title: 'Third', timeout: 3 }));
71
73
 
72
- // BEFORE the fix this was ['First', 'Second', 'Third'] — all three
73
- // strips mounted at once.
74
74
  expect(toastTitles(overlay)).toStrictEqual(['First']);
75
75
  expect(overlay.pendingToasts).toBe(2);
76
76
  expect(overlay.currentToast?.title).toBe('First');
@@ -96,8 +96,9 @@ export const AdwToastOverlayTest = async () => {
96
96
  await it('holds the queue while a sticky toast (timeout 0) is shown', async () => {
97
97
  const { overlay, clock, host } = mountOverlay();
98
98
 
99
- overlay.addToast('Sticky', { timeout: 0 });
100
- overlay.addToast('Next', { timeout: 3 });
99
+ const sticky = new Toast({ title: 'Sticky', timeout: 0 });
100
+ overlay.add_toast(sticky);
101
+ overlay.add_toast(new Toast({ title: 'Next', timeout: 3 }));
101
102
 
102
103
  // No timer was scheduled for the sticky toast, so time does not advance it.
103
104
  clock.advance(60_000);
@@ -105,7 +106,7 @@ export const AdwToastOverlayTest = async () => {
105
106
  expect(overlay.pendingToasts).toBe(1);
106
107
 
107
108
  // Dismissing it hands the slot to the queued toast.
108
- overlay.dismiss();
109
+ sticky.dismiss();
109
110
  expect(toastTitles(overlay)).toStrictEqual(['Next']);
110
111
  expect(overlay.pendingToasts).toBe(0);
111
112
 
@@ -115,30 +116,34 @@ export const AdwToastOverlayTest = async () => {
115
116
  await it('advances the queue when the close button is pressed', async () => {
116
117
  const { overlay, host } = mountOverlay();
117
118
 
118
- overlay.addToast('First', { timeout: 3 });
119
- overlay.addToast('Second', { timeout: 3 });
119
+ const first = new Toast({ title: 'First', timeout: 3 });
120
+ let dismissed = 0;
121
+ first.connect('dismissed', () => {
122
+ dismissed += 1;
123
+ });
124
+ overlay.add_toast(first);
125
+ overlay.add_toast(new Toast({ title: 'Second', timeout: 3 }));
120
126
 
121
127
  const close = overlay.querySelector('.adw-toast-close-button') as HTMLButtonElement;
122
128
  close.click();
123
129
 
130
+ expect(dismissed).toBe(1);
124
131
  expect(toastTitles(overlay)).toStrictEqual(['Second']);
125
132
  expect(overlay.pendingToasts).toBe(0);
126
133
 
127
134
  host.remove();
128
135
  });
129
136
 
130
- await it('runs the action callback and advances the queue', async () => {
137
+ await it('emits button-clicked and advances the queue', async () => {
131
138
  const { overlay, host } = mountOverlay();
132
139
 
140
+ const toast = new Toast({ title: 'File moved to Trash', timeout: 3, button_label: 'Undo' });
133
141
  let undone = 0;
134
- overlay.addToast('File moved to Trash', {
135
- timeout: 3,
136
- buttonLabel: 'Undo',
137
- onAction: () => {
138
- undone += 1;
139
- },
142
+ toast.connect('button-clicked', () => {
143
+ undone += 1;
140
144
  });
141
- overlay.addToast('Second', { timeout: 3 });
145
+ overlay.add_toast(toast);
146
+ overlay.add_toast(new Toast({ title: 'Second', timeout: 3 }));
142
147
 
143
148
  const action = overlay.querySelector('.adw-toast-action-button') as HTMLButtonElement;
144
149
  expect(action.textContent).toBe('Undo');
@@ -150,12 +155,51 @@ export const AdwToastOverlayTest = async () => {
150
155
  host.remove();
151
156
  });
152
157
 
153
- await it('drops the queued toasts on clearToasts()', async () => {
158
+ await it('draws no action button without a button_label', async () => {
159
+ const { overlay, host } = mountOverlay();
160
+
161
+ overlay.add_toast(new Toast({ title: 'Saved' }));
162
+ expect(overlay.querySelector('.adw-toast-action-button')).toBeNull();
163
+
164
+ host.remove();
165
+ });
166
+
167
+ await it('renders the title as markup unless use_markup is off', async () => {
168
+ const { overlay, host } = mountOverlay();
169
+
170
+ overlay.add_toast(new Toast({ title: '<b>Bold</b>', timeout: 0 }));
171
+ expect(overlay.querySelector('.adw-toast-title b')?.textContent).toBe('Bold');
172
+ overlay.dismiss_all();
173
+
174
+ const { overlay: plain, host: plainHost } = mountOverlay();
175
+ plain.add_toast(new Toast({ title: '<b>Bold</b>', timeout: 0, use_markup: false }));
176
+ expect(plain.querySelector('.adw-toast-title b')).toBeNull();
177
+ expect(toastTitles(plain)).toStrictEqual(['<b>Bold</b>']);
178
+
179
+ host.remove();
180
+ plainHost.remove();
181
+ });
182
+
183
+ await it('shows a HIGH priority toast ahead of the current one', async () => {
184
+ const { overlay, host } = mountOverlay();
185
+
186
+ overlay.add_toast(new Toast({ title: 'Normal', timeout: 0 }));
187
+ const urgent = new Toast({ title: 'Urgent', timeout: 0, priority: 1 });
188
+ overlay.add_toast(urgent);
189
+
190
+ expect(toastTitles(overlay)).toStrictEqual(['Urgent']);
191
+ urgent.dismiss();
192
+ expect(toastTitles(overlay)).toStrictEqual(['Normal']);
193
+
194
+ host.remove();
195
+ });
196
+
197
+ await it('dismisses the visible toast and the queue on dismiss_all()', async () => {
154
198
  const { overlay, clock, host } = mountOverlay();
155
199
 
156
- overlay.addToast('First', { timeout: 3 });
157
- overlay.addToast('Second', { timeout: 3 });
158
- overlay.clearToasts();
200
+ overlay.add_toast(new Toast({ title: 'First', timeout: 3 }));
201
+ overlay.add_toast(new Toast({ title: 'Second', timeout: 3 }));
202
+ overlay.dismiss_all();
159
203
 
160
204
  expect(overlay.currentToast).toBeNull();
161
205
  expect(overlay.pendingToasts).toBe(0);
@@ -95,6 +95,50 @@ export const ApplicationTest = async () => {
95
95
  (window as unknown as { application: object | null }).application = null;
96
96
  expect(press({ key: 's', ctrlKey: true })).toBe(false);
97
97
  });
98
+ await it('inhibit(LOGOUT) cancels beforeunload until uninhibit', () => {
99
+ const app = new Adw.Application({ application_id: 'org.gjsify.vec.WebInhibit' });
100
+ const unload = (): boolean => {
101
+ const event = new Event('beforeunload', { cancelable: true });
102
+ window.dispatchEvent(event);
103
+ return event.defaultPrevented;
104
+ };
105
+ expect(unload()).toBe(false);
106
+ const cookie = app.inhibit(null, Gtk.ApplicationInhibitFlags.LOGOUT, 'unsaved changes');
107
+ expect(cookie > 0).toBe(true);
108
+ expect(unload()).toBe(true);
109
+ app.uninhibit(cookie);
110
+ expect(unload()).toBe(false);
111
+ });
112
+ await it('every window class has application, get_application and set_application', () => {
113
+ const app = new Adw.Application({ application_id: 'org.gjsify.vec.WebWindowApp' });
114
+ const other = new Adw.Application({ application_id: 'org.gjsify.vec.WebWindowOther' });
115
+ for (const tag of ['gtk-window', 'gtk-application-window', 'adw-window', 'adw-application-window']) {
116
+ const window = document.createElement(tag) as unknown as Gtk.WindowCommon<Element>;
117
+ const notified: unknown[] = [];
118
+ window.connect('notify::application', () => notified.push(window.application));
119
+ expect(window.get_application()).toBe(null);
120
+ window.set_application(app as never);
121
+ expect(window.get_application()).toBe(app);
122
+ expect(window.application).toBe(app);
123
+ expect(app.get_windows().includes(window)).toBe(true);
124
+ window.set_application(other as never);
125
+ expect(app.get_windows().includes(window)).toBe(false);
126
+ expect(other.get_windows().includes(window)).toBe(true);
127
+ window.application = null;
128
+ expect(window.get_application()).toBe(null);
129
+ expect(other.get_windows().includes(window)).toBe(false);
130
+ expect(notified).toStrictEqual([app, other, null]);
131
+ }
132
+ });
133
+ await it('a window reaches inhibit through get_application()', () => {
134
+ const app = new Adw.Application({ application_id: 'org.gjsify.vec.WebWindowInhibit' });
135
+ const window = document.createElement('gtk-window') as unknown as Gtk.WindowCommon<Element>;
136
+ window.set_application(app as never);
137
+ const cookie = window.get_application()?.inhibit(window, Gtk.ApplicationInhibitFlags.LOGOUT, 'unsaved');
138
+ expect((cookie ?? 0) > 0).toBe(true);
139
+ window.get_application()?.uninhibit(cookie ?? 0);
140
+ window.set_application(null);
141
+ });
98
142
  await it('quit() takes the windows it added off the page', async () => {
99
143
  const app = new Adw.Application({ application_id: 'org.gjsify.vec.WebQuit' });
100
144
  const window = scene.window(app as never) as Element;
@@ -10,10 +10,9 @@ import {
10
10
  withGtkApplication,
11
11
  activateWidgetAction as activateIn,
12
12
  activateMenuItem,
13
- applicationOfWindow,
13
+ defineWindowApplicationMembers,
14
14
  dispatchAccelerator,
15
15
  menuActionsFor,
16
- installActionMap,
17
16
  type AdwMenuItem,
18
17
  type AdwMenuActions,
19
18
  type AdwMenuModel,
@@ -40,8 +39,18 @@ export const webHost: ApplicationHost = {
40
39
  },
41
40
  exit() {},
42
41
  iconTheme: () => IconTheme.get_for_display(null),
42
+ guardUnload(active) {
43
+ if (active) window.addEventListener('beforeunload', confirmUnload);
44
+ else window.removeEventListener('beforeunload', confirmUnload);
45
+ },
43
46
  };
44
47
 
48
+ /** The only hook a page has on Ctrl+Q, closing the tab and reload; the browser words the prompt. */
49
+ function confirmUnload(event: BeforeUnloadEvent): void {
50
+ event.preventDefault();
51
+ event.returnValue = '';
52
+ }
53
+
45
54
  /** An element's parent for action resolution; a popover is rooted at the widget that owns it. */
46
55
  export function parentOfElement(node: object): object | null {
47
56
  if (!(node instanceof Element)) return null;
@@ -88,23 +97,9 @@ function followAccelerators(window: object, application: WindowApplication | nul
88
97
  }
89
98
 
90
99
  /**
91
- * `Gtk.Window:application` and the window's `Gio.ActionMap` (`win.` actions) on an element class.
100
+ * `Gtk.Window:application`, `get_application()` and `set_application()` on a window element class.
92
101
  * Setting the property is `add_window`, as on GTK; setting it to `null` is `remove_window`.
93
102
  */
94
103
  export function defineWindowApplication(proto: object): void {
95
- installActionMap(proto);
96
- Object.defineProperty(proto, 'application', {
97
- configurable: true,
98
- enumerable: true,
99
- get(this: object) {
100
- return applicationOfWindow(this);
101
- },
102
- set(this: object, value: WindowApplication | null) {
103
- const current = applicationOfWindow(this) as unknown as WindowApplication | null;
104
- if (current === value) return;
105
- current?.remove_window(this);
106
- value?.add_window(this);
107
- followAccelerators(this, value);
108
- },
109
- });
104
+ defineWindowApplicationMembers(proto, followAccelerators);
110
105
  }
@@ -5,6 +5,7 @@
5
5
  /** @type {import('./capabilities.d.mts').capabilities} */
6
6
  export const capabilities = {
7
7
  layout: 'implemented',
8
+ accessibility: 'implemented',
8
9
  strings: 'implemented',
9
10
  responses: 'implemented',
10
11
  page: 'implemented',
@@ -12,7 +13,5 @@ export const capabilities = {
12
13
  signal: 'implemented',
13
14
  bind: 'implemented',
14
15
  breakpoint: 'implemented',
15
- 'sibling-object': {
16
- refused: 'the web builder mounts one root element and has no consumer for a second root yet (ADR 0093)',
17
- },
16
+ 'sibling-object': 'implemented',
18
17
  };
@@ -12,9 +12,10 @@
12
12
  // so two scenarios sharing the literal name `colour` would share a selection.
13
13
 
14
14
  import { describe, expect, it } from '@gjsify/unit';
15
- import { RADIO_GROUP_VECTORS } from '@gjsify/adwaita-core/conformance';
15
+ import { RADIO_GROUP_VECTORS, driveCheckGroupVectors, type CheckButtonLike } from '@gjsify/adwaita-core/conformance';
16
16
 
17
17
  import type { GtkCheckButton, AdwRadio } from './elements/checks.js';
18
+ import { buildSharedTree } from './shared-tree-builder.js';
18
19
 
19
20
  function host(): HTMLElement {
20
21
  const el = document.createElement('div');
@@ -354,4 +355,53 @@ export const AdwChecksTest = async () => {
354
355
  parent.remove();
355
356
  });
356
357
  });
358
+
359
+ await describe('<gtk-check-button> group (CHECK_GROUP_VECTORS)', async () => {
360
+ await driveCheckGroupVectors(
361
+ {
362
+ name: 'adwaita-web',
363
+ isOracle: false,
364
+ make: () => mountCheckbox().el as unknown as CheckButtonLike,
365
+ activate: (button) => parts(button as unknown as HTMLElement).indicator.click(),
366
+ countToggled(button) {
367
+ let count = 0;
368
+ (button as unknown as HTMLElement).addEventListener('toggled', () => void (count += 1));
369
+ return () => count;
370
+ },
371
+ },
372
+ { describe, it, expect },
373
+ );
374
+
375
+ await it('a grouped button draws the radio and its input announces role=radio', () => {
376
+ const [a, b] = [mountCheckbox().el, mountCheckbox().el];
377
+ b.set_group(a);
378
+ expect([a.hasAttribute('radio'), parts(b).input.getAttribute('role')].join()).toBe('true,radio');
379
+ b.set_group(null);
380
+ expect([a.hasAttribute('radio'), b.hasAttribute('radio'), parts(b).input.hasAttribute('role')].join()).toBe(
381
+ 'true,false,false',
382
+ );
383
+ });
384
+
385
+ await it('a projected `group: follow` resolves by id once the tree is in the document', () => {
386
+ const tree = buildSharedTree({
387
+ tag: 'GtkBox',
388
+ children: [
389
+ { tag: 'GtkCheckButton', id: 'group-follow', props: { active: true } },
390
+ { tag: 'GtkCheckButton', id: 'group-dark', props: { group: 'group-follow' } },
391
+ ],
392
+ });
393
+ host().appendChild(tree);
394
+ const [follow, dark] = ['group-follow', 'group-dark'].map(
395
+ (id) => document.getElementById(id) as GtkCheckButton,
396
+ );
397
+ dark.set_active(true);
398
+ expect([follow.get_active(), dark.get_active()].join()).toBe('false,true');
399
+ });
400
+
401
+ await it('a group id nothing carries is refused', () => {
402
+ const el = document.createElement('gtk-check-button') as GtkCheckButton;
403
+ el.setAttribute('group', 'group-nowhere');
404
+ expect(() => (el as unknown as { connectedCallback(): void }).connectedCallback()).toThrow();
405
+ });
406
+ });
357
407
  };
@@ -0,0 +1,71 @@
1
+ // A `.blp`'s `child:` (and every other widget-valued property) becomes `slot: '<property>'` in the
2
+ // shared tree (`packages/infra/blueprint/src/project.mjs`). An element that routed only its
3
+ // DEFAULT slot refused that name on mount, and before the refusal existed it left the child
4
+ // beside the structure it had built — Learn6502's `Overlay { child: Adw.ToastOverlay { … } }`
5
+ // was the measured case. Every element whose GTK class has such a property is mounted here
6
+ // with it, and the child has to land where the element's own binding says that slot goes.
7
+ import { describe, expect, it } from '@gjsify/unit';
8
+ import type { SharedTreeNode } from '@gjsify/adwaita-core/conformance';
9
+
10
+ import { mountSharedTree } from './shared-tree-builder.js';
11
+ import { slottedChildrenOf } from './slotted-children.js';
12
+
13
+ /** The GIR class, and the widget-valued property the tree writes as its child's slot. */
14
+ const CASES: ReadonlyArray<readonly [string, string]> = [
15
+ ['GtkOverlay', 'child'],
16
+ ['AdwToastOverlay', 'child'],
17
+ ['AdwDialog', 'child'],
18
+ ['AdwAlertDialog', 'extra-child'],
19
+ ['GtkListBoxRow', 'child'],
20
+ ['GtkFlowBoxChild', 'child'],
21
+ ['GtkGraphicsOffload', 'child'],
22
+ ['GtkPaned', 'start-child'],
23
+ ['GtkPaned', 'end-child'],
24
+ ['GtkWindow', 'child'],
25
+ ['GtkWindowHandle', 'child'],
26
+ ['GtkPopover', 'child'],
27
+ ['AdwBreakpointBin', 'child'],
28
+ ['GtkPopoverBin', 'child'],
29
+ ['GtkViewport', 'child'],
30
+ ['GtkTreeExpander', 'child'],
31
+ ['GtkDragIcon', 'child'],
32
+ ['GtkSearchBar', 'child'],
33
+ ['AdwBin', 'child'],
34
+ ['AdwClamp', 'child'],
35
+ ['GtkScrolledWindow', 'child'],
36
+ ['GtkRevealer', 'child'],
37
+ ['GtkFrame', 'child'],
38
+ ['GtkExpander', 'child'],
39
+ ['AdwStatusPage', 'child'],
40
+ ];
41
+
42
+ /** Where `child` ended up, read against the slot the parent itself declares. */
43
+ function report(root: HTMLElement, child: Element, slot: string): string {
44
+ const declared = slottedChildrenOf(root)?.slots.find((candidate) => candidate.name === slot);
45
+ if (declared === undefined) return 'undeclared';
46
+ if (!root.contains(child)) return 'outside';
47
+ if (!('into' in declared)) return 'consumed';
48
+ return child.parentElement === declared.into ? 'routed' : `beside, under <${child.parentElement?.localName}>`;
49
+ }
50
+
51
+ export const AdwChildSlotTest = async () => {
52
+ await describe('adwaita-web: a widget-valued property in a tree is the slot it names', async () => {
53
+ for (const [tag, slot] of CASES) {
54
+ await it(`${tag} { ${slot}: … }`, () => {
55
+ const tree: SharedTreeNode = {
56
+ tag,
57
+ children: [{ tag: 'GtkBox', id: 'probe', slot }],
58
+ };
59
+ let outcome: string;
60
+ try {
61
+ const { root, unmount } = mountSharedTree(tree);
62
+ outcome = report(root, root.querySelector('#probe')!, slot);
63
+ unmount();
64
+ } catch (error) {
65
+ outcome = `refused: ${(error as Error).message}`;
66
+ }
67
+ expect(`${tag} ${slot}: ${outcome}`).toBe(`${tag} ${slot}: routed`);
68
+ });
69
+ }
70
+ });
71
+ };