@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.
@@ -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
- * setting `title-widget` replaces the derived `AdwWindowTitle` outright.
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`'s derived title widget
26
- * is bound to the property and re-renders on every change.
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` destroys the
31
- * derived `AdwWindowTitle` rather than stacking the two, so the late case has to remove
32
- * it too — a bar showing both would be a shape neither GTK nor the declared markup can
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.44.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.44.0",
49
- "@gjsify/adwaita-fonts": "^0.44.0",
50
- "@gjsify/adwaita-icons": "^0.44.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.44.0",
55
- "@gjsify/unit": "^0.44.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
- // `Adw.HeaderBar` builds its centre from `title`/`subtitle` through a derived
8
- // `AdwWindowTitle` bound to the properties, unless a `title-widget` replaces it — the
9
- // either/or the `slot="center"` case below asserts.
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 the `<adw-window-title>` Adw.HeaderBar itself derives, so the three
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 () => {
@@ -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>`, which is what `Adw.HeaderBar`
4
- // itself puts there (`adw-header-bar.c`: the title widget it creates when none is
5
- // given IS an `AdwWindowTitle`). It used to be a bare span with
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
- * setting `title-widget` replaces the derived `AdwWindowTitle` outright.
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 builds an `AdwWindowTitle` exactly while no title-widget
90
- // was given.
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`'s derived title widget
103
- * is bound to the property and re-renders on every change.
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` destroys the
111
- * derived `AdwWindowTitle` rather than stacking the two, so the late case has to remove
112
- * it too — a bar showing both would be a shape neither GTK nor the declared markup can
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
- const view = this.closest('adw-navigation-view') as AdwNavigationView | null;
104
- view?.syncPageProperty(this, name, previous);
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
- customElements.define('adw-navigation-page', AdwNavigationPage);
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
- const view = this.closest('adw-tab-view') as AdwTabView | null;
78
- view?.syncDeclaredPage(this, name, value);
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
- customElements.define('adw-tab-page', AdwTabPage);
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);