@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.
Files changed (44) hide show
  1. package/lib/types/application.d.ts +3 -3
  2. package/lib/types/elements/adw-application-window.d.ts +4 -4
  3. package/lib/types/elements/adw-bin.d.ts +13 -0
  4. package/lib/types/elements/adw-split-button.d.ts +19 -28
  5. package/lib/types/elements/adw-tab-overview.d.ts +4 -4
  6. package/lib/types/elements/gtk-menu-button.d.ts +30 -42
  7. package/lib/types/elements/gtk-popover-bin.d.ts +18 -11
  8. package/lib/types/elements/gtk-popover-menu-bar.d.ts +15 -8
  9. package/lib/types/elements/gtk-popover-menu.d.ts +13 -15
  10. package/lib/types/elements/menu-slot.d.ts +49 -0
  11. package/lib/types/elements/popover-menu.d.ts +11 -7
  12. package/lib/types/index.d.ts +2 -1
  13. package/lib/types/namespace/gio.d.ts +1 -1
  14. package/lib/types/widget-element.d.ts +8 -2
  15. package/package.json +12 -13
  16. package/src/adw-application-window.spec.ts +5 -2
  17. package/src/adw-bin.spec.ts +20 -0
  18. package/src/application.ts +4 -4
  19. package/src/elements/adw-application-window.ts +17 -21
  20. package/src/elements/adw-bin.ts +20 -1
  21. package/src/elements/adw-split-button.ts +35 -59
  22. package/src/elements/adw-tab-overview.ts +18 -28
  23. package/src/elements/gtk-menu-button.ts +60 -82
  24. package/src/elements/gtk-popover-bin.ts +32 -38
  25. package/src/elements/gtk-popover-menu-bar.ts +41 -46
  26. package/src/elements/gtk-popover-menu.ts +35 -54
  27. package/src/elements/menu-slot.ts +139 -0
  28. package/src/elements/popover-menu.ts +26 -16
  29. package/src/gio-menu.spec.ts +173 -34
  30. package/src/gtk-image.spec.ts +3 -2
  31. package/src/gtk-menu-button.spec.ts +19 -16
  32. package/src/gtk-popover-bin.spec.ts +6 -5
  33. package/src/gtk-popover-menu-bar.spec.ts +11 -6
  34. package/src/gtk-popover-menu.spec.ts +22 -22
  35. package/src/index.ts +10 -2
  36. package/src/keyboard-operable.spec.ts +6 -5
  37. package/src/menu-actions.spec.ts +6 -1
  38. package/src/namespace/gio.ts +19 -2
  39. package/src/pango-markup.spec.ts +11 -0
  40. package/src/pango-markup.ts +5 -2
  41. package/src/shared-tree-builder.ts +6 -1
  42. package/src/split-button.spec.ts +25 -8
  43. package/src/widget-element.ts +28 -7
  44. package/src/widget-members.spec.ts +42 -3
@@ -40,9 +40,9 @@
40
40
  // is its name. `gtk_popover_menu_open_submenu(popover, "main")` is
41
41
  // the way back, so this is writeable and readable.
42
42
  // Properties:
43
- // menuModel — the model, normalised (get/set; a `custom` item throws here). Takes
44
- // an already-normalised `AdwMenuModel` too, which is what a menu bar
45
- // hands over for one of its submenus.
43
+ // menuModel — the real `Gio.MenuModel` (ADR 0128), or `null` (get/set; a `custom`
44
+ // item throws here). A menu bar hands one of these over for a submenu
45
+ // of its own; a JSON string is the authored spelling.
46
46
  // actions — what the action group publishes about the actions the model names
47
47
  // (ADR 0042), the source of every row's enabled/checked state.
48
48
  // visibleSubmenu — the open page's name (get/set).
@@ -72,26 +72,22 @@
72
72
  // Copyright (c) GNOME contributors (GTK, libadwaita). LGPLv2.1+
73
73
  // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
74
74
 
75
- import {
76
- ADW_MENU_SURFACE_WEB,
77
- assertMenuRenderable,
78
- menuItemAt,
79
- menuNodeAt,
80
- menuRefusals,
81
- normalizeMenuModel,
82
- parseMenuModel,
83
- } from '@gjsify/adwaita-core';
75
+ import { menuItemAt, menuNodeAt } from '@gjsify/adwaita-core';
84
76
  import { activateElementMenuItem, menuActionsOfElement } from '../application.js';
85
- import type { AdwMenuActions, AdwMenuInput, AdwMenuModel, AdwMenuPath } from '@gjsify/adwaita-core';
77
+ import type { AdwMenuActions, AdwMenuModel, AdwMenuPath, AdwMenuTree } from '@gjsify/adwaita-core';
86
78
 
87
79
  import { GtkPopover } from './gtk-popover.js';
80
+ import { MenuSlot } from './menu-slot.js';
88
81
  import { PopoverMenuView } from './popover-menu.js';
89
82
 
90
83
  /** The name the ROOT page carries — `gtk_popover_menu_open_submenu`'s own "main". */
91
84
  const MAIN_PAGE = 'main';
92
85
 
93
86
  export class GtkPopoverMenu extends GtkPopover {
94
- private _model: AdwMenuModel = [];
87
+ /** The model, its tree snapshot and its `items-changed` subscription (ADR 0128). */
88
+ private readonly _slot = new MenuSlot(() => {
89
+ if (this._menuInitialized) this._renderMenu();
90
+ });
95
91
  private _actions: AdwMenuActions | null = null;
96
92
  private _menuView!: PopoverMenuView;
97
93
  /** Named apart from `GtkPopover`'s own: two `private` fields of one name are two fields. */
@@ -101,26 +97,23 @@ export class GtkPopoverMenu extends GtkPopover {
101
97
  return [...GtkPopover.observedAttributes, 'menu-model', 'menu-title', 'visible-submenu'];
102
98
  }
103
99
 
104
- /** The menu, normalised (ADR 0042). */
105
- get menuModel(): AdwMenuModel {
106
- return this._model;
100
+ /** The menu this popover draws, by identity, or `null` (ADR 0128). */
101
+ get menuModel(): AdwMenuModel | null {
102
+ return this._slot.model;
107
103
  }
108
104
 
109
105
  /**
110
- * Set the menu from anything the portable model accepts, refusing a `custom` item
111
- * HERE — at the assignment, where it is a call the caller can catch. An ATTRIBUTE is
112
- * markup the browser parses, so the attribute path refuses the whole menu instead and
113
- * says why on `console.error`, exactly as `<gtk-menu-button>` does.
106
+ * Take a real model, a JSON string in the portable spelling, or `null`, refusing a
107
+ * `custom` item HERE — at the assignment, where it is a call the caller can catch.
108
+ * An ATTRIBUTE is markup the browser parses, so that path refuses the whole menu and
109
+ * says why on `console.error`; {@link MenuSlot} owns both doors.
114
110
  *
115
- * `AdwMenuModel` is in the union because a menu bar hands over a submenu's own `items`
116
- * untouched, and `normalizeMenuModel` is documented to be IDEMPOTENT over an
117
- * already-normalised node — that is what lets a renderer hand a model it is holding
118
- * straight back. The one cast is where the core's parameter type (`AdwMenuInput`, whose
119
- * entries are the AUTHORED shapes) is narrower than the behaviour it documents.
111
+ * A REAL MODEL IS WHAT A MENU BAR HANDS OVER for one of its submenus, and the model
112
+ * it hands over is `buildMenu(node.items)` — a menu of its own rather than this
113
+ * element's authored one, so the two cannot overwrite each other.
120
114
  */
121
- set menuModel(value: AdwMenuInput | AdwMenuModel) {
122
- this._model = normalizeMenuModel(value as AdwMenuInput);
123
- assertMenuRenderable(this._model, ADW_MENU_SURFACE_WEB);
115
+ set menuModel(value: AdwMenuModel | string | null) {
116
+ this._slot.write(value);
124
117
  if (this._menuInitialized) this._renderMenu();
125
118
  }
126
119
 
@@ -183,10 +176,10 @@ export class GtkPopoverMenu extends GtkPopover {
183
176
  this._menuView = new PopoverMenuView(this, 'adw-popover-menu', (path) => this._activate(path));
184
177
  this._menuView.setActions(this._actions);
185
178
 
186
- if (this._model.length === 0) this._model = this._parseMenuAttr();
187
- // The VIEW is what draws, and it holds the model it walks — seeding `_model`
188
- // directly and rendering without handing the view the same value would leave it
189
- // drawing an EMPTY page over a model the element believes it has.
179
+ if (this._slot.model === null) this._slot.writeMarkup(this.getAttribute('menu-model'));
180
+ // The VIEW is what draws, and it holds the TREE it walks — seeding the slot
181
+ // without handing the view the same snapshot would leave it drawing an EMPTY
182
+ // page over a model the element believes it has.
190
183
  this._renderMenu();
191
184
 
192
185
  // The visible page RESETS when the popover closes, as `gtk_popover_menu_show` does
@@ -194,7 +187,7 @@ export class GtkPopoverMenu extends GtkPopover {
194
187
  // a menu that reopens three levels deep is one the reader cannot get out of.
195
188
  this.subscribe((open) => {
196
189
  if (open) {
197
- this._menuView.setActions(this._actions ?? menuActionsOfElement(this, this._model));
190
+ this._menuView.setActions(this._actions ?? menuActionsOfElement(this, this._slot.tree));
198
191
  this._menuView.render();
199
192
  this._menuView.focusableRows[0]?.focus();
200
193
  } else this._menuView.reset();
@@ -205,7 +198,7 @@ export class GtkPopoverMenu extends GtkPopover {
205
198
  super.attributeChangedCallback(name, previous, value);
206
199
  if (!this._menuInitialized) return;
207
200
  if (name === 'menu-model') {
208
- this._model = this._parseMenuAttr();
201
+ this._slot.writeMarkup(this.getAttribute('menu-model'));
209
202
  this._renderMenu();
210
203
  return;
211
204
  }
@@ -258,7 +251,7 @@ export class GtkPopoverMenu extends GtkPopover {
258
251
  } else {
259
252
  const path = this._findSubmenu(name);
260
253
  if (path === null) return false;
261
- this._menuView.setMenuModel(this._model);
254
+ this._menuView.setMenuTree(this._slot.tree);
262
255
  this._menuView.showPage(path);
263
256
  }
264
257
  this._menuView.render();
@@ -276,12 +269,12 @@ export class GtkPopoverMenu extends GtkPopover {
276
269
  private _openPageLabel(): string | null {
277
270
  const path = this._menuView.page;
278
271
  if (path.length === 0) return null;
279
- const node = menuNodeAt(this._model, path);
272
+ const node = menuNodeAt(this._slot.tree, path);
280
273
  return node !== null && node.kind !== 'item' ? node.label : null;
281
274
  }
282
275
 
283
276
  private _findSubmenu(label: string): AdwMenuPath | null {
284
- const walk = (nodes: AdwMenuModel, path: AdwMenuPath): AdwMenuPath | null => {
277
+ const walk = (nodes: AdwMenuTree, path: AdwMenuPath): AdwMenuPath | null => {
285
278
  for (const [index, node] of nodes.entries()) {
286
279
  if (node.kind === 'item') continue;
287
280
  const here = [...path, index];
@@ -291,7 +284,7 @@ export class GtkPopoverMenu extends GtkPopover {
291
284
  }
292
285
  return null;
293
286
  };
294
- return walk(this._model, []);
287
+ return walk(this._slot.tree, []);
295
288
  }
296
289
 
297
290
  /**
@@ -313,7 +306,7 @@ export class GtkPopoverMenu extends GtkPopover {
313
306
  * open by choosing from it.
314
307
  */
315
308
  private _activate(path: AdwMenuPath): void {
316
- const target = menuItemAt(this._model, path);
309
+ const target = menuItemAt(this._slot.tree, path);
317
310
  this.popdown();
318
311
  this.anchor?.focus();
319
312
  if (target === null) return;
@@ -327,22 +320,10 @@ export class GtkPopoverMenu extends GtkPopover {
327
320
  }
328
321
 
329
322
  private _renderMenu(): void {
330
- this._menuView.setMenuModel(this._model);
323
+ this._menuView.setMenuTree(this._slot.tree);
331
324
  this._menuView.setTitle(this.menuTitle);
332
325
  this._menuView.render();
333
- if (this._model.length === 0) this.popdown();
334
- }
335
-
336
- private _parseMenuAttr(): AdwMenuModel {
337
- const model = normalizeMenuModel(parseMenuModel(this.getAttribute('menu-model')));
338
- const refusals = menuRefusals(model, ADW_MENU_SURFACE_WEB);
339
- if (refusals.length === 0) return model;
340
- // A throw from `connectedCallback` is reported as an uncaught PAGE error and
341
- // reaches nobody, so markup refuses the menu and says why. See
342
- // `<gtk-menu-button>`'s `_acceptMenu` for the same two doors.
343
- console.error(`${ADW_MENU_SURFACE_WEB.name} cannot render this menu, so it has none:`);
344
- for (const refusal of refusals) console.error(` [${refusal.path.join('.')}] ${refusal.message}`);
345
- return [];
326
+ if (this._slot.isEmpty) this.popdown();
346
327
  }
347
328
  }
348
329
 
@@ -0,0 +1,139 @@
1
+ // The `menu-model` SLOT the menu-bearing elements of this package share.
2
+ //
3
+ // ADR 0128 made `menu-model` a real `Gio.MenuModel`, and that turned one assignment into
4
+ // four steps that only work in one order:
5
+ //
6
+ // 1. read the write — `readMenuModelWrite` is the core's one door, and it is what tells a
7
+ // real model apart from the JSON string an attribute carries (ADR 0128 § 4);
8
+ // 2. REFUSE what this surface cannot draw, before the element renders it;
9
+ // 3. take a TREE SNAPSHOT, because every walker (`flattenMenu`, `menuNodeAt`,
10
+ // `menuItemAt`, `menuRefusals`) takes an `AdwMenuTree` and not a model — one snapshot
11
+ // per change rather than a chain of `get_item_link` calls per row;
12
+ // 4. connect `items-changed` — on the root AND on every section and submenu it links,
13
+ // which is what `watchMenuModel` does, because a link's emission does NOT bubble.
14
+ //
15
+ // THE STEP THAT IS EASIEST TO GET WRONG IS THE LAST ONE: the watcher has to be RE-RUN after
16
+ // every snapshot, since a mutation can add a section that was not there to connect to. Six
17
+ // elements here need all four steps, so the arithmetic lives once and there is no sixth
18
+ // place to forget it.
19
+ //
20
+ // THE TWO WRITES REFUSE DIFFERENTLY, and the difference is which side can hear it, exactly
21
+ // as it was before the model became real. {@link MenuSlot.write} is a property assignment,
22
+ // so it is a CALL and it THROWS; {@link MenuSlot.writeMarkup} is an attribute the browser
23
+ // parsed, where a throw out of `connectedCallback` reaches nobody and is reported as an
24
+ // uncaught page error (measured: it broke `adwaita-upgrade-order.spec.ts`, which counts
25
+ // exactly those) — so markup refuses the MENU and says why on `console.error`.
26
+ //
27
+ // THE WATCHER'S LIFETIME IS THE ELEMENT'S, with no `release()` to call. GTK disconnects in
28
+ // `dispose`, and a custom element has no dispose: it has `disconnectedCallback`, which also
29
+ // fires when a node is MOVED, and an element re-inserted after dropping its watcher would
30
+ // be deaf to the menu it still holds. A held model keeping its element alive is the same
31
+ // shape GTK has, where the widget owns a reference to the model it reads.
32
+
33
+ import {
34
+ ADW_MENU_SURFACE_WEB,
35
+ GjsifyMenu,
36
+ assertMenuRenderable,
37
+ menuRefusals,
38
+ menuTree,
39
+ readMenuModelWrite,
40
+ watchMenuModel,
41
+ } from '@gjsify/adwaita-core';
42
+ import type { AdwMenuTree, GioMenuModel } from '@gjsify/adwaita-core';
43
+
44
+ export class MenuSlot {
45
+ /**
46
+ * The menu this element OWNS, so an edit to the same `menu-model="[…]"` attribute is a
47
+ * content change rather than a new menu — the popover already open follows it instead of
48
+ * being rebuilt (ADR 0128 § 5, the same rule `GjsifyOptionList` has for a list).
49
+ */
50
+ private readonly _authored = new GjsifyMenu();
51
+ private _model: GioMenuModel | null = null;
52
+ private _tree: AdwMenuTree = [];
53
+ private _unwatch: (() => void) | null = null;
54
+
55
+ /** @param onItemsChanged what to do when the HELD menu mutates under the element. */
56
+ constructor(private readonly onItemsChanged: () => void) {}
57
+
58
+ /** The model itself, by identity — what `menuModel` answers and what a child is handed. */
59
+ get model(): GioMenuModel | null {
60
+ return this._model;
61
+ }
62
+
63
+ /** The snapshot every walker reads. Re-taken on each `items-changed`, never aliased. */
64
+ get tree(): AdwMenuTree {
65
+ return this._tree;
66
+ }
67
+
68
+ /**
69
+ * Whether there is anything to show — NOT whether there is a menu.
70
+ *
71
+ * `null` and an empty `Gio.Menu` are different menus and GTK treats them differently: an
72
+ * empty non-null model still builds a `GtkPopoverMenu` and still dissociates an
73
+ * application-set popover (measured, `MENU_MODEL_DOOR_VECTORS`). This answers the
74
+ * question the ROWS ask — "is there a row to draw" — and the elements ask
75
+ * `model === null` where GTK asks about the model.
76
+ */
77
+ get isEmpty(): boolean {
78
+ return this._tree.length === 0;
79
+ }
80
+
81
+ /**
82
+ * A PROPERTY write: a real model by identity, a JSON string into the authored menu,
83
+ * `null` for no menu — and a `TypeError` for an array, which this door no longer takes
84
+ * (`buildMenu` is how an array becomes a model now).
85
+ *
86
+ * A REFUSAL PUTS THE AUTHORED MENU BACK, because the door has already reconciled it by
87
+ * the time the tree can be refused: without the restore a refused string would leave the
88
+ * element holding a menu it threw over, and the next render would draw it.
89
+ */
90
+ write(value: unknown): void {
91
+ const before = this._tree;
92
+ const next = readMenuModelWrite(value, this._authored);
93
+ try {
94
+ if (next !== null) assertMenuRenderable(menuTree(next), ADW_MENU_SURFACE_WEB);
95
+ } catch (error) {
96
+ if (next === this._authored) this._authored.reconcile(before);
97
+ throw error;
98
+ }
99
+ this._adopt(next);
100
+ }
101
+
102
+ /**
103
+ * The `menu-model` ATTRIBUTE, through the same door. A `null` attribute is no menu.
104
+ *
105
+ * A REFUSAL AND A TYPO ARE STILL DIFFERENT THINGS: `parseMenuModel` is total because
106
+ * malformed JSON is an author slip that must not stop the element upgrading, while a
107
+ * `custom` item is well-formed, deliberate, and unhonourable on this surface.
108
+ */
109
+ writeMarkup(value: string | null): void {
110
+ const next = readMenuModelWrite(value, this._authored);
111
+ const refusals = next === null ? [] : menuRefusals(menuTree(next), ADW_MENU_SURFACE_WEB);
112
+ if (refusals.length === 0) {
113
+ this._adopt(next);
114
+ return;
115
+ }
116
+ // `console.error` is the browser's own channel for "this is wrong, the page
117
+ // continues" — and the alternative here is a row that lies.
118
+ console.error(`${ADW_MENU_SURFACE_WEB.name} cannot render this menu, so it has none:`);
119
+ for (const refusal of refusals) console.error(` [${refusal.path.join('.')}] ${refusal.message}`);
120
+ this._adopt(null);
121
+ }
122
+
123
+ /** Hold `next`, snapshot it, and listen to it — the three that always happen together. */
124
+ private _adopt(next: GioMenuModel | null): void {
125
+ this._unwatch?.();
126
+ this._unwatch = null;
127
+ this._model = next;
128
+ this._tree = next === null ? [] : menuTree(next);
129
+ if (next === null) return;
130
+ // Re-adopting from inside the handler re-snapshots AND re-watches, which is what
131
+ // makes a section added by the mutation audible. The port's `emit` copies its
132
+ // handler list and skips ones disconnected mid-emission, so disconnecting here is
133
+ // safe (`gobject.ts`, `emit`).
134
+ this._unwatch = watchMenuModel(next, () => {
135
+ this._adopt(this._model);
136
+ this.onItemsChanged();
137
+ });
138
+ }
139
+ }
@@ -47,7 +47,7 @@
47
47
  // Copyright (c) GNOME contributors (GTK, libadwaita). LGPLv2.1+.
48
48
 
49
49
  import { flattenMenu, menuLabelText, menuNodeAt, resolveMenuItemState } from '@gjsify/adwaita-core';
50
- import type { AdwMenuActions, AdwMenuItem, AdwMenuModel, AdwMenuPath } from '@gjsify/adwaita-core';
50
+ import type { AdwMenuActions, AdwMenuItem, AdwMenuPath, AdwMenuTree } from '@gjsify/adwaita-core';
51
51
 
52
52
  import { createGtkImage } from './gtk-image.js';
53
53
  import type { GtkPopover } from './gtk-popover.js';
@@ -71,7 +71,7 @@ const BACK_LABEL = 'Back';
71
71
  * reopens three levels deep is a menu the reader cannot get out of.
72
72
  */
73
73
  export class PopoverMenuView {
74
- private _model: AdwMenuModel = [];
74
+ private _tree: AdwMenuTree = [];
75
75
  private _actions: AdwMenuActions | null = null;
76
76
  private _title = '';
77
77
  /** The submenu whose items are showing; `[]` is the root page. */
@@ -80,7 +80,7 @@ export class PopoverMenuView {
80
80
  private _openers = new Set<HTMLElement>();
81
81
  /** The row that LEAVES a page — what ArrowLeft acts on. */
82
82
  private _backRow: HTMLElement | null = null;
83
- /** The canonical text of the model showing, so an unchanged re-render keeps the page. */
83
+ /** The canonical text of the tree showing, so an unchanged re-render keeps the page. */
84
84
  private _key = '[]';
85
85
  private _rows: HTMLButtonElement[] = [];
86
86
 
@@ -181,24 +181,34 @@ export class PopoverMenuView {
181
181
  }
182
182
 
183
183
  /**
184
- * Named for the value it takes, and NOT `setModel` — the portable LIST model gave
184
+ * Named for the value it takes, which since ADR 0128 is a TREE and not a model: this
185
+ * view never holds a `Gio.MenuModel`, it walks the snapshot its element took, and a
186
+ * `setMenuModel` here would name a type it has no way to read. The element is the one
187
+ * place that owns the model.
188
+ *
189
+ * It was `setMenuModel` for the reason the rename keeps: the portable LIST model gave
185
190
  * `ComboState` a `setModel` (ADR 0046), and two methods called `setModel` over two
186
191
  * unrelated values on one surface is the collision the vocabulary work exists to
187
- * remove. It also un-blinds `check-adwaita-collection-reactivity`, which resolves a
188
- * collection call by the setter's name.
192
+ * remove.
189
193
  */
190
- setMenuModel(model: AdwMenuModel): void {
191
- // A CHANGED model invalidates the open page: the submenu it named may not exist
194
+ setMenuTree(tree: AdwMenuTree): void {
195
+ // A CHANGED tree invalidates the open page: the submenu it named may not exist
192
196
  // any more, and rendering a page that is gone leaves an empty popover with a
193
197
  // back row and no way to see what happened. An UNCHANGED one must not — every
194
198
  // element re-render calls this, and resetting unconditionally shut the submenu
195
199
  // the reader had just opened.
196
- const key = JSON.stringify(model);
200
+ //
201
+ // COMPARED BY VALUE, not by identity, which is the opposite of the guard
202
+ // `SplitButtonState.setMenuModel` has: the element's AUTHORED menu keeps its
203
+ // identity while its content is replaced (ADR 0128 § 5), so an identity guard
204
+ // here would never see an edit to a `menu-model="[…]"` attribute and would keep
205
+ // showing a page that is gone.
206
+ const key = JSON.stringify(tree);
197
207
  if (key !== this._key) {
198
208
  this._key = key;
199
209
  this._page = [];
200
210
  }
201
- this._model = model;
211
+ this._tree = tree;
202
212
  }
203
213
 
204
214
  setActions(actions: AdwMenuActions | null): void {
@@ -215,10 +225,10 @@ export class PopoverMenuView {
215
225
  }
216
226
 
217
227
  /** The items of the page currently showing. */
218
- private currentItems(): AdwMenuModel {
219
- if (this._page.length === 0) return this._model;
220
- const node = menuNodeAt(this._model, this._page);
221
- return node !== null && node.kind !== 'item' ? node.items : this._model;
228
+ private currentItems(): AdwMenuTree {
229
+ if (this._page.length === 0) return this._tree;
230
+ const node = menuNodeAt(this._tree, this._page);
231
+ return node !== null && node.kind !== 'item' ? node.items : this._tree;
222
232
  }
223
233
 
224
234
  /** Rebuild the surface's children from the model. */
@@ -228,7 +238,7 @@ export class PopoverMenuView {
228
238
  this._openers = new Set();
229
239
  this._backRow = null;
230
240
 
231
- const open = menuNodeAt(this._model, this._page);
241
+ const open = menuNodeAt(this._tree, this._page);
232
242
  if (this._page.length > 0 && open !== null && open.kind === 'submenu') {
233
243
  this.surface.appendChild(this.backRow(open.label));
234
244
  } else if (this._title.length > 0) {
@@ -263,7 +273,7 @@ export class PopoverMenuView {
263
273
  /** The label of the section at `path`, or `null` when it is not a labelled section. */
264
274
  private sectionHeading(path: AdwMenuPath): string | null {
265
275
  if (path.length <= this._page.length) return null;
266
- const parent = menuNodeAt(this._model, path);
276
+ const parent = menuNodeAt(this._tree, path);
267
277
  return parent !== null && parent.kind === 'section' && parent.label !== undefined ? parent.label : null;
268
278
  }
269
279