@gjsify/adwaita-web 0.64.13-next.283 → 0.64.14
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/application.d.ts +3 -3
- package/lib/types/elements/adw-application-window.d.ts +4 -4
- package/lib/types/elements/adw-bin.d.ts +13 -0
- package/lib/types/elements/adw-split-button.d.ts +19 -28
- package/lib/types/elements/adw-tab-overview.d.ts +4 -4
- package/lib/types/elements/gtk-menu-button.d.ts +30 -42
- package/lib/types/elements/gtk-popover-bin.d.ts +18 -11
- package/lib/types/elements/gtk-popover-menu-bar.d.ts +15 -8
- package/lib/types/elements/gtk-popover-menu.d.ts +13 -15
- package/lib/types/elements/menu-slot.d.ts +49 -0
- package/lib/types/elements/popover-menu.d.ts +11 -7
- package/lib/types/index.d.ts +2 -1
- package/lib/types/namespace/gio.d.ts +1 -1
- package/lib/types/widget-element.d.ts +8 -2
- package/package.json +12 -13
- package/src/adw-application-window.spec.ts +5 -2
- package/src/adw-bin.spec.ts +20 -0
- package/src/application.ts +4 -4
- package/src/elements/adw-application-window.ts +17 -21
- package/src/elements/adw-bin.ts +20 -1
- package/src/elements/adw-split-button.ts +35 -59
- package/src/elements/adw-tab-overview.ts +18 -28
- package/src/elements/gtk-menu-button.ts +60 -82
- package/src/elements/gtk-popover-bin.ts +32 -38
- package/src/elements/gtk-popover-menu-bar.ts +41 -46
- package/src/elements/gtk-popover-menu.ts +35 -54
- package/src/elements/menu-slot.ts +139 -0
- package/src/elements/popover-menu.ts +26 -16
- package/src/gio-menu.spec.ts +173 -34
- package/src/gtk-image.spec.ts +3 -2
- package/src/gtk-menu-button.spec.ts +19 -16
- package/src/gtk-popover-bin.spec.ts +6 -5
- package/src/gtk-popover-menu-bar.spec.ts +11 -6
- package/src/gtk-popover-menu.spec.ts +22 -22
- package/src/index.ts +10 -2
- package/src/keyboard-operable.spec.ts +6 -5
- package/src/menu-actions.spec.ts +6 -1
- package/src/namespace/gio.ts +19 -2
- package/src/pango-markup.spec.ts +11 -0
- package/src/pango-markup.ts +5 -2
- package/src/shared-tree-builder.ts +6 -1
- package/src/split-button.spec.ts +25 -8
- package/src/widget-element.ts +28 -7
- package/src/widget-members.spec.ts +42 -3
package/src/adw-bin.spec.ts
CHANGED
|
@@ -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
|
package/src/application.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
menuActionsFor,
|
|
16
16
|
type AdwMenuItem,
|
|
17
17
|
type AdwMenuActions,
|
|
18
|
-
type
|
|
18
|
+
type AdwMenuTree,
|
|
19
19
|
type ApplicationHost,
|
|
20
20
|
} from '@gjsify/adwaita-core';
|
|
21
21
|
import { IconTheme } from './namespace/gtk-platform.js';
|
|
@@ -63,9 +63,9 @@ export function activateWidgetAction(widget: Element, fullName: string, paramete
|
|
|
63
63
|
return activateIn(widget, fullName, parentOfElement, parameter);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/** What the items of `
|
|
67
|
-
export function menuActionsOfElement(el: Element,
|
|
68
|
-
return menuActionsFor(
|
|
66
|
+
/** What the items of `tree` read from the actions registered above `el` (ADR 0097 § 3). */
|
|
67
|
+
export function menuActionsOfElement(el: Element, tree: AdwMenuTree): AdwMenuActions {
|
|
68
|
+
return menuActionsFor(tree, el, parentOfElement);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/** What choosing `item` in a menu owned by `el` does: the action it names fires. */
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// is on, and no menubar at all when it is off.
|
|
10
10
|
//
|
|
11
11
|
// A BROWSER HAS NO GtkApplication, so the model is handed to the window instead of to
|
|
12
|
-
// an application that does not exist here. `menu-model` is the same
|
|
13
|
-
// ADR
|
|
14
|
-
//
|
|
12
|
+
// an application that does not exist here. `menu-model` is the same real `Gio.MenuModel`
|
|
13
|
+
// ADR 0128 gave `<gtk-menu-button>` and `<adw-split-button>`, taken through the same
|
|
14
|
+
// {@link MenuSlot} door, so one menu is written once. A menubar is a row of
|
|
15
15
|
// MENU LINKS, which is what `Adw.Application`'s menubar is documented to be, so a
|
|
16
16
|
// section is inlined and a bare item becomes a plain button — `flattenMenu` is the
|
|
17
17
|
// walk that decides both.
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
// show-menubar (boolean — Gtk.ApplicationWindow:show-menubar; the bar is drawn only
|
|
21
21
|
// while this is set AND the model has something in it, exactly as GTK draws no bar
|
|
22
22
|
// for an application that installed none)
|
|
23
|
-
// menu-model (the
|
|
24
|
-
// GtkApplication would own;
|
|
23
|
+
// menu-model (the menu's JSON, parsed into the model this window owns — the
|
|
24
|
+
// stand-in for the model a GtkApplication would own; `<gtk-menu-button menu-model>`'s
|
|
25
|
+
// own spelling, and a mutation of a held model redraws the bar)
|
|
25
26
|
//
|
|
26
27
|
// Extends <gtk-application-window>, as Adw.ApplicationWindow extends Gtk.ApplicationWindow,
|
|
27
28
|
// so the Gtk.Window state members come with it. The content property, the size request, the
|
|
@@ -33,22 +34,17 @@
|
|
|
33
34
|
// Copyright (c) 2021-2023 Alice Mikhaylenko / Purism SPC (libadwaita). LGPLv2.1+
|
|
34
35
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
35
36
|
|
|
36
|
-
import {
|
|
37
|
-
flattenMenu,
|
|
38
|
-
normalizeMenuModel,
|
|
39
|
-
parseMenuModel,
|
|
40
|
-
type AdwMenuInput,
|
|
41
|
-
type AdwMenuModel,
|
|
42
|
-
} from '@gjsify/adwaita-core';
|
|
37
|
+
import { buildMenu, flattenMenu, type AdwMenuModel } from '@gjsify/adwaita-core';
|
|
43
38
|
|
|
44
39
|
import type { AdwBreakpoint } from '@gjsify/adwaita-core';
|
|
45
40
|
import type { AdwSlot } from '../slotted-children.js';
|
|
46
41
|
import { AdwWindowFeatures } from './adw-window-features.js';
|
|
42
|
+
import { MenuSlot } from './menu-slot.js';
|
|
47
43
|
import { GtkApplicationWindow } from './gtk-application-window.js';
|
|
48
44
|
|
|
49
45
|
export class AdwApplicationWindow extends GtkApplicationWindow {
|
|
50
46
|
private readonly _features = new AdwWindowFeatures(this);
|
|
51
|
-
private
|
|
47
|
+
private readonly _slot = new MenuSlot(() => this._renderMenubar());
|
|
52
48
|
private _menubarEl: HTMLDivElement | null = null;
|
|
53
49
|
|
|
54
50
|
// The base's list BY NAME, not `super.observedAttributes`: `AdwPasswordEntryRow`
|
|
@@ -110,12 +106,12 @@ export class AdwApplicationWindow extends GtkApplicationWindow {
|
|
|
110
106
|
}
|
|
111
107
|
|
|
112
108
|
/** The menubar's model — what a GtkApplication would own here. */
|
|
113
|
-
get menuModel(): AdwMenuModel {
|
|
114
|
-
return this.
|
|
109
|
+
get menuModel(): AdwMenuModel | null {
|
|
110
|
+
return this._slot.model;
|
|
115
111
|
}
|
|
116
112
|
|
|
117
|
-
set menuModel(value:
|
|
118
|
-
this.
|
|
113
|
+
set menuModel(value: AdwMenuModel | string | null) {
|
|
114
|
+
this._slot.write(value);
|
|
119
115
|
this._renderMenubar();
|
|
120
116
|
}
|
|
121
117
|
|
|
@@ -145,7 +141,7 @@ export class AdwApplicationWindow extends GtkApplicationWindow {
|
|
|
145
141
|
// The attribute is the declarative form and it wins when it changes — the same
|
|
146
142
|
// precedence `<gtk-menu-button>` gives `menu-model`, through the same parser.
|
|
147
143
|
if (name === 'menu-model') {
|
|
148
|
-
this.
|
|
144
|
+
this._slot.writeMarkup(this.getAttribute('menu-model'));
|
|
149
145
|
this._renderMenubar();
|
|
150
146
|
}
|
|
151
147
|
}
|
|
@@ -197,16 +193,16 @@ export class AdwApplicationWindow extends GtkApplicationWindow {
|
|
|
197
193
|
bar.replaceChildren();
|
|
198
194
|
// GTK draws no menubar for an application that installed none, so neither does
|
|
199
195
|
// this: both the flag and a model with rows in it are required.
|
|
200
|
-
bar.hidden = !this.showMenubar || this.
|
|
196
|
+
bar.hidden = !this.showMenubar || this._slot.isEmpty;
|
|
201
197
|
|
|
202
|
-
for (const row of flattenMenu(this.
|
|
198
|
+
for (const row of flattenMenu(this._slot.tree)) {
|
|
203
199
|
const node = row.node;
|
|
204
200
|
if (node.kind === 'submenu') {
|
|
205
201
|
// A link is a menu the user OPENS — a button with that menu on it,
|
|
206
202
|
// which is the whole of what a Gtk.PopoverMenuBar row is.
|
|
207
203
|
const button = document.createElement('gtk-menu-button');
|
|
208
204
|
button.setAttribute('menu-title', node.label);
|
|
209
|
-
(button as unknown as { menuModel:
|
|
205
|
+
(button as unknown as { menuModel: AdwMenuModel | null }).menuModel = buildMenu(node.items);
|
|
210
206
|
bar.appendChild(button);
|
|
211
207
|
continue;
|
|
212
208
|
}
|
package/src/elements/adw-bin.ts
CHANGED
|
@@ -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
|
|
@@ -37,13 +37,8 @@
|
|
|
37
37
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
38
38
|
|
|
39
39
|
import {
|
|
40
|
-
ADW_MENU_SURFACE_WEB,
|
|
41
40
|
SplitButtonState,
|
|
42
|
-
assertMenuRenderable,
|
|
43
41
|
isSplitButtonDirection,
|
|
44
|
-
menuRefusals,
|
|
45
|
-
normalizeMenuModel,
|
|
46
|
-
parseMenuModel,
|
|
47
42
|
resolveDropdownTooltip,
|
|
48
43
|
splitButtonArrowIcon,
|
|
49
44
|
splitButtonPopupDirection,
|
|
@@ -52,13 +47,13 @@ import {
|
|
|
52
47
|
import type {
|
|
53
48
|
AdwArrowIcon,
|
|
54
49
|
AdwMenuActions,
|
|
55
|
-
AdwMenuInput,
|
|
56
50
|
AdwMenuModel,
|
|
57
51
|
SplitButtonChange,
|
|
58
52
|
SplitButtonDirection,
|
|
59
53
|
} from '@gjsify/adwaita-core';
|
|
60
54
|
import { activateElementMenuItem, menuActionsOfElement } from '../application.js';
|
|
61
55
|
|
|
56
|
+
import { MenuSlot } from './menu-slot.js';
|
|
62
57
|
import { PopoverMenuView } from './popover-menu.js';
|
|
63
58
|
|
|
64
59
|
// SIDE-EFFECT import, deliberately separate from the type import below: it is
|
|
@@ -121,6 +116,15 @@ const POPOVER_POSITIONS = {
|
|
|
121
116
|
|
|
122
117
|
export class AdwSplitButton extends WidgetElement {
|
|
123
118
|
private readonly _state = new SplitButtonState();
|
|
119
|
+
/**
|
|
120
|
+
* The model's door, tree snapshot and `items-changed` subscription (ADR 0128). The
|
|
121
|
+
* STATE still owns the menu as a property — `dropdownEnabled` is derived from it and
|
|
122
|
+
* the notify order is libadwaita's — so every write here goes through the slot FIRST
|
|
123
|
+
* (which is what refuses it and snapshots it) and is then handed to the state.
|
|
124
|
+
*/
|
|
125
|
+
private readonly _slot = new MenuSlot(() => {
|
|
126
|
+
if (this._initialized) this._render();
|
|
127
|
+
});
|
|
124
128
|
private _actionEl!: HTMLButtonElement;
|
|
125
129
|
private _dropdownEl!: HTMLButtonElement;
|
|
126
130
|
private _arrowEl!: GtkImage;
|
|
@@ -180,60 +184,29 @@ export class AdwSplitButton extends WidgetElement {
|
|
|
180
184
|
this.removeAttribute('label');
|
|
181
185
|
}
|
|
182
186
|
|
|
183
|
-
/** The menu, normalised (ADR 0042). Empty when the dropdown has none. */
|
|
184
|
-
get menuModel(): AdwMenuModel {
|
|
185
|
-
return this._state.menuModel ?? [];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
187
|
/**
|
|
189
|
-
*
|
|
190
|
-
* descriptors, sections, submenus.
|
|
188
|
+
* The menu the dropdown pops up, by identity, or `null` when it has none (ADR 0128).
|
|
191
189
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
190
|
+
* `null` is the ONLY spelling of "no menu" now: an empty model is a menu, so an empty
|
|
191
|
+
* one leaves the dropdown enabled exactly as it does in GTK, where a non-null model
|
|
192
|
+
* still builds a popover (measured, `MENU_MODEL_DOOR_VECTORS`).
|
|
195
193
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// had rather than one it cannot draw.
|
|
199
|
-
this._state.setMenuModel(this._acceptMenu(value));
|
|
200
|
-
if (this._initialized) this._render();
|
|
194
|
+
get menuModel(): AdwMenuModel | null {
|
|
195
|
+
return this._state.menuModel;
|
|
201
196
|
}
|
|
202
197
|
|
|
203
198
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* The property setter is not the only way a menu arrives: an attribute is, and the
|
|
207
|
-
* first cut refused only the setter, so a `custom` item written in markup drew
|
|
208
|
-
* exactly the row `assertMenuRenderable` exists to prevent — one that reads like a
|
|
209
|
-
* command and does nothing.
|
|
210
|
-
*
|
|
211
|
-
* THE TWO DOORS REFUSE DIFFERENTLY, and the difference is which side can hear it.
|
|
212
|
-
* A property assignment is a CALL, so it throws and the caller can catch. An
|
|
213
|
-
* attribute is MARKUP, parsed by the browser: a throw from `connectedCallback` is
|
|
214
|
-
* not delivered to whoever appended the element, it is reported as an uncaught
|
|
215
|
-
* page error — measured, it broke `adwaita-upgrade-order.spec.ts`, which counts
|
|
216
|
-
* exactly those. Nobody can handle it and everybody else pays for it. So the
|
|
217
|
-
* attribute path REFUSES THE MENU (no menu, and a menu-less dropdown is
|
|
218
|
-
* insensitive, which is visible) and says why on `console.error`.
|
|
199
|
+
* Take a real model, a JSON string in the portable spelling, or `null`.
|
|
219
200
|
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
201
|
+
* The REFUSAL is at the assignment rather than at render time: a `custom` item names
|
|
202
|
+
* an application widget this surface cannot host, and discovering that as a blank row
|
|
203
|
+
* three interactions later is what the refusal exists to prevent. {@link MenuSlot}
|
|
204
|
+
* owns the door; the state is handed only a menu this surface agreed to draw.
|
|
223
205
|
*/
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return model;
|
|
229
|
-
}
|
|
230
|
-
const refusals = menuRefusals(model, ADW_MENU_SURFACE_WEB);
|
|
231
|
-
if (refusals.length === 0) return model;
|
|
232
|
-
// `console.error` is the browser's own channel for "this is wrong, the page
|
|
233
|
-
// continues" — and the alternative here is a row that lies.
|
|
234
|
-
console.error(`${ADW_MENU_SURFACE_WEB.name} cannot render this menu, so it has none:`);
|
|
235
|
-
for (const refusal of refusals) console.error(` [${refusal.path.join('.')}] ${refusal.message}`);
|
|
236
|
-
return [];
|
|
206
|
+
set menuModel(value: AdwMenuModel | string | null) {
|
|
207
|
+
this._slot.write(value);
|
|
208
|
+
this._state.setMenuModel(this._slot.model);
|
|
209
|
+
if (this._initialized) this._render();
|
|
237
210
|
}
|
|
238
211
|
|
|
239
212
|
/**
|
|
@@ -387,7 +360,8 @@ export class AdwSplitButton extends WidgetElement {
|
|
|
387
360
|
this._state.setDropdownTooltip(value ?? '');
|
|
388
361
|
break;
|
|
389
362
|
case 'menu-model':
|
|
390
|
-
this.
|
|
363
|
+
this._slot.writeMarkup(value);
|
|
364
|
+
this._state.setMenuModel(this._slot.model);
|
|
391
365
|
break;
|
|
392
366
|
default:
|
|
393
367
|
break;
|
|
@@ -409,10 +383,12 @@ export class AdwSplitButton extends WidgetElement {
|
|
|
409
383
|
const direction = this.getAttribute('direction');
|
|
410
384
|
if (isSplitButtonDirection(direction)) this._state.setDirection(direction);
|
|
411
385
|
this._state.setDropdownTooltip(this.getAttribute('dropdown-tooltip') ?? '');
|
|
412
|
-
// The `menuModel` property may have been set before the element upgraded;
|
|
413
|
-
//
|
|
386
|
+
// The `menuModel` property may have been set before the element upgraded; the
|
|
387
|
+
// attribute only seeds a widget that has NO menu — which since ADR 0128 means a
|
|
388
|
+
// null one, an empty model being a menu an author may have meant.
|
|
414
389
|
if (this._state.menuModel === null) {
|
|
415
|
-
this.
|
|
390
|
+
this._slot.writeMarkup(this.getAttribute('menu-model'));
|
|
391
|
+
this._state.setMenuModel(this._slot.model);
|
|
416
392
|
}
|
|
417
393
|
}
|
|
418
394
|
|
|
@@ -528,9 +504,9 @@ export class AdwSplitButton extends WidgetElement {
|
|
|
528
504
|
|
|
529
505
|
private _renderMenu(): void {
|
|
530
506
|
this._menuEl.open = this._state.open;
|
|
531
|
-
const
|
|
532
|
-
this._menuView.
|
|
533
|
-
this._menuView.setActions(this._actions ?? menuActionsOfElement(this,
|
|
507
|
+
const tree = this._slot.tree;
|
|
508
|
+
this._menuView.setMenuTree(tree);
|
|
509
|
+
this._menuView.setActions(this._actions ?? menuActionsOfElement(this, tree));
|
|
534
510
|
this._menuView.render();
|
|
535
511
|
}
|
|
536
512
|
}
|
|
@@ -46,8 +46,9 @@
|
|
|
46
46
|
//
|
|
47
47
|
// `view` is a widget REFERENCE (`Adw.TabView`), so the attribute is the element id
|
|
48
48
|
// `<adw-tab-button view>` also takes, while `view`/`setView` take the element.
|
|
49
|
-
// `secondary-menu` is a `GMenuModel
|
|
50
|
-
// attribute takes its JSON, which is `<gtk-menu-button menu-model>`'s own
|
|
49
|
+
// `secondary-menu` is a `GMenuModel` and takes a real one (ADR 0128): the property takes
|
|
50
|
+
// the model, the attribute takes its JSON, which is `<gtk-menu-button menu-model>`'s own
|
|
51
|
+
// spelling, and {@link MenuSlot} is the door both go through.
|
|
51
52
|
//
|
|
52
53
|
// A11Y: the overview is a `role="grid"` of `role="gridcell"` thumbnails under a
|
|
53
54
|
// header bar, and the pinned grid is a second grid — C's `AdwTabGrid` is a
|
|
@@ -68,16 +69,9 @@
|
|
|
68
69
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web; the icon,
|
|
69
70
|
// indicator and portrait nodes are <gtk-image> and the grids are <div role="grid">.
|
|
70
71
|
|
|
71
|
-
import {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
tabSearchMatches,
|
|
75
|
-
tabTooltip,
|
|
76
|
-
type AdwMenuInput,
|
|
77
|
-
type AdwMenuModel,
|
|
78
|
-
} from '@gjsify/adwaita-core';
|
|
79
|
-
import { normalizeMenuModel } from '@gjsify/adwaita-core';
|
|
80
|
-
import { assertMenuRenderable, ADW_MENU_SURFACE_WEB } from '@gjsify/adwaita-core';
|
|
72
|
+
import { gridColumns, gridNavigate, tabSearchMatches, tabTooltip, type AdwMenuModel } from '@gjsify/adwaita-core';
|
|
73
|
+
|
|
74
|
+
import { MenuSlot } from './menu-slot.js';
|
|
81
75
|
|
|
82
76
|
import type { AdwTabView, AdwTabViewPage } from './adw-tab-view.js';
|
|
83
77
|
import { createGtkImage, type GtkImage } from './gtk-image.js';
|
|
@@ -119,7 +113,7 @@ export class AdwTabOverview extends WidgetElement {
|
|
|
119
113
|
private readonly _thumbs = new Map<string, Thumbnail>();
|
|
120
114
|
|
|
121
115
|
private _view: AdwTabView | null = null;
|
|
122
|
-
private
|
|
116
|
+
private readonly _slot = new MenuSlot(() => this._applyMenu());
|
|
123
117
|
private _searchActive = false;
|
|
124
118
|
private _initialized = false;
|
|
125
119
|
/** The last visibility actually painted, so `notify::open` fires on a real change. */
|
|
@@ -225,16 +219,14 @@ export class AdwTabOverview extends WidgetElement {
|
|
|
225
219
|
}
|
|
226
220
|
|
|
227
221
|
/** `Adw.TabOverview:secondary-menu` — the portal menu of extra actions. */
|
|
228
|
-
get secondaryMenu(): AdwMenuModel {
|
|
229
|
-
return this.
|
|
222
|
+
get secondaryMenu(): AdwMenuModel | null {
|
|
223
|
+
return this._slot.model;
|
|
230
224
|
}
|
|
231
225
|
|
|
232
|
-
set secondaryMenu(value:
|
|
233
|
-
const model = normalizeMenuModel(value);
|
|
226
|
+
set secondaryMenu(value: AdwMenuModel | string | null) {
|
|
234
227
|
// The property setter is a CALL, so a `custom` item throws here where the caller
|
|
235
228
|
// can catch it — `<gtk-menu-button>`'s own rule, which keeps the two doors honest.
|
|
236
|
-
|
|
237
|
-
this._model = model;
|
|
229
|
+
this._slot.write(value);
|
|
238
230
|
this._applyMenu();
|
|
239
231
|
}
|
|
240
232
|
|
|
@@ -398,7 +390,8 @@ export class AdwTabOverview extends WidgetElement {
|
|
|
398
390
|
|
|
399
391
|
const declaredMenu = this.getAttribute('secondary-menu');
|
|
400
392
|
if (declaredMenu !== null) {
|
|
401
|
-
this.
|
|
393
|
+
this._slot.writeMarkup(declaredMenu);
|
|
394
|
+
this._applyMenu();
|
|
402
395
|
this.removeAttribute('secondary-menu');
|
|
403
396
|
}
|
|
404
397
|
|
|
@@ -423,11 +416,8 @@ export class AdwTabOverview extends WidgetElement {
|
|
|
423
416
|
if (value === null) return;
|
|
424
417
|
// An attribute is MARKUP: a `custom` item here is reported, not thrown
|
|
425
418
|
// (adw-sidebar/`gtk-menu-button`'s rule for the same two doors).
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
} catch (error) {
|
|
429
|
-
console.error(`<adw-tab-overview secondary-menu> is not valid JSON: ${String(error)}`);
|
|
430
|
-
}
|
|
419
|
+
this._slot.writeMarkup(value);
|
|
420
|
+
this._applyMenu();
|
|
431
421
|
return;
|
|
432
422
|
}
|
|
433
423
|
case 'enable-new-tab':
|
|
@@ -557,7 +547,7 @@ export class AdwTabOverview extends WidgetElement {
|
|
|
557
547
|
* slot is where an application puts its own controls.
|
|
558
548
|
*/
|
|
559
549
|
private _applyHeaderBar(): void {
|
|
560
|
-
const hasMenu = this.
|
|
550
|
+
const hasMenu = !this._slot.isEmpty;
|
|
561
551
|
this._headerEl.hidden = !(
|
|
562
552
|
this.enableSearch ||
|
|
563
553
|
hasMenu ||
|
|
@@ -572,8 +562,8 @@ export class AdwTabOverview extends WidgetElement {
|
|
|
572
562
|
}
|
|
573
563
|
|
|
574
564
|
private _applyMenu(): void {
|
|
575
|
-
const view = this._menuBtn as HTMLElement & { menuModel?: AdwMenuModel };
|
|
576
|
-
if (this.
|
|
565
|
+
const view = this._menuBtn as HTMLElement & { menuModel?: AdwMenuModel | null };
|
|
566
|
+
if (this._slot.model !== null) view.menuModel = this._slot.model;
|
|
577
567
|
this._applyHeaderBar();
|
|
578
568
|
}
|
|
579
569
|
|