@gjsify/adwaita-web 0.53.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 (440) hide show
  1. package/dist/adwaita-web.css +8070 -3037
  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 +43 -21
  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/source-view/theme.ts +11 -5
  436. package/src/style-classes.spec.ts +9 -8
  437. package/src/styles.generated.ts +1 -1
  438. package/src/tab-chip.ts +200 -0
  439. package/src/template-classes.ts +45 -0
  440. package/src/test.browser.mts +152 -0
@@ -0,0 +1,1011 @@
1
+ // <adw-tab-overview> — the grid of tab THUMBNAILS that covers an `<adw-tab-view>`
2
+ // (the web counterpart of Adw.TabOverview). Closed, it is just the view: the
3
+ // overview is a second surface stacked on top of the `child`, and `open` decides
4
+ // which one you see.
5
+ //
6
+ // The DOM is adw-tab-overview.ui, node for node: an `AdwBin` (`child_bin`) holding
7
+ // the child, and an `AdwToolbarView` (`.overview`) over it with an `AdwHeaderBar`,
8
+ // a `GtkSearchBar`, and a `GtkOverlay` content holding a scrolled pair of
9
+ // `AdwTabGrid`s — `pinned_grid` and `grid` — plus the two empty-state `AdwStatusPage`s
10
+ // and the new-tab button.
11
+ //
12
+ // THE RULES THAT ARE C AND NOT MARKUP:
13
+ //
14
+ // · `notify_n_pages_cb` (:951-971): the title is `dngettext ("%u Tab", "%u Tabs",
15
+ // n_pages)` — a PLURAL, so 1 reads "1 Tab" and 0 reads "0 Tabs" — and `''` when
16
+ // there is no view at all.
17
+ // · `update_header_bar` (:816-826): the header bar is hidden unless search is
18
+ // enabled OR a secondary menu is set OR one of the two title-button sides is.
19
+ // · `empty_changed_cb` (:805-814): EMPTY is both grids empty, and WHICH empty state
20
+ // shows is the search: "No Open Tabs" with it off, "No Tabs Found" with it on.
21
+ // · `search_changed_cb` / `stop_search_cb` (:846-880): the terms go to BOTH grids
22
+ // and `search-active` is simply "the entry is non-empty", never a mode flag.
23
+ // · `search_activated_cb` (:884-893) and `adw_tab_overview_focus` (:1343-1405):
24
+ // Enter in the search entry, and Down out of the search button or the entry, go to
25
+ // the FIRST row of `pinned_grid` and only then to `grid`'s; Down out of the
26
+ // secondary-menu button goes to the LAST row instead.
27
+ // · `activated_cb` in the grid (adw-tab-grid.c:3005-3006): activating a thumbnail
28
+ // selects the page and CLOSES the overview. Middle-click closes the page instead,
29
+ // and the close button defers to an idle so the click is not eaten mid-animation.
30
+ // · `update_new_tab_button` (:828-832): the new-tab button hides while searching.
31
+ // · `new_tab_clicked_cb` (:911-926): select the new page, close, focus its child.
32
+ //
33
+ // NOT PORTED, and why it is not "unverifiable":
34
+ // · THUMBNAIL REORDERING BY DRAG. `AdwTabGrid`'s reordering is `GtkDropTarget` +
35
+ // `GtkDragSource` compositor work on a `GdkPaintable`, and it is refused outright
36
+ // while searching (adw-tab-grid.c:1526, :2206). This port moves pages through
37
+ // `Adw.TabView`'s reorder API instead, the same way the tab bar does.
38
+ // · THE EXTRA DROP TARGET (`setup_extra_drop_target`, :1749). It is a GDK drag
39
+ // protocol over foreign content; a browser has no equivalent, so `extra-drag-preload`
40
+ // and `extra-drag-preferred-action` are read-only reports that never change here.
41
+ // · THE OPEN ANIMATION. `AdwTabOverview` interpolates between the view's bounds and
42
+ // the selected thumbnail's (:1077-1210) with a `GdkPaintable` snapshot of the page
43
+ // as the transition picture. The browser has no way to paint a widget into a
44
+ // texture, so the portrait area of a thumbnail stands in with the page's own
45
+ // icon and title, and opening is a CSS transition on the same surface.
46
+ //
47
+ // `view` is a widget REFERENCE (`Adw.TabView`), so the attribute is the element id
48
+ // `<adw-tab-button view>` also takes, while `view`/`setView` take the element.
49
+ // `secondary-menu` is a `GMenuModel`: the property takes the portable model and the
50
+ // attribute takes its JSON, which is `<gtk-menu-button menu-model>`'s own spelling.
51
+ //
52
+ // A11Y: the overview is a `role="grid"` of `role="gridcell"` thumbnails under a
53
+ // header bar, and the pinned grid is a second grid — C's `AdwTabGrid` is a
54
+ // `GtkWidget` with `GTK_ORIENTATION_HORIZONTAL` and one focusable cell at a time.
55
+ //
56
+ // KNOWN_GAPS: none — `view`, `child` and `secondary-menu` are widget-typed and not
57
+ // attributes; the seven scalar properties below are all observed.
58
+ //
59
+ // Reference: refs/libadwaita/src/adw-tab-overview.c (title, empty states, search,
60
+ // actions, focus, new tab)
61
+ // Reference: refs/libadwaita/src/adw-tab-overview.ui (the template this DOM copies)
62
+ // Reference: refs/libadwaita/src/adw-tab-grid.c (activated, search filters, reorder gate)
63
+ // Reference: refs/libadwaita/src/adw-tab-thumbnail.c (close/unpin/indicator, tooltip)
64
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_tab-view.scss:187-300 (tabthumbnail,
65
+ // taboverview > .overview)
66
+ // Reference: refs/gtk/gtk/gtkstringfilter.c:70-138 (the search predicate)
67
+ // Copyright (c) 2021-2022 Purism SPC / GNOME contributors (libadwaita). LGPLv2.1+.
68
+ // Modifications: Implemented as a Web Component for @gjsify/adwaita-web; the icon,
69
+ // indicator and portrait nodes are <gtk-image> and the grids are <div role="grid">.
70
+
71
+ import {
72
+ gridColumns,
73
+ gridNavigate,
74
+ tabSearchMatches,
75
+ tabTooltip,
76
+ type AdwMenuInput,
77
+ type AdwMenuModel,
78
+ } from '@gjsify/adwaita-core';
79
+ import { normalizeMenuModel } from '@gjsify/adwaita-core';
80
+ import { assertMenuRenderable, ADW_MENU_SURFACE_WEB } from '@gjsify/adwaita-core';
81
+
82
+ import type { AdwTabView, AdwTabViewPage } from './adw-tab-view.js';
83
+ import { createGtkImage, type GtkImage } from './gtk-image.js';
84
+
85
+ /** The thumbnail nodes of one page, kept so a re-render edits them in place. */
86
+ interface Thumbnail {
87
+ root: HTMLDivElement;
88
+ label: HTMLSpanElement;
89
+ icon: HTMLElement;
90
+ /** The `<adw-spinner>` that has replaced the icon, or null — they share one slot. */
91
+ spinner: HTMLElement | null;
92
+ picture: HTMLDivElement;
93
+ portrait: HTMLSpanElement;
94
+ closeBtn: HTMLButtonElement;
95
+ unpinIcon: HTMLElement;
96
+ indicatorBtn: HTMLButtonElement;
97
+ indicatorIcon: HTMLElement;
98
+ attention: HTMLDivElement;
99
+ }
100
+
101
+ export class AdwTabOverview extends HTMLElement {
102
+ private _childEl!: HTMLDivElement;
103
+ private _overviewEl!: HTMLDivElement;
104
+ private _headerEl!: HTMLDivElement;
105
+ private _startButtonsEl!: HTMLDivElement;
106
+ private _endButtonsEl!: HTMLDivElement;
107
+ private _titleEl!: HTMLElement;
108
+ private _searchBtn!: HTMLButtonElement;
109
+ private _searchEntry!: HTMLInputElement;
110
+ private _searchClamp!: HTMLDivElement;
111
+ private _searchBar!: HTMLDivElement;
112
+ private _menuBtn!: HTMLElement;
113
+ private _gridEl!: HTMLDivElement;
114
+ private _pinnedGridEl!: HTMLDivElement;
115
+ private _emptyState!: HTMLElement;
116
+ private _searchEmptyState!: HTMLElement;
117
+ private _newTabBtn!: HTMLButtonElement;
118
+ private readonly _thumbs = new Map<string, Thumbnail>();
119
+
120
+ private _view: AdwTabView | null = null;
121
+ private _model: AdwMenuModel = [];
122
+ private _searchActive = false;
123
+ private _initialized = false;
124
+ /** The last visibility actually painted, so `notify::open` fires on a real change. */
125
+ private _appliedOpen = false;
126
+ /** Guards the `open` attribute we write ourselves from re-entering `setOpen`. */
127
+ private _reflecting = false;
128
+
129
+ static get observedAttributes() {
130
+ return [
131
+ 'view',
132
+ 'open',
133
+ 'inverted',
134
+ 'enable-search',
135
+ 'enable-new-tab',
136
+ 'secondary-menu',
137
+ 'show-start-title-buttons',
138
+ 'show-end-title-buttons',
139
+ 'extra-drag-preload',
140
+ ];
141
+ }
142
+
143
+ // --- Adw.TabOverview properties ------------------------------------------
144
+
145
+ /** `Adw.TabOverview:view` — the tab view the overview controls. */
146
+ get view(): AdwTabView | null {
147
+ return this._view;
148
+ }
149
+
150
+ set view(value: AdwTabView | string | null) {
151
+ this.setView(value === null ? null : typeof value === 'string' ? this._resolveView(value) : value);
152
+ }
153
+
154
+ /**
155
+ * `adw_tab_overview_set_view`: rebind the view's five signals, then repopulate.
156
+ * The view must be INSIDE the overview (adw-tab-overview.c:1090-1093 refuses
157
+ * otherwise), which the element enforces by looking the view up among its own
158
+ * children when the reference is not a live element.
159
+ */
160
+ setView(view: AdwTabView | null): void {
161
+ if (this._view === view) return;
162
+ this._unbindView();
163
+ this._view = view;
164
+ this._bindView();
165
+ if (this._initialized) this._syncAll();
166
+ this.dispatchEvent(new CustomEvent('notify::view', { bubbles: true, detail: { view } }));
167
+ }
168
+
169
+ /**
170
+ * `Adw.TabOverview:child` — the widget the overview covers. Declared children are
171
+ * it: there is one slot in the template and it holds whatever the author wrote.
172
+ */
173
+ get child(): HTMLElement | null {
174
+ return this._childEl.firstElementChild as HTMLElement | null;
175
+ }
176
+
177
+ /** `Adw.TabOverview:open` — whether the overview is showing. */
178
+ get open(): boolean {
179
+ return this.hasAttribute('open');
180
+ }
181
+
182
+ set open(value: boolean) {
183
+ this.setOpen(value);
184
+ }
185
+
186
+ /**
187
+ * Whether the thumbnails use the INVERTED layout: the close/unpin button at the
188
+ * START and the indicator at the END, rather than the other way round
189
+ * (adw-tab-overview.c:1626-1634).
190
+ */
191
+ get inverted(): boolean {
192
+ return this.hasAttribute('inverted');
193
+ }
194
+
195
+ set inverted(value: boolean) {
196
+ this.toggleAttribute('inverted', value);
197
+ }
198
+
199
+ /**
200
+ * `Adw.TabOverview:enable-search`, TRUE upstream — and so `="false"` is the off
201
+ * switch, absent meaning on. The same MODIFICATION `<adw-carousel>` makes for
202
+ * `allow-scroll-wheel`: an HTML boolean attribute can only mean TRUE.
203
+ */
204
+ get enableSearch(): boolean {
205
+ return this.getAttribute('enable-search') !== 'false';
206
+ }
207
+
208
+ set enableSearch(value: boolean) {
209
+ this.setAttribute('enable-search', String(value));
210
+ }
211
+
212
+ /** `Adw.TabOverview:search-active` — read-only upstream, and read-only here. */
213
+ get searchActive(): boolean {
214
+ return this._searchActive;
215
+ }
216
+
217
+ /** `Adw.TabOverview:enable-new-tab`. */
218
+ get enableNewTab(): boolean {
219
+ return this.hasAttribute('enable-new-tab');
220
+ }
221
+
222
+ set enableNewTab(value: boolean) {
223
+ this.toggleAttribute('enable-new-tab', value);
224
+ }
225
+
226
+ /** `Adw.TabOverview:secondary-menu` — the portal menu of extra actions. */
227
+ get secondaryMenu(): AdwMenuModel {
228
+ return this._model;
229
+ }
230
+
231
+ set secondaryMenu(value: AdwMenuInput) {
232
+ const model = normalizeMenuModel(value);
233
+ // The property setter is a CALL, so a `custom` item throws here where the caller
234
+ // can catch it — `<gtk-menu-button>`'s own rule, which keeps the two doors honest.
235
+ assertMenuRenderable(model, ADW_MENU_SURFACE_WEB);
236
+ this._model = model;
237
+ this._applyMenu();
238
+ }
239
+
240
+ /** `Adw.TabOverview:show-start-title-buttons`, TRUE upstream, so `="false"` turns it off. */
241
+ get showStartTitleButtons(): boolean {
242
+ return this.getAttribute('show-start-title-buttons') !== 'false';
243
+ }
244
+
245
+ set showStartTitleButtons(value: boolean) {
246
+ this.setAttribute('show-start-title-buttons', String(value));
247
+ }
248
+
249
+ /** `Adw.TabOverview:show-end-title-buttons`, TRUE upstream, so `="false"` turns it off. */
250
+ get showEndTitleButtons(): boolean {
251
+ return this.getAttribute('show-end-title-buttons') !== 'false';
252
+ }
253
+
254
+ set showEndTitleButtons(value: boolean) {
255
+ this.setAttribute('show-end-title-buttons', String(value));
256
+ }
257
+
258
+ /**
259
+ * `Adw.TabOverview:extra-drag-preload`. Stored and reported; it gates the GDK drag
260
+ * protocol this port does not have (see the header).
261
+ */
262
+ get extraDragPreload(): boolean {
263
+ return this.hasAttribute('extra-drag-preload');
264
+ }
265
+
266
+ set extraDragPreload(value: boolean) {
267
+ this.toggleAttribute('extra-drag-preload', value);
268
+ }
269
+
270
+ /** `Adw.TabOverview:extra-drag-preferred-action` — always 0 here, for the same reason. */
271
+ get extraDragPreferredAction(): number {
272
+ return 0;
273
+ }
274
+
275
+ // --- The C actions -------------------------------------------------------
276
+
277
+ /**
278
+ * The `overview.open` action (`overview_open_cb`, adw-tab-overview.c:1517-1521),
279
+ * with `update_actions`' enabled rule: closed AND holding a view (:816-826).
280
+ */
281
+ activateOverviewOpen(): boolean {
282
+ if (this.open || this._view === null) return false;
283
+ this.setOpen(true);
284
+ return true;
285
+ }
286
+
287
+ /**
288
+ * The `overview.close` action (`overview_close_cb`, :1523-1527): open, holding a
289
+ * view, and holding at least one page — an empty overview has nothing to close to.
290
+ */
291
+ activateOverviewClose(): boolean {
292
+ if (!this.open || this._view === null || this._view.nPages === 0) return false;
293
+ this.setOpen(false);
294
+ return true;
295
+ }
296
+
297
+ // --- Lifecycle -----------------------------------------------------------
298
+
299
+ connectedCallback() {
300
+ if (this._initialized) {
301
+ // Re-entering the document: `disconnectedCallback` dropped the listeners.
302
+ this._bindView();
303
+ this._syncAll();
304
+ return;
305
+ }
306
+ this._initialized = true;
307
+
308
+ this._childEl = document.createElement('div');
309
+ this._childEl.className = 'adw-tab-overview-child';
310
+ this._overviewEl = document.createElement('div');
311
+ this._overviewEl.className = 'overview adw-tab-overview-toolbar';
312
+
313
+ this._headerEl = document.createElement('div');
314
+ this._headerEl.className = 'adw-header-bar overview-header-bar';
315
+ this._startButtonsEl = document.createElement('div');
316
+ this._startButtonsEl.className = 'overview-title-buttons start';
317
+ this._endButtonsEl = document.createElement('div');
318
+ this._endButtonsEl.className = 'overview-title-buttons end';
319
+
320
+ this._searchBtn = document.createElement('button');
321
+ this._searchBtn.type = 'button';
322
+ this._searchBtn.className = 'adw-button image-button search-button';
323
+ this._searchBtn.title = 'Search Tabs';
324
+ this._searchBtn.appendChild(createGtkImage('system-search', 'search-button-icon'));
325
+
326
+ this._titleEl = document.createElement('div');
327
+ this._titleEl.className = 'adw-window-title numeric overview-title';
328
+
329
+ this._menuBtn = document.createElement('gtk-menu-button');
330
+ this._menuBtn.setAttribute('icon-name', 'view-more');
331
+ this._menuBtn.className = 'secondary-menu-button';
332
+ this._menuBtn.hidden = true;
333
+
334
+ this._headerEl.append(this._startButtonsEl, this._searchBtn, this._titleEl, this._endButtonsEl, this._menuBtn);
335
+
336
+ // `AdwClamp > GtkSearchEntry`, wrapped in the `GtkSearchBar` whose revealer is
337
+ // `search-mode-enabled` — bound to the search button in the template (:42).
338
+ this._searchBar = document.createElement('div');
339
+ this._searchBar.className = 'adw-search-bar overview-search-bar';
340
+ this._searchClamp = document.createElement('div');
341
+ this._searchClamp.className = 'adw-clamp overview-search-clamp';
342
+ this._searchEntry = document.createElement('input');
343
+ this._searchEntry.type = 'search';
344
+ this._searchEntry.className = 'adw-entry overview-search-entry';
345
+ this._searchEntry.placeholder = 'Search tabs';
346
+ this._searchEntry.setAttribute('aria-label', 'Search tabs');
347
+ this._searchClamp.appendChild(this._searchEntry);
348
+ this._searchBar.appendChild(this._searchClamp);
349
+
350
+ const content = document.createElement('div');
351
+ content.className = 'adw-tab-overview-content';
352
+
353
+ const scroll = document.createElement('div');
354
+ scroll.className = 'adw-tab-overview-scroll';
355
+ this._pinnedGridEl = this._createGrid('adw-tab-grid pinned-grid', 'Pinned tabs');
356
+ this._gridEl = this._createGrid('adw-tab-grid', 'Open tabs');
357
+ scroll.append(this._pinnedGridEl, this._gridEl);
358
+
359
+ this._emptyState = document.createElement('adw-status-page');
360
+ this._emptyState.className = 'overview-empty-state';
361
+ this._emptyState.setAttribute('icon', 'view-grid');
362
+ this._emptyState.setAttribute('title', 'No Open Tabs');
363
+ this._emptyState.hidden = true;
364
+
365
+ this._searchEmptyState = document.createElement('adw-status-page');
366
+ this._searchEmptyState.className = 'overview-search-empty-state';
367
+ this._searchEmptyState.setAttribute('icon', 'system-search');
368
+ this._searchEmptyState.setAttribute('title', 'No Tabs Found');
369
+ this._searchEmptyState.setAttribute('description', 'Try a different search');
370
+ this._searchEmptyState.hidden = true;
371
+
372
+ this._newTabBtn = document.createElement('button');
373
+ this._newTabBtn.type = 'button';
374
+ this._newTabBtn.className = 'adw-button pill suggested-action new-tab-button';
375
+ this._newTabBtn.append(createGtkImage('list-add', 'new-tab-button-icon'), document.createTextNode('New Tab'));
376
+
377
+ content.append(scroll, this._emptyState, this._searchEmptyState, this._newTabBtn);
378
+ this._overviewEl.append(this._headerEl, this._searchBar, content);
379
+
380
+ // The declared children become the `child`; the overview is built around them.
381
+ const declared = Array.from(this.children) as HTMLElement[];
382
+ this.replaceChildren(this._childEl, this._overviewEl);
383
+ for (const el of declared) this._childEl.appendChild(el);
384
+
385
+ // `key-capture-widget` is the overview itself (adw-tab-overview.ui:43), which is
386
+ // where a printable key goes when the entry has focus.
387
+ this._searchEntry.addEventListener('input', () => this._onSearchChanged());
388
+ this._searchEntry.addEventListener('keydown', (event) => this._onSearchKeyDown(event));
389
+ this._searchBtn.addEventListener('click', () => this._toggleSearchMode());
390
+ this._newTabBtn.addEventListener('click', () => this._requestCreateTab());
391
+ this.addEventListener('keydown', (event) => this._onKeyDown(event));
392
+
393
+ // `set_view`'s own connection order: the header bar, then the view.
394
+ const declaredView = this.getAttribute('view');
395
+ this.setView(declaredView === null ? this._findView() : this._resolveView(declaredView));
396
+ if (declaredView !== null) this.removeAttribute('view');
397
+
398
+ const declaredMenu = this.getAttribute('secondary-menu');
399
+ if (declaredMenu !== null) {
400
+ this.secondaryMenu = JSON.parse(declaredMenu) as AdwMenuInput;
401
+ this.removeAttribute('secondary-menu');
402
+ }
403
+
404
+ this._applyOpen();
405
+ this._syncAll();
406
+ }
407
+
408
+ disconnectedCallback() {
409
+ this._unbindView();
410
+ }
411
+
412
+ attributeChangedCallback(name: string, _old: string | null, value: string | null) {
413
+ if (!this._initialized) return;
414
+ switch (name) {
415
+ case 'view':
416
+ this.setView(value === null ? this._findView() : this._resolveView(value));
417
+ return;
418
+ case 'open':
419
+ if (!this._reflecting) this._applyOpen();
420
+ return;
421
+ case 'secondary-menu': {
422
+ if (value === null) return;
423
+ // An attribute is MARKUP: a `custom` item here is reported, not thrown
424
+ // (adw-sidebar/`gtk-menu-button`'s rule for the same two doors).
425
+ try {
426
+ this.secondaryMenu = JSON.parse(value) as AdwMenuInput;
427
+ } catch (error) {
428
+ console.error(`<adw-tab-overview secondary-menu> is not valid JSON: ${String(error)}`);
429
+ }
430
+ return;
431
+ }
432
+ case 'enable-new-tab':
433
+ this._applyNewTabButton();
434
+ return;
435
+ case 'inverted':
436
+ // The overview's `inverted` reaches each thumbnail through
437
+ // `adw_tab_thumbnail_set_inverted`, so the grid re-renders here.
438
+ this._syncGrids();
439
+ return;
440
+ case 'enable-search':
441
+ case 'show-start-title-buttons':
442
+ case 'show-end-title-buttons':
443
+ this._applyHeaderBar();
444
+ return;
445
+ }
446
+ // `inverted` and `extra-drag-preload` are read by the stylesheet and reported.
447
+ }
448
+
449
+ // --- View wiring ---------------------------------------------------------
450
+
451
+ private _resolveView(ref: string): AdwTabView | null {
452
+ return (document.getElementById(ref) as AdwTabView | null) ?? this._findView();
453
+ }
454
+
455
+ /** The `AdwTabView` among our own children — the only place the C allows one. */
456
+ private _findView(): AdwTabView | null {
457
+ return this.querySelector('adw-tab-view') as AdwTabView | null;
458
+ }
459
+
460
+ /**
461
+ * `set_view`'s five connections (adw-tab-overview.c:1140-1209), read off the view's
462
+ * own events: the page list's attach/detach/reorder, each page's `pinned` change,
463
+ * the selection, and the page COUNT (which the title and the actions are derived
464
+ * from). The pinned page change arrives as `page-updated` / `page-pinned`.
465
+ */
466
+ private _bindView(): void {
467
+ this._unbindView();
468
+ for (const type of VIEW_EVENTS) this._view?.addEventListener(type, this._onViewChange);
469
+ }
470
+
471
+ private _unbindView(): void {
472
+ for (const type of VIEW_EVENTS) this._view?.removeEventListener(type, this._onViewChange);
473
+ }
474
+
475
+ private _onViewChange = (): void => {
476
+ this._syncAll();
477
+ };
478
+
479
+ // --- Derivation ----------------------------------------------------------
480
+
481
+ private _syncAll(): void {
482
+ this._applyTitle();
483
+ this._syncGrids();
484
+ this._applyEmptyStates();
485
+ this._applyNewTabButton();
486
+ this._applyHeaderBar();
487
+ }
488
+
489
+ /**
490
+ * `notify_n_pages_cb`: `dngettext ("%u Tab", "%u Tabs", n_pages)` — the count is
491
+ * the number of pages, and the singular is only for exactly one. No view means an
492
+ * EMPTY title, which is the C's own branch before the format string (:955-958).
493
+ */
494
+ private _applyTitle(): void {
495
+ const view = this._view;
496
+ this._titleEl.textContent = view === null ? '' : view.nPages === 1 ? '1 Tab' : `${view.nPages} Tabs`;
497
+ }
498
+
499
+ /**
500
+ * The two grids, split the way `populate_sidebar`-shaped code splits them: pinned
501
+ * pages in `pinned_grid` in view order, the rest in `grid`, each filtered by the
502
+ * current search terms. Thumbnails are edited in place; a page that left either
503
+ * grid loses its node.
504
+ */
505
+ private _syncGrids(): void {
506
+ const view = this._view;
507
+ const terms = this._searchActive ? this._searchEntry.value : '';
508
+ const seen = new Set<string>();
509
+ const pinned: HTMLElement[] = [];
510
+ const rest: HTMLElement[] = [];
511
+
512
+ if (view !== null) {
513
+ for (const page of view.pages) {
514
+ const matches = tabSearchMatches(page, terms);
515
+ if (!matches) {
516
+ this._thumbs.get(page.id)?.root.remove();
517
+ this._thumbs.delete(page.id);
518
+ continue;
519
+ }
520
+ seen.add(page.id);
521
+ const thumb = this._thumbFor(page.id);
522
+ this._refreshThumb(thumb, page);
523
+ (page.pinned ? pinned : rest).push(thumb.root);
524
+ }
525
+ }
526
+ for (const [id, thumb] of this._thumbs) {
527
+ if (seen.has(id)) continue;
528
+ thumb.root.remove();
529
+ this._thumbs.delete(id);
530
+ }
531
+
532
+ this._pinnedGridEl.replaceChildren(...pinned);
533
+ this._gridEl.replaceChildren(...rest);
534
+ this._pinnedGridEl.hidden = pinned.length === 0;
535
+ }
536
+
537
+ /**
538
+ * `empty_changed_cb`: EMPTY is BOTH grids empty, and which of the two status pages
539
+ * shows is the search — `empty_state` when it is off, `search_empty_state` when on.
540
+ */
541
+ private _applyEmptyStates(): void {
542
+ const empty = this._pinnedGridEl.childElementCount === 0 && this._gridEl.childElementCount === 0;
543
+ this._emptyState.hidden = !(empty && !this._searchActive);
544
+ this._searchEmptyState.hidden = !(empty && this._searchActive);
545
+ }
546
+
547
+ /** `update_new_tab_button`: the button hides while the search is running. */
548
+ private _applyNewTabButton(): void {
549
+ this._newTabBtn.hidden = !(this.enableNewTab && !this._searchActive);
550
+ }
551
+
552
+ /**
553
+ * `update_header_bar`: the bar shows when search is on, or a secondary menu is set,
554
+ * or either side wants its title buttons. The two sides are drawn as the slots C
555
+ * puts the window buttons in — this renderer has no window chrome to move, so the
556
+ * slot is where an application puts its own controls.
557
+ */
558
+ private _applyHeaderBar(): void {
559
+ const hasMenu = this._model.length > 0;
560
+ this._headerEl.hidden = !(
561
+ this.enableSearch ||
562
+ hasMenu ||
563
+ this.showStartTitleButtons ||
564
+ this.showEndTitleButtons
565
+ );
566
+ this._startButtonsEl.hidden = !this.showStartTitleButtons;
567
+ this._endButtonsEl.hidden = !this.showEndTitleButtons;
568
+ this._searchBtn.hidden = !this.enableSearch;
569
+ this._searchBar.hidden = !this.enableSearch || !this._searchMode;
570
+ this._menuBtn.hidden = !hasMenu;
571
+ }
572
+
573
+ private _applyMenu(): void {
574
+ const view = this._menuBtn as HTMLElement & { menuModel?: AdwMenuModel };
575
+ if (this._model.length > 0) view.menuModel = this._model;
576
+ this._applyHeaderBar();
577
+ }
578
+
579
+ /** `set_open` + `set_overview_visible` (:916-939): one visibility, two surfaces. */
580
+ private _applyOpen(): void {
581
+ const open = this.open;
582
+ this._overviewEl.hidden = !open;
583
+ this._childEl.classList.toggle('background', open);
584
+ // `can_target` / `can_focus` on the bin while the overview is up — the browser's
585
+ // `inert`, which is the same statement in CSS.
586
+ this._childEl.inert = open;
587
+
588
+ if (open === this._appliedOpen) return;
589
+ this._appliedOpen = open;
590
+
591
+ this._reflecting = true;
592
+ if (open) {
593
+ if (!this.hasAttribute('open')) this.setAttribute('open', '');
594
+ } else if (this.hasAttribute('open')) {
595
+ // `open_animation_done_cb` (:1042-1067) turns the search bar off on CLOSE,
596
+ // not on open, so a reopened overview starts from an empty entry.
597
+ this._searchMode = false;
598
+ this._setSearchActive(false);
599
+ this._searchEntry.value = '';
600
+ this.removeAttribute('open');
601
+ }
602
+ this._reflecting = false;
603
+ this.dispatchEvent(new CustomEvent('notify::open', { bubbles: true, detail: { open } }));
604
+ }
605
+
606
+ /** `adw_tab_overview_set_open`, the setter the actions and the grid both call. */
607
+ setOpen(open: boolean): void {
608
+ if (this.open === open) return;
609
+ // The ATTRIBUTE is the state, and `_applyOpen` reads it — so the write comes
610
+ // first, and `_reflecting` keeps the resulting reaction from applying it twice.
611
+ this._reflecting = true;
612
+ this.toggleAttribute('open', open);
613
+ this._reflecting = false;
614
+ this._applyOpen();
615
+ if (open) this._syncAll();
616
+ }
617
+
618
+ // --- Search --------------------------------------------------------------
619
+
620
+ private _searchMode = false;
621
+
622
+ /** The template's `search-mode-enabled` binding: the search BUTTON's own state. */
623
+ private _toggleSearchMode(): void {
624
+ this._searchMode = !this._searchMode;
625
+ this._searchBar.hidden = !this.enableSearch || !this._searchMode;
626
+ if (this._searchMode) this._searchEntry.focus();
627
+ else this._stopSearch();
628
+ }
629
+
630
+ /**
631
+ * `search_changed_cb`: the terms go to BOTH grids and `search-active` is just
632
+ * "the entry is non-empty" — there is no separate mode to keep in step, which is why
633
+ * `stop_search_cb` has to clear the entry itself before reporting FALSE.
634
+ */
635
+ private _onSearchChanged(): void {
636
+ this._setSearchActive(this._searchEntry.value !== '');
637
+ this._syncGrids();
638
+ this._applyEmptyStates();
639
+ this._applyNewTabButton();
640
+ }
641
+
642
+ private _stopSearch(): void {
643
+ this._searchEntry.value = '';
644
+ this._setSearchActive(false);
645
+ this._syncGrids();
646
+ this._applyEmptyStates();
647
+ this._applyNewTabButton();
648
+ }
649
+
650
+ /** `set_search_active` (:838-852): assign, re-derive, and notify only on a change. */
651
+ private _setSearchActive(active: boolean): void {
652
+ if (active === this._searchActive) return;
653
+ this._searchActive = active;
654
+ this.dispatchEvent(
655
+ new CustomEvent('notify::search-active', { bubbles: true, detail: { searchActive: active } }),
656
+ );
657
+ }
658
+
659
+ /**
660
+ * `search_activated_cb` (:884-893): Enter in the entry moves into the grid, and it
661
+ * is the PINNED grid first, because it sits above `grid` in the scrolled window.
662
+ */
663
+ private _onSearchKeyDown(event: KeyboardEvent): void {
664
+ if (event.key === 'Escape') {
665
+ this._stopSearch();
666
+ this._searchMode = false;
667
+ this._searchBar.hidden = !this.enableSearch || !this._searchMode;
668
+ return;
669
+ }
670
+ if (event.key !== 'Enter') return;
671
+ if (!this._searchActive) return;
672
+ if (this._focusFirstRow(this._pinnedGridEl) || this._focusFirstRow(this._gridEl)) event.preventDefault();
673
+ }
674
+
675
+ /**
676
+ * `adw_tab_overview_focus` (:1343-1405), the parts the DOM can honour: the search
677
+ * button and the entry go DOWN into the first row, the secondary-menu button goes
678
+ * down to the LAST row, and the new-tab button goes UP to the last row of `grid`
679
+ * first. GTK's focus is a widget returning TRUE for a direction; a browser moves the
680
+ * focus itself, so this only names the destination.
681
+ */
682
+ private _onKeyDown(event: KeyboardEvent): void {
683
+ if (!this.open) return;
684
+ const target = event.target;
685
+ if (!(target instanceof Node)) return;
686
+ if (this._onGridKey(event, target)) return;
687
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
688
+
689
+ if (event.key === 'ArrowDown') {
690
+ if (this._searchBar.contains(target) || this._searchBtn.contains(target)) {
691
+ if (this._focusFirstRow(this._pinnedGridEl) || this._focusFirstRow(this._gridEl))
692
+ event.preventDefault();
693
+ return;
694
+ }
695
+ if (this._menuBtn.contains(target)) {
696
+ if (this._focusLastRow(this._pinnedGridEl) || this._focusLastRow(this._gridEl)) event.preventDefault();
697
+ return;
698
+ }
699
+ // Out of the last grid and on to the new-tab button, and out of the pinned
700
+ // grid when the ordinary grid is empty (adw-tab-overview.c:1387-1398).
701
+ if (this._newTabBtn.contains(target)) return;
702
+ if (
703
+ (this._gridEl.contains(target) || this._pinnedGridEl.contains(target)) &&
704
+ this._newTabBtn.hidden === false
705
+ ) {
706
+ event.preventDefault();
707
+ this._newTabBtn.focus();
708
+ }
709
+ return;
710
+ }
711
+ if (this._newTabBtn.contains(target)) {
712
+ if (this._focusLastRow(this._gridEl) || this._focusLastRow(this._pinnedGridEl)) event.preventDefault();
713
+ }
714
+ }
715
+
716
+ /**
717
+ * The thumbnail grids are `GtkGridView`s: arrows move a cursor in two dimensions over
718
+ * a column count the layout decides (the grid reflows), Home/End jump, and nothing
719
+ * wraps. Selection follows focus; Enter and Space activate like a click, because a
720
+ * thumbnail is a `div` with no activation of its own. A move that would leave the
721
+ * grid is not claimed for Up/Down, so `_onKeyDown` can still hand focus to the
722
+ * new-tab button.
723
+ */
724
+ private _onGridKey(event: KeyboardEvent, target: Node): boolean {
725
+ if (event.altKey || event.ctrlKey || event.metaKey) return false;
726
+ const grid = [this._pinnedGridEl, this._gridEl].find((el) => el.contains(target));
727
+ if (grid === undefined) return false;
728
+ const chips = [...grid.children].filter((el): el is HTMLElement => el instanceof HTMLElement);
729
+ const from = chips.findIndex((chip) => chip.contains(target));
730
+ if (from < 0) return false;
731
+ // The close and indicator buttons are nested controls with their own Enter/Space.
732
+ if (target instanceof Element && target.closest('button') !== null) return false;
733
+ const view = this._view;
734
+ if (view === null) return false;
735
+
736
+ if (event.key === 'Enter' || event.key === ' ') {
737
+ const id = chips[from]!.dataset.pageId;
738
+ if (id === undefined) return false;
739
+ event.preventDefault();
740
+ view.setSelectedPage(id);
741
+ this.setOpen(false);
742
+ return true;
743
+ }
744
+
745
+ const columns = gridColumns(chips.map((chip) => chip.getBoundingClientRect().top));
746
+ const to = gridNavigate(event.key, from, chips.length, columns);
747
+ if (to === null) {
748
+ const claimed = ['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key);
749
+ if (claimed) event.preventDefault();
750
+ return claimed;
751
+ }
752
+ const id = chips[to]!.dataset.pageId;
753
+ if (id === undefined) return false;
754
+ event.preventDefault();
755
+ view.setSelectedPage(id);
756
+ chips[to]!.focus();
757
+ return true;
758
+ }
759
+
760
+ private _focusFirstRow(grid: HTMLElement): boolean {
761
+ const first = grid.querySelector<HTMLElement>('.adw-tab-thumbnail');
762
+ if (first === null) return false;
763
+ first.focus();
764
+ return true;
765
+ }
766
+
767
+ private _focusLastRow(grid: HTMLElement): boolean {
768
+ const rows = grid.querySelectorAll<HTMLElement>('.adw-tab-thumbnail');
769
+ const last = rows[rows.length - 1];
770
+ if (last === undefined) return false;
771
+ last.focus();
772
+ return true;
773
+ }
774
+
775
+ // --- New tab -------------------------------------------------------------
776
+
777
+ /**
778
+ * `create_tab` (:895-909) and `new_tab_clicked_cb` (:911-926).
779
+ *
780
+ * `create-tab`'s handler RETURNS a page, which a DOM event cannot do: `detail.page`
781
+ * is the return slot, and a listener fills it (usually by appending to the view).
782
+ * Nothing filled is C's `g_critical ("AdwTabOverview::create-tab handler must not
783
+ * return NULL")`, reported on `console.error` because the click cannot be abandoned.
784
+ */
785
+ private _requestCreateTab(): void {
786
+ const view = this._view;
787
+ if (view === null) return;
788
+ const detail: { page: AdwTabViewPage | null } = { page: null };
789
+ this.dispatchEvent(new CustomEvent('create-tab', { bubbles: true, detail }));
790
+ if (detail.page === null) {
791
+ console.error('<adw-tab-overview>: the create-tab handler must not leave `detail.page` unset.');
792
+ return;
793
+ }
794
+ view.setSelectedPage(detail.page.id);
795
+ this.setOpen(false);
796
+ detail.page.content?.focus?.();
797
+ }
798
+
799
+ // --- Thumbnails ----------------------------------------------------------
800
+
801
+ private _createGrid(className: string, label: string): HTMLDivElement {
802
+ const grid = document.createElement('div');
803
+ grid.className = className;
804
+ grid.setAttribute('role', 'grid');
805
+ grid.setAttribute('aria-label', label);
806
+ return grid;
807
+ }
808
+
809
+ /**
810
+ * adw-tab-thumbnail.ui, node for node: the `.thumbnail` overlay over a `.card`
811
+ * overlay holding the PICTURE, the close button, the unpin icon and the indicator
812
+ * button, with the attention dot over that and the icon/title box underneath.
813
+ */
814
+ private _thumbFor(id: string): Thumbnail {
815
+ const existing = this._thumbs.get(id);
816
+ if (existing !== undefined) return existing;
817
+
818
+ const root = document.createElement('div');
819
+ root.className = 'adw-tab-thumbnail';
820
+ root.dataset.pageId = id;
821
+ root.setAttribute('role', 'gridcell');
822
+ root.tabIndex = -1;
823
+
824
+ const contents = document.createElement('div');
825
+ contents.className = 'contents';
826
+
827
+ const thumbnail = document.createElement('div');
828
+ thumbnail.className = 'thumbnail';
829
+
830
+ const card = document.createElement('div');
831
+ card.className = 'card';
832
+
833
+ // The PICTURE. GTK paints the page into a `GdkPaintable`
834
+ // (`adw_tab_page_get_paintable`, adw-tab-thumbnail.c:620) — see the header for
835
+ // why the browser cannot, and what stands in: the page's own icon and title.
836
+ const picture = document.createElement('div');
837
+ picture.className = 'picture';
838
+ const portrait = document.createElement('span');
839
+ portrait.className = 'picture-portrait';
840
+ picture.appendChild(portrait);
841
+
842
+ const closeBtn = document.createElement('button');
843
+ closeBtn.type = 'button';
844
+ closeBtn.className = 'tab-close-button adw-button image-button';
845
+ closeBtn.title = 'Close Tab';
846
+ closeBtn.setAttribute('aria-label', 'Close Tab');
847
+ closeBtn.tabIndex = -1;
848
+ closeBtn.appendChild(createGtkImage('window-close', 'tab-close-icon'));
849
+
850
+ // `adw-tab-unpin-symbolic` is a libadwaita-BUNDLED glyph (refs/libadwaita/src/icons/),
851
+ // in neither the icon theme nor @gjsify/adwaita-icons — the same case the
852
+ // <adw-entry-row> pencil is. The canonical name stays in `data-icon-name` and
853
+ // the shape is drawn in CSS, so the substitution is legible in the DOM. It is a
854
+ // real `<button>`, because `unpin_clicked_cb` is a clicked handler (:183-193).
855
+ const unpinIcon = document.createElement('button');
856
+ unpinIcon.type = 'button';
857
+ unpinIcon.className = 'tab-unpin-icon';
858
+ unpinIcon.tabIndex = -1;
859
+ unpinIcon.title = 'Unpin Tab';
860
+ unpinIcon.setAttribute('aria-label', 'Unpin Tab');
861
+ unpinIcon.dataset.iconName = 'adw-tab-unpin-symbolic';
862
+
863
+ const indicatorBtn = document.createElement('button');
864
+ indicatorBtn.type = 'button';
865
+ indicatorBtn.className = 'tab-indicator adw-button image-button';
866
+ indicatorBtn.tabIndex = -1;
867
+ const indicatorIcon = createGtkImage(null, 'tab-indicator-icon');
868
+ indicatorBtn.appendChild(indicatorIcon);
869
+
870
+ card.append(picture, closeBtn, unpinIcon, indicatorBtn);
871
+
872
+ const attention = document.createElement('div');
873
+ attention.className = 'needs-attention';
874
+ const gizmo = document.createElement('div');
875
+ gizmo.className = 'gizmo';
876
+ attention.appendChild(gizmo);
877
+ thumbnail.append(card, attention);
878
+
879
+ const iconTitleBox = document.createElement('div');
880
+ iconTitleBox.className = 'icon-title-box';
881
+ const icon = createGtkImage(null, 'tab-icon');
882
+ const label = document.createElement('span');
883
+ label.className = 'tab-label';
884
+ iconTitleBox.append(icon, label);
885
+
886
+ contents.append(thumbnail, iconTitleBox);
887
+ root.appendChild(contents);
888
+
889
+ // `activated_cb` (adw-tab-grid.c:3005-3006): select the page, close the overview.
890
+ root.addEventListener('click', () => {
891
+ const view = this._view;
892
+ if (view === null) return;
893
+ view.setSelectedPage(id);
894
+ this.setOpen(false);
895
+ });
896
+ // Middle click closes the page instead — the same line, one branch above.
897
+ root.addEventListener('auxclick', (event) => {
898
+ if (event.button !== 1) return;
899
+ event.preventDefault();
900
+ this._view?.closePage(id);
901
+ });
902
+ closeBtn.addEventListener('click', (event) => {
903
+ event.stopPropagation();
904
+ this._view?.closePage(id);
905
+ });
906
+ // `unpin_idle_cb` (:176-180): the page goes back to the ordinary grid, which is
907
+ // a page-order change, not a selection.
908
+ unpinIcon.addEventListener('click', (event) => {
909
+ event.stopPropagation();
910
+ this._view?.setPagePinned(id, false);
911
+ });
912
+ indicatorBtn.addEventListener('click', (event) => {
913
+ event.stopPropagation();
914
+ this._view?.dispatchEvent(new CustomEvent('indicator-activated', { bubbles: true, detail: { id } }));
915
+ });
916
+
917
+ const thumb: Thumbnail = {
918
+ root,
919
+ label,
920
+ icon,
921
+ spinner: null,
922
+ picture,
923
+ portrait,
924
+ closeBtn,
925
+ unpinIcon,
926
+ indicatorBtn,
927
+ indicatorIcon,
928
+ attention,
929
+ };
930
+ this._thumbs.set(id, thumb);
931
+ return thumb;
932
+ }
933
+
934
+ /**
935
+ * `update_tooltip`, `update_icon`, `update_indicator`, `update_loading` and the
936
+ * needs-attention revealer, in that order (adw-tab-thumbnail.c:82-152).
937
+ */
938
+ private _refreshThumb(
939
+ thumb: Thumbnail,
940
+ page: {
941
+ id: string;
942
+ title: string;
943
+ tooltip: string;
944
+ icon: string | null;
945
+ indicatorIcon: string | null;
946
+ loading: boolean;
947
+ needsAttention: boolean;
948
+ pinned: boolean;
949
+ },
950
+ ): void {
951
+ // `update_tooltip`: a non-empty tooltip wins, otherwise the title. Markup is NOT
952
+ // interpreted here either — the DOM `title` attribute is a text sink.
953
+ thumb.root.title = tabTooltip(page);
954
+ thumb.root.setAttribute('aria-label', tabTooltip(page));
955
+
956
+ // `update_icon`: the spinner REPLACES the icon's paintable while loading, and
957
+ // the icon is visible iff there is an icon or a spinner. C SWAPS the paintable
958
+ // rather than stacking a second node, and this does too — with a real
959
+ // `<adw-spinner>`, the same paintable `Adw.Spinner` draws, so a CSS copy of
960
+ // the ring would inherit every spinner defect independently.
961
+ thumb.icon.hidden = page.icon === null && !page.loading;
962
+ thumb.root.classList.toggle('loading', page.loading);
963
+ if (page.loading && thumb.spinner === null) {
964
+ const spinner = document.createElement('adw-spinner');
965
+ spinner.className = 'tab-spinner';
966
+ spinner.setAttribute('size', String(PAGE_ICON_SPINNER_SIZE));
967
+ thumb.icon.replaceWith(spinner);
968
+ thumb.spinner = spinner;
969
+ } else if (!page.loading && thumb.spinner !== null) {
970
+ thumb.spinner.replaceWith(thumb.icon);
971
+ thumb.spinner = null;
972
+ }
973
+
974
+ // `update_indicator`: visible iff there is an indicator icon, and it takes the
975
+ // page's `indicator-tooltip`.
976
+ thumb.indicatorBtn.hidden = page.indicatorIcon === null;
977
+ (thumb.indicatorIcon as GtkImage).iconName = page.indicatorIcon;
978
+
979
+ thumb.attention.hidden = !page.needsAttention;
980
+
981
+ // `inverted` is a property of the OVERVIEW (adw-tab-overview.c:1626-1634) and
982
+ // reaches each thumbnail through `adw_tab_thumbnail_set_inverted`; the class is
983
+ // what puts the close button and the indicator at the other end.
984
+ thumb.root.classList.toggle('inverted', this.inverted);
985
+ // A PINNED page gets the unpin glyph where an ordinary page gets the close button.
986
+ thumb.closeBtn.hidden = page.pinned;
987
+ thumb.unpinIcon.hidden = !page.pinned;
988
+
989
+ thumb.label.textContent = page.title;
990
+ thumb.portrait.textContent = page.title;
991
+
992
+ const selected = this._view?.selectedId === page.id;
993
+ thumb.root.classList.toggle('selected', selected);
994
+ thumb.root.setAttribute('aria-selected', String(selected));
995
+ }
996
+ }
997
+
998
+ /** `PAGE_ICON_SIZE`: the 16px `AdwSpinnerPaintable` an icon slot gets (adw-tab.c:74). */
999
+ const PAGE_ICON_SPINNER_SIZE = '16';
1000
+
1001
+ /** `set_view`'s five signals, in the names the view dispatches them under. */
1002
+ const VIEW_EVENTS = [
1003
+ 'page-attached',
1004
+ 'page-detached',
1005
+ 'page-reordered',
1006
+ 'page-pinned',
1007
+ 'page-updated',
1008
+ 'notify::selected-page',
1009
+ ] as const;
1010
+
1011
+ customElements.define('adw-tab-overview', AdwTabOverview);