@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,509 @@
1
+ // <gtk-media-controls> — the transport bar under a video: play/pause, position, duration,
2
+ // volume. It owns NO playback. `GtkMediaControls` has exactly one GIR property and it is
3
+ // `media-stream`, an OBJECT (gtkmediacontrols.c:296-306) — so on this renderer the element
4
+ // has no scalar surface to observe at all, and every control it draws is a VIEW of the
5
+ // stream it was handed. That is a fact about the widget, not a shortfall in the port, and it
6
+ // is why this file has no `observedAttributes`.
7
+ //
8
+ // THE NODE TREE IS THE TEMPLATE, VERBATIM. refs/gtk/gtk/ui/gtkmediacontrols.ui builds
9
+ // box (a GtkBox, `sensitive` 0, `spacing` 6)
10
+ // ├── play_button (GtkButton, `has-frame` 0, media-playback-start-symbolic)
11
+ // ├── time_box (GtkBox)
12
+ // │ ├── time_label (GtkLabel, `font-features "tnum=1"`)
13
+ // │ ├── seek_scale (GtkScale, hexpand, aria-label "Position")
14
+ // │ └── duration_label (GtkLabel, `font-features "tnum=1"`)
15
+ // └── volume_button (GtkVolumeButton, aria-label "Volume")
16
+ // Every node above is a node here, in that order, with those two classes on it.
17
+ //
18
+ // `sensitive` 0 ON THE WHOLE BOX IS THE FIRST THING TO NOTICE. The template disables the
19
+ // entire container (ui/gtkmediacontrols.ui), and `gtk_media_controls_set_sensitive` is what
20
+ // re-enables it — the bar is inert until a stream is attached. So this element renders its
21
+ // controls INSENSITIVE by default and only becomes live once it has a media element to
22
+ // drive, which is the same observable consequence the C has: `gtk_media_controls_new()`
23
+ // produces a bar you cannot press.
24
+ //
25
+ // THE TIME FORMAT IS `totem_time_to_string`, AND IT IS NOT `Date`. The function
26
+ // (gtkmediacontrols.c:75-132) formats in seconds from a microsecond count, adds one second
27
+ // when it is showing what REMAINS, and then picks one of four shapes:
28
+ //
29
+ // • `hour > 0 || force_hour` and NOT remaining → `%d:%02d:%02d` ("9:05:02")
30
+ // • `hour > 0 || force_hour` and remaining → `-%d:%02d:%02d` ("-9:05:02")
31
+ // • remaining → `-%d:%02d` ("-5:02")
32
+ // • otherwise → `%d:%02d` ("5:02")
33
+ //
34
+ // Three details in that are load-bearing and all three are in the port. `remaining` bumps
35
+ // the SECOND COUNT UP by one (`:81-82`) because the C wants `current + remaining = total`
36
+ // to hold exactly; the hour/minute split uses INTEGER division on the already-decremented
37
+ // remainder, not a floating-point `mm:ss` (`:86-88`); and the two long formats exist only
38
+ // when an HOUR is present or forced, so a two-hour video shows `2:00:00` while a
39
+ // one-hour-fifty video shows `1:50:00` and a one-minute video shows `1:02`. GTK calls it
40
+ // with `force_hour` FALSE everywhere (`:358, :364, :389`), so nothing here forces it — the
41
+ // parameter is kept because it is part of the function's own contract and a port that
42
+ // dropped it would be unable to answer what it is for.
43
+ //
44
+ // WHICH LABEL SHOWS WHAT CHANGES WITH THE DURATION, AND THAT IS NOT A TASTE DECISION.
45
+ // With no duration yet, the DURATION label shows the elapsed time
46
+ // (`totem_time_to_string (timestamp, FALSE, FALSE)`, :358-359) — the position takes the
47
+ // single label, because there is no total to compare it against. Once a duration is known
48
+ // the DURATION label shows what REMAINS, negatively, and is VISIBLE only when the duration
49
+ // is greater than zero (`:389-391`). So "duration label" is a misnomer: it is the remaining
50
+ // label, and the position label is the elapsed one.
51
+ //
52
+ // THE SEEK SCALE'S ADJUSTMENT IS NOT 0…1. `time_adjustment` has `upper` 10 and
53
+ // `step-increment` 1 and `page-increment` 10 (ui/gtkmediacontrols.ui), and every real
54
+ // duration arrives through `gtk_media_controls_set_duration`, which resets the adjustment's
55
+ // range to the media's own — so the numbers on the scale are SECONDS, and
56
+ // `time_adjustment_changed` writes the position from them. That is why the `<input
57
+ // type="range">` here is `min="0"`, `max` set to the duration in seconds and `step="1"`,
58
+ // and why a range with no duration is disabled rather than scaled to a default of 10: the
59
+ // template's `upper` 10 is a placeholder for the pre-stream state, not a range a user can
60
+ // seek within.
61
+ //
62
+ // VOLUME IS 0…1 AND IT IS MUTED BY ZERO. `volume_adjustment` has `upper` 1,
63
+ // `step-increment` 0.1, `page-increment` 1 and `value` 1 (ui/gtkmediacontrols.ui) — full
64
+ // scale, tenth steps, and FULL volume as the initial value. A `GtkVolumeButton`'s icon is
65
+ // derived from that value by GTK itself rather than by this template, which is why the icon
66
+ // here changes with the level the way it does in C: the element picks the one of four
67
+ // `audio-volume-*` names `GtkVolumeButton` would, and does nothing else with it.
68
+ //
69
+ // AND THE FOUR NAMES ARE THE SYMBOLIC ONES, IN AN ORDER THAT IS NOT THE ORDER OF THE
70
+ // LEVELS. `GtkVolumeButton:use-symbolic` defaults TRUE (gtkvolumebutton.c:186-188) and is a
71
+ // CONSTRUCT property, so `gtk_volume_button_set_property` swaps the template's list for
72
+ // `icons_symbolic` (`:142-148`) before anything reads it — the names in play are
73
+ // `audio-volume-{muted,high,low,medium}-symbolic` (`:74-77`), in THAT order. It is a
74
+ // `GtkScaleButton`, and `gtk_scale_button_update_icon` (gtkscalebutton.c:1066-1104) indexes
75
+ // that list: the exact `lower` takes `[0]`, the exact `upper` takes `[1]`, and everything
76
+ // between is `(guint)((value − lower) / step) + 2` with
77
+ // `step = (upper − lower) / (num_icons − 2)` — which for 0…1 and four icons is a HALF. So
78
+ // `high` belongs to full scale alone, `muted` to silence alone, and `low`/`medium` split at
79
+ // one half, not at thirds. `normalizeIconName` accepts either spelling, and the emitted
80
+ // names keep the suffix the C's own list carries.
81
+ //
82
+ // Reference: refs/gtk/gtk/ui/gtkmediacontrols.ui (the whole node tree and both adjustments)
83
+ // Reference: refs/gtk/gtk/gtkmediacontrols.c (`totem_time_to_string` :75-132,
84
+ // `update_timestamp` :352-366, `update_duration` :380-392, the one object property
85
+ // :296-306, `play_button_clicked`)
86
+ // Copyright (c) The GTK Team. LGPLv2.1+.
87
+ // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
88
+
89
+ // The play/pause pair `ui/gtkvideo.ui`'s `play_button` carries, as constants for the same
90
+ // reason the video's four overlay glyphs are constants — `check-adwaita-icon-masks.mjs` finds
91
+ // an emitted name through a SCREAMING_SNAKE `…ICON` constant holding a plain literal, and a
92
+ // ternary of two literals inside a `setAttribute` is invisible to it.
93
+ const PLAY_ICON = 'media-playback-start-symbolic';
94
+ /** The playing state, and the only pause glyph either element draws. */
95
+ const PAUSE_ICON = 'media-playback-pause-symbolic';
96
+
97
+ /**
98
+ * The four `audio-volume-*` names `GtkVolumeButton` derives from a 0…1 level, one constant
99
+ * each rather than an array of them, in the ORDER `icons_symbolic` holds them
100
+ * (gtkvolumebutton.c:74-77) — which is muted, HIGH, low, medium, and not the order of the
101
+ * levels.
102
+ *
103
+ * The shape is load-bearing, not stylistic. `check-adwaita-icon-masks.mjs` reads an emitted
104
+ * icon name out of the SOURCE with a regex for a SCREAMING_SNAKE constant whose name ends in
105
+ * `ICON` or `ICON_NAME` and whose value is a plain string literal — that is how
106
+ * `PASSWORD_REVEAL_ICON_NAME` and the about dialog's glyph are found. Four literals inside an
107
+ * array are invisible to it, so its second arm would report four ICONS entries nothing emits
108
+ * while all four really are emitted. The `-symbolic` suffix is likewise kept on the value:
109
+ * `normalizeIconName` accepts either spelling, and dropping it buys nothing.
110
+ */
111
+ export const VOLUME_MUTED_ICON = 'audio-volume-muted-symbolic';
112
+ /** The FULL-SCALE name — `[1]` in the list, so only the exact `upper` reaches it. */
113
+ export const VOLUME_HIGH_ICON = 'audio-volume-high-symbolic';
114
+ /** Below a half — `[2]`, the lower of the two middle names. */
115
+ export const VOLUME_LOW_ICON = 'audio-volume-low-symbolic';
116
+ /** From a half up to — but not at — full scale — `[3]`. */
117
+ export const VOLUME_MEDIUM_ICON = 'audio-volume-medium-symbolic';
118
+
119
+ /** `volume_adjustment`'s `lower` (ui/gtkmediacontrols.ui leaves it at the `GtkAdjustment`
120
+ * default of 0) and its `upper` 1 — the two ends `gtk_scale_button_update_icon` tests for. */
121
+ const VOLUME_LOWER = 0;
122
+ const VOLUME_UPPER = 1;
123
+ /** How many names `icons_symbolic` carries — `num_icons` in `gtk_scale_button_update_icon`. */
124
+ const VOLUME_ICON_COUNT = 4;
125
+
126
+ /**
127
+ * The icon `GtkVolumeButton` shows at `volume` (0…1), as `gtk_scale_button_update_icon`
128
+ * derives it (gtkscalebutton.c:1086-1104): the exact `lower` is `[0]` (muted), the exact
129
+ * `upper` is `[1]` (high), and a value between them indexes `[2]`/`[3]` by
130
+ * `(guint)((value − lower) / step) + 2` with `step = (upper − lower) / (num_icons − 2)` — a
131
+ * HALF for a 0…1 adjustment over four icons.
132
+ */
133
+ export function volumeIconFor(volume: number): string {
134
+ const value = Math.min(VOLUME_UPPER, Math.max(VOLUME_LOWER, Number.isFinite(volume) ? volume : 1));
135
+ if (value === VOLUME_LOWER) return VOLUME_MUTED_ICON;
136
+ if (value === VOLUME_UPPER) return VOLUME_HIGH_ICON;
137
+ const step = (VOLUME_UPPER - VOLUME_LOWER) / (VOLUME_ICON_COUNT - 2);
138
+ // The C casts the quotient to `guint`, which truncates toward zero; the value is already
139
+ // inside the range here, so that is the same as a floor.
140
+ return Math.trunc((value - VOLUME_LOWER) / step) + 2 === 2 ? VOLUME_LOW_ICON : VOLUME_MEDIUM_ICON;
141
+ }
142
+
143
+ /**
144
+ * `totem_time_to_string` (gtkmediacontrols.c:75-132), in seconds.
145
+ *
146
+ * The `remaining` bump is the C's own (`:81-82`) and it is not a rounding fudge: the C
147
+ * computes the remaining time as `duration - timestamp` and then increments `_time` — the
148
+ * WHOLE TOTAL, not the seconds field — so that `current + remaining = total` holds for a
149
+ * whole second of playback rather than being one second short at every boundary. That is why
150
+ * 300 s of remaining time reads `-5:01` and not `-5:00`: the seconds field only appears to
151
+ * move when the input's own seconds are zero, and the increment carries at 59.
152
+ *
153
+ * NOTHING HERE FLOORS A NEGATIVE, because the C does not. `_time % 60` in C99 keeps the sign
154
+ * of the dividend, so five seconds negative is zero minutes and `-5` seconds and the
155
+ * function returns the nonsense `0:-5`; the C's CALLERS guard the subtraction instead
156
+ * (`duration > timestamp ? duration - timestamp : 0`, :364), and so does every call site
157
+ * here. The C's arithmetic is transcribed rather than repaired, so a caller that skips the
158
+ * guard sees what GTK would have shown.
159
+ *
160
+ * The hour branch is `hour > 0 || force_hour` — an ELEVEN-MINUTE video is `11:03`, not
161
+ * `0:11:03` — and `force_hour` is FALSE at every call site in the C (`:358, :364, :389`).
162
+ * It stays a parameter because it is part of the function's contract, not because anything
163
+ * here passes it.
164
+ *
165
+ * @param seconds total or remaining time, in seconds; a negative is NOT floored here, exactly
166
+ * as it is not in the C — see above.
167
+ */
168
+ export function mediaTimeToString(seconds: number, remaining = false, forceHour = false): string {
169
+ let time = Math.trunc(seconds);
170
+ if (remaining) time++;
171
+
172
+ const sec = time % 60;
173
+ time -= sec;
174
+ const min = (time % 3600) / 60;
175
+ time -= min * 60;
176
+ const hour = time / 3600;
177
+
178
+ if (hour > 0 || forceHour) {
179
+ // `%d:%02d:%02d` and its negated form — the two "long time format" cases.
180
+ return remaining
181
+ ? `-${hour}:${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`
182
+ : `${hour}:${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
183
+ }
184
+ if (remaining) return `-${min}:${String(sec).padStart(2, '0')}`;
185
+ return `${min}:${String(sec).padStart(2, '0')}`;
186
+ }
187
+
188
+ export class GtkMediaControls extends HTMLElement {
189
+ private _initialized = false;
190
+ private _playButton!: HTMLButtonElement;
191
+ private _timeLabel!: HTMLSpanElement;
192
+ private _seekScale!: HTMLInputElement;
193
+ private _durationLabel!: HTMLSpanElement;
194
+ private _volumeButton!: HTMLButtonElement;
195
+ /** The icon node INSIDE the volume button — the popover is its sibling child. */
196
+ private _volumeIcon!: HTMLSpanElement;
197
+ private _volumePopover!: HTMLElement;
198
+ private _volumeScale!: HTMLInputElement;
199
+ private _media: HTMLVideoElement | null = null;
200
+ /** Whether the user is dragging the seek scale — the C's own re-entrancy guard. */
201
+ private _seeking = false;
202
+ private _volume = 1;
203
+
204
+ connectedCallback() {
205
+ if (this._initialized) {
206
+ this._adoptMedia();
207
+ return;
208
+ }
209
+ this._initialized = true;
210
+
211
+ // `box` — the template's GtkBox, `sensitive` 0 and `spacing` 6
212
+ // (ui/gtkmediacontrols.ui). `insensitive` is the state the C's `sensitive` 0 puts
213
+ // it in, and it is what makes every control inside inert until a stream is attached.
214
+ const box = document.createElement('div');
215
+ box.className = 'adw-media-controls-box';
216
+
217
+ // `play_button` — GtkButton with `has-frame` 0, which is libadwaita's flat button.
218
+ this._playButton = document.createElement('button');
219
+ this._playButton.className = 'adw-media-controls-button adw-flat';
220
+ this._playButton.type = 'button';
221
+ this._playButton.title = 'Play';
222
+ this._playButton.setAttribute('aria-label', 'Play');
223
+ this._playButton.addEventListener('click', () => this._togglePlay());
224
+
225
+ // `time_box` — the middle GtkBox holding the two labels and the scale between them.
226
+ const timeBox = document.createElement('div');
227
+ timeBox.className = 'adw-media-controls-time-box';
228
+
229
+ // Both labels carry `font-features "tnum=1"` (ui/gtkmediacontrols.ui): tabular
230
+ // figures, so a position that counts 9→10 does not shift the scale sideways. That is
231
+ // `_media_controls.scss`'s `font-variant-numeric: tabular-nums`, and it is the reason
232
+ // this is a real class rather than a comment.
233
+ this._timeLabel = document.createElement('span');
234
+ this._timeLabel.className = 'adw-media-controls-time-label';
235
+
236
+ // `seek_scale` — the GtkScale. `min` is the adjustment's 0 and `max` starts at the
237
+ // template's placeholder 10 (ui/gtkmediacontrols.ui), replaced by the real duration
238
+ // the moment one is known; `step` is the adjustment's `step-increment` of 1 second.
239
+ this._seekScale = document.createElement('input');
240
+ this._seekScale.className = 'adw-media-controls-seek-scale';
241
+ this._seekScale.type = 'range';
242
+ this._seekScale.min = '0';
243
+ this._seekScale.max = '10';
244
+ this._seekScale.step = '1';
245
+ this._seekScale.value = '0';
246
+ // The template's own accessibility block: `label` "Position" on the scale.
247
+ this._seekScale.setAttribute('aria-label', 'Position');
248
+ this._seekScale.addEventListener('input', () => this._onSeekInput());
249
+
250
+ this._durationLabel = document.createElement('span');
251
+ this._durationLabel.className = 'adw-media-controls-duration-label';
252
+
253
+ timeBox.append(this._timeLabel, this._seekScale, this._durationLabel);
254
+
255
+ // `volume_button` — a GtkVolumeButton, which is a GtkMenuButton whose popover holds
256
+ // the volume adjustment. Built as a button plus its own popover rather than
257
+ // `<gtk-menu-button>` so the popover is this widget's own surface and no second
258
+ // element's open/close machinery is involved.
259
+ this._volumeButton = document.createElement('button');
260
+ this._volumeButton.className = 'adw-media-controls-button adw-media-controls-volume-button adw-flat';
261
+ this._volumeButton.type = 'button';
262
+ // The template's own accessibility block: `label` "Volume" on the volume button.
263
+ this._volumeButton.setAttribute('aria-label', 'Volume');
264
+ this._volumeButton.addEventListener('click', () => this._toggleVolumePopover());
265
+ this._volumeIcon = document.createElement('span');
266
+ this._volumeIcon.className = 'adw-media-controls-volume-icon';
267
+
268
+ // The popover holds `volume_adjustment`: `upper` 1, `step-increment` 0.1,
269
+ // `page-increment` 1, `value` 1 (ui/gtkmediacontrols.ui).
270
+ // A real `<gtk-popover>`, not a div wearing a borrowed class: `_popover.scss` is the
271
+ // ONE popover surface in this package and its header records that three hand-rolled
272
+ // copies of the plate disagreed with libadwaita and with each other before it was
273
+ // lifted out. `GtkVolumeButton`'s adjustment popover is exactly that surface, so the
274
+ // volume slide goes in one.
275
+ this._volumePopover = document.createElement('gtk-popover');
276
+ this._volumePopover.className = 'adw-media-controls-volume-popover';
277
+ this._volumeScale = document.createElement('input');
278
+ this._volumeScale.className = 'adw-media-controls-volume-scale';
279
+ this._volumeScale.type = 'range';
280
+ this._volumeScale.min = '0';
281
+ this._volumeScale.max = '1';
282
+ this._volumeScale.step = '0.1';
283
+ this._volumeScale.value = '1';
284
+ this._volumeScale.setAttribute('aria-label', 'Volume');
285
+ this._volumeScale.addEventListener('input', () => this._onVolumeInput());
286
+ this._volumePopover.append(this._volumeScale);
287
+ // A click on the SLIDE is not a click on the ANCHOR: without this, dragging the
288
+ // volume would close the popover under the pointer on every input event.
289
+ this._volumePopover.addEventListener('click', (event) => event.stopPropagation());
290
+
291
+ this._volumeButton.append(this._volumeIcon, this._volumePopover);
292
+
293
+ box.append(this._playButton, timeBox, this._volumeButton);
294
+ this.replaceChildren(box);
295
+ this._renderVolume();
296
+ this._adoptMedia();
297
+ }
298
+
299
+ /** The `<video>` these controls drive — the browser's answer to a `GtkMediaStream`. */
300
+ get media(): HTMLVideoElement | null {
301
+ return this._media;
302
+ }
303
+
304
+ /** The current position in seconds. */
305
+ get timestamp(): number {
306
+ return this._media?.currentTime ?? 0;
307
+ }
308
+
309
+ /** The media's duration in seconds, or 0 while unknown. */
310
+ get duration(): number {
311
+ const value = this._media?.duration ?? 0;
312
+ return Number.isFinite(value) ? value : 0;
313
+ }
314
+
315
+ /** The volume level, 0…1, matching `volume_adjustment`'s range. */
316
+ get volume(): number {
317
+ return this._volume;
318
+ }
319
+
320
+ set volume(value: number) {
321
+ this._setVolume(value);
322
+ }
323
+
324
+ /**
325
+ * Whether the bar is live — the C's `gtk_media_controls_set_sensitive`.
326
+ *
327
+ * Loosely typed on purpose: `_adoptMedia` coerces its optional chain to `null`, and this
328
+ * answers "is there a stream" for any field state rather than only the two the coercion
329
+ * produces. A `!== null` test here read `undefined` as a stream.
330
+ */
331
+ get sensitive(): boolean {
332
+ return this._media != null;
333
+ }
334
+
335
+ /** `play_button_clicked` (ui/gtkmediacontrols.ui) — the button's only action. */
336
+ private _togglePlay(): void {
337
+ // `!= null`, not `!== null`: an `undefined` `_media` is "no stream" here for the same
338
+ // reason it is in `sensitive`.
339
+ const media = this._media ?? null;
340
+ if (media === null) return;
341
+ if (media.paused) void media.play().catch(() => {});
342
+ else media.pause();
343
+ }
344
+
345
+ /**
346
+ * `time_adjustment_changed` (ui/gtkmediacontrols.ui): the scale's value IS the position,
347
+ * in seconds. The `_seeking` guard is the C's own — `update_timestamp` writes the scale
348
+ * from the stream, so without it a drag would fight the playback it is dragging through.
349
+ */
350
+ private _onSeekInput(): void {
351
+ this._seeking = true;
352
+ if (this._media) this._media.currentTime = Number.parseFloat(this._seekScale.value);
353
+ this._seeking = false;
354
+ this._updateTimestamp();
355
+ }
356
+
357
+ private _onVolumeInput(): void {
358
+ this._setVolume(Number.parseFloat(this._volumeScale.value));
359
+ }
360
+
361
+ private _setVolume(value: number): void {
362
+ const level = Math.min(1, Math.max(0, Number.isFinite(value) ? value : 1));
363
+ this._volume = level;
364
+ if (this._media) this._media.volume = level;
365
+ this._renderVolume();
366
+ }
367
+
368
+ private _renderVolume(): void {
369
+ if (this._volumeScale) this._volumeScale.value = String(this._volume);
370
+ // The ICON node is replaced, never the button's children: the button also holds the
371
+ // popover (a `GtkVolumeButton` is a `GtkMenuButton`), and a `replaceChildren` here
372
+ // would take the volume slide with it on every level change.
373
+ const icon = document.createElement('gtk-image');
374
+ icon.setAttribute('icon-name', volumeIconFor(this._volume));
375
+ this._volumeIcon?.replaceChildren(icon);
376
+ }
377
+
378
+ private _toggleVolumePopover(): void {
379
+ if (!this._volumePopover) return;
380
+ this._volumePopover.hidden = !this._volumePopover.hidden;
381
+ }
382
+
383
+ /**
384
+ * The media element these controls drive. A `<gtk-media-controls>` normally sits INSIDE a
385
+ * `<gtk-video>` (the template's `controls_revealer` child), so the nearest ancestor video
386
+ * is the stream — and a controls bar used on its own names one with a `for` attribute
387
+ * holding an id.
388
+ *
389
+ * The optional chain's `undefined` IS COERCED TO `null`, and that is not tidiness: `?? null`
390
+ * is what keeps `_media` in the domain `sensitive` and `_render` compare against. A bar with
391
+ * no enclosing video used to store `undefined` here, `undefined !== null` then reported the
392
+ * template's `sensitive` 0 bar as LIVE, and the `!this._media.paused` at the end of
393
+ * `_render` threw `can't access property "paused", this._media is undefined` — which is the
394
+ * uncaught error `tests/browser/specs/adwaita-upgrade-order.spec.ts` fails on. `null` is
395
+ * what `gtk_media_controls_new()` leaves behind: the template has no stream at all, and
396
+ * `sensitive` is 0 until one is set (ui/gtkmediacontrols.ui).
397
+ */
398
+ private _adoptMedia(): void {
399
+ const id = this.getAttribute('for');
400
+ // Narrowed at BOTH lookups rather than after them: `getElementById` answers
401
+ // `HTMLElement | null` and `querySelector` answers `Element | null`, while the whole
402
+ // point of the media is that it has `currentTime`, `volume` and `play`. An id that
403
+ // names something else is no media at all, which is the same answer `null` gives.
404
+ const byId = id ? this.ownerDocument.getElementById(id) : null;
405
+ const media =
406
+ (byId instanceof HTMLVideoElement ? byId : null) ??
407
+ this.closest('gtk-video')?.querySelector<HTMLVideoElement>('video') ??
408
+ null;
409
+ if (media === this._media) {
410
+ this._render();
411
+ return;
412
+ }
413
+ if (this._media) this._detachMedia();
414
+ this._media = media;
415
+ if (media) {
416
+ this._mediaListeners = [
417
+ ['timeupdate', () => this._updateTimestamp()],
418
+ ['durationchange', () => this._updateDuration()],
419
+ ['play', () => this._updatePlaying(true)],
420
+ ['pause', () => this._updatePlaying(false)],
421
+ ['seeked', () => this._updateTimestamp()],
422
+ ];
423
+ for (const [type, handler] of this._mediaListeners) media.addEventListener(type, handler);
424
+ // The adjustment's `value` is 1 (ui/gtkmediacontrols.ui) and the C applies the
425
+ // adjustment's own value to the stream when a stream is attached, so full volume
426
+ // is the state a freshly attached bar starts in.
427
+ this._setVolume(media.volume);
428
+ }
429
+ this._render();
430
+ }
431
+
432
+ /** The listeners bound on `_media`, so `_detachMedia` removes the SAME ones. */
433
+ private _mediaListeners: [string, EventListener][] = [];
434
+
435
+ private _detachMedia(): void {
436
+ if (this._media) {
437
+ for (const [type, handler] of this._mediaListeners) this._media.removeEventListener(type, handler);
438
+ }
439
+ this._mediaListeners = [];
440
+ }
441
+
442
+ /**
443
+ * `update_timestamp` (gtkmediacontrols.c:352-366). TWO cases and they are not
444
+ * symmetric: with no duration, the POSITION label shows the elapsed time and the
445
+ * DURATION label is hidden (`:358-359`); with a duration, the DURATION label shows the
446
+ * NEGATIVE remaining time and is visible only when the duration is greater than zero
447
+ * (`:389-391`).
448
+ */
449
+ private _updateTimestamp(): void {
450
+ if (!this._initialized) return;
451
+ const timestamp = this.timestamp;
452
+ const duration = this.duration;
453
+ // The scale follows the stream unless the user has hold of it.
454
+ if (!this._seeking && duration > 0) this._seekScale.value = String(timestamp);
455
+ this._timeLabel.textContent = mediaTimeToString(timestamp);
456
+ this._durationLabel.textContent = mediaTimeToString(duration > timestamp ? duration - timestamp : 0, true);
457
+ this._durationLabel.hidden = duration <= 0;
458
+ }
459
+
460
+ /** `gtk_media_controls_set_duration` (gtkmediacontrols.c:380-392): the scale's range is
461
+ * the media's own duration, and the `upper` 10 of the template is only the pre-stream
462
+ * placeholder. */
463
+ private _updateDuration(): void {
464
+ if (!this._initialized) return;
465
+ this._seekScale.max = String(this.duration > 0 ? this.duration : 10);
466
+ this._updateTimestamp();
467
+ }
468
+
469
+ /** The play button's icon, `play_button_clicked`'s counterpart in `ui/gtkvideo.ui`'s
470
+ * `gtk_video_update_playing`: `media-playback-start-symbolic` stopped,
471
+ * `media-playback-pause-symbolic` playing. */
472
+ private _updatePlaying(playing: boolean): void {
473
+ if (!this._initialized) return;
474
+ const image = document.createElement('gtk-image');
475
+ // `ui/gtkvideo.ui`'s `play_button` carries `media-playback-start-symbolic`, and the
476
+ // pause glyph is its counterpart — the same two names the video's overlay icon uses,
477
+ // which is why the two elements share the glyphs rather than inventing their own.
478
+ image.setAttribute('icon-name', playing ? PAUSE_ICON : PLAY_ICON);
479
+ this._playButton.replaceChildren(image);
480
+ this._playButton.title = playing ? 'Pause' : 'Play';
481
+ this._playButton.setAttribute('aria-label', playing ? 'Pause' : 'Play');
482
+ }
483
+
484
+ /**
485
+ * The whole bar's sensitivity. The template ships `sensitive` 0
486
+ * (ui/gtkmediacontrols.ui), so an unattached bar is inert — which is what `insensitive`
487
+ * carries here, and why the spec can assert it before any media is adopted.
488
+ */
489
+ private _render(): void {
490
+ if (!this._initialized) return;
491
+ // One truth, one expression. The live test was spelled out a second time at the
492
+ // `!this._media.paused` call below, where an `_media` of `undefined` — which is what
493
+ // an optional chain hands back when there is no enclosing `<gtk-video>` — passes
494
+ // `!== null` and then throws on the property read. Deriving the playing state from the
495
+ // same narrowing makes that unrepresentable.
496
+ const media = this._media ?? null;
497
+ const live = media !== null;
498
+ this.classList.toggle('insensitive', !live);
499
+ this._playButton.disabled = !live;
500
+ this._seekScale.disabled = !live;
501
+ this._volumeButton.disabled = !live;
502
+ this._volumeScale.disabled = !live;
503
+ this._updateDuration();
504
+ this._updateTimestamp();
505
+ this._updatePlaying(live && !media.paused);
506
+ }
507
+ }
508
+
509
+ customElements.define('gtk-media-controls', GtkMediaControls);