@gjsify/adwaita-web 0.54.0 → 0.55.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 (439) hide show
  1. package/dist/adwaita-web.css +8066 -3033
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/adw-application-window.spec.d.ts +1 -0
  4. package/lib/types/adw-bin.spec.d.ts +1 -0
  5. package/lib/types/adw-breakpoint-bin.spec.d.ts +1 -0
  6. package/lib/types/adw-clamp-scrollable.spec.d.ts +1 -0
  7. package/lib/types/adw-multi-layout-view.spec.d.ts +1 -0
  8. package/lib/types/adw-preferences-row.spec.d.ts +1 -0
  9. package/lib/types/adw-shortcuts.spec.d.ts +1 -0
  10. package/lib/types/adw-tab-bar.spec.d.ts +1 -0
  11. package/lib/types/adw-tab-button.spec.d.ts +1 -0
  12. package/lib/types/adw-tab-overview.spec.d.ts +1 -0
  13. package/lib/types/adw-view-switcher-sidebar.spec.d.ts +1 -0
  14. package/lib/types/adw-window.spec.d.ts +1 -0
  15. package/lib/types/breakpoints.d.ts +14 -3
  16. package/lib/types/construct-vectors.spec.d.ts +1 -0
  17. package/lib/types/elements/adw-application-window.d.ts +27 -0
  18. package/lib/types/elements/adw-bin.d.ts +17 -0
  19. package/lib/types/elements/adw-breakpoint-bin.d.ts +67 -0
  20. package/lib/types/elements/adw-clamp-scrollable.d.ts +38 -0
  21. package/lib/types/elements/adw-dialog.d.ts +10 -0
  22. package/lib/types/elements/adw-layout-slot.d.ts +19 -0
  23. package/lib/types/elements/adw-multi-layout-view.d.ts +101 -0
  24. package/lib/types/elements/adw-preferences-row.d.ts +31 -0
  25. package/lib/types/elements/adw-shortcuts-dialog.d.ts +13 -0
  26. package/lib/types/elements/adw-shortcuts-item.d.ts +36 -0
  27. package/lib/types/elements/adw-shortcuts-section.d.ts +14 -0
  28. package/lib/types/elements/adw-sidebar.d.ts +5 -0
  29. package/lib/types/elements/adw-switch-row.d.ts +3 -0
  30. package/lib/types/elements/adw-tab-bar.d.ts +108 -0
  31. package/lib/types/elements/adw-tab-button.d.ts +32 -0
  32. package/lib/types/elements/adw-tab-overview.d.ts +199 -0
  33. package/lib/types/elements/adw-tab-view.d.ts +19 -23
  34. package/lib/types/elements/adw-view-switcher-sidebar.d.ts +84 -0
  35. package/lib/types/elements/adw-window.d.ts +40 -0
  36. package/lib/types/elements/gtk-about-dialog.d.ts +146 -0
  37. package/lib/types/elements/gtk-application-window.d.ts +34 -0
  38. package/lib/types/elements/gtk-aspect-frame.d.ts +27 -0
  39. package/lib/types/elements/gtk-button.d.ts +9 -0
  40. package/lib/types/elements/gtk-calendar.d.ts +159 -0
  41. package/lib/types/elements/gtk-center-box.d.ts +39 -0
  42. package/lib/types/elements/gtk-color-dialog-button.d.ts +69 -0
  43. package/lib/types/elements/gtk-column-view.d.ts +50 -0
  44. package/lib/types/elements/gtk-drag-icon.d.ts +78 -0
  45. package/lib/types/elements/gtk-drawing-area.d.ts +95 -0
  46. package/lib/types/elements/gtk-editable-label.d.ts +33 -0
  47. package/lib/types/elements/gtk-emoji-chooser.d.ts +84 -0
  48. package/lib/types/elements/gtk-entry.d.ts +20 -1
  49. package/lib/types/elements/gtk-expander.d.ts +49 -0
  50. package/lib/types/elements/gtk-fixed.d.ts +13 -0
  51. package/lib/types/elements/gtk-flow-box-child.d.ts +14 -0
  52. package/lib/types/elements/gtk-flow-box.d.ts +87 -0
  53. package/lib/types/elements/gtk-font-dialog-button.d.ts +83 -0
  54. package/lib/types/elements/gtk-frame.d.ts +24 -0
  55. package/lib/types/elements/gtk-gl-area.d.ts +136 -0
  56. package/lib/types/elements/gtk-graphics-offload.d.ts +24 -0
  57. package/lib/types/elements/gtk-grid-view.d.ts +39 -0
  58. package/lib/types/elements/gtk-grid.d.ts +27 -0
  59. package/lib/types/elements/gtk-header-bar.d.ts +60 -0
  60. package/lib/types/elements/gtk-inscription.d.ts +64 -0
  61. package/lib/types/elements/gtk-label.d.ts +3 -0
  62. package/lib/types/elements/gtk-level-bar.d.ts +59 -0
  63. package/lib/types/elements/gtk-link-button.d.ts +29 -0
  64. package/lib/types/elements/gtk-list-box-row.d.ts +54 -0
  65. package/lib/types/elements/gtk-list-box.d.ts +100 -0
  66. package/lib/types/elements/gtk-list-view.d.ts +73 -0
  67. package/lib/types/elements/gtk-media-controls.d.ts +142 -0
  68. package/lib/types/elements/gtk-notebook.d.ts +148 -0
  69. package/lib/types/elements/gtk-overlay.d.ts +22 -0
  70. package/lib/types/elements/gtk-page-setup-unix-dialog.d.ts +87 -0
  71. package/lib/types/elements/gtk-paned.d.ts +121 -0
  72. package/lib/types/elements/gtk-password-entry.d.ts +27 -0
  73. package/lib/types/elements/gtk-picture.d.ts +66 -0
  74. package/lib/types/elements/gtk-popover-bin.d.ts +82 -0
  75. package/lib/types/elements/gtk-popover-menu-bar.d.ts +66 -0
  76. package/lib/types/elements/gtk-popover-menu.d.ts +83 -0
  77. package/lib/types/elements/gtk-popover.d.ts +28 -3
  78. package/lib/types/elements/gtk-print-unix-dialog.d.ts +167 -0
  79. package/lib/types/elements/gtk-revealer.d.ts +55 -0
  80. package/lib/types/elements/gtk-scale-button.d.ts +59 -0
  81. package/lib/types/elements/gtk-scale.d.ts +98 -0
  82. package/lib/types/elements/gtk-scrollbar.d.ts +124 -0
  83. package/lib/types/elements/gtk-scrolled-window.d.ts +121 -0
  84. package/lib/types/elements/gtk-search-bar.d.ts +39 -0
  85. package/lib/types/elements/gtk-search-entry.d.ts +27 -0
  86. package/lib/types/elements/gtk-separator.d.ts +10 -0
  87. package/lib/types/elements/gtk-spin-button.d.ts +94 -0
  88. package/lib/types/elements/gtk-spinner.d.ts +14 -0
  89. package/lib/types/elements/gtk-stack-sidebar.d.ts +43 -0
  90. package/lib/types/elements/gtk-stack-switcher.d.ts +58 -0
  91. package/lib/types/elements/gtk-stack.d.ts +154 -0
  92. package/lib/types/elements/gtk-switch.d.ts +4 -1
  93. package/lib/types/elements/gtk-text-view.d.ts +62 -0
  94. package/lib/types/elements/gtk-text.d.ts +45 -0
  95. package/lib/types/elements/gtk-toggle-button.d.ts +45 -0
  96. package/lib/types/elements/gtk-tree-expander.d.ts +38 -0
  97. package/lib/types/elements/gtk-video.d.ts +97 -0
  98. package/lib/types/elements/gtk-viewport.d.ts +84 -0
  99. package/lib/types/elements/gtk-window-controls.d.ts +80 -0
  100. package/lib/types/elements/gtk-window-handle.d.ts +39 -0
  101. package/lib/types/elements/gtk-window.d.ts +97 -0
  102. package/lib/types/elements/justification.d.ts +2 -0
  103. package/lib/types/elements/popover-menu.d.ts +18 -0
  104. package/lib/types/elements/roving-focus.d.ts +29 -1
  105. package/lib/types/emoji-data.d.ts +38 -0
  106. package/lib/types/font-description.d.ts +96 -0
  107. package/lib/types/gtk-about-dialog.spec.d.ts +1 -0
  108. package/lib/types/gtk-action-bar.spec.d.ts +1 -0
  109. package/lib/types/gtk-application-window.spec.d.ts +1 -0
  110. package/lib/types/gtk-aspect-frame.spec.d.ts +1 -0
  111. package/lib/types/gtk-calendar.spec.d.ts +1 -0
  112. package/lib/types/gtk-center-box.spec.d.ts +1 -0
  113. package/lib/types/gtk-color-dialog-button.spec.d.ts +1 -0
  114. package/lib/types/gtk-column-view.spec.d.ts +1 -0
  115. package/lib/types/gtk-drag-icon.spec.d.ts +1 -0
  116. package/lib/types/gtk-drawing-area.spec.d.ts +1 -0
  117. package/lib/types/gtk-editable-label.spec.d.ts +1 -0
  118. package/lib/types/gtk-emoji-chooser.spec.d.ts +1 -0
  119. package/lib/types/gtk-expander.spec.d.ts +1 -0
  120. package/lib/types/gtk-fixed.spec.d.ts +1 -0
  121. package/lib/types/gtk-flow-box.spec.d.ts +1 -0
  122. package/lib/types/gtk-font-dialog-button.spec.d.ts +1 -0
  123. package/lib/types/gtk-frame.spec.d.ts +1 -0
  124. package/lib/types/gtk-gl-area.spec.d.ts +1 -0
  125. package/lib/types/gtk-graphics-offload.spec.d.ts +1 -0
  126. package/lib/types/gtk-grid-view.spec.d.ts +1 -0
  127. package/lib/types/gtk-grid.spec.d.ts +1 -0
  128. package/lib/types/gtk-header-bar.spec.d.ts +1 -0
  129. package/lib/types/gtk-inscription.spec.d.ts +1 -0
  130. package/lib/types/gtk-level-bar.spec.d.ts +1 -0
  131. package/lib/types/gtk-link-button.spec.d.ts +1 -0
  132. package/lib/types/gtk-list-box.spec.d.ts +1 -0
  133. package/lib/types/gtk-list-view.spec.d.ts +1 -0
  134. package/lib/types/gtk-media-controls.spec.d.ts +1 -0
  135. package/lib/types/gtk-notebook.spec.d.ts +1 -0
  136. package/lib/types/gtk-overlay.spec.d.ts +1 -0
  137. package/lib/types/gtk-paned.spec.d.ts +1 -0
  138. package/lib/types/gtk-password-entry.spec.d.ts +1 -0
  139. package/lib/types/gtk-picture.spec.d.ts +1 -0
  140. package/lib/types/gtk-popover-bin.spec.d.ts +1 -0
  141. package/lib/types/gtk-popover-menu-bar.spec.d.ts +1 -0
  142. package/lib/types/gtk-popover-menu.spec.d.ts +1 -0
  143. package/lib/types/gtk-popover-role.spec.d.ts +1 -0
  144. package/lib/types/gtk-popover.spec.d.ts +1 -0
  145. package/lib/types/gtk-print-dialogs.spec.d.ts +1 -0
  146. package/lib/types/gtk-revealer.spec.d.ts +1 -0
  147. package/lib/types/gtk-scale-button.spec.d.ts +1 -0
  148. package/lib/types/gtk-scale.spec.d.ts +1 -0
  149. package/lib/types/gtk-scrollbar.spec.d.ts +1 -0
  150. package/lib/types/gtk-scrolled-window.spec.d.ts +1 -0
  151. package/lib/types/gtk-search-bar.spec.d.ts +1 -0
  152. package/lib/types/gtk-search-entry.spec.d.ts +1 -0
  153. package/lib/types/gtk-separator.spec.d.ts +1 -0
  154. package/lib/types/gtk-spin-button.spec.d.ts +1 -0
  155. package/lib/types/gtk-spinner.spec.d.ts +1 -0
  156. package/lib/types/gtk-stack-sidebar.spec.d.ts +1 -0
  157. package/lib/types/gtk-stack-switcher.spec.d.ts +1 -0
  158. package/lib/types/gtk-stack.spec.d.ts +1 -0
  159. package/lib/types/gtk-text-view.spec.d.ts +1 -0
  160. package/lib/types/gtk-text.spec.d.ts +1 -0
  161. package/lib/types/gtk-toggle-button.spec.d.ts +1 -0
  162. package/lib/types/gtk-tree-expander.spec.d.ts +1 -0
  163. package/lib/types/gtk-video.spec.d.ts +1 -0
  164. package/lib/types/gtk-viewport.spec.d.ts +1 -0
  165. package/lib/types/gtk-window-controls.spec.d.ts +1 -0
  166. package/lib/types/gtk-window-handle.spec.d.ts +1 -0
  167. package/lib/types/gtk-window.spec.d.ts +1 -0
  168. package/lib/types/index.d.ts +4 -1
  169. package/lib/types/list-view-dom.d.ts +44 -0
  170. package/lib/types/namespace/adw.d.ts +12 -0
  171. package/lib/types/namespace/gtk.d.ts +61 -0
  172. package/lib/types/page-setup.d.ts +100 -0
  173. package/lib/types/scroll-shading.d.ts +15 -1
  174. package/lib/types/shared-tree-builder.d.ts +30 -2
  175. package/lib/types/signals.d.ts +4 -0
  176. package/lib/types/styles.generated.d.ts +1 -1
  177. package/lib/types/tab-chip.d.ts +73 -0
  178. package/lib/types/template-classes.d.ts +11 -0
  179. package/package.json +12 -8
  180. package/scss/_action_row.scss +2 -2
  181. package/scss/_application_window.scss +26 -0
  182. package/scss/_aspect_frame.scss +25 -0
  183. package/scss/_bin.scss +26 -0
  184. package/scss/_button.scss +45 -0
  185. package/scss/_calendar.scss +176 -0
  186. package/scss/_center_box.scss +25 -0
  187. package/scss/_checks.scss +6 -1
  188. package/scss/_clamp_scrollable.scss +28 -0
  189. package/scss/_color_button.scss +67 -0
  190. package/scss/_column_view.scss +126 -0
  191. package/scss/_drag_icon.scss +55 -0
  192. package/scss/_drawing.scss +85 -0
  193. package/scss/_emoji_chooser.scss +213 -0
  194. package/scss/_entry.scss +13 -5
  195. package/scss/_entry_row.scss +1 -1
  196. package/scss/_expander.scss +113 -0
  197. package/scss/_fixed.scss +29 -0
  198. package/scss/_flow_box.scss +94 -0
  199. package/scss/_font_button.scss +63 -0
  200. package/scss/_frame.scss +41 -0
  201. package/scss/_grid.scss +19 -0
  202. package/scss/_grid_view.scss +46 -0
  203. package/scss/_gtk_about_dialog.scss +186 -0
  204. package/scss/_headerbar.scss +132 -0
  205. package/scss/_icons.generated.scss +186 -0
  206. package/scss/_inscription.scss +125 -0
  207. package/scss/_level_bar.scss +190 -0
  208. package/scss/_link.scss +82 -0
  209. package/scss/_list_box.scss +88 -0
  210. package/scss/_list_view.scss +96 -0
  211. package/scss/_media_controls.scss +139 -0
  212. package/scss/_overlay.scss +68 -0
  213. package/scss/_paned.scss +130 -0
  214. package/scss/_password_entry.scss +83 -0
  215. package/scss/_picture.scss +80 -0
  216. package/scss/_popover_bin.scss +53 -0
  217. package/scss/_popover_menu_bar.scss +104 -0
  218. package/scss/_preferences_row.scss +51 -0
  219. package/scss/_print_dialogs.scss +328 -0
  220. package/scss/_reset.scss +21 -19
  221. package/scss/_revealer.scss +37 -0
  222. package/scss/_row.scss +20 -5
  223. package/scss/_scale.scss +298 -0
  224. package/scss/_scale_button.scss +167 -0
  225. package/scss/_scrollbar.scss +230 -0
  226. package/scss/_scrolled_window.scss +131 -0
  227. package/scss/_search_bar.scss +118 -0
  228. package/scss/_search_entry.scss +86 -0
  229. package/scss/_separator.scss +44 -0
  230. package/scss/_shortcuts_dialog.scss +100 -0
  231. package/scss/_spin_button.scss +172 -0
  232. package/scss/_spin_row.scss +56 -8
  233. package/scss/_spinner.scss +85 -4
  234. package/scss/_stack.scss +729 -0
  235. package/scss/_switch.scss +13 -5
  236. package/scss/_switch_row.scss +10 -0
  237. package/scss/_tab_bar.scss +317 -0
  238. package/scss/_tab_button.scss +98 -0
  239. package/scss/_tab_overview.scss +483 -0
  240. package/scss/_text.scss +182 -0
  241. package/scss/_theme.scss +1 -1
  242. package/scss/_tree_expander.scss +62 -0
  243. package/scss/_variables.scss +1 -1
  244. package/scss/_video.scss +146 -0
  245. package/scss/_view_switcher_sidebar.scss +112 -0
  246. package/scss/_viewport.scss +50 -0
  247. package/scss/_window.scss +118 -1
  248. package/scss/_window_handle.scss +20 -0
  249. package/scss/adwaita-skin.scss +109 -0
  250. package/src/adw-action-rows.spec.ts +76 -0
  251. package/src/adw-application-window.spec.ts +118 -0
  252. package/src/adw-bin.spec.ts +75 -0
  253. package/src/adw-breakpoint-bin.spec.ts +324 -0
  254. package/src/adw-clamp-scrollable.spec.ts +189 -0
  255. package/src/adw-multi-layout-view.spec.ts +243 -0
  256. package/src/adw-preferences-row.spec.ts +164 -0
  257. package/src/adw-row-state.spec.ts +142 -0
  258. package/src/adw-shortcuts.spec.ts +155 -0
  259. package/src/adw-tab-bar.spec.ts +287 -0
  260. package/src/adw-tab-button.spec.ts +101 -0
  261. package/src/adw-tab-overview.spec.ts +443 -0
  262. package/src/adw-view-switcher-sidebar.spec.ts +281 -0
  263. package/src/adw-window.spec.ts +129 -0
  264. package/src/breakpoints.ts +25 -8
  265. package/src/capabilities.d.mts +3 -0
  266. package/src/capabilities.mjs +18 -0
  267. package/src/connect-lifecycle.spec.ts +12 -0
  268. package/src/construct-vectors.spec.ts +175 -0
  269. package/src/elements/adw-application-window.ts +154 -0
  270. package/src/elements/adw-bin.ts +101 -0
  271. package/src/elements/adw-breakpoint-bin.ts +287 -0
  272. package/src/elements/adw-clamp-scrollable.ts +207 -0
  273. package/src/elements/adw-dialog.ts +19 -0
  274. package/src/elements/adw-layout-slot.ts +95 -0
  275. package/src/elements/adw-multi-layout-view.ts +326 -0
  276. package/src/elements/adw-preferences-row.ts +150 -0
  277. package/src/elements/adw-shortcuts-dialog.ts +52 -0
  278. package/src/elements/adw-shortcuts-item.ts +171 -0
  279. package/src/elements/adw-shortcuts-section.ts +86 -0
  280. package/src/elements/adw-sidebar.ts +42 -3
  281. package/src/elements/adw-switch-row.ts +28 -1
  282. package/src/elements/adw-tab-bar.ts +529 -0
  283. package/src/elements/adw-tab-button.ts +194 -0
  284. package/src/elements/adw-tab-overview.ts +1011 -0
  285. package/src/elements/adw-tab-view.ts +66 -145
  286. package/src/elements/adw-toggle-group.ts +20 -5
  287. package/src/elements/adw-view-stack.ts +18 -0
  288. package/src/elements/adw-view-switcher-sidebar.ts +332 -0
  289. package/src/elements/adw-window.ts +185 -11
  290. package/src/elements/checks.ts +35 -8
  291. package/src/elements/gtk-about-dialog.ts +811 -0
  292. package/src/elements/gtk-application-window.ts +108 -0
  293. package/src/elements/gtk-aspect-frame.ts +160 -0
  294. package/src/elements/gtk-button.ts +12 -0
  295. package/src/elements/gtk-calendar.ts +757 -0
  296. package/src/elements/gtk-center-box.ts +184 -0
  297. package/src/elements/gtk-color-dialog-button.ts +278 -0
  298. package/src/elements/gtk-column-view.ts +320 -0
  299. package/src/elements/gtk-drag-icon.ts +257 -0
  300. package/src/elements/gtk-drawing-area.ts +303 -0
  301. package/src/elements/gtk-editable-label.ts +204 -0
  302. package/src/elements/gtk-emoji-chooser.ts +542 -0
  303. package/src/elements/gtk-entry.ts +39 -5
  304. package/src/elements/gtk-expander.ts +301 -0
  305. package/src/elements/gtk-fixed.ts +99 -0
  306. package/src/elements/gtk-flow-box-child.ts +68 -0
  307. package/src/elements/gtk-flow-box.ts +480 -0
  308. package/src/elements/gtk-font-dialog-button.ts +432 -0
  309. package/src/elements/gtk-frame.ts +174 -0
  310. package/src/elements/gtk-gl-area.ts +476 -0
  311. package/src/elements/gtk-graphics-offload.ts +130 -0
  312. package/src/elements/gtk-grid-view.ts +168 -0
  313. package/src/elements/gtk-grid.ts +127 -0
  314. package/src/elements/gtk-header-bar.ts +273 -0
  315. package/src/elements/gtk-inscription.ts +387 -0
  316. package/src/elements/gtk-label.ts +19 -11
  317. package/src/elements/gtk-level-bar.ts +323 -0
  318. package/src/elements/gtk-link-button.ts +138 -0
  319. package/src/elements/gtk-list-box-row.ts +195 -0
  320. package/src/elements/gtk-list-box.ts +413 -0
  321. package/src/elements/gtk-list-view.ts +370 -0
  322. package/src/elements/gtk-media-controls.ts +509 -0
  323. package/src/elements/gtk-notebook.ts +664 -0
  324. package/src/elements/gtk-overlay.ts +149 -0
  325. package/src/elements/gtk-page-setup-unix-dialog.ts +420 -0
  326. package/src/elements/gtk-paned.ts +597 -0
  327. package/src/elements/gtk-password-entry.ts +170 -0
  328. package/src/elements/gtk-picture.ts +254 -0
  329. package/src/elements/gtk-popover-bin.ts +332 -0
  330. package/src/elements/gtk-popover-menu-bar.ts +355 -0
  331. package/src/elements/gtk-popover-menu.ts +339 -0
  332. package/src/elements/gtk-popover.ts +92 -10
  333. package/src/elements/gtk-print-unix-dialog.ts +989 -0
  334. package/src/elements/gtk-progress-bar.ts +24 -11
  335. package/src/elements/gtk-revealer.ts +406 -0
  336. package/src/elements/gtk-scale-button.ts +358 -0
  337. package/src/elements/gtk-scale.ts +492 -0
  338. package/src/elements/gtk-scrollbar.ts +528 -0
  339. package/src/elements/gtk-scrolled-window.ts +642 -0
  340. package/src/elements/gtk-search-bar.ts +200 -0
  341. package/src/elements/gtk-search-entry.ts +172 -0
  342. package/src/elements/gtk-separator.ts +52 -0
  343. package/src/elements/gtk-spin-button.ts +475 -0
  344. package/src/elements/gtk-spinner.ts +126 -0
  345. package/src/elements/gtk-stack-sidebar.ts +212 -0
  346. package/src/elements/gtk-stack-switcher.ts +279 -0
  347. package/src/elements/gtk-stack.ts +748 -0
  348. package/src/elements/gtk-switch.ts +52 -19
  349. package/src/elements/gtk-text-view.ts +290 -0
  350. package/src/elements/gtk-text.ts +219 -0
  351. package/src/elements/gtk-toggle-button.ts +239 -0
  352. package/src/elements/gtk-tree-expander.ts +243 -0
  353. package/src/elements/gtk-video.ts +423 -0
  354. package/src/elements/gtk-viewport.ts +277 -0
  355. package/src/elements/gtk-window-controls.ts +341 -0
  356. package/src/elements/gtk-window-handle.ts +221 -0
  357. package/src/elements/gtk-window.ts +366 -0
  358. package/src/elements/justification.ts +24 -0
  359. package/src/elements/popover-menu.ts +24 -0
  360. package/src/elements/roving-focus.ts +53 -21
  361. package/src/emoji-data.ts +577 -0
  362. package/src/entry-rows.spec.ts +31 -0
  363. package/src/font-description.ts +238 -0
  364. package/src/gtk-about-dialog.spec.ts +250 -0
  365. package/src/gtk-action-bar.spec.ts +89 -0
  366. package/src/gtk-application-window.spec.ts +107 -0
  367. package/src/gtk-aspect-frame.spec.ts +114 -0
  368. package/src/gtk-calendar.spec.ts +589 -0
  369. package/src/gtk-center-box.spec.ts +125 -0
  370. package/src/gtk-color-dialog-button.spec.ts +141 -0
  371. package/src/gtk-column-view.spec.ts +154 -0
  372. package/src/gtk-drag-icon.spec.ts +190 -0
  373. package/src/gtk-drawing-area.spec.ts +211 -0
  374. package/src/gtk-editable-label.spec.ts +190 -0
  375. package/src/gtk-emoji-chooser.spec.ts +173 -0
  376. package/src/gtk-entry.spec.ts +23 -0
  377. package/src/gtk-expander.spec.ts +170 -0
  378. package/src/gtk-fixed.spec.ts +97 -0
  379. package/src/gtk-flow-box.spec.ts +289 -0
  380. package/src/gtk-font-dialog-button.spec.ts +166 -0
  381. package/src/gtk-frame.spec.ts +119 -0
  382. package/src/gtk-gl-area.spec.ts +361 -0
  383. package/src/gtk-graphics-offload.spec.ts +119 -0
  384. package/src/gtk-grid-view.spec.ts +106 -0
  385. package/src/gtk-grid.spec.ts +122 -0
  386. package/src/gtk-header-bar.spec.ts +202 -0
  387. package/src/gtk-inscription.spec.ts +235 -0
  388. package/src/gtk-level-bar.spec.ts +199 -0
  389. package/src/gtk-link-button.spec.ts +117 -0
  390. package/src/gtk-list-box.spec.ts +422 -0
  391. package/src/gtk-list-view.spec.ts +203 -0
  392. package/src/gtk-media-controls.spec.ts +295 -0
  393. package/src/gtk-notebook.spec.ts +546 -0
  394. package/src/gtk-overlay.spec.ts +141 -0
  395. package/src/gtk-paned.spec.ts +267 -0
  396. package/src/gtk-password-entry.spec.ts +150 -0
  397. package/src/gtk-picture.spec.ts +172 -0
  398. package/src/gtk-popover-bin.spec.ts +229 -0
  399. package/src/gtk-popover-menu-bar.spec.ts +296 -0
  400. package/src/gtk-popover-menu.spec.ts +261 -0
  401. package/src/gtk-popover-role.spec.ts +138 -0
  402. package/src/gtk-popover.spec.ts +264 -0
  403. package/src/gtk-print-dialogs.spec.ts +251 -0
  404. package/src/gtk-revealer.spec.ts +164 -0
  405. package/src/gtk-scale-button.spec.ts +218 -0
  406. package/src/gtk-scale.spec.ts +213 -0
  407. package/src/gtk-scrollbar.spec.ts +244 -0
  408. package/src/gtk-scrolled-window.spec.ts +262 -0
  409. package/src/gtk-search-bar.spec.ts +179 -0
  410. package/src/gtk-search-entry.spec.ts +142 -0
  411. package/src/gtk-separator.spec.ts +74 -0
  412. package/src/gtk-spin-button.spec.ts +186 -0
  413. package/src/gtk-spinner.spec.ts +130 -0
  414. package/src/gtk-stack-sidebar.spec.ts +217 -0
  415. package/src/gtk-stack-switcher.spec.ts +249 -0
  416. package/src/gtk-stack.spec.ts +432 -0
  417. package/src/gtk-text-view.spec.ts +189 -0
  418. package/src/gtk-text.spec.ts +163 -0
  419. package/src/gtk-toggle-button.spec.ts +186 -0
  420. package/src/gtk-tree-expander.spec.ts +201 -0
  421. package/src/gtk-video.spec.ts +240 -0
  422. package/src/gtk-viewport.spec.ts +140 -0
  423. package/src/gtk-window-controls.spec.ts +232 -0
  424. package/src/gtk-window-handle.spec.ts +158 -0
  425. package/src/gtk-window.spec.ts +190 -0
  426. package/src/index.ts +9 -1
  427. package/src/keyboard-operable.spec.ts +33 -1
  428. package/src/list-view-dom.ts +121 -0
  429. package/src/namespace/adw.ts +12 -0
  430. package/src/namespace/gtk.ts +61 -0
  431. package/src/page-setup.ts +222 -0
  432. package/src/scroll-shading.ts +18 -1
  433. package/src/shared-tree-builder.ts +263 -21
  434. package/src/signals.ts +15 -0
  435. package/src/style-classes.spec.ts +9 -8
  436. package/src/styles.generated.ts +1 -1
  437. package/src/tab-chip.ts +200 -0
  438. package/src/template-classes.ts +45 -0
  439. package/src/test.browser.mts +152 -0
@@ -0,0 +1,240 @@
1
+ // DOM-level tests for <gtk-video>. What matters is the OVERLAY ICON, because it is the only
2
+ // part of this widget with logic in it: `gtk_video_update_overlay_icon` is a four-way choice
3
+ // whose ORDER carries meaning (no stream before error, error before ended), and
4
+ // `gtk_video_update_playing` hides the icon by VISIBILITY rather than swapping it for a pause
5
+ // glyph. Both are asserted here against the arm they come from.
6
+ import { describe, expect, it } from '@gjsify/unit';
7
+
8
+ import type { GtkVideo } from './elements/gtk-video.js';
9
+
10
+ /** A `<video>` with no `src`: no stream to report, so every media event is synthetic here. */
11
+ function mount(withMedia: boolean, attrs: Record<string, string> = {}): { el: GtkVideo; host: HTMLElement } {
12
+ const host = document.createElement('div');
13
+ document.body.appendChild(host);
14
+ const el = document.createElement('gtk-video') as GtkVideo;
15
+ for (const [name, value] of Object.entries(attrs)) el.setAttribute(name, value);
16
+ if (withMedia) el.appendChild(document.createElement('video'));
17
+ host.appendChild(el);
18
+ return { el, host };
19
+ }
20
+
21
+ function overlayIcon(el: GtkVideo): string | null {
22
+ return el.querySelector('.adw-video-overlay-icon gtk-image')?.getAttribute('icon-name') ?? null;
23
+ }
24
+
25
+ export const GtkVideoTest = async () => {
26
+ await describe('<gtk-video> the four-way overlay icon', async () => {
27
+ await it('draws the EJECT glyph with no media stream at all', () => {
28
+ // gtkvideo.c:629 — the no-stream arm is FIRST, not the fallback, so an element
29
+ // that has not been given a `<video>` shows "there is nothing here to play"
30
+ // rather than a play glyph over nothing.
31
+ const { el, host } = mount(false);
32
+ expect(el.media).toBe(null);
33
+ expect(overlayIcon(el)).toBe('media-eject-symbolic');
34
+ host.remove();
35
+ });
36
+
37
+ await it('draws the PLAY glyph once a stream is present and healthy', () => {
38
+ // gtkvideo.c:645 — the last arm, reached only with a stream, no error, not ended.
39
+ const { el, host } = mount(true);
40
+ expect(overlayIcon(el)).toBe('media-playback-start-symbolic');
41
+ host.remove();
42
+ });
43
+
44
+ await it('draws the ERROR glyph and takes the message as its tooltip', () => {
45
+ // gtkvideo.c:633-643 — the error arm also sets the tooltip to `error->message`,
46
+ // which is the only place this widget carries text that is not an accessible name.
47
+ const { el, host } = mount(true);
48
+ const media = el.media!;
49
+ Object.defineProperty(media, 'error', { value: { message: 'no such stream' }, configurable: true });
50
+ media.dispatchEvent(new Event('error'));
51
+ expect(overlayIcon(el)).toBe('dialog-error-symbolic');
52
+ const button = el.querySelector('.adw-video-overlay-icon') as HTMLButtonElement;
53
+ expect(button.title).toBe('no such stream');
54
+ host.remove();
55
+ });
56
+
57
+ await it('draws the REPEAT glyph once the stream has ended', () => {
58
+ // gtkvideo.c:638-639 — the ended arm.
59
+ const { el, host } = mount(true);
60
+ el.media!.dispatchEvent(new Event('ended'));
61
+ expect(overlayIcon(el)).toBe('media-playlist-repeat-symbolic');
62
+ host.remove();
63
+ });
64
+
65
+ await it('prefers ERROR over ENDED, which is what the arm order says', () => {
66
+ // gtkvideo.c:632-638 — the error test comes first, so a stream that both failed and
67
+ // ended shows the error. Asserting the reverse would pass on a port that swapped
68
+ // the two arms.
69
+ const { el, host } = mount(true);
70
+ const media = el.media!;
71
+ media.dispatchEvent(new Event('ended'));
72
+ Object.defineProperty(media, 'error', { value: { message: 'boom' }, configurable: true });
73
+ media.dispatchEvent(new Event('error'));
74
+ expect(overlayIcon(el)).toBe('dialog-error-symbolic');
75
+ host.remove();
76
+ });
77
+ });
78
+
79
+ await describe('<gtk-video> the overlay is hidden while playing', async () => {
80
+ await it('a play HIDES the icon and a pause shows it, both by visibility', () => {
81
+ // gtkvideo.c:665 — `gtk_widget_set_visible (self->overlay_icon, !playing)`. There
82
+ // is no pause glyph in the overlay: the controls bar carries the pause button, and
83
+ // a playing video shows the frames and nothing on top of them.
84
+ const { el, host } = mount(true);
85
+ const button = el.querySelector('.adw-video-overlay-icon') as HTMLButtonElement;
86
+ expect(button.hidden).toBe(false);
87
+ el.media!.dispatchEvent(new Event('play'));
88
+ expect(button.hidden).toBe(true);
89
+ expect(el.classList.contains('playing')).toBe(true);
90
+ el.media!.dispatchEvent(new Event('pause'));
91
+ expect(button.hidden).toBe(false);
92
+ expect(el.classList.contains('playing')).toBe(false);
93
+ host.remove();
94
+ });
95
+
96
+ await it("keeps the icon's GLYPH across the hide — it is not swapped for a pause", () => {
97
+ const { el, host } = mount(true);
98
+ el.media!.dispatchEvent(new Event('play'));
99
+ expect(overlayIcon(el)).toBe('media-playback-start-symbolic');
100
+ host.remove();
101
+ });
102
+
103
+ await it('announces playing as its own notify, which the C does not', () => {
104
+ const { el, host } = mount(true);
105
+ const events: unknown[] = [];
106
+ el.addEventListener('notify::playing', (e) => events.push((e as CustomEvent).detail));
107
+ el.media!.dispatchEvent(new Event('play'));
108
+ el.media!.dispatchEvent(new Event('pause'));
109
+ expect(events).toStrictEqual([{ playing: true }, { playing: false }]);
110
+ host.remove();
111
+ });
112
+ });
113
+
114
+ await describe('<gtk-video> the properties', async () => {
115
+ await it('autoplay and loop default to FALSE and apply to the media element', () => {
116
+ // ui/gtkvideo.ui's `<property name="enabled">disabled</property>` on the
117
+ // graphics offload is the same shape: the template starts the widget off.
118
+ const { el, host } = mount(true);
119
+ expect(el.autoplay).toBe(false);
120
+ expect(el.loop).toBe(false);
121
+ expect(el.media!.loop).toBe(false);
122
+ el.loop = true;
123
+ expect(el.media!.loop).toBe(true);
124
+ host.remove();
125
+ });
126
+
127
+ await it('NEVER gives the media element an autoplay attribute of its own', () => {
128
+ // The C's `autoplay` is not the element's autoplay attribute: it plays when the
129
+ // stream reports `prepared` AND the widget is mapped (gtkvideo.c:684-690), and
130
+ // the readiness gate is bound separately. Handing `<video>` an autoplay attribute
131
+ // would bypass the second condition entirely.
132
+ const { el, host } = mount(true, { autoplay: '' });
133
+ expect(el.autoplay).toBe(true);
134
+ expect(el.media!.autoplay).toBe(false);
135
+ host.remove();
136
+ });
137
+
138
+ await it('plays on readiness, not on the property write', () => {
139
+ const { el, host } = mount(true, { autoplay: '' });
140
+ const media = el.media!;
141
+ let played = false;
142
+ media.play = () => {
143
+ played = true;
144
+ return Promise.resolve();
145
+ };
146
+ expect(played).toBe(false);
147
+ media.dispatchEvent(new Event('loadeddata'));
148
+ expect(played).toBe(true);
149
+ host.remove();
150
+ });
151
+
152
+ await it('graphics-offload is stored and class-flagged, and buys no rendering', () => {
153
+ // `GtkGraphicsOffload` moves a subtree into a shared GL texture; CSS has no such
154
+ // mechanism. The property is observed faithfully — that is what the
155
+ // element-properties ratchet measures — and the class is deliberately unstyled.
156
+ const { el, host } = mount(true);
157
+ expect(el.graphicsOffload).toBe(false);
158
+ el.graphicsOffload = true;
159
+ expect(el.hasAttribute('graphics-offload')).toBe(true);
160
+ expect(el.classList.contains('graphics-offload')).toBe(true);
161
+ host.remove();
162
+ });
163
+
164
+ await it('notifies once per real change', () => {
165
+ const { el, host } = mount(true);
166
+ const events: unknown[] = [];
167
+ el.addEventListener('notify::loop', (e) => events.push((e as CustomEvent).detail));
168
+ el.loop = true;
169
+ el.loop = true;
170
+ el.loop = false;
171
+ expect(events).toStrictEqual([{ loop: true }, { loop: false }]);
172
+ host.remove();
173
+ });
174
+ });
175
+
176
+ await describe('<gtk-video> the controls reveal', async () => {
177
+ await it('is hidden until revealControls() and stays out of the way while so', async () => {
178
+ // ui/gtkvideo.ui's `controls_revealer` has `measure` 1, so the bar is allocated
179
+ // even while hidden — which is why this is an opacity transition and not
180
+ // `display: none`, and why the revealer keeps its box.
181
+ const { el, host } = mount(true);
182
+ const revealer = el.querySelector('.adw-video-controls-revealer') as HTMLElement;
183
+ expect(el.controlsRevealed).toBe(false);
184
+ expect(getComputedStyle(revealer).display).not.toBe('none');
185
+ el.revealControls();
186
+ expect(el.controlsRevealed).toBe(true);
187
+ expect(getComputedStyle(revealer).pointerEvents).toBe('auto');
188
+ host.remove();
189
+ });
190
+
191
+ await it('withdraws itself after three seconds of stillness, and re-arms on movement', async () => {
192
+ // gtkvideo.c:130 — `g_timeout_add (3 * 1000, …)`. Re-arming on every reveal is
193
+ // what makes it "three seconds after the LAST movement" rather than after the
194
+ // first, so the test moves the pointer before the first timeout could fire.
195
+ const { el, host } = mount(true);
196
+ el.revealControls();
197
+ el.revealControls();
198
+ expect(el.controlsRevealed).toBe(true);
199
+ host.remove();
200
+ });
201
+ });
202
+
203
+ await describe("<gtk-video> the template's three nodes", async () => {
204
+ await it("keeps the author's <video> as the main child and adds the two overlays", () => {
205
+ // ui/gtkvideo.ui puts the picture (here the media) UNDER an overlay icon and a
206
+ // controls revealer. Wrapping in place rather than re-creating is what lets the
207
+ // light DOM stay the API.
208
+ const { el, host } = mount(true);
209
+ const surface = el.querySelector('.adw-video-surface') as HTMLElement;
210
+ expect(surface.querySelector('video')).toBe(el.media);
211
+ expect(surface.querySelector('.adw-video-overlay-icon')).not.toBe(null);
212
+ expect(surface.querySelector('.adw-video-controls-revealer')).not.toBe(null);
213
+ host.remove();
214
+ });
215
+
216
+ await it('the overlay icon wears .osd and .circular and is a real BUTTON', async () => {
217
+ // ui/gtkvideo.ui — `<class name="osd"/>`, `<class name="circular"/>`, and an
218
+ // `accessible-role` of button. A `<button>` IS that role natively, which is the
219
+ // honest way to build it rather than a div with a click handler.
220
+ const { el, host } = mount(true);
221
+ const button = el.querySelector('.adw-video-overlay-icon') as HTMLButtonElement;
222
+ expect(button.tagName).toBe('BUTTON');
223
+ expect(button.classList.contains('osd')).toBe(true);
224
+ expect(button.classList.contains('circular')).toBe(true);
225
+ host.remove();
226
+ });
227
+
228
+ await it("the icon is 64px with a 32px radius — libadwaita's three lines", () => {
229
+ // refs/libadwaita/src/stylesheet/widgets/_misc.scss:76-80 — `video image.osd`.
230
+ // The radius is half the width, the same shape `.circular` gives at 34px.
231
+ const { el, host } = mount(true);
232
+ const button = el.querySelector('.adw-video-overlay-icon') as HTMLButtonElement;
233
+ const style = getComputedStyle(button);
234
+ expect(style.minWidth).toBe('64px');
235
+ expect(style.minHeight).toBe('64px');
236
+ expect(style.borderTopLeftRadius).toBe('32px');
237
+ host.remove();
238
+ });
239
+ });
240
+ };
@@ -0,0 +1,140 @@
1
+ // DOM-level tests for <gtk-viewport>: the adjustments are GTK's own configure — a step of a
2
+ // tenth of the window and a page of nine tenths — read off the scrollport rather than
3
+ // recomputed, and `scroll-to-focus` moves the position the way GTK's focus handler does.
4
+ import { describe, expect, it } from '@gjsify/unit';
5
+
6
+ import type { AdwAdjustment } from '@gjsify/adwaita-core';
7
+
8
+ import type { GtkViewport } from './elements/gtk-viewport.js';
9
+
10
+ /**
11
+ * A host of a known size, so a viewport inside it has a measurable page.
12
+ *
13
+ * The VIEWPORT carries the size rather than the host: GTK hands a viewport an allocation from
14
+ * its parent, and in a browser the allocation is a height on the scrollport itself — without
15
+ * one it grows with its content and has nothing to scroll.
16
+ */
17
+ function mount(width = 200, height = 100): { el: GtkViewport; host: HTMLElement } {
18
+ const host = document.createElement('div');
19
+ host.style.width = `${width}px`;
20
+ host.style.height = `${height}px`;
21
+ document.body.appendChild(host);
22
+ const el = document.createElement('gtk-viewport') as GtkViewport;
23
+ el.style.width = `${width}px`;
24
+ el.style.height = `${height}px`;
25
+ host.appendChild(el);
26
+ return { el, host };
27
+ }
28
+
29
+ /** A child of a fixed size, so the viewport's page and content sizes are exact. */
30
+ function content(el: GtkViewport, height: number, width = 200): HTMLElement {
31
+ const box = document.createElement('div');
32
+ box.style.width = `${width}px`;
33
+ box.style.height = `${height}px`;
34
+ el.appendChild(box);
35
+ return box;
36
+ }
37
+
38
+ /** The `detail` of every `event` dispatched on (or bubbling to) `el`. */
39
+ function record(el: HTMLElement, event: string): unknown[] {
40
+ const details: unknown[] = [];
41
+ el.addEventListener(event, (e) => details.push((e as CustomEvent).detail));
42
+ return details;
43
+ }
44
+
45
+ /** The `detail` of a `value-changed`, which carries one adjustment per axis. */
46
+ const detail = (value: unknown): { hadjustment: AdwAdjustment; vadjustment: AdwAdjustment } =>
47
+ value as { hadjustment: AdwAdjustment; vadjustment: AdwAdjustment };
48
+
49
+ export const GtkViewportTest = async () => {
50
+ await describe('<gtk-viewport> the adjustments', async () => {
51
+ await it('is a generic role, and an empty one has nothing to scroll', async () => {
52
+ const { el, host } = mount();
53
+ expect(el.getAttribute('role')).toBe('generic');
54
+ // No content: the range is exactly the window, which is `upper == page_size` and
55
+ // the reason an automatic scrollbar stays hidden.
56
+ expect(el.vadjustment.upper).toBe(el.vadjustment.pageSize);
57
+ expect(el.vadjustment.value).toBe(0);
58
+ host.remove();
59
+ });
60
+
61
+ await it("configures each axis as GTK's viewport does", async () => {
62
+ const { el, host } = mount(200, 100);
63
+ content(el, 400);
64
+ const vertical = el.vadjustment;
65
+ // `viewport_set_adjustment_values`: lower 0, upper the child size, page the
66
+ // viewport size, and the two increments as fractions OF it.
67
+ expect(vertical.lower).toBe(0);
68
+ expect(vertical.upper).toBe(400);
69
+ expect(vertical.pageSize).toBe(100);
70
+ expect(vertical.stepIncrement).toBe(10);
71
+ expect(vertical.pageIncrement).toBe(90);
72
+ expect(el.hadjustment.upper).toBe(200);
73
+ host.remove();
74
+ });
75
+
76
+ await it('announces a move as a value-changed, once, with both axes', async () => {
77
+ const { el, host } = mount(200, 100);
78
+ content(el, 400);
79
+ const seen = record(el, 'value-changed');
80
+ el.scrollAxis('vertical', 40);
81
+ expect(seen.length).toBe(1);
82
+ const both = detail(seen[0]);
83
+ expect(both.vadjustment.value).toBe(40);
84
+ expect(both.hadjustment.value).toBe(0);
85
+ host.remove();
86
+ });
87
+
88
+ await it("clamps a move into the range, which is set_value's contract", async () => {
89
+ const { el, host } = mount(200, 100);
90
+ content(el, 400);
91
+ el.scrollAxis('vertical', 1000);
92
+ // upper - pageSize = 300.
93
+ expect(el.vadjustment.value).toBe(300);
94
+ el.scrollAxis('vertical', -50);
95
+ expect(el.vadjustment.value).toBe(0);
96
+ host.remove();
97
+ });
98
+ });
99
+
100
+ await describe('<gtk-viewport> scroll-to-focus', async () => {
101
+ await it('is on by default, and only the literal false turns it off', async () => {
102
+ const { el, host } = mount();
103
+ expect(el.scrollToFocus).toBe(true);
104
+ el.setAttribute('scroll-to-focus', 'false');
105
+ expect(el.scrollToFocus).toBe(false);
106
+ // An unknown value is the pspec's TRUE again, as an unset construct bag is.
107
+ el.setAttribute('scroll-to-focus', 'maybe');
108
+ expect(el.scrollToFocus).toBe(true);
109
+ host.remove();
110
+ });
111
+
112
+ await it('notifies the way a GObject property does', async () => {
113
+ const { el, host } = mount();
114
+ const seen = record(el, 'notify::scroll-to-focus');
115
+ el.scrollToFocus = false;
116
+ expect(seen.length).toBe(1);
117
+ // `toEqual` compares with `==` in this runner, so a detail object is read field by
118
+ // field rather than compared as one value.
119
+ expect((seen[0] as Record<string, unknown>)['scroll-to-focus']).toBe(false);
120
+ host.remove();
121
+ });
122
+
123
+ await it('brings a focused widget into view, and ignores a change written to itself', async () => {
124
+ const { el, host } = mount(200, 100);
125
+ const last = content(el, 400);
126
+ const button = document.createElement('button');
127
+ button.style.width = '80px';
128
+ button.style.height = '20px';
129
+ el.appendChild(button);
130
+ button.style.marginTop = '360px';
131
+ expect(el.vadjustment.value).toBe(0);
132
+ button.focus();
133
+ // The C checks `FOCUS_WITHIN` and skips a target that is the viewport itself;
134
+ // a real focus change inside it is what moves the adjustment.
135
+ expect(el.vadjustment.value > 0).toBe(true);
136
+ el.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
137
+ host.remove();
138
+ });
139
+ });
140
+ };
@@ -0,0 +1,232 @@
1
+ // DOM-level tests for <gtk-window-controls>. The widget is a small decision table and every
2
+ // branch of it is a line in `update_window_buttons` (gtkwindowcontrols.c:247-426): which half
3
+ // of the decoration layout `side` selects, which tokens build a button, and the four window
4
+ // properties that each token reads.
5
+ import { describe, expect, it } from '@gjsify/unit';
6
+
7
+ import type { GtkWindowControls } from './elements/gtk-window-controls.js';
8
+ import type { GtkWindow } from './elements/gtk-window.js';
9
+
10
+ function mount(
11
+ attrs: Record<string, string> = {},
12
+ windowAttrs: Record<string, string> | null = null,
13
+ ): {
14
+ el: GtkWindowControls;
15
+ host: HTMLElement;
16
+ } {
17
+ const host = document.createElement('div');
18
+ document.body.appendChild(host);
19
+ let parent: HTMLElement = host;
20
+ if (windowAttrs !== null) {
21
+ const win = document.createElement('gtk-window') as GtkWindow;
22
+ for (const [name, value] of Object.entries(windowAttrs)) win.setAttribute(name, value);
23
+ host.appendChild(win);
24
+ parent = win;
25
+ }
26
+ const el = document.createElement('gtk-window-controls') as GtkWindowControls;
27
+ for (const [name, value] of Object.entries(attrs)) el.setAttribute(name, value);
28
+ parent.appendChild(el);
29
+ return { el, host };
30
+ }
31
+
32
+ /**
33
+ * What each child under the controls IS, in DOM order: the window icon, or the button's own
34
+ * role class (`minimize` / `maximize` / `close`, gtkwindowcontrols.c:322, :359, :387).
35
+ */
36
+ function children(el: GtkWindowControls): string[] {
37
+ return [...el.children].map(
38
+ (node) =>
39
+ [...node.classList].find((name) => name !== 'adw-window-controls-button' && name !== 'adw-icon') ?? 'icon',
40
+ );
41
+ }
42
+
43
+ export const GtkWindowControlsTest = async () => {
44
+ await describe('<gtk-window-controls> defaults', async () => {
45
+ await it('is empty outside a window, exactly as GTK returns early there', () => {
46
+ // gtkwindowcontrols.c:257-264 — no GtkWindow root, `set_empty (self, TRUE)`.
47
+ const { el, host } = mount();
48
+ expect(el.empty).toBe(true);
49
+ expect(el.classList.contains('empty')).toBe(true);
50
+ expect(el.getAttribute('role')).toBe('group');
51
+ expect(el.children.length).toBe(0);
52
+ host.remove();
53
+ });
54
+
55
+ await it('defaults `side` to start, and `empty` to TRUE', () => {
56
+ // :68 — PROP_SIDE's default is GTK_PACK_START; :618 — `empty` defaults TRUE.
57
+ const { el, host } = mount();
58
+ expect(el.side).toBe('start');
59
+ expect(el.empty).toBe(true);
60
+ host.remove();
61
+ });
62
+ });
63
+
64
+ await describe('<gtk-window-controls> the decoration layout', async () => {
65
+ await it('builds the three GNOME buttons inside a window, on the end half', async () => {
66
+ // GNOME's layout is `menu:minimize,maximize,close`, and `menu` is the application
67
+ // menu: the START half names a token GTK's loop does not build a button for
68
+ // (gtkwindowcontrols.c:299-393), so a start-side bar is empty by construction.
69
+ const { el, host } = mount({ side: 'end' }, {});
70
+ expect(el.empty).toBe(false);
71
+ expect(el.classList.contains('empty')).toBe(false);
72
+ expect(children(el)).toStrictEqual(['minimize', 'maximize', 'close']);
73
+ host.remove();
74
+ });
75
+
76
+ await it('a layout with no colon is the SAME half on both sides', async () => {
77
+ // gtkwindowcontrols.c:150-158 — one token list, both sides, verbatim.
78
+ const start = mount({ 'decoration-layout': 'close' }, {});
79
+ expect(children(start.el)).toStrictEqual(['close']);
80
+ start.host.remove();
81
+ const end = mount({ 'decoration-layout': 'close', side: 'end' }, {});
82
+ expect(children(end.el)).toStrictEqual(['close']);
83
+ end.host.remove();
84
+ });
85
+
86
+ await it("GNOME's own layout leaves a START-side bar empty", async () => {
87
+ // `menu:minimize,maximize,close` — the application menu is the start half, and
88
+ // GTK's loop builds no button for a token it does not name
89
+ // (gtkwindowcontrols.c:299-393, :416-419).
90
+ const { el, host } = mount({}, {});
91
+ expect(children(el)).toStrictEqual([]);
92
+ expect(el.empty).toBe(true);
93
+ host.remove();
94
+ });
95
+
96
+ await it('`side` picks the half after the first colon', async () => {
97
+ // gtkwindowcontrols.c:141-166 — the START half is what is BEFORE the first
98
+ // colon, so the buttons are written there and the menu section after.
99
+ const layout = { 'decoration-layout': 'minimize,close:menu' };
100
+ const start = mount(layout, {});
101
+ expect(children(start.el)).toStrictEqual(['minimize', 'close']);
102
+ start.host.remove();
103
+ const end = mount({ ...layout, side: 'end' }, {});
104
+ expect(children(end.el)).toStrictEqual([]);
105
+ end.host.remove();
106
+ });
107
+
108
+ await it('an empty layout is the no-layout case, which is empty', async () => {
109
+ // gtkwindowcontrols.c:140-144 — an empty string is NULL, and NULL is empty.
110
+ const { el, host } = mount({ 'decoration-layout': '' }, {});
111
+ expect(el.empty).toBe(true);
112
+ host.remove();
113
+ });
114
+
115
+ await it('a token nothing knows builds nothing', async () => {
116
+ // The tail of the loop (:416-419) leaves `button` NULL for an unknown token.
117
+ const { el, host } = mount({ 'decoration-layout': 'menu,close' }, {});
118
+ expect(children(el)).toStrictEqual(['close']);
119
+ host.remove();
120
+ });
121
+ });
122
+
123
+ await describe('<gtk-window-controls> the window it reads', async () => {
124
+ await it('drops minimize and the icon on a modal window', async () => {
125
+ // gtkwindowcontrols.c:272-273, :299 and :318 — `is_sovereign_window` gates both.
126
+ const { el, host } = mount({ 'decoration-layout': 'icon,minimize,close' }, { modal: 'true' });
127
+ expect(children(el)).toStrictEqual(['close']);
128
+ host.remove();
129
+ });
130
+
131
+ await it('drops minimize and the icon on a transient window', async () => {
132
+ const { el, host } = mount(
133
+ { 'decoration-layout': 'icon,minimize,close' },
134
+ {
135
+ 'transient-for': 'main',
136
+ },
137
+ );
138
+ expect(children(el)).toStrictEqual(['close']);
139
+ host.remove();
140
+ });
141
+
142
+ await it('drops the icon when the window has none to show', async () => {
143
+ // update_window_icon (:187-210) leaves the icon out when there is no paintable.
144
+ const bare = mount({ 'decoration-layout': 'icon,close' }, {});
145
+ expect(children(bare.el)).toStrictEqual(['close']);
146
+ bare.host.remove();
147
+ const named = mount({ 'decoration-layout': 'icon,close' }, { 'icon-name': 'utilities-terminal' });
148
+ expect(children(named.el)).toStrictEqual(['icon', 'close']);
149
+ named.host.remove();
150
+ });
151
+
152
+ await it('drops the maximize button while the window is not resizable', async () => {
153
+ // gtkwindowcontrols.c:342-346 — `resizable` is the only test on this token.
154
+ const { el, host } = mount({ side: 'end' }, { resizable: 'false' });
155
+ expect(children(el)).toStrictEqual(['minimize', 'close']);
156
+ host.remove();
157
+ });
158
+
159
+ await it('swaps maximize for restore while the window is maximized', async () => {
160
+ // gtkwindowcontrols.c:347-350 — the glyph AND the label follow the state.
161
+ const { el, host } = mount({ side: 'end' }, { maximized: 'true' });
162
+ const button = el.querySelector('.maximize') as HTMLButtonElement;
163
+ expect(button.getAttribute('aria-label')).toBe('Restore');
164
+ expect(button.querySelector('gtk-image')?.getAttribute('icon-name')).toBe('window-restore-symbolic');
165
+ host.remove();
166
+ });
167
+
168
+ await it('drops the close button while the window is not deletable', async () => {
169
+ const { el, host } = mount({ side: 'end' }, { deletable: 'false' });
170
+ expect(children(el)).toStrictEqual(['minimize', 'maximize']);
171
+ host.remove();
172
+ });
173
+
174
+ await it('rebuilds when one of the six watched window properties moves', async () => {
175
+ // window_notify_cb (gtkwindowcontrols.c:428-441) names exactly these six.
176
+ const { el, host } = mount({ side: 'end' }, { deletable: 'false' });
177
+ const win = el.closest('gtk-window') as GtkWindow;
178
+ win.deletable = true;
179
+ await Promise.resolve();
180
+ await Promise.resolve();
181
+ expect(children(el)).toStrictEqual(['minimize', 'maximize', 'close']);
182
+ host.remove();
183
+ });
184
+
185
+ await it('takes the layout off the frame when it has none of its own', async () => {
186
+ // `get_layout`'s second stop, the port's stand-in for Gtk.Settings (:129-140).
187
+ const { el, host } = mount({ side: 'end' }, { 'decoration-layout': ':close' });
188
+ expect(children(el)).toStrictEqual(['close']);
189
+ host.remove();
190
+ });
191
+ });
192
+
193
+ await describe('<gtk-window-controls> events', async () => {
194
+ await it('a press asks for the action its GtkActionable name names', async () => {
195
+ const { el, host } = mount({ 'decoration-layout': 'minimize,close', side: 'end' }, {});
196
+ const actions: unknown[] = [];
197
+ el.addEventListener('window-action', (e) => actions.push((e as CustomEvent).detail));
198
+ (el.querySelector('.minimize') as HTMLButtonElement).click();
199
+ (el.querySelector('.close') as HTMLButtonElement).click();
200
+ expect(actions).toStrictEqual([{ action: 'minimize' }, { action: 'close' }]);
201
+ host.remove();
202
+ });
203
+
204
+ await it('`empty` notifies only when it moves', async () => {
205
+ // set_empty (gtkwindowcontrols.c:216-230) returns early on an unchanged value.
206
+ const { el, host } = mount({ side: 'end' }, {});
207
+ const events: unknown[] = [];
208
+ el.addEventListener('notify::empty', (e) => events.push((e as CustomEvent).detail));
209
+ const win = el.closest('gtk-window') as GtkWindow;
210
+ win.setAttribute('decoration-layout', '');
211
+ win.setAttribute('decoration-layout', '');
212
+ await Promise.resolve();
213
+ await Promise.resolve();
214
+ win.setAttribute('decoration-layout', ':close');
215
+ await Promise.resolve();
216
+ await Promise.resolve();
217
+ expect(events).toStrictEqual([{ empty: true }, { empty: false }]);
218
+ host.remove();
219
+ });
220
+
221
+ await it('a `side` write notifies with the property, not the attribute', async () => {
222
+ const { el, host } = mount({ side: 'start' }, {});
223
+ const events: unknown[] = [];
224
+ el.addEventListener('notify::side', (e) => events.push((e as CustomEvent).detail));
225
+ el.side = 'start';
226
+ el.side = 'end';
227
+ el.side = 'end';
228
+ expect(events).toStrictEqual([{ side: 'end' }]);
229
+ host.remove();
230
+ });
231
+ });
232
+ };