@gjsify/adwaita-web 0.64.13-next.283 → 0.64.13

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.
@@ -5,6 +5,19 @@ export declare class AdwBin extends WidgetElement {
5
5
  get child(): Element | null;
6
6
  /** `adw_bin_set_child`: replace the current child, unparenting it. */
7
7
  set child(value: Element | null);
8
+ /**
9
+ * `adw_bin_get_child` / `adw_bin_set_child` — the METHOD spelling of {@link child}.
10
+ *
11
+ * The property alone is not the whole door. `Adw.Bin:child` is reached two ways and only
12
+ * one of them is a property: markup writes `child:`, but code built against GI calls
13
+ * `bin.get_child()` / `bin.set_child(w)`, which is the spelling a `.blp`-driven widget
14
+ * shared across renderers uses — GJS and the NativeScript bridge both answer it
15
+ * (`AdwSingleChildBase`), and here it was missing, so the shared code died with
16
+ * `get_child is not a function` on the web and nowhere else.
17
+ */
18
+ get_child(): Element | null;
19
+ /** `adw_bin_set_child`. Refuses a child that already has a parent, as {@link setChild} does. */
20
+ set_child(child: Element | null): void;
8
21
  connectedCallback(): void;
9
22
  disconnectedCallback(): void;
10
23
  /**
@@ -9,7 +9,13 @@ export declare class WidgetElement extends HTMLElement {
9
9
  get_visible(): boolean;
10
10
  /** `gtk_widget_set_visible`. */
11
11
  set_visible(visible: boolean): void;
12
- /** `gtk_widget_get_first_child`: the first child element, `null` for a leaf. */
12
+ /**
13
+ * `gtk_widget_get_first_child`: the first child WIDGET, `null` for a leaf.
14
+ *
15
+ * The five walkers skip the elements a port builds for its own insides — `isWidgetNode`
16
+ * in `@gjsify/adwaita-core` carries the rule and the incident behind it, and the
17
+ * registered-class copy in `widgetTreeMembers()` answers from the same function.
18
+ */
13
19
  get_first_child(): Element | null;
14
20
  /** `gtk_widget_get_last_child`. */
15
21
  get_last_child(): Element | null;
@@ -17,7 +23,7 @@ export declare class WidgetElement extends HTMLElement {
17
23
  get_next_sibling(): Element | null;
18
24
  /** `gtk_widget_get_prev_sibling`. */
19
25
  get_prev_sibling(): Element | null;
20
- /** `gtk_widget_get_parent`. */
26
+ /** `gtk_widget_get_parent`: the nearest ancestor that is a widget, not a port's own wrapper. */
21
27
  get_parent(): Element | null;
22
28
  /** `gtk_widget_get_width`: the layout box in CSS px; 0 until the widget is rendered. */
23
29
  get_width(): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/adwaita-web",
3
- "version": "0.64.13-next.283",
3
+ "version": "0.64.13",
4
4
  "description": "Adwaita/Libadwaita web components for browser targets",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -58,14 +58,14 @@
58
58
  "@codemirror/language": "^6.12.4",
59
59
  "@codemirror/state": "^6.7.0",
60
60
  "@codemirror/view": "^6.43.4",
61
- "@gjsify/adwaita-core": "0.64.13-next.283",
62
- "@gjsify/adwaita-fonts": "0.64.13-next.283",
63
- "@gjsify/adwaita-icons": "0.64.13-next.283",
64
- "@gjsify/gtksource-core": "0.64.13-next.283",
61
+ "@gjsify/adwaita-core": "^0.64.13",
62
+ "@gjsify/adwaita-fonts": "^0.64.13",
63
+ "@gjsify/adwaita-icons": "^0.64.13",
64
+ "@gjsify/gtksource-core": "^0.64.13",
65
65
  "@lezer/highlight": "^1.2.3"
66
66
  },
67
67
  "peerDependencies": {
68
- "@gjsify/app-settings": "0.64.13-next.283"
68
+ "@gjsify/app-settings": "^0.64.13"
69
69
  },
70
70
  "peerDependenciesMeta": {
71
71
  "@gjsify/app-settings": {
@@ -73,11 +73,11 @@
73
73
  }
74
74
  },
75
75
  "devDependencies": {
76
- "@gjsify/app-settings": "0.64.13-next.283",
77
- "@gjsify/blueprint": "0.64.13-next.283",
78
- "@gjsify/cli": "0.64.13-next.283",
79
- "@gjsify/unit": "0.64.13-next.283",
80
- "@gjsify/vite-plugin-blueprint": "0.64.13-next.283",
76
+ "@gjsify/app-settings": "^0.64.13",
77
+ "@gjsify/blueprint": "^0.64.13",
78
+ "@gjsify/cli": "^0.64.13",
79
+ "@gjsify/unit": "^0.64.13",
80
+ "@gjsify/vite-plugin-blueprint": "^0.64.13",
81
81
  "sass": "^1.101.0",
82
82
  "typescript": "^6.0.3"
83
83
  },
@@ -114,6 +114,5 @@
114
114
  "bugs": {
115
115
  "url": "https://github.com/gjsify/gjsify/issues"
116
116
  },
117
- "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/web/adwaita-web#readme",
118
- "gitHead": "05fae97125a374e84bf57c2d2d086fb53523f205"
117
+ "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/web/adwaita-web#readme"
119
118
  }
@@ -61,6 +61,26 @@ export const AdwBinTest = async () => {
61
61
  other.remove();
62
62
  });
63
63
 
64
+ await it('answers `get_child()` / `set_child()` too, the spelling GI-built code uses', () => {
65
+ // `Adw.Bin:child` has two doors, and markup only takes one of them: code written
66
+ // against GI calls the methods, which is what a `.blp`-driven widget shared with
67
+ // GJS and NativeScript does. Both of those renderers answer them; the web did not,
68
+ // and shared code died here with `get_child is not a function`.
69
+ const { el, host } = mount();
70
+ expect(el.get_child()).toBeNull();
71
+ const child = document.createElement('div');
72
+ el.set_child(child);
73
+ expect(el.get_child()).toBe(child);
74
+ expect(el.child).toBe(child);
75
+ const next = document.createElement('span');
76
+ el.set_child(next);
77
+ expect(el.get_child()).toBe(next);
78
+ expect(el.childElementCount).toBe(1);
79
+ el.set_child(null);
80
+ expect(el.get_child()).toBeNull();
81
+ host.remove();
82
+ });
83
+
64
84
  await it('a bin is a column flex box, and its only child fills it', () => {
65
85
  const { el, host } = mount([document.createElement('div')]);
66
86
  // A bin takes the size it is GIVEN and hands all of it to the child, which
@@ -24,7 +24,7 @@
24
24
  // default `GENERIC` and a bin contributes nothing to the accessibility tree of its own.
25
25
  //
26
26
  // KNOWN_GAPS: none — `AdwBin`'s only property is `child`, a widget, which is a SLOT on
27
- // every renderer.
27
+ // every renderer, and it is reachable BOTH ways: `child` and `get_child()`/`set_child()`.
28
28
  //
29
29
  // Reference: refs/libadwaita/src/adw-bin.c (adw_bin_set_child, class_init)
30
30
  // Copyright (c) 2021 Purism SPC / GNOME contributors (libadwaita). LGPLv2.1+.
@@ -46,6 +46,25 @@ export class AdwBin extends WidgetElement {
46
46
  this.setChild(value);
47
47
  }
48
48
 
49
+ /**
50
+ * `adw_bin_get_child` / `adw_bin_set_child` — the METHOD spelling of {@link child}.
51
+ *
52
+ * The property alone is not the whole door. `Adw.Bin:child` is reached two ways and only
53
+ * one of them is a property: markup writes `child:`, but code built against GI calls
54
+ * `bin.get_child()` / `bin.set_child(w)`, which is the spelling a `.blp`-driven widget
55
+ * shared across renderers uses — GJS and the NativeScript bridge both answer it
56
+ * (`AdwSingleChildBase`), and here it was missing, so the shared code died with
57
+ * `get_child is not a function` on the web and nowhere else.
58
+ */
59
+ get_child(): Element | null {
60
+ return this.child;
61
+ }
62
+
63
+ /** `adw_bin_set_child`. Refuses a child that already has a parent, as {@link setChild} does. */
64
+ set_child(child: Element | null): void {
65
+ this.setChild(child);
66
+ }
67
+
49
68
  connectedCallback() {
50
69
  // The NAME only. Every child of a bin already IS its content — there is no
51
70
  // internal structure to route into — so `.install()` is deliberately not called
@@ -73,6 +73,17 @@ export const PangoMarkupTest = async () => {
73
73
  expect(target.querySelector('i > u')?.textContent).toBe('b');
74
74
  });
75
75
 
76
+ await it("sub and sup carry pango's 1 / 1.2 font_scale fallback", () => {
77
+ const { target } = render('<sub>d</sub><sup>e</sup>');
78
+ // Compared as a computed ratio: browsers serialise the `calc()` differently
79
+ // (Firefox gives `calc(0.833333em)`).
80
+ const base = Number.parseFloat(getComputedStyle(target).fontSize);
81
+ for (const el of target.children) {
82
+ const ratio = Number.parseFloat(getComputedStyle(el).fontSize) / base;
83
+ expect(Math.abs(ratio - 1 / 1.2)).toBeLessThan(0.01);
84
+ }
85
+ });
86
+
76
87
  await it("<markup> draws its children — it is pango's implicit root, not a box", async () => {
77
88
  const { target } = render('<markup><b>x</b></markup>');
78
89
  expect(target.querySelector('b')?.textContent).toBe('x');
@@ -59,8 +59,11 @@ export const TAG_STYLES: Readonly<Record<string, { element: string; style?: Read
59
59
  tt: { element: 'span', style: { 'font-family': 'monospace' } },
60
60
  big: { element: 'span', style: { 'font-size': 'larger' } },
61
61
  small: { element: 'span', style: { 'font-size': 'smaller' } },
62
- sub: { element: 'sub' },
63
- sup: { element: 'sup' },
62
+ // pango's `font_scale` for `<sub>`/`<sup>` falls back to `1 / 1.2` when the font's OpenType
63
+ // metrics carry no script size (pango/itemize.c, `PANGO_FONT_SCALE_SUBSCRIPT`/`SUPERSCRIPT`);
64
+ // spelled out so it does not depend on each UA's `font-size: smaller` ratio.
65
+ sub: { element: 'sub', style: { 'font-size': 'calc(1em / 1.2)' } },
66
+ sup: { element: 'sup', style: { 'font-size': 'calc(1em / 1.2)' } },
64
67
  markup: { element: 'span', style: { display: 'contents' } },
65
68
  span: { element: 'span' },
66
69
  };
@@ -16,10 +16,25 @@
16
16
  // Reference: GTK gtk/gtkwidget.c (gtk_widget_set_visible, gtk_widget_set_tooltip_text)
17
17
  // Copyright (c) GNOME contributors (GTK). LGPLv2.1+.
18
18
 
19
+ import { isWidgetNode } from '@gjsify/adwaita-core';
20
+
19
21
  import { WIDGET_VISIBLE_GETTERS, installElementApi, markLazyElementApi, notifyElement } from './gobject-elements.js';
20
22
  import { dispatchedSignalsOf, type DispatchedSignals } from './signals.js';
21
23
  import { isMapped } from './widget-mapping.js';
22
24
 
25
+ /** Element links, walked until one lands on a widget: `isWidgetNode` says which those are. */
26
+ type ElementLink =
27
+ | 'firstElementChild'
28
+ | 'lastElementChild'
29
+ | 'nextElementSibling'
30
+ | 'previousElementSibling'
31
+ | 'parentElement';
32
+
33
+ function widgetChild(from: Element, first: ElementLink, next: ElementLink): Element | null {
34
+ for (let at = from[first]; at !== null; at = at[next]) if (isWidgetNode(at)) return at;
35
+ return null;
36
+ }
37
+
23
38
  export class WidgetElement extends HTMLElement {
24
39
  /** `Gtk.Widget::map` / `::unmap`, which every subclass inherits (`dispatchedSignalsOf`). */
25
40
  static readonly signals: DispatchedSignals = { map: 'map', unmap: 'unmap' };
@@ -41,25 +56,31 @@ export class WidgetElement extends HTMLElement {
41
56
  set_visible(visible: boolean): void {
42
57
  this.visible = visible;
43
58
  }
44
- /** `gtk_widget_get_first_child`: the first child element, `null` for a leaf. */
59
+ /**
60
+ * `gtk_widget_get_first_child`: the first child WIDGET, `null` for a leaf.
61
+ *
62
+ * The five walkers skip the elements a port builds for its own insides — `isWidgetNode`
63
+ * in `@gjsify/adwaita-core` carries the rule and the incident behind it, and the
64
+ * registered-class copy in `widgetTreeMembers()` answers from the same function.
65
+ */
45
66
  get_first_child(): Element | null {
46
- return this.firstElementChild;
67
+ return widgetChild(this, 'firstElementChild', 'nextElementSibling');
47
68
  }
48
69
  /** `gtk_widget_get_last_child`. */
49
70
  get_last_child(): Element | null {
50
- return this.lastElementChild;
71
+ return widgetChild(this, 'lastElementChild', 'previousElementSibling');
51
72
  }
52
73
  /** `gtk_widget_get_next_sibling`. */
53
74
  get_next_sibling(): Element | null {
54
- return this.nextElementSibling;
75
+ return widgetChild(this, 'nextElementSibling', 'nextElementSibling');
55
76
  }
56
77
  /** `gtk_widget_get_prev_sibling`. */
57
78
  get_prev_sibling(): Element | null {
58
- return this.previousElementSibling;
79
+ return widgetChild(this, 'previousElementSibling', 'previousElementSibling');
59
80
  }
60
- /** `gtk_widget_get_parent`. */
81
+ /** `gtk_widget_get_parent`: the nearest ancestor that is a widget, not a port's own wrapper. */
61
82
  get_parent(): Element | null {
62
- return this.parentElement;
83
+ return widgetChild(this, 'parentElement', 'parentElement');
63
84
  }
64
85
  /** `gtk_widget_get_width`: the layout box in CSS px; 0 until the widget is rendered. */
65
86
  get_width(): number {
@@ -60,10 +60,10 @@ export const WidgetMembersTest = async () => {
60
60
  expect(() => widget.set_direction(7)).toThrow();
61
61
  });
62
62
 
63
- await it('walks the element tree with the GTK link names', () => {
63
+ await it('walks the widget tree with the GTK link names', () => {
64
64
  const parent = make('WidgetMembersParent');
65
- const a = document.createElement('div');
66
- const b = document.createElement('div');
65
+ const a = new Gtk.Label() as unknown as WidgetLike;
66
+ const b = new Gtk.Label() as unknown as WidgetLike;
67
67
  parent.append(a, b);
68
68
  expect(parent.get_first_child()).toBe(a);
69
69
  expect(parent.get_last_child()).toBe(b);
@@ -73,6 +73,45 @@ export const WidgetMembersTest = async () => {
73
73
  expect(parent.get_parent()).toBe(null);
74
74
  });
75
75
 
76
+ await it("steps over the plain elements a port builds for a widget's insides", () => {
77
+ // `gtk_widget_get_first_child` hands back a GtkWidget, always. In a DOM port the
78
+ // same `children` list also holds what the port made for itself — the `<span>` a
79
+ // label's markup renderer writes, the `<div>` a row lays its slots out in — and a
80
+ // caller walking the tree would get one of those and find no `get_first_child` on
81
+ // it at all. The custom-element hyphen separates the two kinds (`isWidgetNode`).
82
+ const parent = make('WidgetMembersInsides');
83
+ const a = new Gtk.Label() as unknown as WidgetLike;
84
+ const b = new Gtk.Label() as unknown as WidgetLike;
85
+ parent.append(
86
+ document.createElement('span'),
87
+ a,
88
+ document.createElement('div'),
89
+ b,
90
+ document.createElement('span'),
91
+ );
92
+ expect(parent.get_first_child()).toBe(a);
93
+ expect(parent.get_last_child()).toBe(b);
94
+ expect(a.get_next_sibling()).toBe(b);
95
+ expect(b.get_prev_sibling()).toBe(a);
96
+ expect(b.get_next_sibling()).toBe(null);
97
+ });
98
+
99
+ await it('a widget holding only its own markup is a LEAF', () => {
100
+ const label = make('WidgetMembersLeaf');
101
+ label.append(document.createElement('span'), document.createElement('span'));
102
+ expect(label.get_first_child()).toBe(null);
103
+ expect(label.get_last_child()).toBe(null);
104
+ });
105
+
106
+ await it('get_parent reports the nearest ancestor that is a widget', () => {
107
+ const box = new Gtk.Box() as unknown as WidgetLike;
108
+ const wrapper = document.createElement('div');
109
+ const label = new Gtk.Label() as unknown as WidgetLike;
110
+ box.append(wrapper);
111
+ wrapper.append(label);
112
+ expect(label.get_parent()).toBe(box);
113
+ });
114
+
76
115
  await it('walks the element tree of a built-in widget with the GTK link names', () => {
77
116
  const box = new Gtk.Box() as unknown as WidgetLike;
78
117
  const a = new Gtk.Label() as unknown as WidgetLike;