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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/README.md +10 -80
  2. package/dist/adwaita-web.css +52 -3
  3. package/dist/adwaita-web.css.map +1 -1
  4. package/lib/types/accessible.d.ts +8 -0
  5. package/lib/types/compute-expand.d.ts +5 -0
  6. package/lib/types/compute-expand.spec.d.ts +1 -0
  7. package/lib/types/elements/adw-about-dialog.d.ts +28 -1
  8. package/lib/types/elements/adw-action-row.d.ts +2 -1
  9. package/lib/types/elements/adw-alert-dialog.d.ts +3 -2
  10. package/lib/types/elements/adw-avatar.d.ts +2 -1
  11. package/lib/types/elements/adw-banner.d.ts +2 -1
  12. package/lib/types/elements/adw-bin.d.ts +2 -1
  13. package/lib/types/elements/adw-bottom-sheet.d.ts +5 -4
  14. package/lib/types/elements/adw-breakpoint-bin.d.ts +2 -1
  15. package/lib/types/elements/adw-button-content.d.ts +2 -1
  16. package/lib/types/elements/adw-button-row.d.ts +2 -1
  17. package/lib/types/elements/adw-card.d.ts +2 -1
  18. package/lib/types/elements/adw-carousel.d.ts +20 -3
  19. package/lib/types/elements/adw-clamp-scrollable.d.ts +2 -1
  20. package/lib/types/elements/adw-clamp.d.ts +2 -1
  21. package/lib/types/elements/adw-combo-row.d.ts +6 -1
  22. package/lib/types/elements/adw-data-grid.d.ts +2 -1
  23. package/lib/types/elements/adw-dialog.d.ts +2 -1
  24. package/lib/types/elements/adw-entry-row.d.ts +2 -1
  25. package/lib/types/elements/adw-expander-row.d.ts +2 -1
  26. package/lib/types/elements/adw-header-bar.d.ts +2 -1
  27. package/lib/types/elements/adw-inline-view-switcher.d.ts +3 -2
  28. package/lib/types/elements/adw-layout-slot.d.ts +2 -1
  29. package/lib/types/elements/adw-multi-layout-view.d.ts +2 -1
  30. package/lib/types/elements/adw-navigation-split-view.d.ts +2 -1
  31. package/lib/types/elements/adw-navigation-view.d.ts +3 -2
  32. package/lib/types/elements/adw-overlay-split-view.d.ts +2 -1
  33. package/lib/types/elements/adw-preferences-dialog.d.ts +5 -4
  34. package/lib/types/elements/adw-preferences-group.d.ts +2 -1
  35. package/lib/types/elements/adw-preferences-row.d.ts +2 -1
  36. package/lib/types/elements/adw-shortcut-label.d.ts +2 -1
  37. package/lib/types/elements/adw-shortcuts-item.d.ts +2 -1
  38. package/lib/types/elements/adw-shortcuts-section.d.ts +2 -1
  39. package/lib/types/elements/adw-sidebar.d.ts +4 -3
  40. package/lib/types/elements/adw-spin-row.d.ts +2 -1
  41. package/lib/types/elements/adw-spinner.d.ts +2 -1
  42. package/lib/types/elements/adw-split-button.d.ts +2 -1
  43. package/lib/types/elements/adw-status-page.d.ts +2 -1
  44. package/lib/types/elements/adw-switch-row.d.ts +4 -1
  45. package/lib/types/elements/adw-tab-bar.d.ts +2 -1
  46. package/lib/types/elements/adw-tab-button.d.ts +2 -1
  47. package/lib/types/elements/adw-tab-overview.d.ts +2 -1
  48. package/lib/types/elements/adw-tab-view.d.ts +3 -2
  49. package/lib/types/elements/adw-toast-overlay.d.ts +2 -1
  50. package/lib/types/elements/adw-toggle-group.d.ts +3 -2
  51. package/lib/types/elements/adw-toolbar-view.d.ts +2 -1
  52. package/lib/types/elements/adw-view-stack.d.ts +3 -1
  53. package/lib/types/elements/adw-view-switcher-bar.d.ts +2 -1
  54. package/lib/types/elements/adw-view-switcher-sidebar.d.ts +2 -1
  55. package/lib/types/elements/adw-view-switcher.d.ts +3 -2
  56. package/lib/types/elements/adw-window-title.d.ts +2 -1
  57. package/lib/types/elements/adw-wrap-box.d.ts +2 -1
  58. package/lib/types/elements/checks.d.ts +16 -1
  59. package/lib/types/elements/gtk-action-bar.d.ts +2 -1
  60. package/lib/types/elements/gtk-aspect-frame.d.ts +2 -1
  61. package/lib/types/elements/gtk-box.d.ts +5 -1
  62. package/lib/types/elements/gtk-button.d.ts +5 -7
  63. package/lib/types/elements/gtk-calendar.d.ts +2 -1
  64. package/lib/types/elements/gtk-center-box.d.ts +2 -1
  65. package/lib/types/elements/gtk-color-dialog-button.d.ts +2 -1
  66. package/lib/types/elements/gtk-drag-icon.d.ts +2 -1
  67. package/lib/types/elements/gtk-drawing-area.d.ts +2 -1
  68. package/lib/types/elements/gtk-drop-down.d.ts +2 -1
  69. package/lib/types/elements/gtk-editable-label.d.ts +2 -1
  70. package/lib/types/elements/gtk-entry.d.ts +2 -1
  71. package/lib/types/elements/gtk-expander.d.ts +2 -1
  72. package/lib/types/elements/gtk-fixed.d.ts +2 -1
  73. package/lib/types/elements/gtk-flow-box-child.d.ts +2 -1
  74. package/lib/types/elements/gtk-flow-box.d.ts +2 -1
  75. package/lib/types/elements/gtk-font-dialog-button.d.ts +2 -1
  76. package/lib/types/elements/gtk-frame.d.ts +2 -1
  77. package/lib/types/elements/gtk-gl-area.d.ts +2 -1
  78. package/lib/types/elements/gtk-graphics-offload.d.ts +2 -1
  79. package/lib/types/elements/gtk-grid.d.ts +2 -1
  80. package/lib/types/elements/gtk-header-bar.d.ts +2 -1
  81. package/lib/types/elements/gtk-image.d.ts +5 -6
  82. package/lib/types/elements/gtk-inscription.d.ts +2 -1
  83. package/lib/types/elements/gtk-label.d.ts +10 -3
  84. package/lib/types/elements/gtk-level-bar.d.ts +2 -1
  85. package/lib/types/elements/gtk-list-box-row.d.ts +2 -1
  86. package/lib/types/elements/gtk-list-box.d.ts +2 -1
  87. package/lib/types/elements/gtk-list-view.d.ts +2 -1
  88. package/lib/types/elements/gtk-media-controls.d.ts +2 -1
  89. package/lib/types/elements/gtk-menu-button.d.ts +2 -1
  90. package/lib/types/elements/gtk-notebook.d.ts +2 -1
  91. package/lib/types/elements/gtk-overlay.d.ts +2 -1
  92. package/lib/types/elements/gtk-paned.d.ts +2 -1
  93. package/lib/types/elements/gtk-picture.d.ts +2 -1
  94. package/lib/types/elements/gtk-popover-bin.d.ts +2 -1
  95. package/lib/types/elements/gtk-popover-menu-bar.d.ts +2 -1
  96. package/lib/types/elements/gtk-popover.d.ts +2 -1
  97. package/lib/types/elements/gtk-progress-bar.d.ts +2 -1
  98. package/lib/types/elements/gtk-revealer.d.ts +2 -1
  99. package/lib/types/elements/gtk-scale-button.d.ts +2 -1
  100. package/lib/types/elements/gtk-scale.d.ts +2 -1
  101. package/lib/types/elements/gtk-scrollbar.d.ts +2 -1
  102. package/lib/types/elements/gtk-scrolled-window.d.ts +2 -1
  103. package/lib/types/elements/gtk-search-bar.d.ts +2 -1
  104. package/lib/types/elements/gtk-separator.d.ts +2 -1
  105. package/lib/types/elements/gtk-spin-button.d.ts +2 -1
  106. package/lib/types/elements/gtk-spinner.d.ts +2 -1
  107. package/lib/types/elements/gtk-stack-page.d.ts +2 -1
  108. package/lib/types/elements/gtk-stack-sidebar.d.ts +2 -1
  109. package/lib/types/elements/gtk-stack-switcher.d.ts +2 -1
  110. package/lib/types/elements/gtk-stack.d.ts +2 -1
  111. package/lib/types/elements/gtk-switch.d.ts +2 -1
  112. package/lib/types/elements/gtk-text-view.d.ts +2 -1
  113. package/lib/types/elements/gtk-text.d.ts +2 -1
  114. package/lib/types/elements/gtk-tree-expander.d.ts +2 -1
  115. package/lib/types/elements/gtk-video.d.ts +2 -1
  116. package/lib/types/elements/gtk-viewport.d.ts +2 -1
  117. package/lib/types/elements/gtk-window-controls.d.ts +2 -1
  118. package/lib/types/elements/gtk-window-handle.d.ts +2 -1
  119. package/lib/types/elements/gtk-window.d.ts +2 -1
  120. package/lib/types/gobject-elements.d.ts +7 -1
  121. package/lib/types/gtksource/gtk-source-view.d.ts +2 -1
  122. package/lib/types/{icon-registry.spec.d.ts → icon-masks.spec.d.ts} +1 -1
  123. package/lib/types/icon-theme.d.ts +15 -35
  124. package/lib/types/index.d.ts +1 -2
  125. package/lib/types/namespace/gdk.d.ts +1 -0
  126. package/lib/types/namespace/gio.d.ts +1 -0
  127. package/lib/types/namespace/glib.d.ts +2 -1
  128. package/lib/types/signals.d.ts +5 -1
  129. package/lib/types/source-view/adw-source-view.d.ts +2 -1
  130. package/lib/types/styles.generated.d.ts +1 -1
  131. package/lib/types/widget-element.d.ts +25 -0
  132. package/lib/types/widget-map.spec.d.ts +1 -0
  133. package/lib/types/widget-mapping.d.ts +7 -0
  134. package/package.json +12 -12
  135. package/scss/_about_dialog.scss +46 -1
  136. package/scss/_checks.scss +5 -2
  137. package/scss/_icon.scss +3 -4
  138. package/scss/_labels.scss +2 -2
  139. package/scss/_overlay_split_view.scss +10 -0
  140. package/scss/_scrolled_window.scss +15 -0
  141. package/scss/_widget.scss +5 -1
  142. package/src/accessible.ts +29 -0
  143. package/src/adw-about-dialog.spec.ts +81 -12
  144. package/src/adw-action-rows.spec.ts +2 -2
  145. package/src/adw-button-content.spec.ts +1 -1
  146. package/src/adw-carousel.spec.ts +49 -1
  147. package/src/capabilities.mjs +1 -0
  148. package/src/checks.spec.ts +51 -1
  149. package/src/compute-expand.spec.ts +186 -0
  150. package/src/compute-expand.ts +134 -0
  151. package/src/construct-vectors.spec.ts +46 -1
  152. package/src/elements/adw-about-dialog.ts +152 -9
  153. package/src/elements/adw-action-row.ts +2 -1
  154. package/src/elements/adw-alert-dialog.ts +3 -2
  155. package/src/elements/adw-avatar.ts +2 -1
  156. package/src/elements/adw-banner.ts +2 -1
  157. package/src/elements/adw-bin.ts +2 -1
  158. package/src/elements/adw-bottom-sheet.ts +5 -4
  159. package/src/elements/adw-breakpoint-bin.ts +2 -1
  160. package/src/elements/adw-button-content.ts +2 -1
  161. package/src/elements/adw-button-row.ts +2 -1
  162. package/src/elements/adw-card.ts +2 -1
  163. package/src/elements/adw-carousel.ts +62 -3
  164. package/src/elements/adw-clamp-scrollable.ts +2 -1
  165. package/src/elements/adw-clamp.ts +2 -1
  166. package/src/elements/adw-combo-row.ts +15 -1
  167. package/src/elements/adw-data-grid.ts +2 -1
  168. package/src/elements/adw-dialog.ts +2 -1
  169. package/src/elements/adw-entry-row.ts +2 -1
  170. package/src/elements/adw-expander-row.ts +2 -1
  171. package/src/elements/adw-header-bar.ts +2 -1
  172. package/src/elements/adw-inline-view-switcher.ts +3 -2
  173. package/src/elements/adw-layout-slot.ts +2 -1
  174. package/src/elements/adw-multi-layout-view.ts +2 -1
  175. package/src/elements/adw-navigation-split-view.ts +2 -1
  176. package/src/elements/adw-navigation-view.ts +3 -2
  177. package/src/elements/adw-overlay-split-view.ts +2 -1
  178. package/src/elements/adw-preferences-dialog.ts +6 -4
  179. package/src/elements/adw-preferences-group.ts +2 -1
  180. package/src/elements/adw-preferences-row.ts +2 -1
  181. package/src/elements/adw-shortcut-label.ts +2 -1
  182. package/src/elements/adw-shortcuts-item.ts +2 -1
  183. package/src/elements/adw-shortcuts-section.ts +2 -1
  184. package/src/elements/adw-sidebar.ts +4 -3
  185. package/src/elements/adw-spin-row.ts +2 -1
  186. package/src/elements/adw-spinner.ts +2 -1
  187. package/src/elements/adw-split-button.ts +2 -1
  188. package/src/elements/adw-status-page.ts +2 -1
  189. package/src/elements/adw-switch-row.ts +10 -1
  190. package/src/elements/adw-tab-bar.ts +2 -1
  191. package/src/elements/adw-tab-button.ts +2 -1
  192. package/src/elements/adw-tab-overview.ts +2 -1
  193. package/src/elements/adw-tab-view.ts +3 -2
  194. package/src/elements/adw-toast-overlay.ts +2 -1
  195. package/src/elements/adw-toggle-group.ts +3 -2
  196. package/src/elements/adw-toolbar-view.ts +2 -1
  197. package/src/elements/adw-view-stack.ts +39 -1
  198. package/src/elements/adw-view-switcher-bar.ts +2 -1
  199. package/src/elements/adw-view-switcher-sidebar.ts +2 -1
  200. package/src/elements/adw-view-switcher.ts +3 -2
  201. package/src/elements/adw-window-title.ts +2 -1
  202. package/src/elements/adw-wrap-box.ts +2 -1
  203. package/src/elements/checks.ts +85 -6
  204. package/src/elements/gtk-action-bar.ts +2 -1
  205. package/src/elements/gtk-aspect-frame.ts +2 -1
  206. package/src/elements/gtk-box.ts +56 -1
  207. package/src/elements/gtk-button.ts +10 -22
  208. package/src/elements/gtk-calendar.ts +2 -1
  209. package/src/elements/gtk-center-box.ts +2 -1
  210. package/src/elements/gtk-color-dialog-button.ts +2 -1
  211. package/src/elements/gtk-drag-icon.ts +2 -1
  212. package/src/elements/gtk-drawing-area.ts +2 -1
  213. package/src/elements/gtk-drop-down.ts +2 -1
  214. package/src/elements/gtk-editable-label.ts +2 -1
  215. package/src/elements/gtk-entry.ts +2 -1
  216. package/src/elements/gtk-expander.ts +2 -1
  217. package/src/elements/gtk-fixed.ts +2 -1
  218. package/src/elements/gtk-flow-box-child.ts +2 -1
  219. package/src/elements/gtk-flow-box.ts +2 -1
  220. package/src/elements/gtk-font-dialog-button.ts +2 -1
  221. package/src/elements/gtk-frame.ts +2 -1
  222. package/src/elements/gtk-gl-area.ts +2 -1
  223. package/src/elements/gtk-graphics-offload.ts +2 -1
  224. package/src/elements/gtk-grid.ts +2 -1
  225. package/src/elements/gtk-header-bar.ts +2 -1
  226. package/src/elements/gtk-image.ts +14 -15
  227. package/src/elements/gtk-inscription.ts +2 -1
  228. package/src/elements/gtk-label.ts +12 -5
  229. package/src/elements/gtk-level-bar.ts +2 -1
  230. package/src/elements/gtk-list-box-row.ts +2 -1
  231. package/src/elements/gtk-list-box.ts +2 -1
  232. package/src/elements/gtk-list-view.ts +2 -1
  233. package/src/elements/gtk-media-controls.ts +2 -1
  234. package/src/elements/gtk-menu-button.ts +2 -1
  235. package/src/elements/gtk-notebook.ts +2 -1
  236. package/src/elements/gtk-overlay.ts +2 -1
  237. package/src/elements/gtk-paned.ts +2 -1
  238. package/src/elements/gtk-picture.ts +2 -1
  239. package/src/elements/gtk-popover-bin.ts +2 -1
  240. package/src/elements/gtk-popover-menu-bar.ts +2 -1
  241. package/src/elements/gtk-popover.ts +2 -1
  242. package/src/elements/gtk-progress-bar.ts +2 -1
  243. package/src/elements/gtk-revealer.ts +2 -1
  244. package/src/elements/gtk-scale-button.ts +2 -1
  245. package/src/elements/gtk-scale.ts +2 -1
  246. package/src/elements/gtk-scrollbar.ts +2 -1
  247. package/src/elements/gtk-scrolled-window.ts +8 -5
  248. package/src/elements/gtk-search-bar.ts +2 -1
  249. package/src/elements/gtk-separator.ts +2 -1
  250. package/src/elements/gtk-spin-button.ts +2 -1
  251. package/src/elements/gtk-spinner.ts +2 -1
  252. package/src/elements/gtk-stack-page.ts +2 -1
  253. package/src/elements/gtk-stack-sidebar.ts +2 -1
  254. package/src/elements/gtk-stack-switcher.ts +2 -1
  255. package/src/elements/gtk-stack.ts +2 -1
  256. package/src/elements/gtk-switch.ts +2 -1
  257. package/src/elements/gtk-text-view.ts +2 -1
  258. package/src/elements/gtk-text.ts +2 -1
  259. package/src/elements/gtk-tree-expander.ts +2 -1
  260. package/src/elements/gtk-video.ts +2 -1
  261. package/src/elements/gtk-viewport.ts +2 -1
  262. package/src/elements/gtk-window-controls.ts +2 -1
  263. package/src/elements/gtk-window-handle.ts +2 -1
  264. package/src/elements/gtk-window.ts +2 -1
  265. package/src/gio.spec.ts +13 -3
  266. package/src/glib.spec.ts +9 -0
  267. package/src/gobject-elements.ts +18 -4
  268. package/src/gtk-label.spec.ts +39 -0
  269. package/src/gtk-scrolled-window.spec.ts +6 -6
  270. package/src/gtk-value-doors.spec.ts +19 -0
  271. package/src/gtk-window.spec.ts +2 -2
  272. package/src/gtksource/gtk-source-view.ts +2 -1
  273. package/src/{icon-registry.spec.ts → icon-masks.spec.ts} +4 -100
  274. package/src/icon-theme.spec.ts +69 -124
  275. package/src/icon-theme.ts +29 -119
  276. package/src/index.ts +3 -7
  277. package/src/namespace/gdk.ts +1 -0
  278. package/src/namespace/gio.ts +8 -0
  279. package/src/namespace/glib.ts +4 -0
  280. package/src/shared-tree-builder.ts +26 -3
  281. package/src/signals.ts +11 -2
  282. package/src/source-view/adw-source-view.ts +2 -1
  283. package/src/split-button.spec.ts +1 -1
  284. package/src/style-manager.spec.ts +13 -0
  285. package/src/style-manager.ts +6 -0
  286. package/src/styles.generated.ts +1 -1
  287. package/src/test.browser.mts +6 -2
  288. package/src/widget-element.ts +75 -0
  289. package/src/widget-map.spec.ts +116 -0
  290. package/src/widget-mapping.ts +100 -0
  291. package/src/widget-visible.spec.ts +44 -0
  292. package/lib/types/icon-registry.d.ts +0 -37
  293. package/src/icon-registry.ts +0 -163
@@ -1,30 +1,38 @@
1
1
  // The colour half of the icon theme, measured through `getComputedStyle` like
2
- // `icon-registry.spec.ts` measures the symbolic half: a class on the node proves nothing
2
+ // `icon-masks.spec.ts` measures the symbolic half: a class on the node proves nothing
3
3
  // about what it paints.
4
4
  //
5
5
  // The order under test is GLib's (`g_themed_icon_update_names`, which `GtkImage`'s lookup
6
6
  // goes through): `X` tries [X, X-symbolic], `X-symbolic` tries [X-symbolic, X], and a name
7
7
  // neither answers draws `image-missing`. Over the names the order is gtkicontheme.c's: the
8
- // current theme (the stylesheet's glyphs) before hicolor, where a colour registration sits.
9
- // The registry is module-level and outlives a test,
10
- // so every case registers names of its own.
8
+ // current theme (the stylesheet's glyphs) before hicolor, where an app's resource files sit.
9
+ // Colour icons come from the shared GResource fixture, on the theme's resource path as
10
+ // `GtkApplication` adds `<resource-base-path>/icons/` (ADR 0116 § 4).
11
+ import { ICON_FIXTURE_PATH, gioResourceFixtures } from '@gjsify/adwaita-core/conformance';
11
12
  import { describe, expect, it } from '@gjsify/unit';
12
13
 
13
14
  import { COLOR_ICON_CLASS, type GtkImage } from './elements/gtk-image.js';
14
- import { fallbackMask, maskOf } from './icon-registry.spec.js';
15
- import { isIconAvailable, registerIcon } from './icon-registry.js';
16
- import { registerColorIcon } from './icon-theme.js';
17
-
18
- /** A full-colour app icon: a red square on a blue disc, nothing `currentColor` about it. */
19
- const COLOR_SVG = `<svg height="128" viewBox="0 0 128 128" width="128" xmlns="http://www.w3.org/2000/svg">
20
- <circle cx="64" cy="64" r="60" fill="#3584e4"/>
21
- <rect x="40" y="40" width="48" height="48" fill="#e01b24"/>
22
- </svg>`;
23
-
24
- /** A symbolic glyph, distinct from the colour one by its path data. */
25
- const SYMBOLIC_SVG = `<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
26
- <path d="m 3 3 h 10 v 10 h -10 z m 0 0" fill="currentColor"/>
27
- </svg>`;
15
+ import { fallbackMask, maskOf } from './icon-masks.spec.js';
16
+ import { isIconAvailable } from './icon-theme.js';
17
+ import { Gio, Gtk } from './index.js';
18
+
19
+ const theme = Gtk.IconTheme.get_for_display(null);
20
+
21
+ /** Runs `body` with the fixture's icons on the theme's resource path. */
22
+ async function withIcons(body: () => void): Promise<void> {
23
+ const resource = Gio.Resource.new_from_data(gioResourceFixtures().icons);
24
+ Gio.resources_register(resource);
25
+ const before = theme.get_resource_path();
26
+ theme.add_resource_path(ICON_FIXTURE_PATH);
27
+ // `changed` is deferred: let it drop the previous scan's URLs first.
28
+ await new Promise((resolve) => setTimeout(resolve, 0));
29
+ try {
30
+ body();
31
+ } finally {
32
+ theme.set_resource_path(before);
33
+ Gio.resources_unregister(resource);
34
+ }
35
+ }
28
36
 
29
37
  function mount(iconName: string): { el: GtkImage; host: HTMLElement } {
30
38
  const host = document.createElement('div');
@@ -40,7 +48,7 @@ function maskClasses(el: HTMLElement): string[] {
40
48
  return [...el.classList].filter((c) => c.startsWith('adw-icon--'));
41
49
  }
42
50
 
43
- /** True when `el` paints the registered colour image and nothing masked. */
51
+ /** True when `el` paints a resource image as-is and nothing masked. */
44
52
  function drawsInColor(el: HTMLElement): boolean {
45
53
  const style = getComputedStyle(el);
46
54
  return (
@@ -48,66 +56,41 @@ function drawsInColor(el: HTMLElement): boolean {
48
56
  maskClasses(el).length === 0 &&
49
57
  style.maskImage === 'none' &&
50
58
  style.backgroundColor === 'rgba(0, 0, 0, 0)' &&
51
- // `#e01b24` survives `toDataUri` as `%23e01b24`.
52
- style.backgroundImage.includes('%23e01b24')
59
+ style.backgroundImage.startsWith('url("blob:')
53
60
  );
54
61
  }
55
62
 
56
63
  export const AdwIconThemeTest = async () => {
57
- await describe('registerColorIcon — a full-colour icon draws as-is', async () => {
58
- await it('a reverse-DNS application id draws its image, unmasked and unfilled', () => {
59
- const APP_ID = 'org.gjsify.ColorProbe';
60
- registerColorIcon(APP_ID, COLOR_SVG);
61
-
62
- const { el, host } = mount(APP_ID);
63
- expect(drawsInColor(el)).toBe(true);
64
- expect(isIconAvailable(APP_ID)).toBe(true);
65
- host.remove();
66
- });
67
-
68
- await it('an already-mounted element redraws when the registration lands', () => {
69
- const APP_ID = 'org.gjsify.LateColorProbe';
70
- const { el, host } = mount(APP_ID);
71
- // Before: what GTK draws for a name its theme does not have.
72
- expect(maskOf(el)).toBe(fallbackMask());
73
-
74
- registerColorIcon(APP_ID, COLOR_SVG);
75
- expect(drawsInColor(el)).toBe(true);
76
- host.remove();
77
- });
78
-
79
- await it("AdwAboutDialog's application-icon draws in colour", () => {
80
- const APP_ID = 'org.gjsify.AboutColorProbe';
81
- registerColorIcon(APP_ID, COLOR_SVG);
82
-
83
- const host = document.createElement('div');
84
- document.body.appendChild(host);
85
- const dialog = document.createElement('adw-about-dialog');
86
- dialog.setAttribute('application-icon', APP_ID);
87
- host.appendChild(dialog);
88
-
89
- const icon = dialog.querySelector('.adw-about-dialog-icon') as HTMLElement;
90
- expect(drawsInColor(icon)).toBe(true);
91
- // The 96px box, and NOT a solid square under the image: the about dialog used to
92
- // force `background-color: currentColor` on it.
93
- expect(getComputedStyle(icon).width).toBe('96px');
94
- host.remove();
64
+ await describe('a full-colour resource icon draws as-is', async () => {
65
+ await it('draws its image, unmasked and unfilled', async () => {
66
+ await withIcons(() => {
67
+ const { el, host } = mount('flat');
68
+ expect(drawsInColor(el)).toBe(true);
69
+ expect(isIconAvailable('flat')).toBe(true);
70
+ host.remove();
71
+ });
95
72
  });
96
73
 
97
- await it('a URL source resolves against the document', () => {
98
- const APP_ID = 'org.gjsify.UrlColorProbe';
99
- registerColorIcon(APP_ID, 'icons/org.gjsify.UrlColorProbe.svg');
100
-
101
- const { el, host } = mount(APP_ID);
102
- const expected = new URL('icons/org.gjsify.UrlColorProbe.svg', document.baseURI).href;
103
- expect(el.classList.contains(COLOR_ICON_CLASS)).toBe(true);
104
- expect(getComputedStyle(el).backgroundImage).toBe(`url("${expected}")`);
105
- host.remove();
74
+ await it("AdwAboutDialog's application-icon draws in colour", async () => {
75
+ await withIcons(() => {
76
+ const host = document.createElement('div');
77
+ document.body.appendChild(host);
78
+ const dialog = document.createElement('adw-about-dialog');
79
+ dialog.setAttribute('application-icon', 'flat');
80
+ host.appendChild(dialog);
81
+
82
+ const icon = dialog.querySelector('.adw-about-dialog-icon') as HTMLElement;
83
+ expect(drawsInColor(icon)).toBe(true);
84
+ // The 96px box, and NOT a solid square under the image: the about dialog used to
85
+ // force `background-color: currentColor` on it.
86
+ expect(getComputedStyle(icon).width).toBe('96px');
87
+ host.remove();
88
+ });
106
89
  });
107
90
  });
108
91
 
109
92
  await describe('the lookup order is GThemedIcon’s', async () => {
110
- await it('a symbolic icon still masks — a compiled one with no colour entry', () => {
93
+ await it('a symbolic icon still masks — a compiled one with no resource', () => {
111
94
  const { el, host } = mount('go-next-symbolic');
112
95
  expect(el.classList.contains(COLOR_ICON_CLASS)).toBe(false);
113
96
  expect(maskClasses(el)).toStrictEqual(['adw-icon--go-next']);
@@ -116,41 +99,28 @@ export const AdwIconThemeTest = async () => {
116
99
  host.remove();
117
100
  });
118
101
 
119
- await it("the theme's X-symbolic answers X and X-symbolic before the colour X, as on GTK", () => {
120
- const NAME = 'gjsify-theme-both-probe';
121
- registerColorIcon(NAME, COLOR_SVG);
122
- registerIcon(`${NAME}-symbolic`, SYMBOLIC_SVG);
123
-
124
- // gtkicontheme.c `real_choose_icon`: theme outer, name inner, and the colour icon is
125
- // hicolor's, so the current theme's `X-symbolic` comes first (ADR 0115 Amendment 2).
126
- const plain = mount(NAME);
127
- expect(plain.el.classList.contains(COLOR_ICON_CLASS)).toBe(false);
128
- expect(maskOf(plain.el).includes('m%203%203')).toBe(true);
129
- plain.host.remove();
130
-
131
- const symbolic = mount(`${NAME}-symbolic`);
132
- expect(symbolic.el.classList.contains(COLOR_ICON_CLASS)).toBe(false);
133
- expect(maskOf(symbolic.el).includes('m%203%203')).toBe(true);
134
- expect(getComputedStyle(symbolic.el).backgroundImage).toBe('none');
135
- symbolic.host.remove();
102
+ await it("the theme's X-symbolic answers X before a resource's colour X, as on GTK", async () => {
103
+ // The fixture ships a colour `16x16/actions/go-next.png`; gtkicontheme.c
104
+ // `real_choose_icon` goes theme outer, name inner, and the file is hicolor's.
105
+ await withIcons(() => {
106
+ const { el, host } = mount('go-next');
107
+ expect(el.classList.contains(COLOR_ICON_CLASS)).toBe(false);
108
+ expect(maskClasses(el)).toStrictEqual(['adw-icon--go-next']);
109
+ host.remove();
110
+ });
136
111
  });
137
112
 
138
- await it('X-symbolic falls back to the colour X, and back again once X-symbolic lands', () => {
139
- const NAME = 'gjsify-theme-fallback-probe';
140
- registerColorIcon(NAME, COLOR_SVG);
141
-
142
- const { el, host } = mount(`${NAME}-symbolic`);
143
- expect(drawsInColor(el)).toBe(true);
144
- expect(isIconAvailable(`${NAME}-symbolic`)).toBe(true);
145
-
146
- registerIcon(NAME, SYMBOLIC_SVG);
147
- expect(el.classList.contains(COLOR_ICON_CLASS)).toBe(false);
148
- expect(maskOf(el).includes('m%203%203')).toBe(true);
149
- host.remove();
113
+ await it('X-symbolic falls back to the colour X', async () => {
114
+ await withIcons(() => {
115
+ const { el, host } = mount('flat-symbolic');
116
+ expect(drawsInColor(el)).toBe(true);
117
+ expect(isIconAvailable('flat-symbolic')).toBe(true);
118
+ host.remove();
119
+ });
150
120
  });
151
121
 
152
122
  await it('an unknown name draws image-missing', () => {
153
- for (const unknown of ['org.gjsify.NeverRegistered', 'gjsify-theme-never-registered']) {
123
+ for (const unknown of ['org.gjsify.NeverShipped', 'gjsify-theme-never-shipped', 'flat']) {
154
124
  const { el, host } = mount(unknown);
155
125
  expect(el.classList.contains(COLOR_ICON_CLASS)).toBe(false);
156
126
  expect(maskOf(el)).toBe(fallbackMask());
@@ -159,29 +129,4 @@ export const AdwIconThemeTest = async () => {
159
129
  }
160
130
  });
161
131
  });
162
-
163
- await describe('registerColorIcon rejects what it cannot draw faithfully', async () => {
164
- await it('throws on a name that is empty, spaced, or symbolic', () => {
165
- expect(() => registerColorIcon('', COLOR_SVG)).toThrow();
166
- expect(() => registerColorIcon('a b', COLOR_SVG)).toThrow();
167
- // The symbolic half has its own door; GTK's theme treats all three suffixes as
168
- // symbolic files.
169
- expect(() => registerColorIcon('gjsify-probe-symbolic', COLOR_SVG)).toThrow();
170
- expect(() => registerColorIcon('gjsify-probe-symbolic-ltr', COLOR_SVG)).toThrow();
171
- expect(() => registerColorIcon('gjsify-probe-symbolic-rtl', COLOR_SVG)).toThrow();
172
- });
173
-
174
- await it('throws on a source that is not SVG or an image URL, and registers nothing', () => {
175
- const REJECTED = 'org.gjsify.RejectedColorProbe';
176
- expect(() => registerColorIcon(REJECTED, '')).toThrow();
177
- expect(() => registerColorIcon(REJECTED, '<p>not svg</p>')).toThrow();
178
- expect(() => registerColorIcon(REJECTED, 'javascript:alert(1)')).toThrow();
179
- expect(() => registerColorIcon(REJECTED, 'data:text/html,<svg></svg>')).toThrow();
180
- expect(isIconAvailable(REJECTED)).toBe(false);
181
-
182
- const { el, host } = mount(REJECTED);
183
- expect(maskOf(el)).toBe(fallbackMask());
184
- host.remove();
185
- });
186
- });
187
132
  };
package/src/icon-theme.ts CHANGED
@@ -13,25 +13,18 @@
13
13
  // and `gtk_icon_theme_lookup_icon` returns the first candidate the theme has, else
14
14
  // `image-missing` — it never returns NULL (refs/gtk/gtk/gtkicontheme.c, `choose_icon`).
15
15
  //
16
- // This package compiles and registers symbolic glyphs only (`icon-registry.ts`, the
17
- // stylesheet's `.adw-icon--<name>` masks). The colour half lives here: a map from an EXACT
18
- // name to an image URL, filled by {@link registerColorIcon}. With the map empty every lookup
19
- // ends where it ended before this module existed, so nothing that only uses symbolic icons
20
- // changes.
21
- //
22
- // The colour map is keyed by the raw name, not by `normalizeIconName`: an application icon
23
- // is named after its application ID (`org.gnome.Builder`, adw-about-dialog.c:1215), which is
24
- // not a CSS token and never could be a mask class, but is a perfectly good Map key.
16
+ // The stylesheet compiles a SUBSET of Adwaita's symbolic glyphs (the `.adw-icon--<name>`
17
+ // masks); that subset is the port's current theme. Everything else — an application's own
18
+ // symbolic glyphs and its full-colour icons alike — comes from GResource files.
25
19
  //
26
20
  // RESOURCE PATHS (ADR 0116 § 4). The names go through the `Gtk.IconTheme` resource paths an
27
21
  // application added (`GtkApplication` adds `<resource-base-path>/icons/` at startup);
28
22
  // `@gjsify/adwaita-core`'s `lookupResourceIcon` decides which file answers, as gtkicontheme.c
29
- // does, and asks this module only whether the stylesheet holds a symbolic glyph. A colour
30
- // registration stands in hicolor there, as the file an app ships in `hicolor/scalable/apps/`
31
- // would: the stylesheet's `X-symbolic` answers `X` before it, as Adwaita's does on GNOME. A file
32
- // answers as a blob URL of its bytes: a symbolic one is drawn as a mask in `currentColor`, the
33
- // rest as-is. A browser draws SVG and PNG; an XPM or a compressed entry draws `image-missing`
34
- // with a warning.
23
+ // does, and asks this module only whether the stylesheet holds a symbolic glyph: the
24
+ // stylesheet's `X-symbolic` answers `X` before a resource's colour `X`, as Adwaita's does on
25
+ // GNOME. A file answers as a blob URL of its bytes: a symbolic one is drawn as a mask in
26
+ // `currentColor`, the rest as-is. A browser draws SVG and PNG; an XPM or a compressed entry
27
+ // draws `image-missing` with a warning.
35
28
  //
36
29
  // Original implementation.
37
30
 
@@ -42,14 +35,11 @@ import {
42
35
  resources_lookup_data,
43
36
  type IconDirection,
44
37
  } from '@gjsify/adwaita-core';
45
- import { toDataUri } from '@gjsify/adwaita-icons/utils';
46
38
 
47
39
  import { IconTheme } from './namespace/gtk-platform.js';
48
40
 
49
41
  /** What one icon name resolves to, in the order GTK's theme would try it. */
50
42
  export type IconLookup =
51
- /** A registered full-colour image, drawn as-is. `url` is ready for `url("…")`. */
52
- | { kind: 'color'; name: string; url: string }
53
43
  /**
54
44
  * A file under a `Gtk.IconTheme` resource path; `symbolic` files are masks in `currentColor`.
55
45
  * `url` is `null` when the browser cannot draw the file (XPM, compressed): draw `image-missing`.
@@ -61,21 +51,9 @@ export type IconLookup =
61
51
  | { kind: 'missing' };
62
52
 
63
53
  /**
64
- * Every name GTK's theme treats as a symbolic FILE (gtkicontheme.c `icon_name_is_symbolic`):
65
- * the directional variants are symbolic too, so none of them is a colour icon.
66
- */
67
- const SYMBOLIC_FILE_NAME = /-symbolic(?:-ltr|-rtl)?$/;
68
-
69
- /** Schemes a colour icon may be fetched from. Anything else can run script or is unknown. */
70
- const ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'blob:', 'file:']);
71
-
72
- const colorIcons = new Map<string, string>();
73
-
74
- /**
75
- * Elements that drew a lookup result and have to redraw when the theme changes. A symbolic
76
- * registration is live through CSS and needs no redraw, but a colour one is an inline
77
- * `background-image`, and GTK's own images re-run their lookup on the theme's `changed`
78
- * signal for the same reason.
54
+ * Elements that drew a lookup result and have to redraw when the theme changes. A resource
55
+ * file is an inline `background-image` or mask, and GTK's own images re-run their lookup on
56
+ * the theme's `changed` signal for the same reason.
79
57
  */
80
58
  const listeners = new Set<() => void>();
81
59
 
@@ -87,11 +65,6 @@ export function onIconThemeChanged(listener: () => void): () => void {
87
65
  };
88
66
  }
89
67
 
90
- /** Run every subscriber — after any registration that can change a lookup's answer. */
91
- export function notifyIconThemeChanged(): void {
92
- for (const listener of listeners) listener();
93
- }
94
-
95
68
  /** Whether a symbolic glyph for an already-normalized name is in the cascade. */
96
69
  function hasSymbolic(normalized: string): boolean {
97
70
  if (normalized === '') return false;
@@ -102,11 +75,9 @@ function hasSymbolic(normalized: string): boolean {
102
75
  * Resolve `iconName` as `gtk_icon_theme_lookup_by_gicon` would for a `GtkImage`. `null` when
103
76
  * no name was given — the state where GTK draws nothing, not `image-missing`.
104
77
  *
105
- * When neither a colour registration nor a resource holds a name, the request is returned as a
106
- * symbolic one WITHOUT checking the cascade: the mask class falls back to `image-missing`
107
- * through CSS by itself and corrects itself when a later `registerIcon` lands, which is how
108
- * `<gtk-image>` behaved before this module. The cascade is read only to choose between a
109
- * symbolic glyph and a colour entry or file.
78
+ * When no resource holds a name, the request is returned as a symbolic one WITHOUT checking
79
+ * the cascade: the mask class falls back to `image-missing` through CSS by itself. The cascade
80
+ * is read only to choose between a symbolic glyph and a resource file.
110
81
  */
111
82
  export function lookupIcon(iconName: string | null | undefined, request: IconRequest = {}): IconLookup | null {
112
83
  if (iconName === null || iconName === undefined || iconName === '') return null;
@@ -133,7 +104,7 @@ const resourceUrls = new Map<string, string | null>();
133
104
  theme.connect('changed', () => {
134
105
  for (const url of resourceUrls.values()) if (url !== null) URL.revokeObjectURL(url);
135
106
  resourceUrls.clear();
136
- notifyIconThemeChanged();
107
+ for (const listener of listeners) listener();
137
108
  });
138
109
 
139
110
  const RESOURCE_TYPES: Record<string, string> = { '.svg': 'image/svg+xml', '.png': 'image/png' };
@@ -159,8 +130,8 @@ function resourceUrl(path: string): string | null {
159
130
  }
160
131
 
161
132
  /**
162
- * The lookup over the colour registrations and the theme's resource paths; `null` when neither
163
- * answers any name, a `symbolic` entry when the current theme's glyph comes first.
133
+ * The lookup over the theme's resource paths; `null` when no resource answers any name, a
134
+ * `symbolic` entry when the current theme's glyph comes first.
164
135
  */
165
136
  export function resourceIcon(iconName: string, request: IconRequest): IconLookup | null {
166
137
  const match = lookupResourceIcon(theme, {
@@ -170,11 +141,9 @@ export function resourceIcon(iconName: string, request: IconRequest): IconLookup
170
141
  },
171
142
  scale: request.scale ?? 1,
172
143
  themeHas: (name) => hasSymbolic(normalizeIconName(name)),
173
- hicolorHas: (name) => colorIcons.has(name),
174
144
  });
175
145
  if (match === null) return null;
176
146
  if (match.kind === 'theme') return { kind: 'symbolic', name: normalizeIconName(match.name) };
177
- if (match.kind === 'hicolor') return { kind: 'color', name: match.name, url: colorIcons.get(match.name)! };
178
147
  return {
179
148
  kind: 'resource',
180
149
  name: match.name,
@@ -185,78 +154,19 @@ export function resourceIcon(iconName: string, request: IconRequest): IconLookup
185
154
  }
186
155
 
187
156
  /**
188
- * Whether `iconName` resolves to something other than `image-missing` — a registered colour
189
- * icon, a file under a resource path, or a symbolic glyph compiled into the stylesheet or
190
- * registered at runtime.
157
+ * Whether `name` draws something other than `image-missing` — a file under a `Gtk.IconTheme`
158
+ * resource path, or a symbolic glyph compiled into the stylesheet. The name is looked up
159
+ * exactly as `<gtk-image icon-name>` looks it up.
160
+ *
161
+ * Reads the live cascade rather than a generated list, so it answers for a glyph that arrived
162
+ * by any route, including a consumer's own `--icon-<name>` declaration. It is deliberately the
163
+ * CUSTOM PROPERTY that is read and not the rendered `mask-image` of a probe element: since
164
+ * `_icon.scss` gives every icon box the `image-missing` fallback, a rendered mask is never
165
+ * `none`, and comparing against the fallback would answer `false` for `image-missing` itself.
191
166
  */
192
- export function iconResolves(iconName: string): boolean {
193
- const found = lookupIcon(iconName);
167
+ export function isIconAvailable(name: string): boolean {
168
+ const found = lookupIcon(name);
194
169
  if (found === null || found.kind === 'missing') return false;
195
170
  if (found.kind === 'resource') return found.url !== null;
196
- return found.kind === 'color' || hasSymbolic(found.name);
197
- }
198
-
199
- /** `source` as a URL `background-image` can load, or a thrown reason why not. */
200
- function colorIconUrl(name: string, source: string): string {
201
- if (source.trimStart().startsWith('<')) {
202
- const root = new DOMParser().parseFromString(source, 'image/svg+xml').documentElement;
203
- if (root.tagName !== 'svg') {
204
- throw new TypeError(
205
- `registerColorIcon(${JSON.stringify(name)}): the source starts with '<' but parsed ` +
206
- `to <${root.tagName}>, not an <svg> root.`,
207
- );
208
- }
209
- return toDataUri(source);
210
- }
211
-
212
- let url: URL;
213
- try {
214
- url = new URL(source, document.baseURI);
215
- } catch {
216
- throw new TypeError(`registerColorIcon(${JSON.stringify(name)}): the source is neither SVG source nor a URL.`);
217
- }
218
- const isImageData = url.protocol === 'data:' && /^data:image\//i.test(url.href);
219
- if (!isImageData && !ALLOWED_PROTOCOLS.has(url.protocol)) {
220
- throw new TypeError(
221
- `registerColorIcon(${JSON.stringify(name)}): ${JSON.stringify(url.protocol)} URLs are not ` +
222
- 'accepted — use http(s):, blob:, file: or a data:image/ URI.',
223
- );
224
- }
225
- return url.href;
226
- }
227
-
228
- /**
229
- * Register a FULL-COLOUR icon under `name` — what an application ships in
230
- * `hicolor/scalable/apps/<app-id>.svg` and GTK finds through its icon theme. Every
231
- * `<gtk-image>` asking for the name draws the image unrecoloured, the way `AdwAboutDialog`'s
232
- * `application-icon` does on GNOME.
233
- *
234
- * `name` is the EXACT icon name, reverse-DNS application IDs included. A name with a
235
- * `-symbolic` suffix THROWS: that is the other half of the theme, and {@link registerIcon}
236
- * is its door. `source` is SVG source, or a URL (resolved against the document) to any
237
- * image the browser can draw.
238
- *
239
- * Re-registering a name replaces the image, and every mounted `<gtk-image>` redraws.
240
- *
241
- * CLIENT-SIDE ONLY, like `registerIcon`: it reads `document`.
242
- */
243
- export function registerColorIcon(name: string, source: string): void {
244
- if (typeof name !== 'string' || name === '' || /\s/.test(name)) {
245
- throw new TypeError(
246
- `registerColorIcon: ${JSON.stringify(name)} is not an icon name — it must be non-empty ` +
247
- 'and contain no whitespace.',
248
- );
249
- }
250
- if (SYMBOLIC_FILE_NAME.test(name)) {
251
- throw new TypeError(
252
- `registerColorIcon: ${JSON.stringify(name)} names a SYMBOLIC icon, which is recoloured ` +
253
- 'rather than drawn as-is — register it with registerIcon() instead.',
254
- );
255
- }
256
- if (typeof source !== 'string' || source.trim() === '') {
257
- throw new TypeError(`registerColorIcon(${JSON.stringify(name)}): the source is empty.`);
258
- }
259
-
260
- colorIcons.set(name, colorIconUrl(name, source));
261
- notifyIconThemeChanged();
171
+ return hasSymbolic(found.name);
262
172
  }
package/src/index.ts CHANGED
@@ -85,13 +85,9 @@ export type { BuildOptions, BuiltTree, MountOptions, MountedSharedTree, SizeSour
85
85
  export { registerTemplateClass } from './template-classes.js';
86
86
 
87
87
  // The stylesheet compiles a chosen SUBSET of `@gjsify/adwaita-icons` (the whole set is
88
- // ~1.07 MB of data-URI), so a name outside it draws the `image-missing` fallback. This is
89
- // the way in for an app that needs a glyph this package does not ship — see
90
- // `icon-registry.ts` for the full recipe.
91
- export { isIconAvailable, registerIcon } from './icon-registry.js';
92
- // The full-colour half of the icon theme — an application icon drawn as-is, the web stand-in
93
- // for an app's `hicolor/…/apps/<app-id>.svg`. See `icon-theme.ts` for the lookup order.
94
- export { registerColorIcon } from './icon-theme.js';
88
+ // ~1.07 MB of data-URI). An app's own icons, symbolic and full-colour, come from its
89
+ // GResource through `Gtk.IconTheme`'s resource paths (ADR 0116); see `icon-theme.ts`.
90
+ export { isIconAvailable } from './icon-theme.js';
95
91
 
96
92
  // THE WIDGET CLASSES ARE REACHABLE ONLY AS `Adw.<Name>` / `Gtk.<Name>` (ADR 0034
97
93
  // clause 2 + § Amendment 6). The namespace used to be the LAST line in the file, under a
@@ -15,6 +15,7 @@ export const Display = gdk.Display;
15
15
  export const Clipboard = gdk.Clipboard;
16
16
  export const ContentProvider = gdk.ContentProvider;
17
17
  export const ContentFormats = gdk.ContentFormats;
18
+ export const AppLaunchContext = gdk.AppLaunchContext;
18
19
 
19
20
  export const CURRENT_TIME = GDK_PLATFORM_CURRENT_TIME;
20
21
  export const {
@@ -24,6 +24,10 @@
24
24
 
25
25
  import '../file-chooser-host.js';
26
26
 
27
+ import { createAppInfo } from '@gjsify/adwaita-core';
28
+
29
+ import { webPlatformHost } from '../platform-host.js';
30
+
27
31
  export { GioMenu as Menu, GioMenuItem as MenuItem } from '@gjsify/adwaita-core';
28
32
  export {
29
33
  GioFile as File,
@@ -47,3 +51,7 @@ export {
47
51
  } from '@gjsify/adwaita-core';
48
52
  export { GioApplication as Application } from '../application.js';
49
53
  export { Settings } from '@gjsify/app-settings/browser';
54
+
55
+ // `Gio.AppInfo.launch_default_for_uri` over the host `Gtk.show_uri` uses (ADR 0101): the core's
56
+ // `gio-app-info.ts`.
57
+ export const AppInfo = createAppInfo(webPlatformHost);
@@ -7,6 +7,8 @@
7
7
  // in `glib-paths.ts` of the core; the host has no filesystem, so `get_current_dir()` is `"/"` and
8
8
  // `get_system_data_dirs()` is `[]`. A port that knows better replaces the member in its own file.
9
9
  //
10
+ // `uri_escape_string` is the core's `glib-uri.ts`, a pure function over UTF-8 bytes.
11
+ //
10
12
  // `Bytes` and `Error` are the ADR 0100 subset a `Gio.File` reads and fails with (`get_data`,
11
13
  // `matches`, `code`, `message`), re-exported from the core's `glib-bytes.ts` / `glib-error.ts`.
12
14
  //
@@ -21,6 +23,7 @@ import {
21
23
  getCurrentDir,
22
24
  getSystemDataDirs,
23
25
  MAXUINT32 as GLIB_MAXUINT32,
26
+ uriEscapeString,
24
27
  } from '@gjsify/adwaita-core';
25
28
 
26
29
  const glib = createGLibTimers();
@@ -40,3 +43,4 @@ export const MAXUINT32 = GLIB_MAXUINT32;
40
43
  export const build_filenamev = buildFilenamev;
41
44
  export const get_current_dir = getCurrentDir;
42
45
  export const get_system_data_dirs = getSystemDataDirs;
46
+ export const uri_escape_string = uriEscapeString;
@@ -28,7 +28,7 @@
28
28
  // into a host `<div>` it was never given.
29
29
  //
30
30
  // Placed flat under `src/`, at parity with this package's other single-purpose modules
31
- // (`accent.ts`, `breakpoints.ts`, `icon-registry.ts`) rather than a new `conformance/` or
31
+ // (`accent.ts`, `breakpoints.ts`, `icon-theme.ts`) rather than a new `conformance/` or
32
32
  // `builder/` directory for one file: unlike `gtk-host`, this package has no existing
33
33
  // `conformance/` home to add to. Exported through the existing barrel, `src/index.ts` — the
34
34
  // package's `exports` map ships only `.`, and a new subpath would buy nothing for a module
@@ -233,7 +233,7 @@ function refuseSiblings(node: SharedTreeNode, door: string): void {
233
233
  function buildTree(node: SharedTreeNode, record: BuildRecord): BuiltTree {
234
234
  // ADR 0093 § 2: the whole tree is checked against the capability table before an element is
235
235
  // created. A refused `layout` is named with its node and the reason the table gives.
236
- assertTreeConstructs('adwaita-web', capabilities, node);
236
+ assertTreeConstructs('adwaita-web', capabilities, node, Object.keys(ARIA_OF));
237
237
  if (node.menus !== undefined) record.menus = node.menus;
238
238
  const root = buildNode(node, record);
239
239
  const siblings = new Map<string, HTMLElement>();
@@ -512,6 +512,29 @@ function writeLook(el: HTMLElement, node: SharedTreeNode, menus?: SharedTreeNode
512
512
  // `.blp`'s `styles ["title-1"]` reached the tree and never the page.
513
513
  if (node.styleClasses !== undefined && node.styleClasses.length > 0) el.classList.add(...node.styleClasses);
514
514
  writeExtensions(el, node);
515
+ writeAccessibility(el, node);
516
+ }
517
+
518
+ /**
519
+ * The `accessibility { }` names this renderer has a door for, and the ARIA attribute each is.
520
+ * The rest (states, relations, `row-index`, …) are refused by name before the build, in
521
+ * {@link assertTreeConstructs}: no element here reads them, so they would be dropped.
522
+ */
523
+ const ARIA_OF: Readonly<Record<string, string>> = {
524
+ label: 'aria-label',
525
+ description: 'aria-description',
526
+ };
527
+
528
+ /**
529
+ * `accessibility { label; description; }` as the host's `aria-label` / `aria-description`.
530
+ * Written after the props, so an element that derives a name from its tooltip sees the
531
+ * authored one first; an element whose role lives on an inner control forwards both
532
+ * (`accessible.ts`). A translatable value is written as its source text, as a prop's is.
533
+ */
534
+ function writeAccessibility(el: HTMLElement, node: SharedTreeNode): void {
535
+ for (const [name, entry] of Object.entries(node.accessibility ?? {})) {
536
+ el.setAttribute(ARIA_OF[name], String(entry.value));
537
+ }
515
538
  }
516
539
 
517
540
  /** The `GtkGridLayoutChild` properties `layout { }` may carry, and the CSS Grid line each one is. */
@@ -586,7 +609,7 @@ export interface BuiltTemplateTree {
586
609
  */
587
610
  export function buildTemplateTree(tree: SharedTreeNode, scope: TemplateScope, host: HTMLElement): BuiltTemplateTree {
588
611
  refuseSiblings(tree, 'buildTemplateTree');
589
- assertTreeConstructs('adwaita-web', capabilities, tree);
612
+ assertTreeConstructs('adwaita-web', capabilities, tree, Object.keys(ARIA_OF));
590
613
  const record = newRecord(undefined, undefined, scope);
591
614
  record.menus = tree.menus;
592
615
  if (tree.id !== undefined) record.ids.set(tree.id, host);
package/src/signals.ts CHANGED
@@ -9,7 +9,16 @@
9
9
  /** GTK signal name → the DOM event type the element dispatches it as. */
10
10
  export type DispatchedSignals = Readonly<Record<string, string>>;
11
11
 
12
- /** The signals `el`'s class declares, or none. */
12
+ /**
13
+ * The signals `el`'s class and every class above it declare, or none. A class inherits its
14
+ * base's signals as a GObject class does (`map` / `unmap` are declared once, on
15
+ * `WidgetElement`); where two declare the same name, the more derived class wins.
16
+ */
13
17
  export function dispatchedSignalsOf(el: Element): DispatchedSignals {
14
- return (el.constructor as { signals?: DispatchedSignals }).signals ?? {};
18
+ const chain: DispatchedSignals[] = [];
19
+ for (let klass: unknown = el.constructor; typeof klass === 'function'; klass = Object.getPrototypeOf(klass)) {
20
+ const own = Object.getOwnPropertyDescriptor(klass, 'signals')?.value as DispatchedSignals | undefined;
21
+ if (own !== undefined) chain.unshift(own);
22
+ }
23
+ return Object.assign({}, ...chain) as DispatchedSignals;
15
24
  }
@@ -36,13 +36,14 @@ import { adwaitaEditorTheme, adwaitaHighlightStyle, ensureSourceViewStyleInjecte
36
36
  import { formatHexAddress, formatLineNumber, stripSourceWhitespace } from '@gjsify/adwaita-core';
37
37
  import { createGtkImage } from '../elements/gtk-image.js';
38
38
  import { createCopyGlyph } from './copy-glyph.js';
39
+ import { WidgetElement } from '../widget-element.js';
39
40
 
40
41
  /** Parse a boolean attribute: present (any value but "false") ⇒ true. */
41
42
  function boolAttr(value: string | null): boolean {
42
43
  return value !== null && value !== 'false';
43
44
  }
44
45
 
45
- export class AdwSourceView extends HTMLElement {
46
+ export class AdwSourceView extends WidgetElement {
46
47
  private _view?: EditorView;
47
48
  private _editorHost!: HTMLDivElement;
48
49
  private _copyButton!: HTMLButtonElement;