@gjsify/adwaita-web 0.64.2-next.252 → 0.64.2-next.255
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.
- package/README.md +10 -80
- package/dist/adwaita-web.css +52 -3
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/accessible.d.ts +8 -0
- package/lib/types/compute-expand.d.ts +5 -0
- package/lib/types/compute-expand.spec.d.ts +1 -0
- package/lib/types/elements/adw-about-dialog.d.ts +28 -1
- package/lib/types/elements/adw-action-row.d.ts +2 -1
- package/lib/types/elements/adw-alert-dialog.d.ts +3 -2
- package/lib/types/elements/adw-avatar.d.ts +2 -1
- package/lib/types/elements/adw-banner.d.ts +2 -1
- package/lib/types/elements/adw-bin.d.ts +2 -1
- package/lib/types/elements/adw-bottom-sheet.d.ts +5 -4
- package/lib/types/elements/adw-breakpoint-bin.d.ts +2 -1
- package/lib/types/elements/adw-button-content.d.ts +2 -1
- package/lib/types/elements/adw-button-row.d.ts +2 -1
- package/lib/types/elements/adw-card.d.ts +2 -1
- package/lib/types/elements/adw-carousel.d.ts +20 -3
- package/lib/types/elements/adw-clamp-scrollable.d.ts +2 -1
- package/lib/types/elements/adw-clamp.d.ts +2 -1
- package/lib/types/elements/adw-combo-row.d.ts +6 -1
- package/lib/types/elements/adw-data-grid.d.ts +2 -1
- package/lib/types/elements/adw-dialog.d.ts +2 -1
- package/lib/types/elements/adw-entry-row.d.ts +2 -1
- package/lib/types/elements/adw-expander-row.d.ts +2 -1
- package/lib/types/elements/adw-header-bar.d.ts +2 -1
- package/lib/types/elements/adw-inline-view-switcher.d.ts +3 -2
- package/lib/types/elements/adw-layout-slot.d.ts +2 -1
- package/lib/types/elements/adw-multi-layout-view.d.ts +2 -1
- package/lib/types/elements/adw-navigation-split-view.d.ts +2 -1
- package/lib/types/elements/adw-navigation-view.d.ts +3 -2
- package/lib/types/elements/adw-overlay-split-view.d.ts +2 -1
- package/lib/types/elements/adw-preferences-dialog.d.ts +5 -4
- package/lib/types/elements/adw-preferences-group.d.ts +2 -1
- package/lib/types/elements/adw-preferences-row.d.ts +2 -1
- package/lib/types/elements/adw-shortcut-label.d.ts +2 -1
- package/lib/types/elements/adw-shortcuts-item.d.ts +2 -1
- package/lib/types/elements/adw-shortcuts-section.d.ts +2 -1
- package/lib/types/elements/adw-sidebar.d.ts +4 -3
- package/lib/types/elements/adw-spin-row.d.ts +2 -1
- package/lib/types/elements/adw-spinner.d.ts +2 -1
- package/lib/types/elements/adw-split-button.d.ts +2 -1
- package/lib/types/elements/adw-status-page.d.ts +2 -1
- package/lib/types/elements/adw-switch-row.d.ts +4 -1
- package/lib/types/elements/adw-tab-bar.d.ts +2 -1
- package/lib/types/elements/adw-tab-button.d.ts +2 -1
- package/lib/types/elements/adw-tab-overview.d.ts +2 -1
- package/lib/types/elements/adw-tab-view.d.ts +3 -2
- package/lib/types/elements/adw-toast-overlay.d.ts +2 -1
- package/lib/types/elements/adw-toggle-group.d.ts +3 -2
- package/lib/types/elements/adw-toolbar-view.d.ts +2 -1
- package/lib/types/elements/adw-view-stack.d.ts +3 -1
- package/lib/types/elements/adw-view-switcher-bar.d.ts +2 -1
- package/lib/types/elements/adw-view-switcher-sidebar.d.ts +2 -1
- package/lib/types/elements/adw-view-switcher.d.ts +3 -2
- package/lib/types/elements/adw-window-title.d.ts +2 -1
- package/lib/types/elements/adw-wrap-box.d.ts +2 -1
- package/lib/types/elements/checks.d.ts +16 -1
- package/lib/types/elements/gtk-action-bar.d.ts +2 -1
- package/lib/types/elements/gtk-aspect-frame.d.ts +2 -1
- package/lib/types/elements/gtk-box.d.ts +5 -1
- package/lib/types/elements/gtk-button.d.ts +5 -7
- package/lib/types/elements/gtk-calendar.d.ts +2 -1
- package/lib/types/elements/gtk-center-box.d.ts +2 -1
- package/lib/types/elements/gtk-color-dialog-button.d.ts +2 -1
- package/lib/types/elements/gtk-drag-icon.d.ts +2 -1
- package/lib/types/elements/gtk-drawing-area.d.ts +2 -1
- package/lib/types/elements/gtk-drop-down.d.ts +2 -1
- package/lib/types/elements/gtk-editable-label.d.ts +2 -1
- package/lib/types/elements/gtk-entry.d.ts +2 -1
- package/lib/types/elements/gtk-expander.d.ts +2 -1
- package/lib/types/elements/gtk-fixed.d.ts +2 -1
- package/lib/types/elements/gtk-flow-box-child.d.ts +2 -1
- package/lib/types/elements/gtk-flow-box.d.ts +2 -1
- package/lib/types/elements/gtk-font-dialog-button.d.ts +2 -1
- package/lib/types/elements/gtk-frame.d.ts +2 -1
- package/lib/types/elements/gtk-gl-area.d.ts +2 -1
- package/lib/types/elements/gtk-graphics-offload.d.ts +2 -1
- package/lib/types/elements/gtk-grid.d.ts +2 -1
- package/lib/types/elements/gtk-header-bar.d.ts +2 -1
- package/lib/types/elements/gtk-image.d.ts +5 -6
- package/lib/types/elements/gtk-inscription.d.ts +2 -1
- package/lib/types/elements/gtk-label.d.ts +10 -3
- package/lib/types/elements/gtk-level-bar.d.ts +2 -1
- package/lib/types/elements/gtk-list-box-row.d.ts +2 -1
- package/lib/types/elements/gtk-list-box.d.ts +2 -1
- package/lib/types/elements/gtk-list-view.d.ts +2 -1
- package/lib/types/elements/gtk-media-controls.d.ts +2 -1
- package/lib/types/elements/gtk-menu-button.d.ts +2 -1
- package/lib/types/elements/gtk-notebook.d.ts +2 -1
- package/lib/types/elements/gtk-overlay.d.ts +2 -1
- package/lib/types/elements/gtk-paned.d.ts +2 -1
- package/lib/types/elements/gtk-picture.d.ts +2 -1
- package/lib/types/elements/gtk-popover-bin.d.ts +2 -1
- package/lib/types/elements/gtk-popover-menu-bar.d.ts +2 -1
- package/lib/types/elements/gtk-popover.d.ts +2 -1
- package/lib/types/elements/gtk-progress-bar.d.ts +2 -1
- package/lib/types/elements/gtk-revealer.d.ts +2 -1
- package/lib/types/elements/gtk-scale-button.d.ts +2 -1
- package/lib/types/elements/gtk-scale.d.ts +2 -1
- package/lib/types/elements/gtk-scrollbar.d.ts +2 -1
- package/lib/types/elements/gtk-scrolled-window.d.ts +2 -1
- package/lib/types/elements/gtk-search-bar.d.ts +2 -1
- package/lib/types/elements/gtk-separator.d.ts +2 -1
- package/lib/types/elements/gtk-spin-button.d.ts +2 -1
- package/lib/types/elements/gtk-spinner.d.ts +2 -1
- package/lib/types/elements/gtk-stack-page.d.ts +2 -1
- package/lib/types/elements/gtk-stack-sidebar.d.ts +2 -1
- package/lib/types/elements/gtk-stack-switcher.d.ts +2 -1
- package/lib/types/elements/gtk-stack.d.ts +2 -1
- package/lib/types/elements/gtk-switch.d.ts +2 -1
- package/lib/types/elements/gtk-text-view.d.ts +2 -1
- package/lib/types/elements/gtk-text.d.ts +2 -1
- package/lib/types/elements/gtk-tree-expander.d.ts +2 -1
- package/lib/types/elements/gtk-video.d.ts +2 -1
- package/lib/types/elements/gtk-viewport.d.ts +2 -1
- package/lib/types/elements/gtk-window-controls.d.ts +2 -1
- package/lib/types/elements/gtk-window-handle.d.ts +2 -1
- package/lib/types/elements/gtk-window.d.ts +2 -1
- package/lib/types/gobject-elements.d.ts +7 -1
- package/lib/types/gtksource/gtk-source-view.d.ts +2 -1
- package/lib/types/{icon-registry.spec.d.ts → icon-masks.spec.d.ts} +1 -1
- package/lib/types/icon-theme.d.ts +15 -35
- package/lib/types/index.d.ts +1 -2
- package/lib/types/namespace/gdk.d.ts +1 -0
- package/lib/types/namespace/gio.d.ts +1 -0
- package/lib/types/namespace/glib.d.ts +2 -1
- package/lib/types/signals.d.ts +5 -1
- package/lib/types/source-view/adw-source-view.d.ts +2 -1
- package/lib/types/styles.generated.d.ts +1 -1
- package/lib/types/widget-element.d.ts +25 -0
- package/lib/types/widget-map.spec.d.ts +1 -0
- package/lib/types/widget-mapping.d.ts +7 -0
- package/package.json +12 -12
- package/scss/_about_dialog.scss +46 -1
- package/scss/_checks.scss +5 -2
- package/scss/_icon.scss +3 -4
- package/scss/_labels.scss +2 -2
- package/scss/_overlay_split_view.scss +10 -0
- package/scss/_scrolled_window.scss +15 -0
- package/scss/_widget.scss +5 -1
- package/src/accessible.ts +29 -0
- package/src/adw-about-dialog.spec.ts +81 -12
- package/src/adw-action-rows.spec.ts +2 -2
- package/src/adw-button-content.spec.ts +1 -1
- package/src/adw-carousel.spec.ts +49 -1
- package/src/capabilities.mjs +1 -0
- package/src/checks.spec.ts +51 -1
- package/src/compute-expand.spec.ts +186 -0
- package/src/compute-expand.ts +134 -0
- package/src/construct-vectors.spec.ts +46 -1
- package/src/elements/adw-about-dialog.ts +152 -9
- package/src/elements/adw-action-row.ts +2 -1
- package/src/elements/adw-alert-dialog.ts +3 -2
- package/src/elements/adw-avatar.ts +2 -1
- package/src/elements/adw-banner.ts +2 -1
- package/src/elements/adw-bin.ts +2 -1
- package/src/elements/adw-bottom-sheet.ts +5 -4
- package/src/elements/adw-breakpoint-bin.ts +2 -1
- package/src/elements/adw-button-content.ts +2 -1
- package/src/elements/adw-button-row.ts +2 -1
- package/src/elements/adw-card.ts +2 -1
- package/src/elements/adw-carousel.ts +62 -3
- package/src/elements/adw-clamp-scrollable.ts +2 -1
- package/src/elements/adw-clamp.ts +2 -1
- package/src/elements/adw-combo-row.ts +15 -1
- package/src/elements/adw-data-grid.ts +2 -1
- package/src/elements/adw-dialog.ts +2 -1
- package/src/elements/adw-entry-row.ts +2 -1
- package/src/elements/adw-expander-row.ts +2 -1
- package/src/elements/adw-header-bar.ts +2 -1
- package/src/elements/adw-inline-view-switcher.ts +3 -2
- package/src/elements/adw-layout-slot.ts +2 -1
- package/src/elements/adw-multi-layout-view.ts +2 -1
- package/src/elements/adw-navigation-split-view.ts +2 -1
- package/src/elements/adw-navigation-view.ts +3 -2
- package/src/elements/adw-overlay-split-view.ts +2 -1
- package/src/elements/adw-preferences-dialog.ts +6 -4
- package/src/elements/adw-preferences-group.ts +2 -1
- package/src/elements/adw-preferences-row.ts +2 -1
- package/src/elements/adw-shortcut-label.ts +2 -1
- package/src/elements/adw-shortcuts-item.ts +2 -1
- package/src/elements/adw-shortcuts-section.ts +2 -1
- package/src/elements/adw-sidebar.ts +4 -3
- package/src/elements/adw-spin-row.ts +2 -1
- package/src/elements/adw-spinner.ts +2 -1
- package/src/elements/adw-split-button.ts +2 -1
- package/src/elements/adw-status-page.ts +2 -1
- package/src/elements/adw-switch-row.ts +10 -1
- package/src/elements/adw-tab-bar.ts +2 -1
- package/src/elements/adw-tab-button.ts +2 -1
- package/src/elements/adw-tab-overview.ts +2 -1
- package/src/elements/adw-tab-view.ts +3 -2
- package/src/elements/adw-toast-overlay.ts +2 -1
- package/src/elements/adw-toggle-group.ts +3 -2
- package/src/elements/adw-toolbar-view.ts +2 -1
- package/src/elements/adw-view-stack.ts +39 -1
- package/src/elements/adw-view-switcher-bar.ts +2 -1
- package/src/elements/adw-view-switcher-sidebar.ts +2 -1
- package/src/elements/adw-view-switcher.ts +3 -2
- package/src/elements/adw-window-title.ts +2 -1
- package/src/elements/adw-wrap-box.ts +2 -1
- package/src/elements/checks.ts +85 -6
- package/src/elements/gtk-action-bar.ts +2 -1
- package/src/elements/gtk-aspect-frame.ts +2 -1
- package/src/elements/gtk-box.ts +56 -1
- package/src/elements/gtk-button.ts +10 -22
- package/src/elements/gtk-calendar.ts +2 -1
- package/src/elements/gtk-center-box.ts +2 -1
- package/src/elements/gtk-color-dialog-button.ts +2 -1
- package/src/elements/gtk-drag-icon.ts +2 -1
- package/src/elements/gtk-drawing-area.ts +2 -1
- package/src/elements/gtk-drop-down.ts +2 -1
- package/src/elements/gtk-editable-label.ts +2 -1
- package/src/elements/gtk-entry.ts +2 -1
- package/src/elements/gtk-expander.ts +2 -1
- package/src/elements/gtk-fixed.ts +2 -1
- package/src/elements/gtk-flow-box-child.ts +2 -1
- package/src/elements/gtk-flow-box.ts +2 -1
- package/src/elements/gtk-font-dialog-button.ts +2 -1
- package/src/elements/gtk-frame.ts +2 -1
- package/src/elements/gtk-gl-area.ts +2 -1
- package/src/elements/gtk-graphics-offload.ts +2 -1
- package/src/elements/gtk-grid.ts +2 -1
- package/src/elements/gtk-header-bar.ts +2 -1
- package/src/elements/gtk-image.ts +14 -15
- package/src/elements/gtk-inscription.ts +2 -1
- package/src/elements/gtk-label.ts +12 -5
- package/src/elements/gtk-level-bar.ts +2 -1
- package/src/elements/gtk-list-box-row.ts +2 -1
- package/src/elements/gtk-list-box.ts +2 -1
- package/src/elements/gtk-list-view.ts +2 -1
- package/src/elements/gtk-media-controls.ts +2 -1
- package/src/elements/gtk-menu-button.ts +2 -1
- package/src/elements/gtk-notebook.ts +2 -1
- package/src/elements/gtk-overlay.ts +2 -1
- package/src/elements/gtk-paned.ts +2 -1
- package/src/elements/gtk-picture.ts +2 -1
- package/src/elements/gtk-popover-bin.ts +2 -1
- package/src/elements/gtk-popover-menu-bar.ts +2 -1
- package/src/elements/gtk-popover.ts +2 -1
- package/src/elements/gtk-progress-bar.ts +2 -1
- package/src/elements/gtk-revealer.ts +2 -1
- package/src/elements/gtk-scale-button.ts +2 -1
- package/src/elements/gtk-scale.ts +2 -1
- package/src/elements/gtk-scrollbar.ts +2 -1
- package/src/elements/gtk-scrolled-window.ts +8 -5
- package/src/elements/gtk-search-bar.ts +2 -1
- package/src/elements/gtk-separator.ts +2 -1
- package/src/elements/gtk-spin-button.ts +2 -1
- package/src/elements/gtk-spinner.ts +2 -1
- package/src/elements/gtk-stack-page.ts +2 -1
- package/src/elements/gtk-stack-sidebar.ts +2 -1
- package/src/elements/gtk-stack-switcher.ts +2 -1
- package/src/elements/gtk-stack.ts +2 -1
- package/src/elements/gtk-switch.ts +2 -1
- package/src/elements/gtk-text-view.ts +2 -1
- package/src/elements/gtk-text.ts +2 -1
- package/src/elements/gtk-tree-expander.ts +2 -1
- package/src/elements/gtk-video.ts +2 -1
- package/src/elements/gtk-viewport.ts +2 -1
- package/src/elements/gtk-window-controls.ts +2 -1
- package/src/elements/gtk-window-handle.ts +2 -1
- package/src/elements/gtk-window.ts +2 -1
- package/src/gio.spec.ts +13 -3
- package/src/glib.spec.ts +9 -0
- package/src/gobject-elements.ts +18 -4
- package/src/gtk-label.spec.ts +39 -0
- package/src/gtk-scrolled-window.spec.ts +6 -6
- package/src/gtk-value-doors.spec.ts +19 -0
- package/src/gtk-window.spec.ts +2 -2
- package/src/gtksource/gtk-source-view.ts +2 -1
- package/src/{icon-registry.spec.ts → icon-masks.spec.ts} +4 -100
- package/src/icon-theme.spec.ts +69 -124
- package/src/icon-theme.ts +29 -119
- package/src/index.ts +3 -7
- package/src/namespace/gdk.ts +1 -0
- package/src/namespace/gio.ts +8 -0
- package/src/namespace/glib.ts +4 -0
- package/src/shared-tree-builder.ts +26 -3
- package/src/signals.ts +11 -2
- package/src/source-view/adw-source-view.ts +2 -1
- package/src/split-button.spec.ts +1 -1
- package/src/style-manager.spec.ts +13 -0
- package/src/style-manager.ts +6 -0
- package/src/styles.generated.ts +1 -1
- package/src/test.browser.mts +6 -2
- package/src/widget-element.ts +75 -0
- package/src/widget-map.spec.ts +116 -0
- package/src/widget-mapping.ts +100 -0
- package/src/widget-visible.spec.ts +44 -0
- package/lib/types/icon-registry.d.ts +0 -37
- package/src/icon-registry.ts +0 -163
package/src/test.browser.mts
CHANGED
|
@@ -28,6 +28,7 @@ import { AdwMultiLayoutViewTest } from './adw-multi-layout-view.spec.js';
|
|
|
28
28
|
import { AdwDataGridTest } from './adw-data-grid.spec.js';
|
|
29
29
|
import { GtkSourceViewTest } from './gtksource/gtk-source-view.spec.js';
|
|
30
30
|
import { AdwDialogTest } from './adw-dialog.spec.js';
|
|
31
|
+
import { WidgetMapTest } from './widget-map.spec.js';
|
|
31
32
|
import { AdwWindowTest } from './adw-window.spec.js';
|
|
32
33
|
import { AdwShortcutsTest } from './adw-shortcuts.spec.js';
|
|
33
34
|
import { AdwApplicationWindowTest } from './adw-application-window.spec.js';
|
|
@@ -65,7 +66,7 @@ import { PangoMarkupTest } from './pango-markup.spec.js';
|
|
|
65
66
|
import { CopyGlyphTest } from './source-view/copy-glyph.spec.js';
|
|
66
67
|
import { AdwButtonContentTest } from './adw-button-content.spec.js';
|
|
67
68
|
import { GtkImageTest } from './gtk-image.spec.js';
|
|
68
|
-
import {
|
|
69
|
+
import { AdwIconMasksTest } from './icon-masks.spec.js';
|
|
69
70
|
import { AdwIconThemeTest } from './icon-theme.spec.js';
|
|
70
71
|
import { GioResourceTest } from './gio-resource.spec.js';
|
|
71
72
|
import { GtkSwitchTest } from './gtk-switch.spec.js';
|
|
@@ -95,6 +96,7 @@ import { GtkToggleButtonTest } from './gtk-toggle-button.spec.js';
|
|
|
95
96
|
import { GtkExpanderTest } from './gtk-expander.spec.js';
|
|
96
97
|
import { GtkOverlayTest } from './gtk-overlay.spec.js';
|
|
97
98
|
import { ContainerFillTest } from './container-fill.spec.js';
|
|
99
|
+
import { ComputeExpandTest } from './compute-expand.spec.js';
|
|
98
100
|
import { GtkPanedTest } from './gtk-paned.spec.js';
|
|
99
101
|
import { GtkRevealerTest } from './gtk-revealer.spec.js';
|
|
100
102
|
import { GtkScaleTest } from './gtk-scale.spec.js';
|
|
@@ -209,7 +211,7 @@ run({
|
|
|
209
211
|
CopyGlyphTest,
|
|
210
212
|
AdwButtonContentTest,
|
|
211
213
|
GtkImageTest,
|
|
212
|
-
|
|
214
|
+
AdwIconMasksTest,
|
|
213
215
|
AdwIconThemeTest,
|
|
214
216
|
GioResourceTest,
|
|
215
217
|
GtkSwitchTest,
|
|
@@ -244,6 +246,7 @@ run({
|
|
|
244
246
|
GtkExpanderTest,
|
|
245
247
|
GtkOverlayTest,
|
|
246
248
|
ContainerFillTest,
|
|
249
|
+
ComputeExpandTest,
|
|
247
250
|
GtkPanedTest,
|
|
248
251
|
GtkRevealerTest,
|
|
249
252
|
GtkScaleTest,
|
|
@@ -297,6 +300,7 @@ run({
|
|
|
297
300
|
AdwDataGridTest,
|
|
298
301
|
GtkSourceViewTest,
|
|
299
302
|
AdwDialogTest,
|
|
303
|
+
WidgetMapTest,
|
|
300
304
|
AdwWindowTest,
|
|
301
305
|
AdwShortcutsTest,
|
|
302
306
|
AdwApplicationWindowTest,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// `Gtk.Widget`'s `visible` and `tooltip-text` on the element classes, once, so the types of every
|
|
2
|
+
// widget carry them and so does a plain `document.createElement()`, not only a built tree.
|
|
3
|
+
//
|
|
4
|
+
// `WidgetElement` is `HTMLElement` plus `gtk_widget_get_visible` / `gtk_widget_set_visible` over
|
|
5
|
+
// the DOM's `hidden` attribute, TRUE until hidden as GtkWidget's default is, and the `map` /
|
|
6
|
+
// `unmap` signals with `gtk_widget_get_mapped` (`widget-mapping.ts`); `installVisible`
|
|
7
|
+
// (`gobject-elements.ts`) gives the same to a class that does not extend it. It notifies only
|
|
8
|
+
// where the element declares `notify::visible`. A subclass with its own `visible`
|
|
9
|
+
// (`<gtk-stack-page>`, `<adw-sidebar>`) keeps its own.
|
|
10
|
+
//
|
|
11
|
+
// `TooltipElement` adds `Gtk.Widget:tooltip-text` for the elements that render the
|
|
12
|
+
// `tooltip-text` attribute: a button, and the rows (`row-tooltip.ts`). It reflects that attribute
|
|
13
|
+
// and the element's `attributeChangedCallback` draws it, so a widget that does not draw it does
|
|
14
|
+
// not extend this class.
|
|
15
|
+
//
|
|
16
|
+
// Reference: GTK gtk/gtkwidget.c (gtk_widget_set_visible, gtk_widget_set_tooltip_text)
|
|
17
|
+
// Copyright (c) GNOME contributors (GTK). LGPLv2.1+.
|
|
18
|
+
|
|
19
|
+
import { WIDGET_VISIBLE_GETTERS, notifyElement } from './gobject-elements.js';
|
|
20
|
+
import { dispatchedSignalsOf, type DispatchedSignals } from './signals.js';
|
|
21
|
+
import { isMapped } from './widget-mapping.js';
|
|
22
|
+
|
|
23
|
+
export class WidgetElement extends HTMLElement {
|
|
24
|
+
/** `Gtk.Widget::map` / `::unmap`, which every subclass inherits (`dispatchedSignalsOf`). */
|
|
25
|
+
static readonly signals: DispatchedSignals = { map: 'map', unmap: 'unmap' };
|
|
26
|
+
|
|
27
|
+
/** `Gtk.Widget:visible`. */
|
|
28
|
+
get visible(): boolean {
|
|
29
|
+
return !this.hidden;
|
|
30
|
+
}
|
|
31
|
+
set visible(value: boolean) {
|
|
32
|
+
const before = !this.hidden;
|
|
33
|
+
this.toggleAttribute('hidden', !value);
|
|
34
|
+
if (!this.hidden !== before && 'notify::visible' in dispatchedSignalsOf(this)) notifyElement(this, 'visible');
|
|
35
|
+
}
|
|
36
|
+
/** `gtk_widget_get_visible`. */
|
|
37
|
+
get_visible(): boolean {
|
|
38
|
+
return this.visible;
|
|
39
|
+
}
|
|
40
|
+
/** `gtk_widget_set_visible`. */
|
|
41
|
+
set_visible(visible: boolean): void {
|
|
42
|
+
this.visible = visible;
|
|
43
|
+
}
|
|
44
|
+
/** `gtk_widget_get_mapped`: on screen, which is in the document and rendered. */
|
|
45
|
+
get_mapped(): boolean {
|
|
46
|
+
return isMapped(this);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
WIDGET_VISIBLE_GETTERS.add(Object.getOwnPropertyDescriptor(WidgetElement.prototype, 'visible')!.get!);
|
|
51
|
+
|
|
52
|
+
export class TooltipElement extends WidgetElement {
|
|
53
|
+
/** `Gtk.Widget:tooltip-text`, the `tooltip-text` attribute; `null` while unset. */
|
|
54
|
+
get tooltip_text(): string | null {
|
|
55
|
+
return this.getAttribute('tooltip-text');
|
|
56
|
+
}
|
|
57
|
+
set tooltip_text(value: string | null) {
|
|
58
|
+
if (value === null || value === undefined) this.removeAttribute('tooltip-text');
|
|
59
|
+
else this.setAttribute('tooltip-text', value);
|
|
60
|
+
}
|
|
61
|
+
get tooltipText(): string | null {
|
|
62
|
+
return this.tooltip_text;
|
|
63
|
+
}
|
|
64
|
+
set tooltipText(value: string | null) {
|
|
65
|
+
this.tooltip_text = value;
|
|
66
|
+
}
|
|
67
|
+
/** `gtk_widget_get_tooltip_text`. */
|
|
68
|
+
get_tooltip_text(): string | null {
|
|
69
|
+
return this.tooltip_text;
|
|
70
|
+
}
|
|
71
|
+
/** `gtk_widget_set_tooltip_text`. */
|
|
72
|
+
set_tooltip_text(text: string | null): void {
|
|
73
|
+
this.tooltip_text = text;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// `Gtk.Widget::map` / `::unmap` and `get_mapped()` on an element: WIDGET_MAP_VECTORS, plus the
|
|
2
|
+
// program that found the gap — a registered `Adw.PreferencesDialog` subclass connecting `map` in
|
|
3
|
+
// its constructor (Learn6502's preferences dialog), which threw "declares no signal 'map'".
|
|
4
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
5
|
+
import { driveWidgetMapVectors, WIDGET_MAP_VECTORS, type WidgetMapSubject } from '@gjsify/adwaita-core/conformance';
|
|
6
|
+
|
|
7
|
+
import { Adw, GObject } from './index.js';
|
|
8
|
+
import { installElementApi } from './gobject-elements.js';
|
|
9
|
+
import type { WidgetElement } from './widget-element.js';
|
|
10
|
+
|
|
11
|
+
type Connectable = HTMLElement & { connect(signal: string, handler: (...args: unknown[]) => unknown): number };
|
|
12
|
+
|
|
13
|
+
function settle(): Promise<void> {
|
|
14
|
+
return new Promise((resolve) => setTimeout(resolve));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const hosts: HTMLElement[] = [];
|
|
18
|
+
|
|
19
|
+
const subject: WidgetMapSubject = {
|
|
20
|
+
name: 'adwaita-web',
|
|
21
|
+
isOracle: false,
|
|
22
|
+
box() {
|
|
23
|
+
const el = document.createElement('gtk-box');
|
|
24
|
+
installElementApi(el);
|
|
25
|
+
return el;
|
|
26
|
+
},
|
|
27
|
+
append: (parent, child) => void (parent as HTMLElement).append(child as HTMLElement),
|
|
28
|
+
remove: (_parent, child) => void (child as HTMLElement).remove(),
|
|
29
|
+
present(widget) {
|
|
30
|
+
const host = document.createElement('div');
|
|
31
|
+
host.append(widget as HTMLElement);
|
|
32
|
+
document.body.append(host);
|
|
33
|
+
hosts.push(host);
|
|
34
|
+
},
|
|
35
|
+
setVisible: (widget, visible) => void ((widget as WidgetElement).visible = visible),
|
|
36
|
+
connect: (widget, signal, handler) => (widget as Connectable).connect(signal, handler),
|
|
37
|
+
isMapped: (widget) => (widget as WidgetElement).get_mapped(),
|
|
38
|
+
settle,
|
|
39
|
+
dispose() {
|
|
40
|
+
for (const host of hosts.splice(0)) host.remove();
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
class GoWebMapDialog extends Adw.PreferencesDialog {
|
|
45
|
+
log: string[] = [];
|
|
46
|
+
constructor(params?: Record<string, unknown>) {
|
|
47
|
+
// @ts-expect-error the element's own constructor takes none; the registered class's takes the params
|
|
48
|
+
super(params);
|
|
49
|
+
const self = this as unknown as Connectable & { get_mapped(): boolean };
|
|
50
|
+
self.connect('map', () => this.log.push(`map:${self.get_mapped()}`));
|
|
51
|
+
self.connect('unmap', () => this.log.push(`unmap:${self.get_mapped()}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
GObject.registerClass({ GTypeName: 'GoWebMapDialog' }, GoWebMapDialog);
|
|
55
|
+
|
|
56
|
+
export const WidgetMapTest = async () => {
|
|
57
|
+
await driveWidgetMapVectors(subject, { describe, it, expect }, WIDGET_MAP_VECTORS);
|
|
58
|
+
|
|
59
|
+
await describe('adwaita-web: map / unmap on an element', async () => {
|
|
60
|
+
await it('every widget declares both, a subclass with its own signals too', () => {
|
|
61
|
+
for (const tag of ['gtk-box', 'gtk-button', 'adw-preferences-dialog']) {
|
|
62
|
+
const el = document.createElement(tag);
|
|
63
|
+
installElementApi(el);
|
|
64
|
+
expect(() => (el as Connectable).connect('map', () => {})).not.toThrow();
|
|
65
|
+
expect(() => (el as Connectable).connect('unmap', () => {})).not.toThrow();
|
|
66
|
+
}
|
|
67
|
+
// `<gtk-button>` declares `clicked` itself and still has `map`.
|
|
68
|
+
const button = document.createElement('gtk-button');
|
|
69
|
+
installElementApi(button);
|
|
70
|
+
expect(() => (button as Connectable).connect('clicked', () => {})).not.toThrow();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await it('a registered dialog connecting `map` in its constructor maps on present() and unmaps on close()', async () => {
|
|
74
|
+
const dialog = new (GoWebMapDialog as unknown as new () => GoWebMapDialog)();
|
|
75
|
+
await settle();
|
|
76
|
+
expect(dialog.log).toStrictEqual([]);
|
|
77
|
+
(dialog as unknown as { present(): void }).present();
|
|
78
|
+
await settle();
|
|
79
|
+
expect(dialog.log).toStrictEqual(['map:true']);
|
|
80
|
+
(dialog as unknown as { close(): void }).close();
|
|
81
|
+
await settle();
|
|
82
|
+
expect(dialog.log).toStrictEqual(['map:true', 'unmap:false']);
|
|
83
|
+
dialog.remove();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await it('a disconnected handler stops the watch: no emission after it', async () => {
|
|
87
|
+
const box = subject.box() as Connectable & { disconnect(id: number): void };
|
|
88
|
+
let emitted = 0;
|
|
89
|
+
const id = box.connect('map', () => void emitted++);
|
|
90
|
+
box.disconnect(id);
|
|
91
|
+
subject.present(box);
|
|
92
|
+
await settle();
|
|
93
|
+
expect(emitted).toBe(0);
|
|
94
|
+
subject.dispose();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await it('a popover that opens maps, one that closes unmaps', async () => {
|
|
98
|
+
const host = document.createElement('div');
|
|
99
|
+
const popover = document.createElement('div');
|
|
100
|
+
popover.setAttribute('popover', '');
|
|
101
|
+
const inner = subject.box() as Connectable;
|
|
102
|
+
popover.append(inner);
|
|
103
|
+
host.append(popover);
|
|
104
|
+
document.body.append(host);
|
|
105
|
+
const log: string[] = [];
|
|
106
|
+
inner.connect('map', () => void log.push('map'));
|
|
107
|
+
inner.connect('unmap', () => void log.push('unmap'));
|
|
108
|
+
popover.showPopover();
|
|
109
|
+
await settle();
|
|
110
|
+
popover.hidePopover();
|
|
111
|
+
await settle();
|
|
112
|
+
host.remove();
|
|
113
|
+
expect(log).toStrictEqual(['map', 'unmap']);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// `Gtk.Widget`'s `map` / `unmap` signals and `gtk_widget_get_mapped` on an element.
|
|
2
|
+
//
|
|
3
|
+
// A widget is mapped while it is on screen: it is visible and so is every ancestor. Here that is
|
|
4
|
+
// an element in the document that renders (`isConnected && checkVisibility()`), which covers a
|
|
5
|
+
// `hidden` ancestor, a closed `<dialog>`, an inactive stack page and a popover that is not open.
|
|
6
|
+
// `map` fires on the transition into that state and `unmap` on the way out, never on connect.
|
|
7
|
+
//
|
|
8
|
+
// The DOM has no event for either transition, so an element is only WATCHED while something
|
|
9
|
+
// listens to one of the two: one `MutationObserver` on the document (children and attributes,
|
|
10
|
+
// the whole subtree) and a capturing `toggle` listener (a popover opening changes no attribute)
|
|
11
|
+
// re-check every watched element after each batch. A batch is delivered after the change, not
|
|
12
|
+
// inside it as `gtk_widget_show` delivers it; a hide undone within the same turn emits nothing.
|
|
13
|
+
//
|
|
14
|
+
// Order is GTK's post-order (`map` and `unmap` are RUN_FIRST, and the class handler maps or
|
|
15
|
+
// unmaps the children first): the deepest element of a batch emits first, unmaps before maps.
|
|
16
|
+
//
|
|
17
|
+
// Reference: GTK gtk/gtkwidget.c (gtk_widget_map, gtk_widget_unmap, gtk_widget_real_map,
|
|
18
|
+
// gtk_widget_real_unmap, gtk_widget_get_mapped)
|
|
19
|
+
// Copyright (c) GNOME contributors (GTK). LGPLv2.1+.
|
|
20
|
+
|
|
21
|
+
/** Watched elements and the state each last emitted for. */
|
|
22
|
+
const STATE = new WeakMap<HTMLElement, { mapped: boolean; listeners: number }>();
|
|
23
|
+
const WATCHED = new Set<WeakRef<HTMLElement>>();
|
|
24
|
+
const REFS = new WeakMap<HTMLElement, WeakRef<HTMLElement>>();
|
|
25
|
+
let observer: MutationObserver | null = null;
|
|
26
|
+
|
|
27
|
+
/** `gtk_widget_get_mapped`: in the document, and rendered. */
|
|
28
|
+
export function isMapped(el: Element): boolean {
|
|
29
|
+
return el.isConnected && el.checkVisibility();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function depthOf(el: Element): number {
|
|
33
|
+
let depth = 0;
|
|
34
|
+
for (let at = el.parentElement; at !== null; at = at.parentElement) depth++;
|
|
35
|
+
return depth;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function recheck(): void {
|
|
39
|
+
const changed: HTMLElement[] = [];
|
|
40
|
+
for (const ref of WATCHED) {
|
|
41
|
+
const el = ref.deref();
|
|
42
|
+
if (el === undefined) {
|
|
43
|
+
WATCHED.delete(ref);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const state = STATE.get(el)!;
|
|
47
|
+
if (isMapped(el) !== state.mapped) changed.push(el);
|
|
48
|
+
}
|
|
49
|
+
if (changed.length === 0) return;
|
|
50
|
+
const deepestFirst = (list: HTMLElement[]) =>
|
|
51
|
+
list.map((el) => [el, depthOf(el)] as const).sort((a, b) => b[1] - a[1]);
|
|
52
|
+
const unmapped = deepestFirst(changed.filter((el) => STATE.get(el)!.mapped));
|
|
53
|
+
const mapped = deepestFirst(changed.filter((el) => !STATE.get(el)!.mapped));
|
|
54
|
+
for (const [el] of [...unmapped, ...mapped]) {
|
|
55
|
+
const state = STATE.get(el)!;
|
|
56
|
+
state.mapped = !state.mapped;
|
|
57
|
+
el.dispatchEvent(new CustomEvent(state.mapped ? 'map' : 'unmap'));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function observe(): void {
|
|
62
|
+
if (observer !== null) return;
|
|
63
|
+
observer = new MutationObserver(recheck);
|
|
64
|
+
observer.observe(document, { childList: true, subtree: true, attributes: true });
|
|
65
|
+
document.addEventListener('toggle', recheck, true);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function unobserve(): void {
|
|
69
|
+
if (observer === null || WATCHED.size > 0) return;
|
|
70
|
+
observer.disconnect();
|
|
71
|
+
observer = null;
|
|
72
|
+
document.removeEventListener('toggle', recheck, true);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Watches `el` for `map` / `unmap` while the returned function has not been called. Its present
|
|
77
|
+
* state is recorded and not emitted, as connecting a handler in GTK emits nothing.
|
|
78
|
+
*/
|
|
79
|
+
export function watchMapping(el: HTMLElement): () => void {
|
|
80
|
+
let state = STATE.get(el);
|
|
81
|
+
if (state === undefined || state.listeners === 0) {
|
|
82
|
+
state = { mapped: isMapped(el), listeners: 0 };
|
|
83
|
+
STATE.set(el, state);
|
|
84
|
+
const ref = new WeakRef(el);
|
|
85
|
+
REFS.set(el, ref);
|
|
86
|
+
WATCHED.add(ref);
|
|
87
|
+
observe();
|
|
88
|
+
}
|
|
89
|
+
state.listeners++;
|
|
90
|
+
let watching = true;
|
|
91
|
+
return () => {
|
|
92
|
+
if (!watching) return;
|
|
93
|
+
watching = false;
|
|
94
|
+
const current = STATE.get(el)!;
|
|
95
|
+
if (--current.listeners > 0) return;
|
|
96
|
+
WATCHED.delete(REFS.get(el)!);
|
|
97
|
+
REFS.delete(el);
|
|
98
|
+
unobserve();
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { describe, expect, it } from '@gjsify/unit';
|
|
4
4
|
import type { SharedTreeNode } from '@gjsify/adwaita-core/conformance';
|
|
5
5
|
|
|
6
|
+
import { Adw, Gtk } from './index.js';
|
|
7
|
+
|
|
6
8
|
import { mountSharedTree } from './shared-tree-builder.js';
|
|
7
9
|
|
|
8
10
|
function settle(): Promise<void> {
|
|
@@ -101,4 +103,46 @@ export const AdwWidgetVisibleTest = async () => {
|
|
|
101
103
|
expect(item.hasAttribute('hidden')).toBe(true);
|
|
102
104
|
});
|
|
103
105
|
});
|
|
106
|
+
|
|
107
|
+
await describe('adwaita-web: Gtk.Widget:visible and tooltip-text on the element class', async () => {
|
|
108
|
+
await it('a created button has visible, get_visible and set_visible, typed on the class', () => {
|
|
109
|
+
const button: Gtk.Button = new Gtk.Button();
|
|
110
|
+
expect(button.visible).toBe(true);
|
|
111
|
+
button.set_visible(false);
|
|
112
|
+
expect(button.hidden).toBe(true);
|
|
113
|
+
expect(button.get_visible()).toBe(false);
|
|
114
|
+
button.visible = true;
|
|
115
|
+
expect(button.hasAttribute('hidden')).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await it('a created widget that is not a button has them too', () => {
|
|
119
|
+
const box = new Gtk.Box();
|
|
120
|
+
box.visible = false;
|
|
121
|
+
expect(box.hidden).toBe(true);
|
|
122
|
+
expect(box.get_visible()).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await it('a button has tooltip_text, tooltipText and get/set_tooltip_text over one attribute', () => {
|
|
126
|
+
const button = new Gtk.Button();
|
|
127
|
+
document.body.append(button);
|
|
128
|
+
expect(button.get_tooltip_text()).toBe(null);
|
|
129
|
+
button.set_tooltip_text('Copy');
|
|
130
|
+
expect(button.tooltip_text).toBe('Copy');
|
|
131
|
+
expect(button.tooltipText).toBe('Copy');
|
|
132
|
+
expect(button.getAttribute('tooltip-text')).toBe('Copy');
|
|
133
|
+
expect(button.querySelector('button')?.title).toBe('Copy');
|
|
134
|
+
button.tooltip_text = null;
|
|
135
|
+
expect(button.hasAttribute('tooltip-text')).toBe(false);
|
|
136
|
+
button.remove();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await it('a row has them and draws the tooltip on its parts', () => {
|
|
140
|
+
const row: Adw.ActionRow = new Adw.ActionRow();
|
|
141
|
+
document.body.append(row);
|
|
142
|
+
row.set_tooltip_text('Hint');
|
|
143
|
+
expect(row.get_tooltip_text()).toBe('Hint');
|
|
144
|
+
expect(row.querySelector('[title="Hint"]') !== null).toBe(true);
|
|
145
|
+
row.remove();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
104
148
|
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Make `name` drawable by every `.adw-icon` in the document, from an Adwaita symbolic SVG
|
|
3
|
-
* source string (what `@gjsify/adwaita-icons` exports).
|
|
4
|
-
*
|
|
5
|
-
* BOTH arguments are checked and a wrong one THROWS rather than registering nothing: unlike
|
|
6
|
-
* a render, this is an explicit call with a wrong argument, and silently doing nothing is
|
|
7
|
-
* the failure mode this whole area exists to end. The name is normalized exactly as
|
|
8
|
-
* `<gtk-image icon-name>` normalizes it — a single `-symbolic` suffix comes off, and the rest
|
|
9
|
-
* must be one CSS token; the SVG has to parse to an `<svg>` root. `<gtk-image>` draws
|
|
10
|
-
* `image-missing` for the same unusable name instead — there it came from markup, and
|
|
11
|
-
* taking the page down over a typo is not proportionate.
|
|
12
|
-
*
|
|
13
|
-
* Registering a name the stylesheet already compiles is allowed and REPLACES the glyph:
|
|
14
|
-
* the property is set on the document element, so it wins over the `:root` rule.
|
|
15
|
-
*
|
|
16
|
-
* CLIENT-SIDE ONLY: this touches `document.documentElement` and `document.head`, so at
|
|
17
|
-
* module top level in an SSR/SSG build it throws before a page is rendered.
|
|
18
|
-
*/
|
|
19
|
-
export declare function registerIcon(name: string, svg: string): void;
|
|
20
|
-
/**
|
|
21
|
-
* Whether `name` draws something other than `image-missing` — a symbolic glyph compiled into
|
|
22
|
-
* the stylesheet or registered at runtime, or a colour icon from `registerColorIcon`. The
|
|
23
|
-
* name is looked up exactly as `<gtk-image icon-name>` looks it up (`icon-theme.ts`).
|
|
24
|
-
*
|
|
25
|
-
* Reads the live cascade rather than a generated list, so it answers for a glyph that
|
|
26
|
-
* arrived by any route, including a consumer's own `--icon-<name>` declaration. It is
|
|
27
|
-
* deliberately the CUSTOM PROPERTY that is read and not the rendered `mask-image` of a
|
|
28
|
-
* probe element: since `_icon.scss` gives every icon box the `image-missing` fallback,
|
|
29
|
-
* a rendered mask is never `none`, and comparing against the fallback would answer
|
|
30
|
-
* `false` for `image-missing` itself — the one name where being drawn and having fallen
|
|
31
|
-
* back look identical.
|
|
32
|
-
*
|
|
33
|
-
* Narrowing worth knowing: a consumer who writes `.adw-icon--x { mask-image: url(…) }`
|
|
34
|
-
* with no matching `--icon-x` gets `false` here and a drawn icon anyway. The generator and
|
|
35
|
-
* {@link registerIcon} are the two things that create the pair, and both create both.
|
|
36
|
-
*/
|
|
37
|
-
export declare function isIconAvailable(name: string): boolean;
|
package/src/icon-registry.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
// The runtime half of icon resolution — how a name OUTSIDE the compiled set gets drawn.
|
|
2
|
-
//
|
|
3
|
-
// `scripts/build-scss.mjs` inlines a chosen subset of `@gjsify/adwaita-icons` into the
|
|
4
|
-
// stylesheet as `--icon-<name>` custom properties plus matching `.adw-icon--<name>` mask
|
|
5
|
-
// classes. The subset exists because the whole set does not fit — inlining every icon that
|
|
6
|
-
// package exports costs a measured ~1.07 MB of data-URI, some five times the stylesheet it
|
|
7
|
-
// would sit in — so a consumer whose app needs a glyph this package does not ship needs a
|
|
8
|
-
// way in that is not "wait for a release".
|
|
9
|
-
//
|
|
10
|
-
// That is this module. `registerIcon` writes the SAME pair the generator writes — the
|
|
11
|
-
// custom property on the document element, the mask class into a stylesheet this module
|
|
12
|
-
// owns — so a registered icon is indistinguishable from a compiled one at every later
|
|
13
|
-
// point: `<gtk-image icon-name="…">`, a hand-written `class="adw-icon adw-icon--…"`, a
|
|
14
|
-
// widget attribute, `var(--icon-…)` in the consumer's own CSS.
|
|
15
|
-
//
|
|
16
|
-
// import { registerIcon } from '@gjsify/adwaita-web';
|
|
17
|
-
// import { dialogErrorSymbolic } from '@gjsify/adwaita-icons/status';
|
|
18
|
-
//
|
|
19
|
-
// registerIcon('dialog-error-symbolic', dialogErrorSymbolic);
|
|
20
|
-
// // …then anywhere: <gtk-image icon-name="dialog-error-symbolic"></gtk-image>
|
|
21
|
-
//
|
|
22
|
-
// Register BEFORE the icon is mounted where you can. A `<gtk-image>` already in the
|
|
23
|
-
// document draws the `image-missing` fallback (see `_icon.scss`) until the registration
|
|
24
|
-
// lands; the CSS is live, so it corrects itself on the next style recalculation with no
|
|
25
|
-
// re-render, but there is a visible frame in between.
|
|
26
|
-
//
|
|
27
|
-
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+ — the icon SVGs.
|
|
28
|
-
// Original implementation.
|
|
29
|
-
|
|
30
|
-
import { normalizeIconName } from '@gjsify/adwaita-core';
|
|
31
|
-
import { toDataUri } from '@gjsify/adwaita-icons/utils';
|
|
32
|
-
|
|
33
|
-
import { MASK_CLASS_PREFIX } from './elements/gtk-image.js';
|
|
34
|
-
import { iconResolves, notifyIconThemeChanged } from './icon-theme.js';
|
|
35
|
-
|
|
36
|
-
/** Marks the one `<style>` element this module owns, so a reload reuses it. */
|
|
37
|
-
const REGISTRY_STYLE_ID = 'adwaita-web-icon-registry';
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Names already given a mask rule. Re-registering a name has to update the GLYPH without
|
|
41
|
-
* appending a second rule: the custom property carries the glyph and the rule only points
|
|
42
|
-
* at it, so the property assignment is the whole update and a duplicate rule would be
|
|
43
|
-
* dead weight that grows without bound under a live theme switcher.
|
|
44
|
-
*/
|
|
45
|
-
const ruled = new Set<string>();
|
|
46
|
-
|
|
47
|
-
/** The one rule a registration adds: the class, pointing at the custom property. */
|
|
48
|
-
function insertMaskRule(sheet: CSSStyleSheet, name: string): void {
|
|
49
|
-
sheet.insertRule(
|
|
50
|
-
`.${MASK_CLASS_PREFIX}${name} { mask-image: var(--icon-${name}); ` +
|
|
51
|
-
`-webkit-mask-image: var(--icon-${name}); }`,
|
|
52
|
-
sheet.cssRules.length,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** This module's stylesheet, created on first use and appended last so nothing hides it. */
|
|
57
|
-
function registrySheet(): CSSStyleSheet {
|
|
58
|
-
const existing = document.getElementById(REGISTRY_STYLE_ID) as HTMLStyleElement | null;
|
|
59
|
-
if (existing?.sheet) return existing.sheet;
|
|
60
|
-
|
|
61
|
-
const style = document.createElement('style');
|
|
62
|
-
style.id = REGISTRY_STYLE_ID;
|
|
63
|
-
document.head.appendChild(style);
|
|
64
|
-
// `sheet` is non-null the moment a <style> is in a document — the property is only
|
|
65
|
-
// null while the element is disconnected, which the append above rules out.
|
|
66
|
-
const sheet = style.sheet as CSSStyleSheet;
|
|
67
|
-
|
|
68
|
-
// Reaching here a SECOND time means the previous element was taken away — an SPA head
|
|
69
|
-
// swap, an Astro view transition — and every rule it held went with it. The custom
|
|
70
|
-
// properties did NOT: they sit on the document element. So re-issue the rules, or every
|
|
71
|
-
// name registered before the swap keeps its glyph in a property nothing consumes and
|
|
72
|
-
// draws the fallback forever. Nothing observes the head, so the recovery happens on the
|
|
73
|
-
// next `registerIcon` call rather than at the moment of the swap.
|
|
74
|
-
for (const name of ruled) insertMaskRule(sheet, name);
|
|
75
|
-
return sheet;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* The second argument has to be SVG SOURCE, and nothing else can tell us so: `toDataUri`
|
|
80
|
-
* percent-encodes whatever it is handed, so `registerIcon('x', 'not an svg')` produces a
|
|
81
|
-
* perfectly well-formed `mask-image` that masks NOTHING — an icon strictly worse than an
|
|
82
|
-
* unregistered one, because the registered class beats `:where(.adw-icon)` and switches the
|
|
83
|
-
* `image-missing` fallback OFF, while `isIconAvailable` reports `true`. Measured in Firefox:
|
|
84
|
-
* `url("data:image/svg+xml,not%20an%20svg")`, 0 % of the box painted, no throw, no warning.
|
|
85
|
-
*
|
|
86
|
-
* A real parser rather than a `^<svg` test: an icon read from a file carries an XML prolog
|
|
87
|
-
* or a DOCTYPE before its root element, and both are legitimate here.
|
|
88
|
-
*/
|
|
89
|
-
function svgRootOf(source: string): Element {
|
|
90
|
-
return new DOMParser().parseFromString(source, 'image/svg+xml').documentElement;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Make `name` drawable by every `.adw-icon` in the document, from an Adwaita symbolic SVG
|
|
95
|
-
* source string (what `@gjsify/adwaita-icons` exports).
|
|
96
|
-
*
|
|
97
|
-
* BOTH arguments are checked and a wrong one THROWS rather than registering nothing: unlike
|
|
98
|
-
* a render, this is an explicit call with a wrong argument, and silently doing nothing is
|
|
99
|
-
* the failure mode this whole area exists to end. The name is normalized exactly as
|
|
100
|
-
* `<gtk-image icon-name>` normalizes it — a single `-symbolic` suffix comes off, and the rest
|
|
101
|
-
* must be one CSS token; the SVG has to parse to an `<svg>` root. `<gtk-image>` draws
|
|
102
|
-
* `image-missing` for the same unusable name instead — there it came from markup, and
|
|
103
|
-
* taking the page down over a typo is not proportionate.
|
|
104
|
-
*
|
|
105
|
-
* Registering a name the stylesheet already compiles is allowed and REPLACES the glyph:
|
|
106
|
-
* the property is set on the document element, so it wins over the `:root` rule.
|
|
107
|
-
*
|
|
108
|
-
* CLIENT-SIDE ONLY: this touches `document.documentElement` and `document.head`, so at
|
|
109
|
-
* module top level in an SSR/SSG build it throws before a page is rendered.
|
|
110
|
-
*/
|
|
111
|
-
export function registerIcon(name: string, svg: string): void {
|
|
112
|
-
const resolved = normalizeIconName(name);
|
|
113
|
-
if (resolved === '') {
|
|
114
|
-
throw new TypeError(
|
|
115
|
-
`registerIcon: ${JSON.stringify(name)} is not usable as an icon name — it has to be a single ` +
|
|
116
|
-
'CSS token ([A-Za-z0-9_-]+) once an optional `-symbolic` suffix is removed.',
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const root = svgRootOf(svg);
|
|
121
|
-
if (root.tagName !== 'svg') {
|
|
122
|
-
throw new TypeError(
|
|
123
|
-
`registerIcon(${JSON.stringify(resolved)}): the second argument has to be SVG SOURCE — the ` +
|
|
124
|
-
'kind of string `@gjsify/adwaita-icons` exports, not a URL, a data-URI or a DOM node. ' +
|
|
125
|
-
`Parsing it produced <${root.tagName}>, so it would have masked nothing at all.`,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
document.documentElement.style.setProperty(`--icon-${resolved}`, toDataUri(svg));
|
|
130
|
-
|
|
131
|
-
// BEFORE the `ruled` early return: taking the sheet is what detects a lost <style>,
|
|
132
|
-
// and a name already in `ruled` is exactly the one the early return would skip.
|
|
133
|
-
const sheet = registrySheet();
|
|
134
|
-
if (!ruled.has(resolved)) {
|
|
135
|
-
insertMaskRule(sheet, resolved);
|
|
136
|
-
ruled.add(resolved);
|
|
137
|
-
}
|
|
138
|
-
// The CSS above is live for every mask class, but a `name-symbolic` request that fell
|
|
139
|
-
// back to a registered COLOUR icon has no mask class to update — the symbolic glyph
|
|
140
|
-
// now wins that lookup, so the element has to run it again.
|
|
141
|
-
notifyIconThemeChanged();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Whether `name` draws something other than `image-missing` — a symbolic glyph compiled into
|
|
146
|
-
* the stylesheet or registered at runtime, or a colour icon from `registerColorIcon`. The
|
|
147
|
-
* name is looked up exactly as `<gtk-image icon-name>` looks it up (`icon-theme.ts`).
|
|
148
|
-
*
|
|
149
|
-
* Reads the live cascade rather than a generated list, so it answers for a glyph that
|
|
150
|
-
* arrived by any route, including a consumer's own `--icon-<name>` declaration. It is
|
|
151
|
-
* deliberately the CUSTOM PROPERTY that is read and not the rendered `mask-image` of a
|
|
152
|
-
* probe element: since `_icon.scss` gives every icon box the `image-missing` fallback,
|
|
153
|
-
* a rendered mask is never `none`, and comparing against the fallback would answer
|
|
154
|
-
* `false` for `image-missing` itself — the one name where being drawn and having fallen
|
|
155
|
-
* back look identical.
|
|
156
|
-
*
|
|
157
|
-
* Narrowing worth knowing: a consumer who writes `.adw-icon--x { mask-image: url(…) }`
|
|
158
|
-
* with no matching `--icon-x` gets `false` here and a drawn icon anyway. The generator and
|
|
159
|
-
* {@link registerIcon} are the two things that create the pair, and both create both.
|
|
160
|
-
*/
|
|
161
|
-
export function isIconAvailable(name: string): boolean {
|
|
162
|
-
return iconResolves(name);
|
|
163
|
-
}
|