@gjsify/adwaita-web 0.36.0 → 0.37.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 +433 -33
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
- package/lib/types/breakpoints.d.ts +10 -12
- package/lib/types/elements/adw-about-dialog.d.ts +19 -36
- package/lib/types/elements/adw-action-row.d.ts +9 -12
- package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
- package/lib/types/elements/adw-avatar.d.ts +1 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
- package/lib/types/elements/adw-button-content.d.ts +3 -4
- package/lib/types/elements/adw-button-row.d.ts +1 -1
- package/lib/types/elements/adw-carousel.d.ts +28 -49
- package/lib/types/elements/adw-checks.d.ts +2 -8
- package/lib/types/elements/adw-combo-row.d.ts +6 -6
- package/lib/types/elements/adw-data-grid.d.ts +5 -10
- package/lib/types/elements/adw-drop-down.d.ts +3 -11
- package/lib/types/elements/adw-entry-row.d.ts +20 -23
- package/lib/types/elements/adw-icon.d.ts +2 -4
- package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
- package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
- package/lib/types/elements/adw-navigation-view.d.ts +2 -10
- package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
- package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
- package/lib/types/elements/adw-popover.d.ts +2 -7
- package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
- package/lib/types/elements/adw-preferences-group.d.ts +12 -14
- package/lib/types/elements/adw-progress-bar.d.ts +3 -4
- package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
- package/lib/types/elements/adw-sidebar.d.ts +5 -10
- package/lib/types/elements/adw-spinner.d.ts +3 -5
- package/lib/types/elements/adw-split-button.d.ts +6 -16
- package/lib/types/elements/adw-switch-row.d.ts +3 -3
- package/lib/types/elements/adw-tab-view.d.ts +16 -26
- package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
- package/lib/types/elements/adw-view-stack.d.ts +7 -17
- package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
- package/lib/types/elements/adw-view-switcher.d.ts +18 -38
- package/lib/types/elements/adw-wrap-box.d.ts +7 -17
- package/lib/types/elements/view-switcher-dom.d.ts +10 -13
- package/lib/types/index.d.ts +1 -0
- package/lib/types/source-view/hex.d.ts +7 -13
- package/lib/types/style-classes.spec.d.ts +11 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +6 -6
- package/scss/_about_dialog.scss +3 -2
- package/scss/_action_row.scss +33 -0
- package/scss/_avatar.scss +12 -0
- package/scss/_button.scss +44 -22
- package/scss/_card.scss +11 -8
- package/scss/_checks.scss +2 -4
- package/scss/_dialog.scss +3 -2
- package/scss/_headerbar.scss +23 -0
- package/scss/_popover.scss +8 -4
- package/scss/_preferences.scss +7 -7
- package/scss/_progress_bar.scss +9 -6
- package/scss/_reset.scss +1 -1
- package/scss/_shortcut_label.scss +80 -0
- package/scss/_sidebar.scss +57 -0
- package/scss/_status_page.scss +20 -0
- package/scss/_style_classes.scss +407 -0
- package/scss/_switch.scss +4 -3
- package/scss/_variables.scss +25 -0
- package/scss/adwaita-skin.scss +12 -0
- package/src/adw-about-dialog.spec.ts +34 -49
- package/src/adw-action-rows.spec.ts +25 -33
- package/src/adw-avatar.spec.ts +51 -20
- package/src/adw-banner.spec.ts +5 -8
- package/src/adw-button-content.spec.ts +9 -12
- package/src/adw-button-row.spec.ts +2 -4
- package/src/adw-carousel.spec.ts +40 -53
- package/src/adw-checks.spec.ts +36 -47
- package/src/adw-drop-down.spec.ts +25 -32
- package/src/adw-header-bar.spec.ts +5 -7
- package/src/adw-icon.spec.ts +23 -32
- package/src/adw-progress-bar.spec.ts +10 -12
- package/src/adw-row-state.spec.ts +9 -10
- package/src/adw-shortcut-label.spec.ts +112 -0
- package/src/adw-sidebar.spec.ts +17 -24
- package/src/adw-switch.spec.ts +11 -17
- package/src/adw-tab-view.spec.ts +7 -10
- package/src/adw-toast-overlay.spec.ts +7 -11
- package/src/adw-view-switcher-bar.spec.ts +10 -14
- package/src/adw-wrap-box.spec.ts +25 -36
- package/src/bottom-sheet.spec.ts +30 -36
- package/src/breakpoints.ts +15 -18
- package/src/chrome.spec.ts +13 -21
- package/src/elements/adw-about-dialog.ts +71 -114
- package/src/elements/adw-action-row.ts +22 -37
- package/src/elements/adw-alert-dialog.ts +25 -50
- package/src/elements/adw-avatar.ts +40 -15
- package/src/elements/adw-banner.ts +14 -18
- package/src/elements/adw-bottom-sheet.ts +31 -52
- package/src/elements/adw-button-content.ts +30 -37
- package/src/elements/adw-button-row.ts +14 -24
- package/src/elements/adw-carousel.ts +87 -148
- package/src/elements/adw-checks.ts +43 -56
- package/src/elements/adw-clamp.ts +8 -9
- package/src/elements/adw-combo-row.ts +17 -25
- package/src/elements/adw-data-grid.ts +26 -38
- package/src/elements/adw-dialog.ts +25 -56
- package/src/elements/adw-drop-down.ts +31 -51
- package/src/elements/adw-entry-row.ts +39 -46
- package/src/elements/adw-expander-row.ts +30 -53
- package/src/elements/adw-icon.ts +30 -53
- package/src/elements/adw-inline-view-switcher.ts +38 -67
- package/src/elements/adw-menu-button.ts +28 -58
- package/src/elements/adw-navigation-split-view.ts +35 -47
- package/src/elements/adw-navigation-view.ts +12 -28
- package/src/elements/adw-overlay-split-view.ts +100 -175
- package/src/elements/adw-password-entry-row.ts +18 -28
- package/src/elements/adw-popover.ts +28 -44
- package/src/elements/adw-preferences-dialog.ts +41 -66
- package/src/elements/adw-preferences-group.ts +33 -46
- package/src/elements/adw-progress-bar.ts +27 -47
- package/src/elements/adw-shortcut-label.ts +138 -0
- package/src/elements/adw-sidebar.ts +31 -39
- package/src/elements/adw-spin-row.ts +7 -14
- package/src/elements/adw-spinner.ts +31 -43
- package/src/elements/adw-split-button.ts +45 -75
- package/src/elements/adw-switch-row.ts +29 -46
- package/src/elements/adw-switch.ts +28 -47
- package/src/elements/adw-tab-view.ts +62 -103
- package/src/elements/adw-toast-overlay.ts +28 -35
- package/src/elements/adw-toggle-group.ts +9 -16
- package/src/elements/adw-view-stack.ts +32 -51
- package/src/elements/adw-view-switcher-bar.ts +26 -50
- package/src/elements/adw-view-switcher.ts +60 -110
- package/src/elements/adw-window-title.ts +4 -4
- package/src/elements/adw-wrap-box.ts +33 -59
- package/src/elements/view-switcher-dom.ts +15 -22
- package/src/entry-rows.spec.ts +18 -24
- package/src/index.ts +13 -15
- package/src/navigation-view.spec.ts +7 -10
- package/src/preferences-search.ts +11 -15
- package/src/preferences.spec.ts +13 -15
- package/src/source-view/adw-source-view.ts +6 -19
- package/src/source-view/hex.ts +7 -13
- package/src/split-button.spec.ts +32 -44
- package/src/split-views.spec.ts +83 -149
- package/src/style-classes.spec.ts +181 -0
- package/src/style-isolation.spec.ts +3 -6
- package/src/styles.generated.ts +1 -1
- package/src/tab-view.spec.ts +26 -35
- package/src/test.browser.mts +4 -0
- package/src/view-stack.spec.ts +23 -34
- package/src/view-switcher.spec.ts +56 -84
package/src/adw-sidebar.spec.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
// DOM-level conformance tests for <adw-sidebar>, driven by the SAME vectors the
|
|
2
2
|
// NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// off the DECLARATION index, so an empty leading section drew a stray separator
|
|
9
|
-
// and cost the next section its `.first` flush-to-top padding. Nothing failed,
|
|
10
|
-
// because nothing compared them. This suite is that comparison.
|
|
4
|
+
// Two rules independent selection logic drifted from: an out-of-range `selected` means
|
|
5
|
+
// NO selection in GTK, not a clamp to the last row and not a dropped write; and section
|
|
6
|
+
// headers key off the RENDERED index, not the declaration index, or an empty leading
|
|
7
|
+
// section draws a stray separator and costs the next section its `.first` padding.
|
|
11
8
|
import { describe, expect, it } from '@gjsify/unit';
|
|
12
9
|
|
|
13
10
|
import type { AdwSidebarItemSpec, AdwSidebarSectionSpec } from '@gjsify/adwaita-core';
|
|
@@ -22,9 +19,8 @@ import {
|
|
|
22
19
|
import type { AdwSidebar } from './elements/adw-sidebar.js';
|
|
23
20
|
|
|
24
21
|
/**
|
|
25
|
-
* Mount a sidebar
|
|
26
|
-
*
|
|
27
|
-
* the host is appended.
|
|
22
|
+
* Mount a sidebar from DECLARED children, the way an author writes it: the element
|
|
23
|
+
* consumes them at connect time, so they must all exist before the host is appended.
|
|
28
24
|
*/
|
|
29
25
|
function mountSidebar(
|
|
30
26
|
sections: ReadonlyArray<AdwSidebarSectionSpec>,
|
|
@@ -144,8 +140,8 @@ export const AdwSidebarTest = async () => {
|
|
|
144
140
|
}
|
|
145
141
|
|
|
146
142
|
await it('draws no section container for a section that renders no rows', () => {
|
|
147
|
-
//
|
|
148
|
-
//
|
|
143
|
+
// A header plus an empty `.adw-sidebar-section` per DECLARED section is a
|
|
144
|
+
// visible empty card in page mode.
|
|
149
145
|
const { sidebar, host } = mountSidebar([{ title: 'Empty', items: [] }, { items: [{ title: 'A' }] }]);
|
|
150
146
|
expect(sidebar.querySelectorAll('.adw-sidebar-section')).toHaveLength(1);
|
|
151
147
|
expect(headersOf(sidebar)).toHaveLength(0);
|
|
@@ -303,10 +299,9 @@ export const AdwSidebarTest = async () => {
|
|
|
303
299
|
});
|
|
304
300
|
|
|
305
301
|
await it('follows a declared item attribute change without touching the selection', () => {
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
// consumed, so the link back to its spec cannot be a DOM lookup.
|
|
302
|
+
// `observedAttributes` with no attributeChangedCallback behind it loses every
|
|
303
|
+
// post-construction change. The declared element is detached once consumed, so
|
|
304
|
+
// the link back to its spec cannot be a DOM lookup.
|
|
310
305
|
const declared = createItem({ title: 'A' });
|
|
311
306
|
const sectionEl = document.createElement('adw-sidebar-section');
|
|
312
307
|
sectionEl.append(declared, createItem({ title: 'B' }));
|
|
@@ -330,15 +325,13 @@ export const AdwSidebarTest = async () => {
|
|
|
330
325
|
});
|
|
331
326
|
});
|
|
332
327
|
|
|
333
|
-
// A row's own
|
|
328
|
+
// A row's own BOX, which asking what a row contains never measures.
|
|
334
329
|
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
// corners clipped away. Every earlier test asked what a row CONTAINED, so
|
|
341
|
-
// all of them passed.
|
|
330
|
+
// `width: 100%` on top of `margin: 0 6px 2px` makes a row 12px too wide: the list is a
|
|
331
|
+
// column flex container, so the row already stretches to it and stretching subtracts
|
|
332
|
+
// margins, while `width: 100%` resolves against the containing block, which does not —
|
|
333
|
+
// leaving the row inset on the left and hanging past the right edge with its rounded
|
|
334
|
+
// corners clipped.
|
|
342
335
|
await describe('a sidebar row sits inside its list, not over its edge', async () => {
|
|
343
336
|
await it('leaves the same margin on both sides', async () => {
|
|
344
337
|
const { sidebar, host } = mountSidebar([{ items: [{ title: 'Inbox' }, { title: 'Starred' }] }], {
|
package/src/adw-switch.spec.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
// DOM-level tests for <adw-switch>, plus the
|
|
2
|
-
//
|
|
1
|
+
// DOM-level tests for <adw-switch>, plus the proof that both <adw-switch-row> and
|
|
2
|
+
// <adw-expander-row> still work now that the switch is lifted out of them. Two
|
|
3
|
+
// invariants:
|
|
3
4
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// - the GEOMETRY now comes from ONE unscoped partial. `measure()` compares the
|
|
12
|
-
// computed box of the row's switch against the expander's; before the lift
|
|
13
|
-
// they were two independently-maintained blocks that happened to agree, and
|
|
14
|
-
// deleting either one silently unstyled its widget.
|
|
15
|
-
// - the EVENTS are unchanged. Both rows compose an element that emits its own
|
|
16
|
-
// `notify::active`, so each row stops it at the switch and publishes its own
|
|
17
|
-
// name — a row listener must still see exactly one event per toggle.
|
|
5
|
+
// - the GEOMETRY comes from ONE unscoped partial, so `measure()` compares the
|
|
6
|
+
// computed box of the row's switch against the expander's. Scoping `.adw-switch`
|
|
7
|
+
// inside `adw-switch-row {` is what forced the expander row to keep a duplicate
|
|
8
|
+
// block, where deleting either copy silently unstyled its widget.
|
|
9
|
+
// - the EVENTS are unchanged: both rows compose an element emitting its own
|
|
10
|
+
// `notify::active`, so each row stops it at the switch and publishes under its own
|
|
11
|
+
// name — a row listener must see exactly one event per toggle.
|
|
18
12
|
import { describe, expect, it } from '@gjsify/unit';
|
|
19
13
|
|
|
20
14
|
import type { AdwExpanderRow } from './elements/adw-expander-row.js';
|
|
@@ -59,7 +53,7 @@ export const AdwSwitchTest = async () => {
|
|
|
59
53
|
expect(el.active).toBe(false);
|
|
60
54
|
expect(input).toBeTruthy();
|
|
61
55
|
expect(input.checked).toBe(false);
|
|
62
|
-
// The focus ring is `input:focus-visible
|
|
56
|
+
// The focus ring is `input:focus-visible +.adw-switch-slider`, so the
|
|
63
57
|
// slider MUST be the input's immediate next sibling.
|
|
64
58
|
expect(input.nextElementSibling).toBe(slider);
|
|
65
59
|
expect(el.classList.contains('adw-switch')).toBe(true);
|
package/src/adw-tab-view.spec.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
// DOM
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// selected↔click sync, notify/close events, and the expand-tabs / no-close
|
|
5
|
-
// attributes — i.e. the DOM/CSS surface.
|
|
1
|
+
// The DOM/CSS surface of <adw-tab-view>, in a real browser via the @gjsify/adwaita-web
|
|
2
|
+
// test axis — the entry self-applies the compiled stylesheet, so computed-style
|
|
3
|
+
// assertions are valid.
|
|
6
4
|
//
|
|
7
|
-
//
|
|
8
|
-
// conformance vectors
|
|
9
|
-
// style, that one is about behaviour both renderers must agree on.
|
|
5
|
+
// Keep the split: the MODEL is asserted in `tab-view.spec.ts` against the shared
|
|
6
|
+
// libadwaita conformance vectors, this file is markup and computed style only.
|
|
10
7
|
import { describe, expect, it } from '@gjsify/unit';
|
|
11
8
|
|
|
12
9
|
import type { AdwTabView } from './elements/adw-tab-view.js';
|
|
@@ -65,10 +62,10 @@ export const AdwTabViewTest = async () => {
|
|
|
65
62
|
expect(closed).toBe(1);
|
|
66
63
|
// The element used to emit and remove NOTHING, so every close was
|
|
67
64
|
// permanently denied; the default handler now confirms a non-pinned
|
|
68
|
-
// page (close_page_cb
|
|
65
|
+
// page (close_page_cb).
|
|
69
66
|
expect(view.querySelectorAll('.adw-tab').length).toBe(2);
|
|
70
67
|
// Closing a page BEFORE the selection must not also select the tab,
|
|
71
|
-
// and must leave the selected PAGE selected
|
|
68
|
+
// and must leave the selected PAGE selected.
|
|
72
69
|
expect(view.selected).toBe(0);
|
|
73
70
|
view.parentElement?.remove();
|
|
74
71
|
});
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
// DOM-level behaviour tests for <adw-toast-overlay
|
|
2
|
-
//
|
|
1
|
+
// DOM-level behaviour tests for <adw-toast-overlay>, in a real browser via the
|
|
2
|
+
// @gjsify/adwaita-web browser test axis.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// at a time and queues the rest FIFO, advancing only when the visible one is
|
|
8
|
-
// dismissed (manually or by its timeout) — the policy `AdwToastQueue` in
|
|
9
|
-
// `@gjsify/adwaita-core` owns and this element now composes.
|
|
4
|
+
// `Adw.ToastOverlay` shows exactly ONE toast at a time and queues the rest FIFO,
|
|
5
|
+
// advancing only when the visible one is dismissed (manually or by its timeout) — the
|
|
6
|
+
// policy `AdwToastQueue` owns in `@gjsify/adwaita-core` and this element composes.
|
|
10
7
|
//
|
|
11
|
-
// Timing
|
|
12
|
-
//
|
|
13
|
-
// real timers.
|
|
8
|
+
// Timing runs through core's injected `ToastScheduler` seam (a virtual clock), so the
|
|
9
|
+
// auto-dismiss lifecycle is deterministic with no real timers.
|
|
14
10
|
import { describe, expect, it } from '@gjsify/unit';
|
|
15
11
|
|
|
16
12
|
import type { AdwToastOverlay, ToastScheduler, ToastTimerHandle } from './elements/adw-toast-overlay.js';
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// bar rendered visible until scss/_view_switcher_bar.scss re-asserted
|
|
6
|
-
// `&[hidden] { display: none }`. This test pins that fix.
|
|
1
|
+
// That the derived reveal state actually shows and hides the bar. The bar's base scss
|
|
2
|
+
// sets `display: block`, an AUTHOR rule that defeats the UA `[hidden]{display:none}` the
|
|
3
|
+
// component relies on (cascade origin beats specificity), so
|
|
4
|
+
// `scss/_view_switcher_bar.scss` has to re-assert `&[hidden] { display: none }`.
|
|
7
5
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// when `reveal` is set AND MORE THAN ONE page is visible
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// bar rendered as an empty strip. The full derivation is driven from the shared
|
|
14
|
-
// conformance vectors in `view-switcher.spec.ts`.
|
|
6
|
+
// Staged against a real two-page <adw-view-stack>, because the request alone is not
|
|
7
|
+
// enough: `update_bar_revealed` (refs/libadwaita/src/adw-view-switcher-bar.c) reveals
|
|
8
|
+
// the bar only when `reveal` is set AND MORE THAN ONE page is visible. The one-page case
|
|
9
|
+
// is asserted here too. The full derivation is driven from the shared conformance
|
|
10
|
+
// vectors in `view-switcher.spec.ts`.
|
|
15
11
|
import { describe, expect, it } from '@gjsify/unit';
|
|
16
12
|
|
|
17
13
|
import type { AdwViewStack } from './elements/adw-view-stack.js';
|
|
@@ -66,7 +62,7 @@ export const AdwViewSwitcherBarTest = async () => {
|
|
|
66
62
|
});
|
|
67
63
|
|
|
68
64
|
await it('stays collapsed for a ONE-page stack, however loudly the layout asks', () => {
|
|
69
|
-
// `count > 1
|
|
65
|
+
// `count > 1`. The old bar showed an
|
|
70
66
|
// empty strip here because it never consulted the page count.
|
|
71
67
|
const { bar, host } = mountBar(1);
|
|
72
68
|
bar.reveal = true;
|
package/src/adw-wrap-box.spec.ts
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
// DOM-level conformance tests for <adw-wrap-box>, driven by the SAME tables the
|
|
2
2
|
// NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
// `
|
|
6
|
-
//
|
|
4
|
+
// The portable question is where a line's leftover space goes, and three `Adw.WrapBox`
|
|
5
|
+
// rules answer it: `align` is a MAIN-axis offset of the whole line block, not
|
|
6
|
+
// `align-items` on the cross axis (writing it there pushes children DOWN); `fill` grows
|
|
7
|
+
// the CHILDREN where `spread` grows the GAPS, so they are not both `space-between`; and
|
|
8
|
+
// `justify-last-line` governs the final line, whose C default is NOT justified.
|
|
7
9
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// (adw-wrap-layout.c:317-341);
|
|
14
|
-
// - `justify-last-line` was observed and never read, so the final line was
|
|
15
|
-
// always justified — the opposite of the C default (adw-wrap-box.c:379-381).
|
|
16
|
-
//
|
|
17
|
-
// The element publishes its resolved decision as `data-line-justify` (what
|
|
18
|
-
// governs a complete line) and `data-last-line-justify` (what governs the final
|
|
19
|
-
// one), which is both what the stylesheet keys off and what these vectors read:
|
|
20
|
-
// the rendered `justify-content` / `flex-grow` are asserted alongside, so a
|
|
21
|
-
// correct data attribute with a stylesheet that ignores it still fails.
|
|
10
|
+
// The element publishes its resolved decision as `data-line-justify` (complete lines)
|
|
11
|
+
// and `data-last-line-justify` (the final one) — what the stylesheet keys off and what
|
|
12
|
+
// these vectors read. The rendered `justify-content` / `flex-grow` are asserted
|
|
13
|
+
// alongside, so a correct data attribute with a stylesheet that ignores it still
|
|
14
|
+
// fails.
|
|
22
15
|
|
|
23
16
|
import { describe, expect, it } from '@gjsify/unit';
|
|
24
17
|
|
|
@@ -61,10 +54,9 @@ function label(value: unknown): string {
|
|
|
61
54
|
/**
|
|
62
55
|
* `justify-content` for an un-justified line at `align`.
|
|
63
56
|
*
|
|
64
|
-
* C offsets the line block by `roundf (length_delta * align)`, a continuum
|
|
65
|
-
* flexbox has three positions
|
|
66
|
-
*
|
|
67
|
-
* renderer's, not libadwaita's.
|
|
57
|
+
* C offsets the line block by `roundf (length_delta * align)`, a continuum, where
|
|
58
|
+
* flexbox has three positions — so the port takes the nearest. The approximation lives
|
|
59
|
+
* here rather than in the vectors because it is the browser renderer's, not libadwaita's.
|
|
68
60
|
*/
|
|
69
61
|
function expectedJustifyContent(align: number): string {
|
|
70
62
|
if (align < 0.25) return 'flex-start';
|
|
@@ -82,10 +74,9 @@ export const AdwWrapBoxTest = async () => {
|
|
|
82
74
|
await it(`${title}: ${vector.rule}`, () => {
|
|
83
75
|
const attrs: Record<string, string> = { justify: vector.justify, align: String(vector.align) };
|
|
84
76
|
if (vector.justifyLastLine) attrs['justify-last-line'] = '';
|
|
85
|
-
// A box with ONE child has exactly one line
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
// rule at all.
|
|
77
|
+
// A box with ONE child has exactly one line and it is the final one — the
|
|
78
|
+
// only line membership CSS can know without a layout pass, and why the
|
|
79
|
+
// stylesheet has an `:only-child` rule.
|
|
89
80
|
const childCount = vector.childrenInLine === 1 ? 1 : 3;
|
|
90
81
|
const { box, children } = mountWrapBox(attrs, childCount);
|
|
91
82
|
|
|
@@ -93,9 +84,8 @@ export const AdwWrapBoxTest = async () => {
|
|
|
93
84
|
expect(resolved).toBe(vector.layout.justify);
|
|
94
85
|
|
|
95
86
|
if (!vector.lastLine && vector.childrenInLine > 1) {
|
|
96
|
-
// `justify-content` is the container knob
|
|
97
|
-
//
|
|
98
|
-
// COMPLETE-line rule.
|
|
87
|
+
// `justify-content` is the container knob and a flex container applies
|
|
88
|
+
// it to every line, so it carries the COMPLETE-line rule.
|
|
99
89
|
expect(getComputedStyle(box).justifyContent).toBe(
|
|
100
90
|
vector.layout.growGaps ? 'space-between' : expectedJustifyContent(vector.layout.align),
|
|
101
91
|
);
|
|
@@ -114,7 +104,7 @@ export const AdwWrapBoxTest = async () => {
|
|
|
114
104
|
const style = getComputedStyle(box);
|
|
115
105
|
expect(style.justifyContent).toBe('flex-end');
|
|
116
106
|
// The cross axis is libadwaita's `h = line_size` — every child takes
|
|
117
|
-
// the full line extent
|
|
107
|
+
// the full line extent.
|
|
118
108
|
expect(style.alignItems).toBe('stretch');
|
|
119
109
|
unmountAll();
|
|
120
110
|
});
|
|
@@ -134,9 +124,8 @@ export const AdwWrapBoxTest = async () => {
|
|
|
134
124
|
|
|
135
125
|
await it('a spread box whose last line is not justified gets the filler that packs it', () => {
|
|
136
126
|
const { box } = mountWrapBox({ justify: 'spread' });
|
|
137
|
-
// The only per-line hook flexbox offers: a generated flex item on the
|
|
138
|
-
//
|
|
139
|
-
// the way an un-justified line does.
|
|
127
|
+
// The only per-line hook flexbox offers: a generated flex item on the final
|
|
128
|
+
// line absorbs its leftover, so the line packs at the start.
|
|
140
129
|
expect(getComputedStyle(box, '::after').flexGrow).toBe('1');
|
|
141
130
|
unmountAll();
|
|
142
131
|
|
|
@@ -216,8 +205,8 @@ export const AdwWrapBoxTest = async () => {
|
|
|
216
205
|
});
|
|
217
206
|
|
|
218
207
|
for (const { value, unit, dpi, px, rule } of WRAP_BOX_LENGTH_VECTORS) {
|
|
219
|
-
// The element resolves at the default dpi; the scaled rows are the
|
|
220
|
-
//
|
|
208
|
+
// The element resolves at the default dpi; the scaled rows are the core
|
|
209
|
+
// suite's, since a browser has no `gtk-xft-dpi` to report.
|
|
221
210
|
if (dpi !== 96 || value < 0) continue;
|
|
222
211
|
await it(`child-spacing ${value}${String(unit)} → ${px}px: ${rule}`, () => {
|
|
223
212
|
const attrs: Record<string, string> = { 'child-spacing': String(value) };
|
|
@@ -314,8 +303,8 @@ export const AdwWrapBoxTest = async () => {
|
|
|
314
303
|
return view;
|
|
315
304
|
};
|
|
316
305
|
for (const id of children) box.appendChild(make(id));
|
|
317
|
-
// The child
|
|
318
|
-
//
|
|
306
|
+
// The child placed may be one of the mounted ones (a reorder, or the
|
|
307
|
+
// "already parented" refusal) or a fresh one.
|
|
319
308
|
const target = byId.get(child) ?? make(child);
|
|
320
309
|
// A sibling the box does not have still has to be a real element.
|
|
321
310
|
const after = sibling === null ? null : (byId.get(sibling) ?? make(sibling));
|
package/src/bottom-sheet.spec.ts
CHANGED
|
@@ -2,18 +2,15 @@
|
|
|
2
2
|
// the core suite and the NativeScript renderer assert against
|
|
3
3
|
// (`@gjsify/adwaita-core/conformance`).
|
|
4
4
|
//
|
|
5
|
-
// `Adw.BottomSheet` has four dismissal paths and they are four
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// already-closed sheet did nothing (libadwaita forwards it to the parent).
|
|
11
|
-
// Nothing failed, because nothing compared the element to the C source.
|
|
5
|
+
// `Adw.BottomSheet` has four dismissal paths and they are four DIFFERENT gates, so one
|
|
6
|
+
// shared `_attemptClose()` cannot express them: the drag handle is `can_target = FALSE`
|
|
7
|
+
// and closes nothing; Escape on a closed-but-focused sheet still signals
|
|
8
|
+
// `close-attempt`; and the `sheet.close` action on an already-closed sheet forwards to
|
|
9
|
+
// the parent.
|
|
12
10
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// `notify::open` carrying an unchanged payload.
|
|
11
|
+
// The open STATE must be HELD, not read back off the attribute — reading it made
|
|
12
|
+
// `open="false"` render OPEN and let a re-spelt attribute fire a `notify::open` with an
|
|
13
|
+
// unchanged payload.
|
|
17
14
|
import { describe, expect, it } from '@gjsify/unit';
|
|
18
15
|
|
|
19
16
|
import type { AdwBottomSheet } from './elements/adw-bottom-sheet.js';
|
|
@@ -75,8 +72,8 @@ export const AdwBottomSheetTest = async () => {
|
|
|
75
72
|
});
|
|
76
73
|
|
|
77
74
|
expect(returned).toBe(outcome);
|
|
78
|
-
// The verdict must be VISIBLE, not just returned:
|
|
79
|
-
//
|
|
75
|
+
// The verdict must be VISIBLE, not just returned: returning the right word
|
|
76
|
+
// and closing anyway is still wrong.
|
|
80
77
|
expect(sheet.open).toBe(outcome === 'close' ? false : open);
|
|
81
78
|
expect(attempts).toBe(outcome === 'close-attempt' ? 1 : 0);
|
|
82
79
|
expect(delegated).toBe(outcome === 'delegate' ? 1 : 0);
|
|
@@ -120,9 +117,9 @@ export const AdwBottomSheetTest = async () => {
|
|
|
120
117
|
|
|
121
118
|
await describe('adw-bottom-sheet open attribute semantics', async () => {
|
|
122
119
|
await it('open="false" renders CLOSED', () => {
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
// adw-bottom-sheet.c
|
|
120
|
+
// `hasAttribute('open')` for the getter while every other boolean uses
|
|
121
|
+
// `!== 'false'` gives one element two conventions.
|
|
122
|
+
// adw-bottom-sheet.c — `open = !!open` on a strict gboolean.
|
|
126
123
|
const sheet = document.createElement('adw-bottom-sheet') as AdwBottomSheet;
|
|
127
124
|
sheet.setAttribute('open', 'false');
|
|
128
125
|
document.body.appendChild(sheet);
|
|
@@ -141,10 +138,9 @@ export const AdwBottomSheetTest = async () => {
|
|
|
141
138
|
});
|
|
142
139
|
|
|
143
140
|
await it('re-spelling the attribute fires no spurious notify::open', () => {
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// adw-bottom-sheet.c:1672-1682 returns before notifying.
|
|
141
|
+
// Guarding only on `oldValue === newValue` lets `open=""` → `open="open"` fire
|
|
142
|
+
// a second notification carrying an UNCHANGED payload.
|
|
143
|
+
// adw-bottom-sheet.c returns before notifying.
|
|
148
144
|
const sheet = mountSheet();
|
|
149
145
|
const payloads: boolean[] = [];
|
|
150
146
|
sheet.addEventListener('notify::open', (event) => {
|
|
@@ -170,9 +166,8 @@ export const AdwBottomSheetTest = async () => {
|
|
|
170
166
|
|
|
171
167
|
await describe('adw-bottom-sheet affordances', async () => {
|
|
172
168
|
await it('the drag handle is decorative — not focusable, not a button, closes nothing', () => {
|
|
173
|
-
// gtk_widget_set_can_focus/can_target (self->drag_handle, FALSE)
|
|
174
|
-
//
|
|
175
|
-
// role="button", tabindex=0 and a click-to-close handler.
|
|
169
|
+
// `gtk_widget_set_can_focus/can_target (self->drag_handle, FALSE)` — so no
|
|
170
|
+
// role="button", no tabindex=0 and no click-to-close handler.
|
|
176
171
|
const sheet = mountSheet();
|
|
177
172
|
sheet.open = true;
|
|
178
173
|
const handle = dragHandle(sheet);
|
|
@@ -207,8 +202,8 @@ export const AdwBottomSheetTest = async () => {
|
|
|
207
202
|
});
|
|
208
203
|
|
|
209
204
|
await it('a dimming click on a CLOSED sheet does nothing and signals nothing', () => {
|
|
210
|
-
// The scrim is `can_target = open
|
|
211
|
-
//
|
|
205
|
+
// The scrim is `can_target = open`, so it is not reachable at all — unlike
|
|
206
|
+
// Escape, it does not signal.
|
|
212
207
|
const sheet = mountSheet();
|
|
213
208
|
sheet.canClose = false;
|
|
214
209
|
const { attempts } = recordSignals(sheet, () => {
|
|
@@ -228,10 +223,9 @@ export const AdwBottomSheetTest = async () => {
|
|
|
228
223
|
});
|
|
229
224
|
|
|
230
225
|
await it('Escape on a CLOSED but focused sheet still raises close-attempt', () => {
|
|
231
|
-
// maybe_close_cb's emit is the fallthrough for every case that is
|
|
232
|
-
// (can_close && open)
|
|
233
|
-
//
|
|
234
|
-
// `_attemptClose`), so the corner was unreachable.
|
|
226
|
+
// `maybe_close_cb`'s emit is the fallthrough for every case that is NOT
|
|
227
|
+
// (can_close && open); guarding it away in the key handler and in
|
|
228
|
+
// `_attemptClose` makes the corner unreachable.
|
|
235
229
|
const sheet = mountSheet();
|
|
236
230
|
const { attempts } = recordSignals(sheet, () => {
|
|
237
231
|
insideSheet(sheet).dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
@@ -254,9 +248,9 @@ export const AdwBottomSheetTest = async () => {
|
|
|
254
248
|
});
|
|
255
249
|
|
|
256
250
|
await it('sheet.close on an already-closed sheet delegates upward', () => {
|
|
257
|
-
// sheet_close_cb activates the PARENT's `sheet.close` action
|
|
258
|
-
//
|
|
259
|
-
//
|
|
251
|
+
// `sheet_close_cb` activates the PARENT's `sheet.close` action rather than
|
|
252
|
+
// doing nothing; a bubbling event is the DOM equivalent, so an enclosing
|
|
253
|
+
// sheet or dialog can act on it.
|
|
260
254
|
const sheet = mountSheet();
|
|
261
255
|
const host = sheet.parentElement as HTMLElement;
|
|
262
256
|
let seenOnAncestor = 0;
|
|
@@ -278,10 +272,10 @@ export const AdwBottomSheetTest = async () => {
|
|
|
278
272
|
});
|
|
279
273
|
|
|
280
274
|
await it('consumes the slot wrappers instead of leaving them in the content', () => {
|
|
281
|
-
// `<child type="sheet">` is GtkBuilder markup
|
|
282
|
-
//
|
|
283
|
-
// <adw-bottom-sheet-
|
|
284
|
-
//
|
|
275
|
+
// `<child type="sheet">` is GtkBuilder markup and nothing of it survives into
|
|
276
|
+
// the widget tree, so the emptied <adw-bottom-sheet-sheet> /
|
|
277
|
+
// <adw-bottom-sheet-content> wrappers must not fall through to `unslotted` and
|
|
278
|
+
// land in the content layer.
|
|
285
279
|
const sheet = mountSheet();
|
|
286
280
|
const content = sheet.querySelector('.adw-bottom-sheet-content') as HTMLElement;
|
|
287
281
|
expect(Array.from(content.children).map((el) => el.tagName.toLowerCase())).toStrictEqual(['button']);
|
package/src/breakpoints.ts
CHANGED
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
// `ResizeObserver` — which this module supplies under the same name, so the two
|
|
9
9
|
// ports read alike.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// on GTK and on NativeScript stayed frozen in one layout in the browser.
|
|
11
|
+
// Without a size source `<adw-navigation-split-view collapsed>` is a manual attribute the
|
|
12
|
+
// application has to flip itself, and markup that adapts on GTK and on NativeScript stays
|
|
13
|
+
// frozen in one layout in the browser.
|
|
15
14
|
//
|
|
16
15
|
// Reference: refs/libadwaita/src/adw-breakpoint.c
|
|
17
16
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
@@ -21,19 +20,17 @@ import { AdwBreakpoint } from '@gjsify/adwaita-core';
|
|
|
21
20
|
/**
|
|
22
21
|
* Drive `breakpoints` from `element`'s own box, and return a dispose function.
|
|
23
22
|
*
|
|
24
|
-
* `ResizeObserver` delivers an initial observation on `observe()`, so the
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* The size read is the BORDER box in CSS pixels, the browser's counterpart to
|
|
29
|
-
* the `sp`/DIP units an Adwaita condition is written in.
|
|
23
|
+
* `ResizeObserver` delivers an initial observation on `observe()`, so the breakpoints
|
|
24
|
+
* settle on the correct state before first paint — no flash of the wrong layout, no
|
|
25
|
+
* separate seeding pass. The size read is the BORDER box in CSS pixels, the browser's
|
|
26
|
+
* counterpart to the `sp`/DIP units an Adwaita condition is written in.
|
|
30
27
|
*/
|
|
31
28
|
export function addBreakpoints(element: Element, breakpoints: readonly AdwBreakpoint[]): () => void {
|
|
32
29
|
const observer = new ResizeObserver((entries) => {
|
|
33
30
|
const entry = entries[entries.length - 1];
|
|
34
31
|
if (!entry) return;
|
|
35
|
-
// borderBoxSize is the spec'd path; contentRect is the fallback for the
|
|
36
|
-
//
|
|
32
|
+
// borderBoxSize is the spec'd path; contentRect is the fallback for the shape older
|
|
33
|
+
// engines report.
|
|
37
34
|
const box = entry.borderBoxSize?.[0];
|
|
38
35
|
const size = box
|
|
39
36
|
? { width: box.inlineSize, height: box.blockSize }
|
|
@@ -45,13 +42,13 @@ export function addBreakpoints(element: Element, breakpoints: readonly AdwBreakp
|
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
/**
|
|
48
|
-
* Wire a `breakpoint="<condition>"` attribute to a boolean setter, the browser
|
|
49
|
-
*
|
|
45
|
+
* Wire a `breakpoint="<condition>"` attribute to a boolean setter, the browser equivalent
|
|
46
|
+
* of an `Adw.Breakpoint` with one `add_setter()` call. Returns a dispose function; call it
|
|
47
|
+
* before re-binding and on disconnect.
|
|
50
48
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* flip state because its condition had a typo.
|
|
49
|
+
* A missing or unparsable condition binds nothing and returns a no-op, leaving whatever
|
|
50
|
+
* the attribute already says untouched — an element must not silently flip state because
|
|
51
|
+
* its condition had a typo.
|
|
55
52
|
*/
|
|
56
53
|
export function bindBreakpointSetter(
|
|
57
54
|
element: Element,
|