@gjsify/adwaita-web 0.44.0 → 0.45.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/lib/types/elements/adw-header-bar.d.ts +16 -8
- package/package.json +9 -6
- package/src/adw-header-bar.spec.ts +9 -4
- package/src/adw-tab-view.spec.ts +71 -0
- package/src/elements/adw-header-bar.ts +34 -13
- package/src/elements/adw-navigation-view.ts +23 -3
- package/src/elements/adw-tab-view.ts +25 -3
|
@@ -6,8 +6,10 @@ export declare class AdwHeaderBar extends HTMLElement {
|
|
|
6
6
|
private _endEl;
|
|
7
7
|
/**
|
|
8
8
|
* The derived `<adw-window-title>`, or `null` when a `slot="center"`
|
|
9
|
-
* title-widget took the centre. `Adw.HeaderBar` has the same either/or
|
|
10
|
-
*
|
|
9
|
+
* title-widget took the centre. `Adw.HeaderBar` has the same either/or —
|
|
10
|
+
* `adw_header_bar_set_title_widget` empties the centre bin before installing
|
|
11
|
+
* (adw-header-bar.c:1201) — over a derived `GtkLabel`, not an `AdwWindowTitle`;
|
|
12
|
+
* see the divergence at the top of this file.
|
|
11
13
|
*/
|
|
12
14
|
private _titleEl;
|
|
13
15
|
static get observedAttributes(): string[];
|
|
@@ -22,15 +24,21 @@ export declare class AdwHeaderBar extends HTMLElement {
|
|
|
22
24
|
/**
|
|
23
25
|
* `title` used to be read ONCE, in `connectedCallback`, so every later write
|
|
24
26
|
* was a silent no-op — a header bar whose title tracked the open document
|
|
25
|
-
* kept whatever it was created with. `Adw.HeaderBar`
|
|
26
|
-
*
|
|
27
|
+
* kept whatever it was created with. `Adw.HeaderBar` never reads its title once
|
|
28
|
+
* either: it has no `title` property at all, and re-runs `update_title`
|
|
29
|
+
* (adw-header-bar.c:475) on every ancestor change that could move the answer.
|
|
27
30
|
*/
|
|
28
31
|
attributeChangedCallback(): void;
|
|
29
32
|
/**
|
|
30
|
-
* Give the centre up to a title-widget. `adw_header_bar_set_title_widget`
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* produce.
|
|
33
|
+
* Give the centre up to a title-widget. `adw_header_bar_set_title_widget` empties the
|
|
34
|
+
* centre bin and drops its derived label (adw-header-bar.c:1201, :1209) rather than
|
|
35
|
+
* stacking the two, so the late case has to remove ours too — a bar showing both would
|
|
36
|
+
* be a shape neither GTK nor the declared markup can produce.
|
|
37
|
+
*
|
|
38
|
+
* NOT SYMMETRIC HERE, AND KNOWN: C rebuilds the label the moment the title-widget goes
|
|
39
|
+
* away (:1211); nothing in this element brings the derived centre back, so a bar that
|
|
40
|
+
* gave it up has given it up for good. `HeaderBarState` in `@gjsify/adwaita-core` holds
|
|
41
|
+
* the rebuild, and this element adopting it is the rewiring tracked in #1343.
|
|
34
42
|
*/
|
|
35
43
|
private _dropDerivedTitle;
|
|
36
44
|
private _renderTitle;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/adwaita-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Adwaita/Libadwaita web components for browser targets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
"@codemirror/language": "^6.12.4",
|
|
46
46
|
"@codemirror/state": "^6.7.0",
|
|
47
47
|
"@codemirror/view": "^6.43.4",
|
|
48
|
-
"@gjsify/adwaita-core": "^0.
|
|
49
|
-
"@gjsify/adwaita-fonts": "^0.
|
|
50
|
-
"@gjsify/adwaita-icons": "^0.
|
|
48
|
+
"@gjsify/adwaita-core": "^0.45.0",
|
|
49
|
+
"@gjsify/adwaita-fonts": "^0.45.0",
|
|
50
|
+
"@gjsify/adwaita-icons": "^0.45.0",
|
|
51
51
|
"@lezer/highlight": "^1.2.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@gjsify/cli": "^0.
|
|
55
|
-
"@gjsify/unit": "^0.
|
|
54
|
+
"@gjsify/cli": "^0.45.0",
|
|
55
|
+
"@gjsify/unit": "^0.45.0",
|
|
56
56
|
"sass": "^1.101.0",
|
|
57
57
|
"typescript": "^6.0.3"
|
|
58
58
|
},
|
|
@@ -75,6 +75,9 @@
|
|
|
75
75
|
"navigator",
|
|
76
76
|
"process"
|
|
77
77
|
]
|
|
78
|
+
},
|
|
79
|
+
"widgetVocabulary": {
|
|
80
|
+
"role": "renderer"
|
|
78
81
|
}
|
|
79
82
|
},
|
|
80
83
|
"license": "MIT",
|
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
// the open document keeps its construction-time title otherwise, and nothing notices
|
|
5
5
|
// because the attribute is still there and A title still renders.
|
|
6
6
|
//
|
|
7
|
-
// `
|
|
8
|
-
// `
|
|
9
|
-
//
|
|
7
|
+
// The either/or the `slot="center"` case below asserts IS `Adw.HeaderBar`'s: setting
|
|
8
|
+
// `title-widget` empties the centre bin (adw-header-bar.c:1201), and clearing it builds the
|
|
9
|
+
// derived centre again (:1211). What C builds there is a plain `gtk_label_new (NULL)`
|
|
10
|
+
// (`construct_title_label`, :512) with no subtitle, and no `title` property behind it —
|
|
11
|
+
// `update_title` (:475) resolves one from the ancestry. The `<adw-window-title>` centre and
|
|
12
|
+
// the title/subtitle PAIR are this port's DIVERGENCE, named in `elements/adw-header-bar.ts`
|
|
13
|
+
// and recorded as `HeaderBarRenderState.derivedSubtitle` in `@gjsify/adwaita-core`.
|
|
10
14
|
|
|
11
15
|
import { describe, expect, it } from '@gjsify/unit';
|
|
12
16
|
|
|
@@ -122,7 +126,8 @@ export const AdwHeaderBarTest = async () => {
|
|
|
122
126
|
});
|
|
123
127
|
});
|
|
124
128
|
|
|
125
|
-
// The centre is
|
|
129
|
+
// The centre is an `<adw-window-title>` — this port's divergence, not the bare
|
|
130
|
+
// `GtkLabel` `construct_title_label` builds (adw-header-bar.c:512) — so the three
|
|
126
131
|
// rules that element holds through @gjsify/adwaita-core reach the header bar too.
|
|
127
132
|
// None of them did while the centre was a bare span.
|
|
128
133
|
await describe('<adw-header-bar> derives an <adw-window-title>', async () => {
|
package/src/adw-tab-view.spec.ts
CHANGED
|
@@ -75,6 +75,77 @@ export const AdwTabViewTest = async () => {
|
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
// ONE HALF of the upgrade-order defect; the other half is the define order at the
|
|
79
|
+
// foot of `elements/adw-tab-view.ts`, which no test in this realm can observe —
|
|
80
|
+
// both names are already registered by the time a spec runs, and `define` is what
|
|
81
|
+
// upgrades. What IS reproducible here is the STATE that order produced: a real
|
|
82
|
+
// `<adw-tab-page>` next to an `<adw-tab-view>` that is still an ordinary
|
|
83
|
+
// HTMLElement. `document.implementation.createHTMLDocument()` has no browsing
|
|
84
|
+
// context, so nothing it creates is ever upgraded; adopting the element carries it
|
|
85
|
+
// into this document without upgrading it, which INSERTING it would do.
|
|
86
|
+
//
|
|
87
|
+
// Constructing both with `document.createElement` instead would pass either way —
|
|
88
|
+
// both are defined, so both come back upgraded and the ancestor always has the
|
|
89
|
+
// method. The parse-then-define order the site hits is covered in
|
|
90
|
+
// `tests/browser/specs/adwaita-upgrade-order.spec.ts`.
|
|
91
|
+
await describe('adw-tab-page beside an un-upgraded view', async () => {
|
|
92
|
+
await it('reports nothing when the view has none of its methods yet', async () => {
|
|
93
|
+
const view = document.adoptNode(
|
|
94
|
+
document.implementation.createHTMLDocument('').createElement('adw-tab-view'),
|
|
95
|
+
);
|
|
96
|
+
expect('syncDeclaredPage' in view).toBe(false);
|
|
97
|
+
const page = document.createElement('adw-tab-page');
|
|
98
|
+
view.appendChild(page);
|
|
99
|
+
|
|
100
|
+
// A custom-element reaction never throws INTO its caller: the exception is
|
|
101
|
+
// REPORTED, so `setAttribute` returns normally whether or not the callback
|
|
102
|
+
// blew up and only a listener sees the difference. `preventDefault` keeps a
|
|
103
|
+
// red run from also logging it at the console.
|
|
104
|
+
const reported: string[] = [];
|
|
105
|
+
const onError = (event: ErrorEvent) => {
|
|
106
|
+
reported.push(event.message);
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
};
|
|
109
|
+
window.addEventListener('error', onError);
|
|
110
|
+
page.setAttribute('title', 'One');
|
|
111
|
+
window.removeEventListener('error', onError);
|
|
112
|
+
|
|
113
|
+
expect(reported).toStrictEqual([]);
|
|
114
|
+
expect(page.getAttribute('title')).toBe('One');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await it('leaves a page appended after connect unadopted rather than throwing', async () => {
|
|
118
|
+
// The imperative surface is `appendPage`; a bare DOM append is out of
|
|
119
|
+
// contract, and the point here is only that it fails QUIETLY — the page
|
|
120
|
+
// carries no `data-page-id`, so `syncDeclaredPage` has no record to touch.
|
|
121
|
+
//
|
|
122
|
+
// "rather than throwing" is ASSERTED and not assumed. Without the listener
|
|
123
|
+
// this test measured only the two counts, and both hold whether or not the
|
|
124
|
+
// callback blew up: a custom-element reaction is REPORTED, never rethrown
|
|
125
|
+
// into `setAttribute`, so the model is untouched either way. Measured — with
|
|
126
|
+
// `syncDeclaredPage` mutated to throw on exactly this path, the whole
|
|
127
|
+
// in-bundle suite of 4631 stayed green and only an unrelated spec's global
|
|
128
|
+
// error sweep noticed.
|
|
129
|
+
const view = makeTabView();
|
|
130
|
+
const late = document.createElement('adw-tab-page');
|
|
131
|
+
view.appendChild(late);
|
|
132
|
+
|
|
133
|
+
const reported: string[] = [];
|
|
134
|
+
const onError = (event: ErrorEvent) => {
|
|
135
|
+
reported.push(event.message);
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
};
|
|
138
|
+
window.addEventListener('error', onError);
|
|
139
|
+
late.setAttribute('title', 'Late');
|
|
140
|
+
window.removeEventListener('error', onError);
|
|
141
|
+
|
|
142
|
+
expect(reported).toStrictEqual([]);
|
|
143
|
+
expect(view.nPages).toBe(3);
|
|
144
|
+
expect(chips(view).length).toBe(3);
|
|
145
|
+
view.parentElement?.remove();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
78
149
|
await describe('adw-tab-view expand-tabs / no-close', async () => {
|
|
79
150
|
await it('no-close hides every close affordance', async () => {
|
|
80
151
|
const view = makeTabView('no-close');
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
// <adw-header-bar> — Adwaita header bar with centered title and start/end button slots.
|
|
2
2
|
//
|
|
3
|
-
// The derived centre is an `<adw-window-title
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// The derived centre is an `<adw-window-title>`. That is a DIVERGENCE, and the comment
|
|
4
|
+
// here used to claim the opposite — that `Adw.HeaderBar` puts one there. Measured
|
|
5
|
+
// against the source: `construct_title_label` (adw-header-bar.c:512) builds a plain
|
|
6
|
+
// `gtk_label_new (NULL)` with the `title` class, `single-line-mode` and
|
|
7
|
+
// `ellipsize=END`, and `Adw.HeaderBar` has NO `title` property at all — `update_title`
|
|
8
|
+
// (:475) resolves one from the navigation page, then the dialog, then the root window,
|
|
9
|
+
// then `g_get_application_name`, then `g_get_prgname`. An app that wants a subtitle
|
|
10
|
+
// there sets an `AdwWindowTitle` as its title-widget.
|
|
11
|
+
//
|
|
12
|
+
// The divergence is kept: a declarative surface wants `title=` / `subtitle=` attributes
|
|
13
|
+
// rather than a widget handoff, and both renderers already use them. It is recorded as
|
|
14
|
+
// `HeaderBarRenderState.derivedSubtitle` in `@gjsify/adwaita-core`, so it is a named
|
|
15
|
+
// difference instead of a mistaken claim of fidelity.
|
|
16
|
+
//
|
|
17
|
+
// It used to be a bare span with
|
|
6
18
|
// `textContent = title ?? ''`, and that span carried none of the three rules the
|
|
7
19
|
// window title already held in `@gjsify/adwaita-core`: an EMPTY title still
|
|
8
20
|
// reserved a blank line, re-setting the same value repainted, and there was no
|
|
@@ -29,8 +41,10 @@ export class AdwHeaderBar extends HTMLElement {
|
|
|
29
41
|
private _endEl: HTMLDivElement | null = null;
|
|
30
42
|
/**
|
|
31
43
|
* The derived `<adw-window-title>`, or `null` when a `slot="center"`
|
|
32
|
-
* title-widget took the centre. `Adw.HeaderBar` has the same either/or
|
|
33
|
-
*
|
|
44
|
+
* title-widget took the centre. `Adw.HeaderBar` has the same either/or —
|
|
45
|
+
* `adw_header_bar_set_title_widget` empties the centre bin before installing
|
|
46
|
+
* (adw-header-bar.c:1201) — over a derived `GtkLabel`, not an `AdwWindowTitle`;
|
|
47
|
+
* see the divergence at the top of this file.
|
|
34
48
|
*/
|
|
35
49
|
private _titleEl: HTMLElement | null = null;
|
|
36
50
|
|
|
@@ -86,8 +100,9 @@ export class AdwHeaderBar extends HTMLElement {
|
|
|
86
100
|
).install(this._startEl, this._centerEl, this._endEl);
|
|
87
101
|
|
|
88
102
|
// Derived only when the centre is still free — the same either/or as
|
|
89
|
-
// `Adw.HeaderBar`, which
|
|
90
|
-
//
|
|
103
|
+
// `Adw.HeaderBar`, which runs `construct_title_label` exactly while no
|
|
104
|
+
// title-widget holds the centre (adw-header-bar.c:1211). What it builds there is
|
|
105
|
+
// a `GtkLabel`; this is the documented `<adw-window-title>` divergence.
|
|
91
106
|
if (this._centerEl.childElementCount === 0) {
|
|
92
107
|
this._titleEl = document.createElement('adw-window-title');
|
|
93
108
|
this._titleEl.className = 'adw-header-bar-title';
|
|
@@ -99,18 +114,24 @@ export class AdwHeaderBar extends HTMLElement {
|
|
|
99
114
|
/**
|
|
100
115
|
* `title` used to be read ONCE, in `connectedCallback`, so every later write
|
|
101
116
|
* was a silent no-op — a header bar whose title tracked the open document
|
|
102
|
-
* kept whatever it was created with. `Adw.HeaderBar`
|
|
103
|
-
*
|
|
117
|
+
* kept whatever it was created with. `Adw.HeaderBar` never reads its title once
|
|
118
|
+
* either: it has no `title` property at all, and re-runs `update_title`
|
|
119
|
+
* (adw-header-bar.c:475) on every ancestor change that could move the answer.
|
|
104
120
|
*/
|
|
105
121
|
attributeChangedCallback() {
|
|
106
122
|
if (this._initialized) this._renderTitle();
|
|
107
123
|
}
|
|
108
124
|
|
|
109
125
|
/**
|
|
110
|
-
* Give the centre up to a title-widget. `adw_header_bar_set_title_widget`
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* produce.
|
|
126
|
+
* Give the centre up to a title-widget. `adw_header_bar_set_title_widget` empties the
|
|
127
|
+
* centre bin and drops its derived label (adw-header-bar.c:1201, :1209) rather than
|
|
128
|
+
* stacking the two, so the late case has to remove ours too — a bar showing both would
|
|
129
|
+
* be a shape neither GTK nor the declared markup can produce.
|
|
130
|
+
*
|
|
131
|
+
* NOT SYMMETRIC HERE, AND KNOWN: C rebuilds the label the moment the title-widget goes
|
|
132
|
+
* away (:1211); nothing in this element brings the derived centre back, so a bar that
|
|
133
|
+
* gave it up has given it up for good. `HeaderBarState` in `@gjsify/adwaita-core` holds
|
|
134
|
+
* the rebuild, and this element adopting it is the rewiring tracked in #1343.
|
|
114
135
|
*/
|
|
115
136
|
private _dropDerivedTitle() {
|
|
116
137
|
this._titleEl?.remove();
|
|
@@ -100,8 +100,20 @@ export class AdwNavigationPage extends HTMLElement {
|
|
|
100
100
|
// back-button derivation read the CORE's copy — so a runtime change has to
|
|
101
101
|
// be pushed into it. Without this the back button only ever reflected the
|
|
102
102
|
// `can-pop` value it had at the last stack mutation.
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
//
|
|
104
|
+
// `instanceof` rather than a cast plus `?.`: the two failures the `?.`
|
|
105
|
+
// conflated are different. A MISSING ancestor is one; an ancestor that IS
|
|
106
|
+
// there but is still an ordinary HTMLElement, because its own definition has
|
|
107
|
+
// not upgraded it yet, is the other, and the cast asserted it away. Measured
|
|
108
|
+
// on `dist/test.browser.mjs` against declared markup parsed before the module
|
|
109
|
+
// loaded: `syncPageProperty is not a function`, once per observed attribute
|
|
110
|
+
// per declared page. Dropping the notification for an un-upgraded view loses
|
|
111
|
+
// nothing — such a view has not registered this page, `syncPageProperty`
|
|
112
|
+
// already returns on `!isRegistered`, and `connectedCallback` reads the
|
|
113
|
+
// properties back out of the element through `readPageProps`.
|
|
114
|
+
const view = this.closest('adw-navigation-view');
|
|
115
|
+
if (!(view instanceof AdwNavigationView)) return;
|
|
116
|
+
view.syncPageProperty(this, name, previous);
|
|
105
117
|
}
|
|
106
118
|
|
|
107
119
|
revertAttribute(name: string, value: string | null): void {
|
|
@@ -464,5 +476,13 @@ export class AdwNavigationView extends HTMLElement {
|
|
|
464
476
|
}
|
|
465
477
|
}
|
|
466
478
|
|
|
467
|
-
|
|
479
|
+
// The VIEW first, and the order carries weight: `define` upgrades every matching
|
|
480
|
+
// element already in the document, immediately, so these two calls are a sequence and
|
|
481
|
+
// not a pair. Registering the page first upgraded every declared
|
|
482
|
+
// `<adw-navigation-page>` while its `<adw-navigation-view>` parent was still an
|
|
483
|
+
// ordinary HTMLElement, and `AdwNavigationPage.attributeChangedCallback` reaches for
|
|
484
|
+
// that parent. Parent before child keeps the window shut; the `instanceof` guard in
|
|
485
|
+
// the callback is what holds when something outside this file reopens it.
|
|
486
|
+
// `scripts/check-adwaita-upgrade-order.mjs` holds the order itself.
|
|
468
487
|
customElements.define('adw-navigation-view', AdwNavigationView);
|
|
488
|
+
customElements.define('adw-navigation-page', AdwNavigationPage);
|
|
@@ -74,8 +74,21 @@ export class AdwTabPage extends HTMLElement {
|
|
|
74
74
|
attributeChangedCallback(name: string, _old: string | null, value: string | null) {
|
|
75
75
|
// `closest` still finds the view after adoption: the element is moved
|
|
76
76
|
// into the view's page container, not replaced by a wrapper.
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
//
|
|
78
|
+
// `instanceof` rather than a cast plus `?.`, because the two failures the
|
|
79
|
+
// `?.` conflated are different: a MISSING ancestor, and an ancestor that is
|
|
80
|
+
// there but is still an ordinary HTMLElement because its own definition has
|
|
81
|
+
// not upgraded it. The cast asserted the second away, and it is the one that
|
|
82
|
+
// happened — `syncDeclaredPage is not a function`, 19 times on
|
|
83
|
+
// `/getting-started/`, once per declared page.
|
|
84
|
+
//
|
|
85
|
+
// Dropping the notification for an un-upgraded view is correct and not
|
|
86
|
+
// merely quiet: such a view has not adopted this page yet, and
|
|
87
|
+
// `_adoptDeclaredPage` reads every attribute in PAGE_ATTRIBUTES straight off
|
|
88
|
+
// the element when it does, so there is nothing to lose and nothing to defer.
|
|
89
|
+
const view = this.closest('adw-tab-view');
|
|
90
|
+
if (!(view instanceof AdwTabView)) return;
|
|
91
|
+
view.syncDeclaredPage(this, name, value);
|
|
79
92
|
}
|
|
80
93
|
}
|
|
81
94
|
|
|
@@ -797,5 +810,14 @@ export class AdwTabView extends HTMLElement {
|
|
|
797
810
|
}
|
|
798
811
|
}
|
|
799
812
|
|
|
800
|
-
|
|
813
|
+
// The VIEW first, and the order carries weight: `define` upgrades every matching
|
|
814
|
+
// element already in the document, immediately. Registering the page first therefore
|
|
815
|
+
// upgraded every declared `<adw-tab-page>` while its `<adw-tab-view>` parent was still
|
|
816
|
+
// un-upgraded, and `AdwTabPage.attributeChangedCallback` reaches for that parent.
|
|
817
|
+
// Parent before child keeps the window shut; the `instanceof` guard in the callback is
|
|
818
|
+
// what holds when something outside this file reopens it — and because the guard makes
|
|
819
|
+
// the order UNOBSERVABLE (with it in place, page-first renders identically and every
|
|
820
|
+
// test still passes, measured), the order itself is held by
|
|
821
|
+
// `scripts/check-adwaita-upgrade-order.mjs` rather than by a spec.
|
|
801
822
|
customElements.define('adw-tab-view', AdwTabView);
|
|
823
|
+
customElements.define('adw-tab-page', AdwTabPage);
|