@gjsify/adwaita-web 0.51.1 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/adwaita-web.css +259 -31
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/accent.d.ts +12 -2
- package/lib/types/adw-appearance.spec.d.ts +2 -0
- package/lib/types/appearance.d.ts +41 -0
- package/lib/types/blueprint-layout.spec.d.ts +1 -0
- package/lib/types/blueprint-markup.spec.d.ts +4 -0
- package/lib/types/blueprint-tree.spec.d.ts +1 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +31 -13
- package/lib/types/elements/adw-carousel.d.ts +5 -0
- package/lib/types/elements/adw-header-bar.d.ts +15 -0
- package/lib/types/elements/adw-inline-view-switcher.d.ts +1 -0
- package/lib/types/elements/adw-navigation-view.d.ts +1 -0
- package/lib/types/elements/adw-sidebar.d.ts +10 -0
- package/lib/types/elements/adw-tab-view.d.ts +1 -0
- package/lib/types/elements/adw-toggle-group.d.ts +11 -0
- package/lib/types/elements/gtk-action-bar.d.ts +19 -0
- package/lib/types/elements/gtk-box.d.ts +17 -0
- package/lib/types/elements/gtk-button.d.ts +2 -0
- package/lib/types/elements/gtk-label.d.ts +122 -0
- package/lib/types/font-stack.spec.d.ts +2 -0
- package/lib/types/gio-menu.spec.d.ts +1 -0
- package/lib/types/gtk-box.spec.d.ts +1 -0
- package/lib/types/gtk-label.spec.d.ts +1 -0
- package/lib/types/gtk-value-doors.spec.d.ts +1 -0
- package/lib/types/index.d.ts +6 -1
- package/lib/types/namespace/gio.d.ts +1 -0
- package/lib/types/namespace/gtk.d.ts +5 -0
- package/lib/types/shared-tree-builder.d.ts +64 -0
- package/lib/types/source-view/asm6502.d.ts +1 -20
- package/lib/types/source-view/index.d.ts +3 -2
- package/lib/types/source-view/theme.d.ts +1 -1
- package/lib/types/string-list-slot.d.ts +4 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/lib/types/tags.spec.d.ts +1 -0
- package/lib/types/value-lists.spec.d.ts +1 -0
- package/package.json +8 -6
- package/scss/_action_bar.scss +51 -0
- package/scss/_banner.scss +8 -1
- package/scss/_bottom_sheet.scss +57 -0
- package/scss/_box.scss +23 -0
- package/scss/_button.scss +29 -7
- package/scss/_button_content.scss +6 -1
- package/scss/_button_row.scss +5 -2
- package/scss/_carousel.scss +6 -0
- package/scss/_labels.scss +126 -12
- package/scss/_navigation_split_view.scss +18 -0
- package/scss/_overlay_split_view.scss +5 -0
- package/scss/_reset.scss +29 -4
- package/scss/_split_button.scss +5 -2
- package/scss/_status_page.scss +31 -17
- package/scss/_theme.scss +11 -3
- package/scss/_toolbar_view.scss +10 -0
- package/scss/_variables.scss +23 -3
- package/scss/_widget.scss +55 -0
- package/scss/_window_title.scss +9 -0
- package/scss/adwaita-skin.scss +8 -2
- package/src/accent.ts +28 -5
- package/src/adw-appearance.spec.ts +178 -0
- package/src/adw-banner.spec.ts +16 -0
- package/src/adw-button-row.spec.ts +8 -0
- package/src/adw-header-bar.spec.ts +17 -0
- package/src/appearance.ts +207 -0
- package/src/blueprint-layout.spec.ts +223 -0
- package/src/blueprint-markup.spec.ts +155 -0
- package/src/blueprint-tree.spec.ts +174 -0
- package/src/bottom-sheet.spec.ts +166 -1
- package/src/chrome.spec.ts +5 -2
- package/src/elements/adw-bottom-sheet.ts +166 -59
- package/src/elements/adw-carousel.ts +10 -5
- package/src/elements/adw-clamp.ts +12 -0
- package/src/elements/adw-combo-row.ts +13 -4
- package/src/elements/adw-header-bar.ts +46 -3
- package/src/elements/adw-inline-view-switcher.ts +11 -0
- package/src/elements/adw-navigation-view.ts +10 -0
- package/src/elements/adw-sidebar.ts +22 -0
- package/src/elements/adw-spin-row.ts +44 -5
- package/src/elements/adw-status-page.ts +12 -6
- package/src/elements/adw-tab-view.ts +20 -3
- package/src/elements/adw-toggle-group.ts +45 -4
- package/src/elements/adw-toolbar-view.ts +9 -0
- package/src/elements/gtk-action-bar.ts +90 -0
- package/src/elements/gtk-box.ts +87 -0
- package/src/elements/gtk-button.ts +60 -13
- package/src/elements/gtk-drop-down.ts +10 -1
- package/src/elements/gtk-label.ts +415 -0
- package/src/elements/swipe-drag.ts +1 -1
- package/src/font-stack.spec.ts +110 -0
- package/src/gio-menu.spec.ts +125 -0
- package/src/globals.d.ts +7 -0
- package/src/gtk-box.spec.ts +168 -0
- package/src/gtk-button.spec.ts +123 -1
- package/src/gtk-label.spec.ts +595 -0
- package/src/gtk-value-doors.spec.ts +104 -0
- package/src/index.ts +32 -1
- package/src/keyboard-operable.spec.ts +5 -4
- package/src/namespace/gio.ts +19 -0
- package/src/namespace/gtk.ts +14 -0
- package/src/shared-tree-builder.ts +268 -0
- package/src/shared-trees.spec.ts +296 -32
- package/src/source-view/adw-source-view.ts +3 -3
- package/src/source-view/asm6502.ts +11 -191
- package/src/source-view/index.ts +17 -11
- package/src/source-view/theme.ts +23 -17
- package/src/split-button.spec.ts +7 -0
- package/src/string-list-slot.ts +21 -0
- package/src/styles.generated.ts +1 -1
- package/src/tags.spec.ts +51 -0
- package/src/test.browser.mts +25 -0
- package/src/value-lists.spec.ts +93 -0
- package/lib/types/source-view/hex.d.ts +0 -21
- package/src/source-view/hex.ts +0 -37
package/scss/_status_page.scss
CHANGED
|
@@ -4,57 +4,71 @@
|
|
|
4
4
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
5
5
|
// Modifications: Adapted for @gjsify/adwaita-web Web Components.
|
|
6
6
|
|
|
7
|
+
// THE METRICS ARE UPSTREAM'S, measured against a GTK render of the gallery's status page
|
|
8
|
+
// `.blp`. They were the vendored web port's — a 96px icon, an 18pt title, the description
|
|
9
|
+
// capped at 32ch and 12px gaps all round — and beside GTK the page was a different
|
|
10
|
+
// composition: the description wrapped at 290px where GTK sets it on one line, and the child
|
|
11
|
+
// sat 61px higher. `_misc.scss` (statuspage) has a 36px 12px margin, 12px between icon,
|
|
12
|
+
// title and description, 24px more under the icon, `.title-1` for the title and 36px
|
|
13
|
+
// between that column and the child; `adw-status-page.ui` clamps the column at AdwClamp's
|
|
14
|
+
// default 600px.
|
|
7
15
|
adw-status-page {
|
|
8
16
|
display: flex;
|
|
9
17
|
flex-direction: column;
|
|
10
18
|
align-items: center;
|
|
11
19
|
justify-content: center;
|
|
12
20
|
text-align: center;
|
|
13
|
-
gap:
|
|
14
|
-
padding:
|
|
21
|
+
gap: 12px;
|
|
22
|
+
padding: 36px 12px;
|
|
15
23
|
color: var(--window-fg-color);
|
|
16
24
|
|
|
17
25
|
.adw-status-page-icon {
|
|
18
|
-
width:
|
|
19
|
-
height:
|
|
26
|
+
width: 128px;
|
|
27
|
+
height: 128px;
|
|
28
|
+
margin-bottom: 24px;
|
|
20
29
|
background-color: currentColor;
|
|
21
30
|
opacity: 0.45;
|
|
22
31
|
-webkit-mask-size: contain;
|
|
23
32
|
mask-size: contain;
|
|
24
33
|
}
|
|
25
34
|
|
|
35
|
+
// `.title-1` (`_labels.scss`), restated: each partial compiles on its own, so an
|
|
36
|
+
// `@extend` across them finds no target.
|
|
26
37
|
.adw-status-page-title {
|
|
27
|
-
font-size: 18pt;
|
|
28
38
|
font-weight: 800;
|
|
39
|
+
font-size: 181%;
|
|
40
|
+
max-width: 600px;
|
|
29
41
|
}
|
|
30
42
|
|
|
31
43
|
.adw-status-page-description {
|
|
32
44
|
font-size: var(--font-size-base);
|
|
33
45
|
opacity: var(--dim-opacity);
|
|
34
|
-
max-width:
|
|
46
|
+
max-width: 600px;
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
// 36px from the column, of which the flex gap already gives 12.
|
|
37
50
|
.adw-status-page-child {
|
|
38
|
-
margin-top:
|
|
51
|
+
margin-top: 24px;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
// `.compact` — the documented smaller status page, for a sidebar or a popover
|
|
42
|
-
// (style-classes.md "Compact Status Page")
|
|
43
|
-
//
|
|
44
|
-
// `.title-2` (`_misc.scss:154-172`); this port's resting icon is already 96px
|
|
45
|
-
// because it renders at the size the vendored web port used, so `.compact`
|
|
46
|
-
// steps down from there in the same proportion.
|
|
55
|
+
// (style-classes.md "Compact Status Page"): margins 24px 12px, the icon 96px with 12px
|
|
56
|
+
// under it, `.title-2`, and 24px to the child (`_misc.scss`, statuspage.compact).
|
|
47
57
|
&.compact {
|
|
48
|
-
|
|
49
|
-
padding: var(--spacing-m);
|
|
58
|
+
padding: 24px 12px;
|
|
50
59
|
|
|
51
60
|
.adw-status-page-icon {
|
|
52
|
-
width:
|
|
53
|
-
height:
|
|
61
|
+
width: 96px;
|
|
62
|
+
height: 96px;
|
|
63
|
+
margin-bottom: 12px;
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
.adw-status-page-title {
|
|
57
|
-
font-size:
|
|
67
|
+
font-size: 136%;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.adw-status-page-child {
|
|
71
|
+
margin-top: 12px;
|
|
58
72
|
}
|
|
59
73
|
}
|
|
60
74
|
}
|
package/scss/_theme.scss
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
--entry-border-hover-color: rgba(255, 255, 255, 0.26);
|
|
46
46
|
--destructive-bg-color: #c01c28;
|
|
47
47
|
--destructive-fg-color: #ffffff;
|
|
48
|
+
--destructive-color: oklab(from var(--destructive-bg-color) max(l, 0.85) a b);
|
|
48
49
|
// Semantic states — libadwaita's dark overrides (_colors.scss:83-92): the
|
|
49
50
|
// backgrounds darken (green_5 / a hand-picked amber / red_4) and the
|
|
50
51
|
// standalone colour LIGHTENS instead of darkening, so it stays readable on a
|
|
@@ -57,9 +58,16 @@
|
|
|
57
58
|
--error-color: oklab(from var(--error-bg-color) max(l, 0.85) a b);
|
|
58
59
|
--switch-off-bg: rgba(255, 255, 255, 0.2);
|
|
59
60
|
--switch-knob-bg: #deddda;
|
|
60
|
-
// The active toggle is
|
|
61
|
-
//
|
|
62
|
-
|
|
61
|
+
// The active toggle chip is NOT near-white in both themes — that premise was
|
|
62
|
+
// wrong, and it is what made the selected toggle a solid white pill on a dark
|
|
63
|
+
// window while GTK draws a grey one. libadwaita's own _colors.scss:247-248 says
|
|
64
|
+
// dark takes a TRANSLUCENT white over the dark background, with white text:
|
|
65
|
+
// --active-toggle-bg-color: rgb(255 255 255 / 20%);
|
|
66
|
+
// --active-toggle-fg-color: #ffffff;
|
|
67
|
+
// The foreground needs no entry at all — _toggle_group.scss defaults it to
|
|
68
|
+
// var(--window-fg-color), which is already #ffffff here. Only the background
|
|
69
|
+
// was ever missing; the old dark override forced dark text to compensate for it.
|
|
70
|
+
--active-toggle-bg-color: rgb(255 255 255 / 20%);
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
// Auto dark theme via prefers-color-scheme — opt out with .theme-light
|
package/scss/_toolbar_view.scss
CHANGED
|
@@ -60,6 +60,16 @@ adw-toolbar-view {
|
|
|
60
60
|
flex: 1 1 auto;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// …UNLESS IT ASKED FOR LESS. A content widget with a `valign` other than fill is
|
|
64
|
+
// allocated its natural height and placed by that alignment (`_widget.scss`'s auto
|
|
65
|
+
// margin), which a growing flex item cannot be: the grow claims the free space before
|
|
66
|
+
// an auto margin sees any. Measured on the gallery's navigation view, whose `.blp`
|
|
67
|
+
// centres a pill button in a toolbar view: GTK drew it 44px tall mid-pane, this port
|
|
68
|
+
// stretched it over the whole 293px content rect.
|
|
69
|
+
.adw-toolbar-view-content > :only-child:is([valign='start'], [valign='end'], [valign='center']) {
|
|
70
|
+
flex-grow: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
// top-bar-style / bottom-bar-style: an opaque background plus a persistent
|
|
64
74
|
// shadow, which `.border` swaps for a hairline (adw-toolbar-view.c:1011-1024,
|
|
65
75
|
// _toolbars.scss `> .top-bar.raised`).
|
package/scss/_variables.scss
CHANGED
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
// Destructive action
|
|
84
84
|
--destructive-bg-color: #e01b24;
|
|
85
85
|
--destructive-fg-color: #ffffff;
|
|
86
|
+
--destructive-color: oklab(from var(--destructive-bg-color) min(l, 0.5) a b);
|
|
86
87
|
|
|
87
88
|
// Semantic states. `.error` / `.warning` / `.success` (see _labels.scss) do
|
|
88
89
|
// not just recolour text — they REBIND the three accent tokens to these, so
|
|
@@ -117,8 +118,21 @@
|
|
|
117
118
|
--spacing-l: 18px;
|
|
118
119
|
--spacing-xl: 24px;
|
|
119
120
|
|
|
120
|
-
// Typography — GNOME default: Adwaita Sans 11
|
|
121
|
-
|
|
121
|
+
// Typography — GNOME default: Adwaita Sans 11. The tail is a PLATFORM-NATIVE
|
|
122
|
+
// ladder, and it is there because the families above it resolve on a GNOME desktop
|
|
123
|
+
// and NOWHERE ELSE: 'Adwaita Sans' is NAMED, not shipped (`status/stylesheet-font-
|
|
124
|
+
// families.json`), and 'Cantarell' is its predecessor. A macOS host has neither,
|
|
125
|
+
// and the `sans-serif` generic is Helvetica there — #1817, a widget set that looks
|
|
126
|
+
// right on the machine it was authored on and lands on Helvetica in every screenshot
|
|
127
|
+
// a consumer takes. `system-ui` asks the host for its own UI face (San Francisco,
|
|
128
|
+
// Segoe UI); `-apple-system` and 'Segoe UI' are the pre-`system-ui` spellings of
|
|
129
|
+
// those same two answers, kept for the engines that need them.
|
|
130
|
+
//
|
|
131
|
+
// The caption is this block's FIRST SENTENCE on purpose: `generate-adwaita-tokens.mjs`
|
|
132
|
+
// titles a group from the block touching its declaration, cut at the first `. `, so a
|
|
133
|
+
// caption written as a trailing line is unreachable and an uncapped one becomes a
|
|
134
|
+
// section header on the website's theming page.
|
|
135
|
+
--font-family: 'Adwaita Sans', 'Cantarell', 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
122
136
|
--font-size-base: 11pt;
|
|
123
137
|
--font-size-small: 9pt;
|
|
124
138
|
--font-size-heading: 12pt;
|
|
@@ -171,9 +185,15 @@
|
|
|
171
185
|
// GNOME's PROPORTIONAL UI font, so a monospace fallback chain that reaches it
|
|
172
186
|
// second lands on a font whose figures do not line up — the exact property
|
|
173
187
|
// `.monospace` exists for.
|
|
188
|
+
//
|
|
189
|
+
// 'Consolas' is the same repair as `system-ui` beside it in `--font-family` (#1817):
|
|
190
|
+
// `ui-monospace` is the right way to ask and is not universally supported, and the
|
|
191
|
+
// engines without it fell to the `monospace` generic — Courier New on Windows. The
|
|
192
|
+
// `'SF Mono'` / `'Menlo'` pair answers on macOS and `ui-monospace` on Linux, so this
|
|
193
|
+
// is the Windows half, not a second opinion about the other two.
|
|
174
194
|
--document-font-family: var(--font-family);
|
|
175
195
|
--document-font-size: var(--font-size-base);
|
|
176
|
-
--monospace-font-family: 'Adwaita Mono', ui-monospace, 'SF Mono', 'Menlo', monospace;
|
|
196
|
+
--monospace-font-family: 'Adwaita Mono', ui-monospace, 'SF Mono', 'Menlo', 'Consolas', monospace;
|
|
177
197
|
--monospace-font-size: var(--font-size-base);
|
|
178
198
|
}
|
|
179
199
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// _widget.scss — the GtkWidget layout properties an element carries as attributes.
|
|
2
|
+
//
|
|
3
|
+
// Every GTK widget inherits `halign`, `valign`, `hexpand` and `vexpand` from GtkWidget, and a
|
|
4
|
+
// projected `.blp` hands them to `buildSharedTree` as attributes (`halign="center"`). Until
|
|
5
|
+
// this partial nothing read them, so the gallery's bottom sheet stretched the button its
|
|
6
|
+
// Blueprint centres. The margins are not here: an attribute cannot carry a length into CSS
|
|
7
|
+
// portably, so `shared-tree-builder.ts` writes them as inline style.
|
|
8
|
+
//
|
|
9
|
+
// ALIGNMENT IS AN AUTO MARGIN, because that is the one CSS placement that means the same on
|
|
10
|
+
// both axes of a flex container and of a grid: GTK allocates a non-FILL child its natural
|
|
11
|
+
// size and places it inside the space its parent gave it, and an auto margin shrinks a flex
|
|
12
|
+
// or grid item to its content and puts the leftover on the named side. It does NOT place a
|
|
13
|
+
// child of a BLOCK container on the vertical axis, where CSS resolves an auto block margin to
|
|
14
|
+
// zero — so a GTK bin that lays its child out by alignment is a grid or a flex box here
|
|
15
|
+
// (`.adw-bottom-sheet-content`, `gtk-box`). And it takes the side it pushes from: a centred
|
|
16
|
+
// child cannot also keep a `margin-start`, where GTK would centre it inside the remaining
|
|
17
|
+
// space.
|
|
18
|
+
//
|
|
19
|
+
// EXPAND IS FLEX-GROW ALONG THE BOX'S OWN AXIS, which is why it lives with the box rules
|
|
20
|
+
// below and not on the attribute alone: `vexpand` in a horizontal box asks for the cross
|
|
21
|
+
// axis, which `align-items: stretch` already fills, and a `flex-grow` there would widen the
|
|
22
|
+
// child instead.
|
|
23
|
+
//
|
|
24
|
+
// Reference: refs/gtk/gtk/gtkwidget.c (halign, valign, hexpand, vexpand)
|
|
25
|
+
// Reference: refs/gtk/gtk/gtkboxlayout.c (spare space shared among expanding children)
|
|
26
|
+
// Copyright (c) The GTK Team. LGPLv2.1+.
|
|
27
|
+
|
|
28
|
+
[halign='start'] {
|
|
29
|
+
margin-inline-end: auto;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[halign='end'] {
|
|
33
|
+
margin-inline-start: auto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[halign='center'] {
|
|
37
|
+
margin-inline: auto;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
[valign='start'] {
|
|
41
|
+
margin-block-end: auto;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[valign='end'] {
|
|
45
|
+
margin-block-start: auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[valign='center'] {
|
|
49
|
+
margin-block: auto;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
gtk-box:not([orientation='vertical']) > [hexpand],
|
|
53
|
+
gtk-box[orientation='vertical'] > [vexpand] {
|
|
54
|
+
flex-grow: 1;
|
|
55
|
+
}
|
package/scss/_window_title.scss
CHANGED
|
@@ -11,6 +11,15 @@ adw-window-title {
|
|
|
11
11
|
line-height: 1.15;
|
|
12
12
|
min-width: 0;
|
|
13
13
|
|
|
14
|
+
// Both labels carry 12px sides upstream (_header-bar.scss:214-227). Without them the
|
|
15
|
+
// gallery's window title measured 27px narrower than GTK's render of the same `.blp`,
|
|
16
|
+
// and a header bar's title could run up against the buttons beside it.
|
|
17
|
+
.adw-window-title-title,
|
|
18
|
+
.adw-window-title-subtitle {
|
|
19
|
+
padding-left: 12px;
|
|
20
|
+
padding-right: 12px;
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
.adw-window-title-title {
|
|
15
24
|
font-size: var(--font-size-base);
|
|
16
25
|
font-weight: 700;
|
package/scss/adwaita-skin.scss
CHANGED
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
// specificity.
|
|
20
20
|
@use 'icon';
|
|
21
21
|
// libadwaita's typography + semantic-colour UTILITY CLASSES (`.dimmed`,
|
|
22
|
-
// `.numeric`, `.monospace`, `.title-1`…)
|
|
23
|
-
//
|
|
22
|
+
// `.numeric`, `.monospace`, `.title-1`…), and <gtk-label>, the element they are
|
|
23
|
+
// written for — see _labels.scss. Emitted BEFORE every widget so a widget's
|
|
24
24
|
// own rule wins on ordinary specificity, and because two partials @extend from
|
|
25
25
|
// it (`_progress_bar.scss`'s text node, `_data_grid.scss`'s cells) exactly as
|
|
26
26
|
// libadwaita's own `_progress-bar.scss` does.
|
|
27
27
|
@use 'labels';
|
|
28
|
+
// <gtk-box>. Before every widget, which may place its own children in one.
|
|
29
|
+
@use 'box';
|
|
28
30
|
// The REST of libadwaita's documented style-class surface — the widget-
|
|
29
31
|
// independent half: the surfaces (`.background`, `.view`, `.frame`), `.card` and
|
|
30
32
|
// the two boxed lists, `.toolbar`, `.spacer`, `.osd`, `.linked`, the app-icon
|
|
@@ -36,6 +38,7 @@
|
|
|
36
38
|
@use 'style_classes';
|
|
37
39
|
@use 'window';
|
|
38
40
|
@use 'headerbar';
|
|
41
|
+
@use 'action_bar';
|
|
39
42
|
@use 'window_title';
|
|
40
43
|
@use 'button';
|
|
41
44
|
@use 'button_content';
|
|
@@ -95,6 +98,9 @@
|
|
|
95
98
|
@use 'alert_dialog';
|
|
96
99
|
@use 'preferences_dialog';
|
|
97
100
|
@use 'dialog';
|
|
101
|
+
// Last among the widgets: an authored `halign` / `valign` is placement the author asked
|
|
102
|
+
// for, and an element rule that sets a margin must not win it back on source order.
|
|
103
|
+
@use 'widget';
|
|
98
104
|
|
|
99
105
|
// Ensure the `hidden` attribute always wins: components that toggle visibility
|
|
100
106
|
// via `element.hidden = true` set the HTML attribute, but a custom `display`
|
package/src/accent.ts
CHANGED
|
@@ -58,17 +58,40 @@ export function isAdwaitaDark(element: HTMLElement): boolean {
|
|
|
58
58
|
return globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Elements whose accent the APP chose. The desktop-appearance follower
|
|
63
|
+
* (`appearance.ts`) never writes over one: an explicit choice outranks every
|
|
64
|
+
* source it reads (ADR 0078). A WeakSet, so a removed element is not kept alive.
|
|
65
|
+
*/
|
|
66
|
+
const appChosen = new WeakSet<HTMLElement>();
|
|
65
67
|
|
|
68
|
+
/** Whether the app has chosen `target`'s accent with {@link applyAdwaitaAccent}. */
|
|
69
|
+
export function hasAppChosenAccent(target: HTMLElement): boolean {
|
|
70
|
+
return appChosen.has(target);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Write the two properties — the one place they are set, for the app's choice and the follower's alike. */
|
|
74
|
+
export function writeAdwaitaAccent(name: AdwAccentColorName, target: HTMLElement, dark: boolean): void {
|
|
66
75
|
target.style.setProperty(ACCENT_BG_PROPERTY, adwaitaAccentBgColor(name));
|
|
67
76
|
target.style.setProperty(ACCENT_PROPERTY, adwaitaAccentColor(name, dark));
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Set the accent custom properties for `name` — the app's own choice, which
|
|
81
|
+
* from now on wins over the desktop's appearance on this element.
|
|
82
|
+
*/
|
|
83
|
+
export function applyAdwaitaAccent(name: AdwAccentColorName, options: ApplyAccentOptions = {}): void {
|
|
84
|
+
const target = options.target ?? document.documentElement;
|
|
85
|
+
appChosen.add(target);
|
|
86
|
+
writeAdwaitaAccent(name, target, options.dark ?? isAdwaitaDark(target));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Drop the properties, so the stylesheet's own values apply again, and hand
|
|
91
|
+
* the element back to the desktop's appearance.
|
|
92
|
+
*/
|
|
71
93
|
export function clearAdwaitaAccent(target: HTMLElement = document.documentElement): void {
|
|
94
|
+
appChosen.delete(target);
|
|
72
95
|
target.style.removeProperty(ACCENT_BG_PROPERTY);
|
|
73
96
|
target.style.removeProperty(ACCENT_PROPERTY);
|
|
74
97
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Following the desktop's appearance in a real browser (ADR 0078): the handoff
|
|
2
|
+
// tags, live JSON, the CSS system colour, and the precedence between them — all
|
|
3
|
+
// read back through the root's COMPUTED style, since the point is what reaches
|
|
4
|
+
// the cascade.
|
|
5
|
+
//
|
|
6
|
+
// Every test restores the root: the follower acts on `document.documentElement`,
|
|
7
|
+
// which every other suite in this bundle renders under.
|
|
8
|
+
|
|
9
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
10
|
+
import { ADW_ACCENT_BG_COLORS, renderAppearanceMeta } from '@gjsify/adwaita-core';
|
|
11
|
+
import { APPEARANCE_HANDOFF_VECTORS, NEAREST_ACCENT_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
12
|
+
|
|
13
|
+
import '@gjsify/adwaita-web';
|
|
14
|
+
import { ACCENT_BG_PROPERTY, applyAdwaitaAccent, clearAdwaitaAccent } from './accent.js';
|
|
15
|
+
import { adwaitaAccentSource, applyDesktopAppearance, applySystemAccent, readSystemAccent } from './appearance.js';
|
|
16
|
+
|
|
17
|
+
const root = () => document.documentElement;
|
|
18
|
+
const rootAccent = () => getComputedStyle(root()).getPropertyValue(ACCENT_BG_PROPERTY).trim();
|
|
19
|
+
|
|
20
|
+
/** MutationObserver callbacks are microtasks; one macrotask later they have run. */
|
|
21
|
+
const settle = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
22
|
+
|
|
23
|
+
function addMeta(name: string, content: string): HTMLMetaElement {
|
|
24
|
+
const meta = document.createElement('meta');
|
|
25
|
+
meta.name = name;
|
|
26
|
+
meta.content = content;
|
|
27
|
+
document.head.append(meta);
|
|
28
|
+
return meta;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function restore(...metas: HTMLMetaElement[]): Promise<void> {
|
|
32
|
+
for (const meta of metas) meta.remove();
|
|
33
|
+
await settle();
|
|
34
|
+
applySystemAccent(false);
|
|
35
|
+
applyDesktopAppearance({});
|
|
36
|
+
clearAdwaitaAccent(root());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const AdwAppearanceTest = async () => {
|
|
40
|
+
await describe('desktop appearance: the <meta> handoff', async () => {
|
|
41
|
+
await it('applies a handed-over accent without any code in the page', async () => {
|
|
42
|
+
const defaultAccent = rootAccent();
|
|
43
|
+
const meta = addMeta('adw-accent', 'purple');
|
|
44
|
+
await settle();
|
|
45
|
+
expect(rootAccent()).toBe(ADW_ACCENT_BG_COLORS.purple);
|
|
46
|
+
expect(adwaitaAccentSource()).toBe('handoff');
|
|
47
|
+
|
|
48
|
+
// Removing the tag hands the root back to the stylesheet.
|
|
49
|
+
meta.remove();
|
|
50
|
+
await settle();
|
|
51
|
+
expect(rootAccent()).toBe(defaultAccent);
|
|
52
|
+
expect(adwaitaAccentSource()).toBe('default');
|
|
53
|
+
await restore();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await it('follows a changed content attribute', async () => {
|
|
57
|
+
const meta = addMeta('adw-accent', 'teal');
|
|
58
|
+
await settle();
|
|
59
|
+
meta.content = 'orange';
|
|
60
|
+
await settle();
|
|
61
|
+
expect(rootAccent()).toBe(ADW_ACCENT_BG_COLORS.orange);
|
|
62
|
+
await restore(meta);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await it('hands over the colour scheme as a theme class, and takes it back', async () => {
|
|
66
|
+
const meta = addMeta('adw-color-scheme', 'dark');
|
|
67
|
+
await settle();
|
|
68
|
+
expect(root().classList.contains('theme-dark')).toBe(true);
|
|
69
|
+
meta.content = 'no-preference';
|
|
70
|
+
await settle();
|
|
71
|
+
expect(root().classList.contains('theme-dark')).toBe(false);
|
|
72
|
+
await restore(meta);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await it('does not re-read the tags for an unrelated <head> change', async () => {
|
|
76
|
+
// A live JSON handoff must survive a framework injecting a <style>.
|
|
77
|
+
applyDesktopAppearance({ accent: 'green' });
|
|
78
|
+
document.head.append(document.createElement('style'));
|
|
79
|
+
await settle();
|
|
80
|
+
expect(rootAccent()).toBe(ADW_ACCENT_BG_COLORS.green);
|
|
81
|
+
document.head.lastElementChild?.remove();
|
|
82
|
+
await restore();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await describe('desktop appearance: APPEARANCE_HANDOFF_VECTORS through a real <head>', async () => {
|
|
87
|
+
for (const vector of APPEARANCE_HANDOFF_VECTORS) {
|
|
88
|
+
await it(`${JSON.stringify(vector.meta)} paints ${vector.paints ?? 'the default'}`, async () => {
|
|
89
|
+
const defaultAccent = rootAccent();
|
|
90
|
+
const holder = document.createElement('template');
|
|
91
|
+
holder.innerHTML = renderAppearanceMeta(vector.appearance);
|
|
92
|
+
const metas = [...holder.content.children] as HTMLMetaElement[];
|
|
93
|
+
document.head.append(...metas);
|
|
94
|
+
await settle();
|
|
95
|
+
expect(rootAccent()).toBe(vector.paints ? ADW_ACCENT_BG_COLORS[vector.paints] : defaultAccent);
|
|
96
|
+
const themeClass = root().classList.contains('theme-dark')
|
|
97
|
+
? 'theme-dark'
|
|
98
|
+
: root().classList.contains('theme-light')
|
|
99
|
+
? 'theme-light'
|
|
100
|
+
: null;
|
|
101
|
+
expect(themeClass).toBe(vector.themeClass);
|
|
102
|
+
await restore(...metas);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await describe('desktop appearance: a colour-only handoff is snapped (NEAREST_ACCENT_VECTORS)', async () => {
|
|
108
|
+
// A server that sends only `accentRgb` gets libadwaita's snapping in the page, read
|
|
109
|
+
// back from the cascade: the table is held by what the page paints, not by the function.
|
|
110
|
+
for (const vector of NEAREST_ACCENT_VECTORS) {
|
|
111
|
+
await it(`${vector.source}: ${vector.color} paints ${vector.expected}`, async () => {
|
|
112
|
+
applyDesktopAppearance({ accentRgb: vector.color });
|
|
113
|
+
expect(rootAccent()).toBe(ADW_ACCENT_BG_COLORS[vector.expected]);
|
|
114
|
+
await restore();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await describe('desktop appearance: live JSON and precedence', async () => {
|
|
120
|
+
await it('applies validated JSON and ignores what is not an accent', async () => {
|
|
121
|
+
applyDesktopAppearance({ accent: 'red', colorScheme: 'light' });
|
|
122
|
+
expect(rootAccent()).toBe(ADW_ACCENT_BG_COLORS.red);
|
|
123
|
+
applyDesktopAppearance({ accent: '"><script>' });
|
|
124
|
+
expect(adwaitaAccentSource()).toBe('default');
|
|
125
|
+
await restore();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await it("never overrides the app's own choice", async () => {
|
|
129
|
+
applyAdwaitaAccent('yellow');
|
|
130
|
+
applyDesktopAppearance({ accent: 'pink' });
|
|
131
|
+
const meta = addMeta('adw-accent', 'slate');
|
|
132
|
+
await settle();
|
|
133
|
+
expect(rootAccent()).toBe(ADW_ACCENT_BG_COLORS.yellow);
|
|
134
|
+
expect(adwaitaAccentSource()).toBe('app');
|
|
135
|
+
|
|
136
|
+
// Clearing the app's choice hands the root back to the desktop.
|
|
137
|
+
clearAdwaitaAccent(root());
|
|
138
|
+
applyDesktopAppearance({ accent: 'pink' });
|
|
139
|
+
expect(rootAccent()).toBe(ADW_ACCENT_BG_COLORS.pink);
|
|
140
|
+
await restore(meta);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await it('ranks the handoff above the CSS system colour', async () => {
|
|
144
|
+
applySystemAccent();
|
|
145
|
+
applyDesktopAppearance({ accent: 'teal' });
|
|
146
|
+
expect(adwaitaAccentSource()).toBe('handoff');
|
|
147
|
+
expect(rootAccent()).toBe(ADW_ACCENT_BG_COLORS.teal);
|
|
148
|
+
await restore();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await describe('desktop appearance: the CSS system colour (#1821)', async () => {
|
|
153
|
+
await it('snaps AccentColor to one of the nine where the engine resolves it, else keeps blue', async () => {
|
|
154
|
+
// Both outcomes are asserted: support for `AccentColor` differs per engine and
|
|
155
|
+
// per OS, and "unsupported" must leave the stylesheet's blue, not a guess.
|
|
156
|
+
const defaultAccent = rootAccent();
|
|
157
|
+
const system = readSystemAccent();
|
|
158
|
+
const applied = applySystemAccent();
|
|
159
|
+
expect(applied).toBe(system?.accent ?? null);
|
|
160
|
+
if (system) {
|
|
161
|
+
expect(adwaitaAccentSource()).toBe('system');
|
|
162
|
+
expect(rootAccent()).toBe(ADW_ACCENT_BG_COLORS[system.accent]);
|
|
163
|
+
} else {
|
|
164
|
+
expect(adwaitaAccentSource()).toBe('default');
|
|
165
|
+
expect(rootAccent()).toBe(defaultAccent);
|
|
166
|
+
}
|
|
167
|
+
await restore();
|
|
168
|
+
expect(rootAccent()).toBe(defaultAccent);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await it('the meta keyword "system" asks for the same thing', async () => {
|
|
172
|
+
const meta = addMeta('adw-accent', 'system');
|
|
173
|
+
await settle();
|
|
174
|
+
expect(adwaitaAccentSource()).toBe(readSystemAccent() ? 'system' : 'default');
|
|
175
|
+
await restore(meta);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
};
|
package/src/adw-banner.spec.ts
CHANGED
|
@@ -146,4 +146,20 @@ export const AdwBannerTest = async () => {
|
|
|
146
146
|
host.remove();
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
|
+
|
|
150
|
+
// adw-banner.ui: the title is a `.heading` label and the button a plain text button.
|
|
151
|
+
// Beside a GTK render of the gallery's banner `.blp` this banner was 8px shorter,
|
|
152
|
+
// with a regular-weight title.
|
|
153
|
+
await describe('adw-banner geometry (adw-banner.ui)', async () => {
|
|
154
|
+
await it('draws a bold title and a 34px text button, so the banner is 46px tall', () => {
|
|
155
|
+
const { el, host } = mount({ revealed: '', title: 'Updates paused', 'button-label': 'Resume' });
|
|
156
|
+
host.style.width = '460px';
|
|
157
|
+
expect(getComputedStyle(titleEl(el)).fontWeight).toBe('700');
|
|
158
|
+
const button = getComputedStyle(buttonEl(el));
|
|
159
|
+
expect([button.paddingLeft, button.paddingRight]).toStrictEqual(['17px', '17px']);
|
|
160
|
+
expect(Math.round(buttonEl(el).getBoundingClientRect().height)).toBe(34);
|
|
161
|
+
expect(Math.round(el.getBoundingClientRect().height)).toBe(46);
|
|
162
|
+
host.remove();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
149
165
|
};
|
|
@@ -53,6 +53,14 @@ export const AdwButtonRowTest = async () => {
|
|
|
53
53
|
// adw-action-rows.spec.ts, which asserts that every spelling of the
|
|
54
54
|
// attribute leaves the row activatable.
|
|
55
55
|
|
|
56
|
+
// `row.button > box { min-height: 40px }` (_lists.scss:458-462): a GTK render of the
|
|
57
|
+
// gallery's button-row `.blp` measures the row at 44, where this one stood at 50.
|
|
58
|
+
await it('stands 44px tall, as a GTK button row does', () => {
|
|
59
|
+
const { row, host } = parseRow('<adw-button-row title="Add account"></adw-button-row>');
|
|
60
|
+
expect(Math.round(row.getBoundingClientRect().height)).toBe(44);
|
|
61
|
+
host.remove();
|
|
62
|
+
});
|
|
63
|
+
|
|
56
64
|
await it('emits `activated` on click', async () => {
|
|
57
65
|
const { row, host } = parseRow('<adw-button-row title="Add account"></adw-button-row>');
|
|
58
66
|
let activated = false;
|
|
@@ -212,4 +212,21 @@ export const AdwHeaderBarTest = async () => {
|
|
|
212
212
|
unmountAll();
|
|
213
213
|
});
|
|
214
214
|
});
|
|
215
|
+
|
|
216
|
+
// `windowtitle .title, .subtitle { padding: 0 12px }` (_header-bar.scss:214-227).
|
|
217
|
+
// Measured beside a GTK render of the gallery's window-title `.blp`: 27px narrower here.
|
|
218
|
+
await describe('adw-window-title label sides', async () => {
|
|
219
|
+
await it('pads its title and subtitle 12px on each side', () => {
|
|
220
|
+
const title = document.createElement('adw-window-title');
|
|
221
|
+
title.setAttribute('title', 'Inbox');
|
|
222
|
+
title.setAttribute('subtitle', '3 unread messages');
|
|
223
|
+
document.body.appendChild(title);
|
|
224
|
+
const sides = ['.adw-window-title-title', '.adw-window-title-subtitle'].map((selector) => {
|
|
225
|
+
const style = getComputedStyle(title.querySelector(selector) as HTMLElement);
|
|
226
|
+
return `${style.paddingLeft} ${style.paddingRight}`;
|
|
227
|
+
});
|
|
228
|
+
title.remove();
|
|
229
|
+
expect(sides).toStrictEqual(['12px 12px', '12px 12px']);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
215
232
|
};
|