@gjsify/adwaita-web 0.51.0 → 0.52.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/dist/adwaita-web.css +255 -27
- package/dist/adwaita-web.css.map +1 -1
- 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/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 +4 -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 +1 -0
- package/scss/_widget.scss +55 -0
- package/scss/_window_title.scss +9 -0
- package/scss/adwaita-skin.scss +8 -2
- 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/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/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 +14 -1
- package/src/keyboard-operable.spec.ts +2 -1
- 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 +16 -16
- 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 +18 -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
|
|
@@ -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/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
|
};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// A REAL `.blp` OF GtkWidget LAYOUT PROPERTIES, MOUNTED AND MEASURED.
|
|
2
|
+
//
|
|
3
|
+
// `bottom-sheet-layout.blp` (`@gjsify/adwaita-core/src/conformance/blueprints/`) is the shape
|
|
4
|
+
// the gallery's `Adw.BottomSheet` block writes: a button placed with `halign`/`valign`, a box
|
|
5
|
+
// inset by four `margin-*` lines, a title at the start edge, and one expanding child. The
|
|
6
|
+
// NativeScript port builds the same file (`adwaita-nativescript`'s `blueprint-trees.spec.ts`).
|
|
7
|
+
//
|
|
8
|
+
// Before `_widget.scss` and the builder's margin table, this renderer wrote every one of these
|
|
9
|
+
// as an attribute nothing read — no refusal, since the web builder refuses nothing it writes as
|
|
10
|
+
// an attribute, and no effect: the centred button stretched across the sheet's content. So the
|
|
11
|
+
// assertions here are about GEOMETRY, read off the laid-out page, never about the attribute
|
|
12
|
+
// being present.
|
|
13
|
+
|
|
14
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
15
|
+
|
|
16
|
+
import { mountSharedTree } from './shared-tree-builder.js';
|
|
17
|
+
|
|
18
|
+
import sheetTree from '../../adwaita-core/src/conformance/blueprints/bottom-sheet-layout.blp?shared-tree';
|
|
19
|
+
import carouselTree from '../../adwaita-core/src/conformance/blueprints/carousel-indicators.blp?shared-tree';
|
|
20
|
+
|
|
21
|
+
import { GALLERY_BLUEPRINTS } from './blueprint-markup.spec.js';
|
|
22
|
+
|
|
23
|
+
/** Mount the fixture at a size a layout can be measured in; the caller unmounts. */
|
|
24
|
+
function mountSheet() {
|
|
25
|
+
const mounted = mountSharedTree(sheetTree);
|
|
26
|
+
mounted.root.style.width = '400px';
|
|
27
|
+
mounted.root.style.height = '340px';
|
|
28
|
+
const byId = (id: string): HTMLElement => {
|
|
29
|
+
const el = mounted.root.querySelector<HTMLElement>(`#${id}`);
|
|
30
|
+
if (el === null) throw new Error(`the mounted tree has no element with the id '${id}'`);
|
|
31
|
+
return el;
|
|
32
|
+
};
|
|
33
|
+
return { ...mounted, byId };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const AdwBlueprintLayoutTest = async () => {
|
|
37
|
+
await describe('adwaita-web: bottom-sheet-layout.blp, laid out', async () => {
|
|
38
|
+
await it('halign/valign center place the button in the middle of the content at its own size', async () => {
|
|
39
|
+
const { root, unmount, byId } = mountSheet();
|
|
40
|
+
try {
|
|
41
|
+
const content = root.querySelector('.adw-bottom-sheet-content') as HTMLElement;
|
|
42
|
+
const area = content.getBoundingClientRect();
|
|
43
|
+
const button = byId('toggle').getBoundingClientRect();
|
|
44
|
+
|
|
45
|
+
expect(button.width < area.width).toBe(true);
|
|
46
|
+
expect(button.height < area.height).toBe(true);
|
|
47
|
+
expect(Math.abs(button.left - area.left - (area.right - button.right)) <= 1).toBe(true);
|
|
48
|
+
expect(Math.abs(button.top - area.top - (area.bottom - button.bottom)) <= 1).toBe(true);
|
|
49
|
+
} finally {
|
|
50
|
+
unmount();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await it('the four margins inset the box, start and end on the logical edges', async () => {
|
|
55
|
+
const { unmount, byId } = mountSheet();
|
|
56
|
+
try {
|
|
57
|
+
const style = getComputedStyle(byId('box'));
|
|
58
|
+
|
|
59
|
+
expect(style.marginLeft).toBe('18px');
|
|
60
|
+
expect(style.marginRight).toBe('12px');
|
|
61
|
+
expect(style.marginTop).toBe('18px');
|
|
62
|
+
expect(style.marginBottom).toBe('24px');
|
|
63
|
+
} finally {
|
|
64
|
+
unmount();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await it('halign start keeps the title at the start edge of the box at its own width', async () => {
|
|
69
|
+
const { unmount, byId } = mountSheet();
|
|
70
|
+
try {
|
|
71
|
+
const box = byId('box').getBoundingClientRect();
|
|
72
|
+
const title = byId('title').getBoundingClientRect();
|
|
73
|
+
|
|
74
|
+
expect(Math.abs(title.left - box.left) <= 1).toBe(true);
|
|
75
|
+
expect(title.width < box.width).toBe(true);
|
|
76
|
+
} finally {
|
|
77
|
+
unmount();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await it('expand grows along the box axis only', async () => {
|
|
82
|
+
const { unmount, byId } = mountSheet();
|
|
83
|
+
try {
|
|
84
|
+
// The box is vertical: `vexpand` is its own axis and takes the spare height,
|
|
85
|
+
// `hexpand` is the cross axis, which `align-items: stretch` already fills.
|
|
86
|
+
expect(getComputedStyle(byId('body')).flexGrow).toBe('1');
|
|
87
|
+
expect(getComputedStyle(byId('caption')).flexGrow).toBe('0');
|
|
88
|
+
} finally {
|
|
89
|
+
unmount();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await it('the open modal sheet shows its dimming, and the non-modal one would not', async () => {
|
|
94
|
+
const { root, unmount } = mountSheet();
|
|
95
|
+
try {
|
|
96
|
+
const sheet = root as HTMLElement & { modal: boolean };
|
|
97
|
+
const dimming = root.querySelector('.adw-bottom-sheet-dimming') as HTMLElement;
|
|
98
|
+
|
|
99
|
+
expect(dimming.classList.contains('visible')).toBe(true);
|
|
100
|
+
sheet.modal = false;
|
|
101
|
+
expect(dimming.classList.contains('visible')).toBe(false);
|
|
102
|
+
} finally {
|
|
103
|
+
unmount();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// `carousel: carousel` projects as the attribute `carousel="carousel"`: GTK's property
|
|
109
|
+
// name, which the indicators now read beside the `for` they had first.
|
|
110
|
+
await describe('adwaita-web: carousel-indicators.blp, mounted', async () => {
|
|
111
|
+
await it('both indicators bind to the carousel by id and mark its first page', async () => {
|
|
112
|
+
const { root, unmount } = mountSharedTree(carouselTree);
|
|
113
|
+
try {
|
|
114
|
+
const dots = [...root.querySelectorAll('#dots .adw-carousel-dot')];
|
|
115
|
+
const lines = [...root.querySelectorAll('#lines .adw-carousel-line')];
|
|
116
|
+
|
|
117
|
+
expect(dots.length).toBe(3);
|
|
118
|
+
expect(lines.length).toBe(3);
|
|
119
|
+
expect(dots.map((dot) => dot.classList.contains('active'))).toStrictEqual([true, false, false]);
|
|
120
|
+
expect(lines[0]?.classList.contains('active')).toBe(true);
|
|
121
|
+
} finally {
|
|
122
|
+
unmount();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// The gallery's third batch of `.blp` files, mounted at the size of the gallery's stage
|
|
128
|
+
// and held to a GTK render of the same file (GJS, `Gtk.Builder`, an `Adw.ApplicationWindow`
|
|
129
|
+
// of that size, libadwaita 1.9). The GTK numbers are written beside each assertion; the
|
|
130
|
+
// tolerance is the 1-2px a header bar and text metrics differ by.
|
|
131
|
+
await describe('adwaita-web: the gallery Blueprints, laid out beside GTK', async () => {
|
|
132
|
+
/** The tree mounted in a grid of `width` x `height`, as the gallery stage holds it. */
|
|
133
|
+
const mountAt = (file: string, width: number, height: number) => {
|
|
134
|
+
const tree = GALLERY_BLUEPRINTS[file];
|
|
135
|
+
if (tree === undefined) throw new Error(`no gallery Blueprint ${file}`);
|
|
136
|
+
const mounted = mountSharedTree(tree);
|
|
137
|
+
const host = mounted.root.parentElement as HTMLElement;
|
|
138
|
+
host.style.cssText = `display: grid; width: ${width}px; height: ${height}px;`;
|
|
139
|
+
const box = (el: Element) => {
|
|
140
|
+
const r = el.getBoundingClientRect();
|
|
141
|
+
const h = host.getBoundingClientRect();
|
|
142
|
+
return { x: r.left - h.left, y: r.top - h.top, width: r.width, height: r.height };
|
|
143
|
+
};
|
|
144
|
+
const byId = (id: string): HTMLElement => {
|
|
145
|
+
const el = host.querySelector<HTMLElement>(`#${id}`);
|
|
146
|
+
if (el === null) throw new Error(`${file} mounted no element with the id '${id}'`);
|
|
147
|
+
return el;
|
|
148
|
+
};
|
|
149
|
+
return { ...mounted, host, box, byId };
|
|
150
|
+
};
|
|
151
|
+
const near = (actual: number, gtk: number, tolerance = 2) => Math.abs(actual - gtk) <= tolerance;
|
|
152
|
+
|
|
153
|
+
await it('a split view page fills its pane, and its header bar shows the page title', async () => {
|
|
154
|
+
const { unmount, box, byId, host } = mountAt('adwaita/navigation-split-view.blp', 652, 340);
|
|
155
|
+
try {
|
|
156
|
+
// GTK: sidebar 0,46 180x294 — the list fills the pane under the header bar.
|
|
157
|
+
expect(near(box(byId('sidebar')).height, 294)).toBe(true);
|
|
158
|
+
const titles = [...host.querySelectorAll('adw-header-bar')].map(
|
|
159
|
+
(bar) => bar.querySelector('adw-window-title')?.getAttribute('title') ?? null,
|
|
160
|
+
);
|
|
161
|
+
expect(titles).toStrictEqual(['Mailboxes', 'All Mail']);
|
|
162
|
+
} finally {
|
|
163
|
+
unmount();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await it('an overlay split view sidebar fills its pane', async () => {
|
|
168
|
+
const { unmount, box, byId } = mountAt('adwaita/overlay-split-view.blp', 652, 340);
|
|
169
|
+
try {
|
|
170
|
+
// GTK: sidebar 0,46 180x294.
|
|
171
|
+
expect(near(box(byId('sidebar')).height, 294)).toBe(true);
|
|
172
|
+
} finally {
|
|
173
|
+
unmount();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await it('a centred toolbar-view content widget keeps its own height', async () => {
|
|
178
|
+
const { unmount, box, byId } = mountAt('adwaita/navigation-view.blp', 652, 340);
|
|
179
|
+
try {
|
|
180
|
+
// GTK: open_button 245,171 161x44.
|
|
181
|
+
const button = box(byId('open_button'));
|
|
182
|
+
expect(near(button.height, 44)).toBe(true);
|
|
183
|
+
expect(near(button.y, 171)).toBe(true);
|
|
184
|
+
} finally {
|
|
185
|
+
unmount();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await it("a status page has upstream's metrics: a 128px icon, the child where GTK puts it", async () => {
|
|
190
|
+
const { unmount, box, byId, host } = mountAt('adwaita/status-page.blp', 652, 400);
|
|
191
|
+
try {
|
|
192
|
+
const icon = host.querySelector('.adw-status-page-icon') as HTMLElement;
|
|
193
|
+
expect(box(icon).width).toBe(128);
|
|
194
|
+
expect(box(icon).height).toBe(128);
|
|
195
|
+
// GTK: new_button 239,311 174x44.
|
|
196
|
+
expect(near(box(byId('new_button')).y, 311, 3)).toBe(true);
|
|
197
|
+
} finally {
|
|
198
|
+
unmount();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
await it('a carousel does not widen the box it fills to the sum of its pages', async () => {
|
|
203
|
+
const { unmount, box, root } = mountAt('adwaita/carousel.blp', 652, 260);
|
|
204
|
+
try {
|
|
205
|
+
// GTK: the box is 652 wide, the window's width.
|
|
206
|
+
expect(box(root).width).toBe(652);
|
|
207
|
+
} finally {
|
|
208
|
+
unmount();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await it('tooltip-text becomes the icon button tooltip and accessible name', async () => {
|
|
213
|
+
const { unmount, byId } = mountAt('adwaita/toolbar-view.blp', 652, 300);
|
|
214
|
+
try {
|
|
215
|
+
const inner = byId('add_button').querySelector('button') as HTMLButtonElement;
|
|
216
|
+
expect(inner.title).toBe('Add');
|
|
217
|
+
expect(inner.getAttribute('aria-label')).toBe('Add');
|
|
218
|
+
} finally {
|
|
219
|
+
unmount();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
};
|