@gjsify/adwaita-web 0.64.1 → 0.64.2-next.252

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.
Files changed (83) hide show
  1. package/README.md +12 -8
  2. package/dist/adwaita-web.css +39 -9
  3. package/dist/adwaita-web.css.map +1 -1
  4. package/lib/types/application.d.ts +1 -1
  5. package/lib/types/child-slot.spec.d.ts +1 -0
  6. package/lib/types/container-fill.spec.d.ts +1 -0
  7. package/lib/types/elements/adw-alert-dialog.d.ts +77 -17
  8. package/lib/types/elements/adw-toast-overlay.d.ts +8 -29
  9. package/lib/types/elements/adw-window-features.d.ts +8 -0
  10. package/lib/types/elements/gtk-application-window.d.ts +1 -4
  11. package/lib/types/elements/gtk-emoji-chooser.d.ts +2 -0
  12. package/lib/types/elements/gtk-popover-menu.d.ts +2 -0
  13. package/lib/types/elements/gtk-popover.d.ts +5 -0
  14. package/lib/types/elements/gtk-stack-page.d.ts +28 -0
  15. package/lib/types/elements/gtk-stack.d.ts +11 -0
  16. package/lib/types/elements/gtk-window.d.ts +16 -1
  17. package/lib/types/gobject-elements.d.ts +9 -0
  18. package/lib/types/index.d.ts +2 -2
  19. package/lib/types/learn6502-window.spec.d.ts +1 -0
  20. package/lib/types/namespace/adw.d.ts +1 -1
  21. package/lib/types/namespace/gtk.d.ts +2 -0
  22. package/lib/types/shared-tree-builder.d.ts +31 -0
  23. package/lib/types/styles.generated.d.ts +1 -1
  24. package/lib/types/widget-visible.spec.d.ts +1 -0
  25. package/lib/types/window-maximize.spec.d.ts +1 -0
  26. package/package.json +13 -12
  27. package/scss/_bin.scss +6 -3
  28. package/scss/_overlay.scss +11 -1
  29. package/scss/_reset.scss +1 -1
  30. package/scss/_stack.scss +8 -2
  31. package/scss/_toast.scss +13 -3
  32. package/scss/_window.scss +16 -0
  33. package/src/adw-alert-dialog.spec.ts +71 -5
  34. package/src/adw-bin.spec.ts +4 -3
  35. package/src/adw-header-bar.spec.ts +18 -0
  36. package/src/adw-toast-overlay.spec.ts +68 -24
  37. package/src/application.spec.ts +44 -0
  38. package/src/application.ts +13 -18
  39. package/src/capabilities.mjs +1 -3
  40. package/src/child-slot.spec.ts +71 -0
  41. package/src/construct-vectors.spec.ts +51 -6
  42. package/src/container-fill.spec.ts +70 -0
  43. package/src/elements/adw-alert-dialog.ts +174 -44
  44. package/src/elements/adw-application-window.ts +0 -3
  45. package/src/elements/adw-breakpoint-bin.ts +3 -0
  46. package/src/elements/adw-dialog.ts +3 -1
  47. package/src/elements/adw-header-bar.ts +4 -0
  48. package/src/elements/adw-toast-overlay.ts +35 -69
  49. package/src/elements/adw-window-features.ts +14 -2
  50. package/src/elements/gtk-application-window.ts +3 -8
  51. package/src/elements/gtk-drag-icon.ts +4 -0
  52. package/src/elements/gtk-emoji-chooser.ts +5 -0
  53. package/src/elements/gtk-flow-box-child.ts +3 -1
  54. package/src/elements/gtk-graphics-offload.ts +3 -1
  55. package/src/elements/gtk-list-box-row.ts +3 -1
  56. package/src/elements/gtk-overlay.ts +5 -4
  57. package/src/elements/gtk-paned.ts +7 -1
  58. package/src/elements/gtk-popover-bin.ts +3 -0
  59. package/src/elements/gtk-popover-menu.ts +5 -0
  60. package/src/elements/gtk-popover.ts +12 -0
  61. package/src/elements/gtk-search-bar.ts +5 -5
  62. package/src/elements/gtk-stack-page.ts +111 -0
  63. package/src/elements/gtk-stack.ts +50 -5
  64. package/src/elements/gtk-tree-expander.ts +3 -0
  65. package/src/elements/gtk-viewport.ts +4 -0
  66. package/src/elements/gtk-window-handle.ts +4 -0
  67. package/src/elements/gtk-window.ts +55 -6
  68. package/src/gobject-elements.ts +28 -0
  69. package/src/gtk-stack.spec.ts +170 -0
  70. package/src/index.ts +2 -2
  71. package/src/keyboard-operable.spec.ts +3 -3
  72. package/src/learn6502-window.spec.ts +113 -0
  73. package/src/namespace/adw.ts +1 -1
  74. package/src/namespace/gtk.ts +2 -0
  75. package/src/shared-tree-builder.ts +105 -10
  76. package/src/shared-trees.spec.ts +15 -1
  77. package/src/slotted-children.spec.ts +24 -0
  78. package/src/slotted-children.ts +18 -2
  79. package/src/styles.generated.ts +1 -1
  80. package/src/test.browser.mts +10 -0
  81. package/src/value-lists.spec.ts +3 -3
  82. package/src/widget-visible.spec.ts +104 -0
  83. package/src/window-maximize.spec.ts +93 -0
@@ -2,44 +2,34 @@
2
2
  // content and shows transient toast strips (title, optional action button, close
3
3
  // affordance) layered over the bottom of its own bounds.
4
4
  //
5
- // The QUEUE — the one-at-a-time policy, FIFO ordering and auto-dismiss lifecycle —
6
- // is HEADLESS and lives in `@gjsify/adwaita-core` (ADR 0004) as {@link AdwToastQueue};
7
- // this element composes it and keeps only the DOM half: build the strip on `onShow`,
8
- // tear it down on `onHide`, run the enter/exit transitions.
5
+ // A toast is an `Adw.Toast` and goes in through `add_toast(toast)`, exactly as on GTK, so a
6
+ // GNOME app's toast code runs here unchanged. The QUEUE — one toast at a time, priority,
7
+ // re-add, auto-dismiss — is HEADLESS and lives in `@gjsify/adwaita-core` (ADR 0004) as
8
+ // {@link ToastOverlayQueue}; this element keeps only the DOM half: build the strip on
9
+ // `onShow`, tear it down on `onHide`, run the enter/exit transitions.
9
10
  //
10
11
  // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
11
12
  // Reference: refs/adwaita-web/adwaita-web/scss/_toast.scss, _toast_overlay.scss
12
- // Reference: refs/libadwaita/src/adw-toast-overlay.c, adw-toast.c (add_toast queues,
13
- // one toast is shown at a time, dismissing advances to the next)
13
+ // Reference: refs/libadwaita/src/adw-toast-overlay.c, adw-toast-widget.c (add_toast queues,
14
+ // one toast is shown at a time, the button emits button-clicked, activates the action and
15
+ // dismisses)
14
16
  // Copyright (c) 2025 csm (adwaita-web). MIT License.
15
17
  // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
16
- // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web;
17
- // extended addToast() with action button + close button + options API; the
18
+ // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web; the
18
19
  // queue/auto-dismiss state machine composed from @gjsify/adwaita-core.
19
20
 
20
- import { AdwToast, AdwToastQueue } from '@gjsify/adwaita-core';
21
+ import { ToastOverlayQueue } from '@gjsify/adwaita-core';
21
22
 
23
+ import { activateWidgetAction } from '../application.js';
24
+ import { renderPangoMarkup } from '../pango-markup.js';
22
25
  import { bindSlottedChildren } from '../slotted-children.js';
23
26
  import { createGtkImage } from './gtk-image.js';
24
- import type { ToastScheduler, ToastTimerHandle } from '@gjsify/adwaita-core';
27
+ import type { Toast, ToastScheduler, ToastTimerHandle } from '@gjsify/adwaita-core';
25
28
 
26
29
  // The timing seam is re-exported so a consumer can type a custom scheduler for
27
30
  // {@link AdwToastOverlay.scheduler} without depending on the core package.
28
31
  export type { ToastScheduler, ToastTimerHandle } from '@gjsify/adwaita-core';
29
32
 
30
- /** Options for {@link AdwToastOverlay.addToast}, mirroring Adw.Toast's properties. */
31
- export interface AdwToastOptions {
32
- /** Seconds before the toast auto-dismisses; `0` keeps it until dismissed. Mirrors Adw.Toast:timeout (default 5). */
33
- timeout?: number;
34
- /** Label for the action button; omitted/empty means no button. Mirrors Adw.Toast:button-label. */
35
- buttonLabel?: string;
36
- /** Invoked when the action button is pressed. Mirrors the activation of Adw.Toast:action-name. */
37
- onAction?: () => void;
38
- }
39
-
40
- /** Default toast lifetime in SECONDS — this element's public unit (Adw.Toast:timeout). */
41
- const DEFAULT_TIMEOUT_SECONDS = 5;
42
-
43
33
  /**
44
34
  * Fallback budget in ms for the exit transition: must stay at or above the
45
35
  * `.adw-toast.hiding` transition-duration in `_toast.scss` (0.15s today), because it
@@ -56,18 +46,16 @@ const domScheduler: ToastScheduler = {
56
46
  export class AdwToastOverlay extends HTMLElement {
57
47
  private _toasts!: HTMLDivElement;
58
48
  private _initialized = false;
59
- /** The headless one-at-a-time + FIFO + auto-dismiss state machine (ADR 0004). */
60
- private readonly _queue: AdwToastQueue;
49
+ /** The headless queue of `adw-toast-overlay.c` (ADR 0004). */
50
+ private readonly _queue: ToastOverlayQueue;
61
51
  /** The timing source the queue's auto-dismiss runs on — swappable, see {@link scheduler}. */
62
52
  private _scheduler: ToastScheduler = domScheduler;
63
- /** The strip currently mounted for {@link AdwToastQueue.current}, if any. */
53
+ /** The strip currently mounted for {@link ToastOverlayQueue.current}, if any. */
64
54
  private _currentEl: HTMLDivElement | null = null;
65
- /** Per-toast action callbacks (the core toast descriptor carries only the label). */
66
- private readonly _actions = new WeakMap<AdwToast, () => void>();
67
55
 
68
56
  constructor() {
69
57
  super();
70
- this._queue = new AdwToastQueue({
58
+ this._queue = new ToastOverlayQueue({
71
59
  // Indirect through `this._scheduler` so the timing source stays
72
60
  // swappable after construction (the queue takes its scheduler once).
73
61
  scheduler: {
@@ -97,7 +85,7 @@ export class AdwToastOverlay extends HTMLElement {
97
85
  this._toasts = document.createElement('div');
98
86
  this._toasts.className = 'adw-toast-overlay-toasts';
99
87
 
100
- bindSlottedChildren(this, [{ into: content }]).install(content, this._toasts);
88
+ bindSlottedChildren(this, [{ name: 'child', into: content }, { into: content }]).install(content, this._toasts);
101
89
  }
102
90
 
103
91
  /**
@@ -115,7 +103,7 @@ export class AdwToastOverlay extends HTMLElement {
115
103
  }
116
104
 
117
105
  /** The toast currently on screen, or `null` when nothing is shown. */
118
- get currentToast(): AdwToast | null {
106
+ get currentToast(): Toast | null {
119
107
  return this._queue.current;
120
108
  }
121
109
 
@@ -124,45 +112,21 @@ export class AdwToastOverlay extends HTMLElement {
124
112
  return this._queue.pending;
125
113
  }
126
114
 
127
- /**
128
- * Show a toast — the web equivalent of Adw.ToastOverlay.add_toast() with a
129
- * freshly-built Adw.Toast. Only ONE toast is visible at a time; this one shows
130
- * immediately when the slot is free, otherwise it waits its turn.
131
- *
132
- * `options` is either an {@link AdwToastOptions} bag or, as a legacy shorthand, a
133
- * timeout in MILLISECONDS.
134
- */
135
- addToast(title: string, options: number | AdwToastOptions = {}): void {
136
- // Ensure the toast container exists even if addToast() races a not-yet
115
+ /** `adw_toast_overlay_add_toast`: show `toast` now, or queue it by its priority. */
116
+ add_toast(toast: Toast): void {
117
+ // Ensure the toast container exists even if add_toast() races a not-yet
137
118
  // connected element (mirrors Adw.ToastOverlay queuing before realize).
138
119
  if (!this._toasts) this.connectedCallback();
139
-
140
- // Back-compat: the original signature was addToast(title, timeoutMs).
141
- const opts: AdwToastOptions = typeof options === 'number' ? { timeout: options / 1000 } : options;
142
-
143
- // This element's public timeout unit is SECONDS (Adw.Toast:timeout); the
144
- // headless descriptor counts milliseconds.
145
- const toast = new AdwToast(title, {
146
- timeout: (opts.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1000,
147
- buttonLabel: opts.buttonLabel,
148
- });
149
- if (opts.onAction) this._actions.set(toast, opts.onAction);
150
-
151
- this._queue.add(toast);
152
- }
153
-
154
- /** Dismiss the visible toast early; the next queued toast takes its place. */
155
- dismiss(): void {
156
- this._queue.dismiss();
120
+ this._queue.add_toast(toast);
157
121
  }
158
122
 
159
- /** Dismiss the visible toast and discard everything still queued behind it. */
160
- clearToasts(): void {
161
- this._queue.clear();
123
+ /** `adw_toast_overlay_dismiss_all`: dismiss every queued toast, then the visible one. */
124
+ dismiss_all(): void {
125
+ this._queue.dismiss_all();
162
126
  }
163
127
 
164
128
  /** Build + attach the strip for `toast` — the DOM half of the queue's `onShow`. */
165
- private _mountToast(toast: AdwToast): void {
129
+ private _mountToast(toast: Toast): void {
166
130
  const el = document.createElement('div');
167
131
  el.className = 'adw-toast';
168
132
 
@@ -171,18 +135,20 @@ export class AdwToastOverlay extends HTMLElement {
171
135
 
172
136
  const titleEl = document.createElement('span');
173
137
  titleEl.className = 'adw-toast-title';
174
- titleEl.textContent = toast.title;
138
+ if (toast.use_markup) renderPangoMarkup(titleEl, toast.title);
139
+ else titleEl.textContent = toast.title;
175
140
  wrapper.appendChild(titleEl);
176
141
  el.appendChild(wrapper);
177
142
 
178
- if (toast.hasButton) {
143
+ if (toast.button_label) {
179
144
  const button = document.createElement('button');
180
145
  button.type = 'button';
181
146
  button.className = 'adw-toast-action-button';
182
- button.textContent = toast.buttonLabel;
147
+ button.textContent = toast.button_label;
183
148
  button.addEventListener('click', () => {
184
- this._actions.get(toast)?.();
185
- this._queue.dismiss();
149
+ toast.emit('button-clicked');
150
+ if (toast.action_name) activateWidgetAction(this, toast.action_name);
151
+ toast.dismiss();
186
152
  });
187
153
  el.appendChild(button);
188
154
  }
@@ -197,7 +163,7 @@ export class AdwToastOverlay extends HTMLElement {
197
163
  close.className = 'adw-toast-close-button';
198
164
  close.setAttribute('aria-label', 'Close');
199
165
  close.appendChild(createGtkImage('window-close'));
200
- close.addEventListener('click', () => this._queue.dismiss());
166
+ close.addEventListener('click', () => toast.dismiss());
201
167
  el.appendChild(close);
202
168
 
203
169
  this._toasts.appendChild(el);
@@ -23,6 +23,15 @@ function requested(host: HTMLElement, name: string, fallback: number): number {
23
23
  return Number.isFinite(value) && value >= 0 ? value : fallback;
24
24
  }
25
25
 
26
+ /**
27
+ * A maximized or fullscreen `Gtk.Window` ignores its default size and fills the monitor's work
28
+ * area (gtkwindow.c: the default size is only the RESTORED size). The page's work area is the
29
+ * frame's containing block, which the skin sizes from these two attributes.
30
+ */
31
+ export function fillsContainingBlock(host: HTMLElement): boolean {
32
+ return ['maximized', 'fullscreened'].some((name) => host.hasAttribute(name) && host.getAttribute(name) !== 'false');
33
+ }
34
+
26
35
  export class AdwWindowFeatures {
27
36
  private _contentEl: HTMLDivElement | null = null;
28
37
  private _breakpoints: AdwBreakpoint[] = [];
@@ -128,13 +137,16 @@ export class AdwWindowFeatures {
128
137
  * size; `width-request`/`height-request` override it). `default-width/height` is only
129
138
  * a default: an authored size is the box, and an absent one leaves the box to the
130
139
  * stylesheet, which fills the viewport — a page has no window manager to pick a size.
140
+ * While maximized or fullscreened the default size is not applied at all: the stylesheet
141
+ * fills the containing block, and the minimum still holds.
131
142
  */
132
143
  applySizeRequest(): void {
133
144
  const host = this._host;
134
145
  host.style.minWidth = `${requested(host, 'width-request', DEFAULT_WIDTH)}px`;
135
146
  host.style.minHeight = `${requested(host, 'height-request', DEFAULT_HEIGHT)}px`;
136
- const width = host.getAttribute('default-width') ?? host.getAttribute('width');
137
- const height = host.getAttribute('default-height') ?? host.getAttribute('height');
147
+ const fills = fillsContainingBlock(host);
148
+ const width = fills ? null : (host.getAttribute('default-width') ?? host.getAttribute('width'));
149
+ const height = fills ? null : (host.getAttribute('default-height') ?? host.getAttribute('height'));
138
150
  if (width) host.style.width = `${width}px`;
139
151
  else host.style.removeProperty('width');
140
152
  if (height) host.style.height = `${height}px`;
@@ -16,7 +16,7 @@
16
16
  //
17
17
  // THE `win.` / `app.` PREFIXES. `GtkApplicationWindow` exports window actions under `win.` and
18
18
  // application-wide ones under `app.` (:48-56), and any `GtkActionable` inside the window can
19
- // activate either. `defineWindowApplication` installs the `Gio.ActionMap` half on the prototype
19
+ // activate either. `installActionMap` installs the `Gio.ActionMap` half on the prototype
20
20
  // (`add_action`, `lookup_action`, `remove_action`, the `win.` registry); the `Gio.ActionGroup`
21
21
  // half stays off because a window's `activate_action` is the widget's. Plain `<gtk-window>` has
22
22
  // no ActionMap. What this element adds over it is that map, the strip and the property that shows it.
@@ -26,17 +26,12 @@
26
26
  // Copyright (c) The GTK Team. LGPLv2.1+.
27
27
  // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
28
28
 
29
- import type { ActionLike } from '@gjsify/adwaita-core';
30
- import type { GtkApplication } from '../application.js';
29
+ import { installActionMap, type ActionLike } from '@gjsify/adwaita-core';
31
30
  import type { DispatchedSignals } from '../signals.js';
32
31
  import type { AdwSlot } from '../slotted-children.js';
33
32
  import { GtkWindow } from './gtk-window.js';
34
- import { defineWindowApplication } from '../application.js';
35
33
 
36
34
  export class GtkApplicationWindow extends GtkWindow {
37
- /** `Gtk.Window:application`: setting it is `add_window` (ADR 0098), set by `defineWindowApplication`. */
38
- declare application: GtkApplication | null;
39
-
40
35
  /** `Gio.ActionMap.add_action`: installed by `installActionMap`; the action is reachable as `win.<name>`. */
41
36
  declare add_action: (action: ActionLike) => void;
42
37
  /** `Gio.ActionMap.lookup_action`: the window's own action, or `null`. */
@@ -127,4 +122,4 @@ export class GtkApplicationWindow extends GtkWindow {
127
122
 
128
123
  customElements.define('gtk-application-window', GtkApplicationWindow);
129
124
 
130
- defineWindowApplication(GtkApplicationWindow.prototype);
125
+ installActionMap(GtkApplicationWindow.prototype);
@@ -53,6 +53,8 @@
53
53
  // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
54
54
  // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
55
55
 
56
+ import { bindSlottedChildren } from '../slotted-children.js';
57
+
56
58
  /** The `rgba()` string a `GdkRGBA` value paints, as the colour swatch shows it. */
57
59
  interface Rgba {
58
60
  readonly red: number;
@@ -166,6 +168,8 @@ export class GtkDragIcon extends HTMLElement {
166
168
  // `gtk_widget_set_can_target (self, FALSE)` (:386-390), in CSS: the icon is pointed
167
169
  // at, and takes no pointer event of any kind.
168
170
  this.style.pointerEvents = 'none';
171
+ // The NAME only, for the reason `adw-bin.ts` gives: the child already IS the content.
172
+ bindSlottedChildren(this, [{ name: 'child', into: this }]);
169
173
  if (this._dragging) this._observe();
170
174
  }
171
175
 
@@ -103,6 +103,11 @@ export class GtkEmojiChooser extends GtkPopover {
103
103
  */
104
104
  private _ctrl = false;
105
105
 
106
+ /** The chooser's template is the child (gtkemojichooser.ui). */
107
+ protected override takesChild(): boolean {
108
+ return false;
109
+ }
110
+
106
111
  connectedCallback(): void {
107
112
  super.connectedCallback();
108
113
  if (this._built) return;
@@ -38,7 +38,9 @@ export class GtkFlowBoxChild extends HTMLElement {
38
38
  this._contentsEl = document.createElement('div');
39
39
  this._contentsEl.className = 'adw-flow-box-child-contents';
40
40
 
41
- bindSlottedChildren(this, [{ into: this._contentsEl }]).install(this._contentsEl);
41
+ bindSlottedChildren(this, [{ name: 'child', into: this._contentsEl }, { into: this._contentsEl }]).install(
42
+ this._contentsEl,
43
+ );
42
44
 
43
45
  this.setAttribute('role', 'gridcell');
44
46
  // `gtk_flow_box_child_init` makes the cell focusable (:578-580) — the box's cursor
@@ -98,7 +98,9 @@ export class GtkGraphicsOffload extends HTMLElement {
98
98
  this._childEl.className = 'adw-graphics-offload-child';
99
99
  // `GTK_TYPE_BIN_LAYOUT` gives the child the whole allocation, and `child` is the one
100
100
  // GtkBuildable slot the widget has, so it is the default one here.
101
- bindSlottedChildren(this, [{ into: this._childEl }]).install(this._childEl);
101
+ bindSlottedChildren(this, [{ name: 'child', into: this._childEl }, { into: this._childEl }]).install(
102
+ this._childEl,
103
+ );
102
104
  this.replaceChildren(this._childEl);
103
105
  this._render();
104
106
  }
@@ -110,7 +110,9 @@ export class GtkListBoxRow extends HTMLElement {
110
110
  // `child` is a WIDGET property, so it is a slot here (gtklistbox.c:3307-3331): the
111
111
  // authored child lands in the same node `gtk_list_box_row_set_child` would put it in,
112
112
  // and a child appended after connect lands there too.
113
- bindSlottedChildren(this, [{ into: this._contentsEl }]).install(this._contentsEl);
113
+ bindSlottedChildren(this, [{ name: 'child', into: this._contentsEl }, { into: this._contentsEl }]).install(
114
+ this._contentsEl,
115
+ );
114
116
 
115
117
  // GTK declares the accessible role unconditionally (gtklistbox.c:3955), so there is
116
118
  // no `selection-mode` to condition it on here.
@@ -98,10 +98,11 @@ export class GtkOverlay extends HTMLElement {
98
98
  // bare `<child>` — the default — is the main child, which is the pairing
99
99
  // `packages/framework/gtk-host/src/descriptors/gtk.ts` curates for `GtkOverlay`
100
100
  // (`slots: { child, overlay }`, `defaultSlot: 'child'`).
101
- bindSlottedChildren(this, [{ into: this._childEl }, { name: 'overlay', into: this._layerEl }]).install(
102
- this._childEl,
103
- this._layerEl,
104
- );
101
+ bindSlottedChildren(this, [
102
+ { name: 'child', into: this._childEl },
103
+ { into: this._childEl },
104
+ { name: 'overlay', into: this._layerEl },
105
+ ]).install(this._childEl, this._layerEl);
105
106
 
106
107
  this._observe();
107
108
  }
@@ -278,7 +278,13 @@ export class GtkPaned extends HTMLElement {
278
278
  // (gtkpaned.c:816-826).
279
279
  bindSlottedChildren(
280
280
  this,
281
- [{ into: this._startEl }, { name: 'start', into: this._startEl }, { name: 'end', into: this._endEl }],
281
+ [
282
+ { into: this._startEl },
283
+ { name: 'start', into: this._startEl },
284
+ { name: 'end', into: this._endEl },
285
+ { name: 'start-child', into: this._startEl },
286
+ { name: 'end-child', into: this._endEl },
287
+ ],
282
288
  () => this._observe(),
283
289
  ).install(this._startEl, this._handleEl, this._endEl);
284
290
 
@@ -75,6 +75,7 @@ import type { AdwMenuActions, AdwMenuInput, AdwMenuModel } from '@gjsify/adwaita
75
75
  import './gtk-popover-menu.js';
76
76
 
77
77
  import type { GtkPopoverMenu } from './gtk-popover-menu.js';
78
+ import { bindSlottedChildren } from '../slotted-children.js';
78
79
 
79
80
  /** How long a press must last to count as a long press — GTK's own default. */
80
81
  const LONG_PRESS_MS = 500;
@@ -150,6 +151,8 @@ export class GtkPopoverBin extends HTMLElement {
150
151
  this._initialized = true;
151
152
  // `gtk_widget_class_set_css_name (widget_class, "popoverbin")` (gtkpopoverbin.c:382).
152
153
  this.classList.add('adw-popover-bin');
154
+ // The NAME only, for the reason `adw-bin.ts` gives: the child already IS the content.
155
+ bindSlottedChildren(this, [{ name: 'child', into: this }]);
153
156
  this.addEventListener('contextmenu', (event) => this._onContextMenu(event));
154
157
  this.addEventListener('keydown', (event) => this._onKeyDown(event));
155
158
  this._wireGestures();
@@ -161,6 +161,11 @@ export class GtkPopoverMenu extends GtkPopover {
161
161
  this.openSubmenu(value);
162
162
  }
163
163
 
164
+ /** The menu's own stack is the child (gtkpopovermenu.c `gtk_popover_menu_init`). */
165
+ protected override takesChild(): boolean {
166
+ return false;
167
+ }
168
+
164
169
  connectedCallback() {
165
170
  super.connectedCallback();
166
171
  if (this._menuInitialized) return;
@@ -82,6 +82,8 @@
82
82
 
83
83
  import { PopoverState, resolvePopoverKey } from '@gjsify/adwaita-core';
84
84
 
85
+ import { bindSlottedChildren } from '../slotted-children.js';
86
+
85
87
  /** Which side of the anchor the surface sits on. */
86
88
  export type GtkPopoverPosition = 'bottom' | 'top' | 'start' | 'end';
87
89
 
@@ -257,10 +259,20 @@ export class GtkPopover extends HTMLElement {
257
259
  if (this._state.open) this._bindDocument();
258
260
  }
259
261
 
262
+ /**
263
+ * Whether `Gtk.Popover:child` is the author's to set. A subclass that builds its own
264
+ * content (GtkPopoverMenu, GtkEmojiChooser) sets the child itself, as their C does.
265
+ */
266
+ protected takesChild(): boolean {
267
+ return true;
268
+ }
269
+
260
270
  private _buildOnce() {
261
271
  if (this._initialized) return;
262
272
  this._initialized = true;
263
273
 
274
+ // The NAME only, for the reason `adw-bin.ts` gives: the child already IS the content.
275
+ if (this.takesChild()) bindSlottedChildren(this, [{ name: 'child', into: this }]);
264
276
  this.classList.add('adw-popover');
265
277
  if (!this.hasAttribute('role')) this.setAttribute('role', 'menu');
266
278
  // The `menu` variant is NOT defaulted or inferred here — it is GTK's `.menu`
@@ -37,6 +37,8 @@
37
37
  // Copyright (c) The GTK Team, GNOME contributors. LGPLv2.1+.
38
38
  // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
39
39
 
40
+ import { bindSlottedChildren } from '../slotted-children.js';
41
+
40
42
  export class GtkSearchBar extends HTMLElement {
41
43
  private _box!: HTMLDivElement;
42
44
  private _center!: HTMLDivElement;
@@ -93,10 +95,6 @@ export class GtkSearchBar extends HTMLElement {
93
95
 
94
96
  this._center = document.createElement('div');
95
97
  this._center.className = 'adw-search-bar-center';
96
- // Whatever the author put inside the bar is the child, moved into the box's centre —
97
- // the `GtkCenterBox` the C builds around it (gtksearchbar.c:378-386).
98
- this._center.append(...this.childNodes);
99
-
100
98
  this._close = document.createElement('button');
101
99
  this._close.className = 'adw-search-bar-close';
102
100
  this._close.type = 'button';
@@ -106,7 +104,9 @@ export class GtkSearchBar extends HTMLElement {
106
104
  this._box = document.createElement('div');
107
105
  this._box.className = 'adw-search-bar-box';
108
106
  this._box.append(this._center, this._close);
109
- this.replaceChildren(this._box);
107
+ // Whatever the author put inside the bar is the child, moved into the box's centre —
108
+ // the `GtkCenterBox` the C builds around it (gtksearchbar.c:378-386).
109
+ bindSlottedChildren(this, [{ name: 'child', into: this._center }, { into: this._center }]).install(this._box);
110
110
 
111
111
  // `input-intercepted`: typing into the connected editable reveals the bar
112
112
  // (gtksearchbar.c:210-218). Captured, because the editable may be nested two
@@ -0,0 +1,111 @@
1
+ // <gtk-stack-page> — the page record a `<gtk-stack>` is AUTHORED with.
2
+ //
3
+ // NOT A WIDGET, AS UPSTREAM. `GtkStackPage` descends from `GObject.Object` (gtkstack.c:212-233):
4
+ // it carries a page's `name`, `title`, `icon-name` and attention state beside the `child` that IS
5
+ // the page. A Blueprint spells it `Stack { StackPage { name: "debugger"; child: … } }`, and
6
+ // without an element for it the shared-tree builder refused every such `.blp`.
7
+ //
8
+ // THE STACK ADOPTS IT AND KEEPS NO RECORD, the shape `<adw-view-stack>` gives
9
+ // `<adw-view-stack-page>`: `<gtk-stack>` already reads a page's six properties as attributes on
10
+ // its CHILD (see `gtk-stack.ts`), so adopting a record copies them onto the child and puts the
11
+ // child where the record was. The record leaves the tree, which is the browser half of what
12
+ // `@gjsify/adwaita-nativescript`'s `stack-page.ts` refuses after adoption: a write to a record
13
+ // the stack no longer reads would be a silent no-op.
14
+ // name / title / icon-name / needs-attention / use-underline — the same attributes
15
+ // visible — the DOM's own `hidden`, as on the child (`GtkStackPage:visible`, gtkstack.c:567)
16
+ // child — the slot `child`, or the record's one element child (a bare child IS the page)
17
+ //
18
+ // Reference: refs/gtk/gtk/gtkstack.c (GtkStackPage)
19
+ // Copyright (c) The GTK Team. LGPLv2.1+.
20
+ // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
21
+
22
+ import { bindSlottedChildren } from '../slotted-children.js';
23
+
24
+ /** The attributes a record hands to its child on adoption — `GtkStackPage`'s string/boolean properties. */
25
+ export const STACK_PAGE_ATTRIBUTES = ['name', 'title', 'icon-name', 'needs-attention', 'use-underline'] as const;
26
+
27
+ export class GtkStackPage extends HTMLElement {
28
+ static get observedAttributes() {
29
+ return [...STACK_PAGE_ATTRIBUTES];
30
+ }
31
+
32
+ /** `GtkStackPage:name` — what `visible-child-name` selects by. */
33
+ get name(): string {
34
+ return this.getAttribute('name') ?? '';
35
+ }
36
+
37
+ set name(value: string | null) {
38
+ if (value === null) this.removeAttribute('name');
39
+ else this.setAttribute('name', value);
40
+ }
41
+
42
+ // `GtkStackPage:title` is `HTMLElement.title`, which already reflects the `title` attribute.
43
+
44
+ /** `GtkStackPage:icon-name`. */
45
+ get iconName(): string | null {
46
+ return this.getAttribute('icon-name');
47
+ }
48
+
49
+ set iconName(value: string | null) {
50
+ if (value === null) this.removeAttribute('icon-name');
51
+ else this.setAttribute('icon-name', value);
52
+ }
53
+
54
+ /** `GtkStackPage:needs-attention`. */
55
+ get needsAttention(): boolean {
56
+ return this.hasAttribute('needs-attention');
57
+ }
58
+
59
+ set needsAttention(value: boolean) {
60
+ this.toggleAttribute('needs-attention', !!value);
61
+ }
62
+
63
+ /** `GtkStackPage:use-underline`. */
64
+ get useUnderline(): boolean {
65
+ return this.hasAttribute('use-underline');
66
+ }
67
+
68
+ set useUnderline(value: boolean) {
69
+ this.toggleAttribute('use-underline', !!value);
70
+ }
71
+
72
+ /** `GtkStackPage:visible` — `hidden`, as on the child the stack reads it from. */
73
+ get visible(): boolean {
74
+ return !this.hasAttribute('hidden');
75
+ }
76
+
77
+ set visible(value: boolean) {
78
+ this.toggleAttribute('hidden', !value);
79
+ }
80
+
81
+ /** `GtkStackPage:child` — the element the stack shows for this page, while not yet adopted. */
82
+ get child(): HTMLElement | null {
83
+ return pageChildOf(this);
84
+ }
85
+
86
+ connectedCallback(): void {
87
+ // `child:` in a `.blp` authors `slot="child"`, and the mount refuses a name this element
88
+ // never declared. The stack adopts on ITS connect, which runs first, so by now this
89
+ // record is usually detached; enrolling the name is all that is left to do.
90
+ bindSlottedChildren(this, [{ name: 'child', into: this }]);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * The one element a record holds: its `slot="child"` child or its only element child. A record
96
+ * with none, or with more than one, is refused by name — GtkStackPage has exactly one `child`.
97
+ */
98
+ export function pageChildOf(page: HTMLElement): HTMLElement | null {
99
+ const elements = Array.from(page.children) as HTMLElement[];
100
+ const slotted = elements.filter((child) => child.getAttribute('slot') === 'child');
101
+ const candidates = slotted.length > 0 ? slotted : elements;
102
+ if (candidates.length > 1) {
103
+ throw new Error(
104
+ `<gtk-stack-page name="${page.getAttribute('name') ?? ''}"> holds ${candidates.length} children, ` +
105
+ 'and a GtkStackPage has one `child`.',
106
+ );
107
+ }
108
+ return candidates[0] ?? null;
109
+ }
110
+
111
+ customElements.define('gtk-stack-page', GtkStackPage);