@gjsify/adwaita-web 0.55.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/adwaita-web.css +137 -29
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/adw-status-page.spec.d.ts +1 -0
- package/lib/types/application.d.ts +24 -0
- package/lib/types/application.spec.d.ts +1 -0
- package/lib/types/attributes.d.ts +19 -0
- package/lib/types/elements/adw-action-row.d.ts +4 -0
- package/lib/types/elements/adw-application-window.d.ts +2 -0
- package/lib/types/elements/adw-combo-row.d.ts +4 -0
- package/lib/types/elements/adw-entry-row.d.ts +6 -0
- package/lib/types/elements/adw-expander-row.d.ts +2 -0
- package/lib/types/elements/adw-header-bar.d.ts +6 -0
- package/lib/types/elements/adw-overlay-split-view.d.ts +13 -0
- package/lib/types/elements/adw-sidebar.d.ts +3 -2
- package/lib/types/elements/adw-spin-row.d.ts +6 -0
- package/lib/types/elements/adw-switch-row.d.ts +4 -0
- package/lib/types/elements/adw-toggle-group.d.ts +17 -0
- package/lib/types/elements/adw-view-stack.d.ts +3 -0
- package/lib/types/elements/checks.d.ts +6 -0
- package/lib/types/elements/gtk-application-window.d.ts +2 -0
- package/lib/types/elements/gtk-drop-down.d.ts +3 -0
- package/lib/types/elements/gtk-entry.d.ts +10 -0
- package/lib/types/elements/gtk-menu-button.d.ts +2 -0
- package/lib/types/elements/gtk-scrolled-window.d.ts +26 -2
- package/lib/types/elements/gtk-stack.d.ts +3 -0
- package/lib/types/elements/gtk-switch.d.ts +3 -0
- package/lib/types/elements/gtk-window.d.ts +8 -2
- package/lib/types/glib.spec.d.ts +1 -0
- package/lib/types/gobject-door.d.ts +4 -0
- package/lib/types/gobject-door.spec.d.ts +1 -0
- package/lib/types/gobject-elements.d.ts +17 -0
- package/lib/types/gtksource/gtk-source-view.d.ts +77 -0
- package/lib/types/gtksource/gtk-source-view.spec.d.ts +2 -0
- package/lib/types/gtksource/web-editor-driver.d.ts +44 -0
- package/lib/types/index.d.ts +4 -1
- package/lib/types/menu-actions.spec.d.ts +1 -0
- package/lib/types/namespace/adw.d.ts +1 -0
- package/lib/types/namespace/gio.d.ts +2 -0
- package/lib/types/namespace/glib.d.ts +10 -0
- package/lib/types/namespace/gobject.d.ts +27 -0
- package/lib/types/namespace/gtk-enums.d.ts +22 -0
- package/lib/types/namespace/gtk.d.ts +2 -0
- package/lib/types/namespace/gtksource.d.ts +2 -0
- package/lib/types/row-line-clamp.d.ts +20 -0
- package/lib/types/row-tooltip.d.ts +8 -0
- package/lib/types/row-tooltip.spec.d.ts +1 -0
- package/lib/types/shared-tree-builder.d.ts +69 -20
- package/lib/types/shared-tree-mount.spec.d.ts +1 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/lib/types/widget-size.spec.d.ts +1 -0
- package/package.json +11 -9
- package/scss/_action_row.scss +39 -15
- package/scss/_button_row.scss +5 -0
- package/scss/_expander_row.scss +36 -3
- package/scss/_headerbar.scss +2 -1
- package/scss/_preferences_row.scss +9 -0
- package/scss/_reset.scss +1 -1
- package/scss/_row.scss +16 -0
- package/scss/_status_page.scss +1 -1
- package/scss/_toggle_group.scss +28 -0
- package/scss/_widget.scss +14 -3
- package/scss/_window.scss +13 -1
- package/src/adw-action-rows.spec.ts +255 -0
- package/src/adw-header-bar.spec.ts +18 -0
- package/src/adw-status-page.spec.ts +124 -0
- package/src/application.spec.ts +75 -0
- package/src/application.ts +92 -0
- package/src/attributes.ts +52 -0
- package/src/capabilities.mjs +3 -0
- package/src/construct-vectors.spec.ts +156 -1
- package/src/elements/adw-action-row.ts +31 -2
- package/src/elements/adw-alert-dialog.ts +4 -1
- package/src/elements/adw-application-window.ts +5 -0
- package/src/elements/adw-button-row.ts +7 -1
- package/src/elements/adw-combo-row.ts +26 -1
- package/src/elements/adw-entry-row.ts +22 -12
- package/src/elements/adw-expander-row.ts +34 -1
- package/src/elements/adw-header-bar.ts +18 -4
- package/src/elements/adw-overlay-split-view.ts +29 -6
- package/src/elements/adw-preferences-row.ts +5 -2
- package/src/elements/adw-sidebar.ts +33 -5
- package/src/elements/adw-spin-row.ts +37 -2
- package/src/elements/adw-split-button.ts +5 -1
- package/src/elements/adw-status-page.ts +10 -2
- package/src/elements/adw-switch-row.ts +25 -1
- package/src/elements/adw-toggle-group.ts +119 -10
- package/src/elements/adw-view-stack.ts +14 -0
- package/src/elements/adw-window.ts +9 -4
- package/src/elements/checks.ts +25 -1
- package/src/elements/gtk-application-window.ts +5 -0
- package/src/elements/gtk-drop-down.ts +16 -0
- package/src/elements/gtk-entry.ts +45 -7
- package/src/elements/gtk-menu-button.ts +12 -1
- package/src/elements/gtk-popover-menu.ts +7 -2
- package/src/elements/gtk-scrolled-window.ts +76 -7
- package/src/elements/gtk-stack.ts +8 -0
- package/src/elements/gtk-switch.ts +8 -0
- package/src/elements/gtk-window.ts +9 -3
- package/src/glib.spec.ts +34 -0
- package/src/gobject-door.spec.ts +119 -0
- package/src/gobject-door.ts +137 -0
- package/src/gobject-elements.ts +172 -0
- package/src/gtk-window.spec.ts +12 -0
- package/src/gtksource/gtk-source-view.spec.ts +69 -0
- package/src/gtksource/gtk-source-view.ts +379 -0
- package/src/gtksource/web-editor-driver.ts +260 -0
- package/src/index.ts +11 -1
- package/src/keyboard-operable.spec.ts +47 -11
- package/src/menu-actions.spec.ts +47 -0
- package/src/namespace/adw.ts +1 -0
- package/src/namespace/gio.ts +2 -0
- package/src/namespace/glib.ts +22 -0
- package/src/namespace/gobject.ts +37 -0
- package/src/namespace/gtk-enums.ts +15 -0
- package/src/namespace/gtk.ts +2 -0
- package/src/namespace/gtksource.ts +9 -0
- package/src/row-line-clamp.ts +74 -0
- package/src/row-tooltip.spec.ts +313 -0
- package/src/row-tooltip.ts +52 -0
- package/src/shared-tree-builder.ts +230 -42
- package/src/shared-tree-mount.spec.ts +130 -0
- package/src/shared-trees.spec.ts +15 -0
- package/src/split-button.spec.ts +2 -0
- package/src/styles.generated.ts +1 -1
- package/src/test.browser.mts +18 -0
- package/src/widget-size.spec.ts +117 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// <adw-status-page> — A centered empty/placeholder state: a large symbolic
|
|
2
2
|
// icon, a title, a description and an optional action (slotted child).
|
|
3
3
|
// Attributes: icon (symbolic name, with or without -symbolic) or icon-name, the GObject
|
|
4
|
-
// spelling a projected `.blp` writes; title, description.
|
|
4
|
+
// spelling a projected `.blp` writes; title, description, and the boolean compact.
|
|
5
5
|
// Reference: refs/adwaita-web/adwaita-web/scss/_status_page.scss
|
|
6
6
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_misc.scss (AdwStatusPage)
|
|
7
|
+
// Reference: refs/libadwaita/doc/style-classes.md (Compact Status Page)
|
|
7
8
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
8
9
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web; the
|
|
9
10
|
// icon node is <gtk-image>.
|
|
@@ -22,7 +23,7 @@ export class AdwStatusPage extends HTMLElement {
|
|
|
22
23
|
private _initialized = false;
|
|
23
24
|
|
|
24
25
|
static get observedAttributes() {
|
|
25
|
-
return ['icon', 'icon-name', 'title', 'description'];
|
|
26
|
+
return ['icon', 'icon-name', 'title', 'description', 'compact'];
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
connectedCallback() {
|
|
@@ -64,6 +65,13 @@ export class AdwStatusPage extends HTMLElement {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
private _render() {
|
|
68
|
+
// `.compact` (style-classes.md "Compact Status Page") is keyed off the HOST by
|
|
69
|
+
// `_status_page.scss`, so this door writes the class itself. ADDED, never toggled:
|
|
70
|
+
// `shared-tree-builder.ts` puts a `.blp`'s `styles ["compact"]` on this same class,
|
|
71
|
+
// and a `toggle` would strip it on the first attribute change — `title`, on any page
|
|
72
|
+
// that sets one. Additive, the way `gtk_button_add_css_class` is.
|
|
73
|
+
if (this.hasAttribute('compact')) this.classList.add('compact');
|
|
74
|
+
|
|
67
75
|
const icon = this.getAttribute('icon') ?? this.getAttribute('icon-name');
|
|
68
76
|
// The element swaps the mask class and keeps the size class.
|
|
69
77
|
this._iconEl.iconName = icon;
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
// `<adw-action-row>` — libadwaita's AdwSwitchRow IS an AdwActionRow
|
|
15
15
|
// (adw-switch-row.c:50), so `add_prefix` is inherited, not invented here.
|
|
16
16
|
//
|
|
17
|
+
// ATTRIBUTES: `title`, `subtitle`, `active`, `tooltip-text` (`Gtk.Widget:tooltip-text`),
|
|
18
|
+
// and `title-lines` / `subtitle-lines` —
|
|
19
|
+
// clamp either label to N lines, `0` = unlimited, the AdwActionRow pspec's sentinel
|
|
20
|
+
// (`@girs/adw-1`, min 0, default 0).
|
|
17
21
|
// Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
|
|
18
22
|
// Copyright (c) 2025 csm. MIT License.
|
|
19
23
|
// Modifications: Reimplemented as Web Component for @gjsify/adwaita-web;
|
|
@@ -23,7 +27,9 @@
|
|
|
23
27
|
import { SwitchRowState, deriveRowLabels } from '@gjsify/adwaita-core';
|
|
24
28
|
|
|
25
29
|
import { bindEmptySections } from '../empty-sections.js';
|
|
30
|
+
import { applyRowTooltip } from '../row-tooltip.js';
|
|
26
31
|
import { bindSlottedChildren } from '../slotted-children.js';
|
|
32
|
+
import { applyRowLineClamp, parseRowLinesAttribute } from '../row-line-clamp.js';
|
|
27
33
|
|
|
28
34
|
// SIDE-EFFECT import, deliberately separate from the type import below: it guarantees
|
|
29
35
|
// `gtk-switch` is defined before this module's `customElements.define` can upgrade a
|
|
@@ -38,6 +44,8 @@ import type { GtkSwitch } from './gtk-switch.js';
|
|
|
38
44
|
export class AdwSwitchRow extends HTMLElement {
|
|
39
45
|
private _switchEl!: GtkSwitch;
|
|
40
46
|
private _prefixEl!: HTMLDivElement;
|
|
47
|
+
/** The label column — kept for `tooltip-text`, which the browser reads off a PART. */
|
|
48
|
+
private _textEl!: HTMLDivElement;
|
|
41
49
|
private _titleEl!: HTMLSpanElement;
|
|
42
50
|
private _subtitleEl!: HTMLSpanElement;
|
|
43
51
|
/** The headless active flag + its notify rule (ADR 0004). */
|
|
@@ -45,7 +53,7 @@ export class AdwSwitchRow extends HTMLElement {
|
|
|
45
53
|
private _initialized = false;
|
|
46
54
|
|
|
47
55
|
static get observedAttributes() {
|
|
48
|
-
return ['title', 'subtitle', 'active'];
|
|
56
|
+
return ['title', 'subtitle', 'active', 'tooltip-text', 'title-lines', 'subtitle-lines'];
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
/** The start (prefix) section — append icons/widgets here imperatively. */
|
|
@@ -69,6 +77,7 @@ export class AdwSwitchRow extends HTMLElement {
|
|
|
69
77
|
|
|
70
78
|
const text = document.createElement('div');
|
|
71
79
|
text.className = 'adw-row-text';
|
|
80
|
+
this._textEl = text;
|
|
72
81
|
this._titleEl = document.createElement('span');
|
|
73
82
|
this._titleEl.className = 'adw-row-title';
|
|
74
83
|
this._subtitleEl = document.createElement('span');
|
|
@@ -160,6 +169,21 @@ export class AdwSwitchRow extends HTMLElement {
|
|
|
160
169
|
this._titleEl.hidden = !titleVisible;
|
|
161
170
|
this._subtitleEl.textContent = subtitle;
|
|
162
171
|
this._subtitleEl.hidden = !subtitleVisible;
|
|
172
|
+
// The switch is NOT a part here: `unfocusable` and consumer-facing, and a
|
|
173
|
+
// tooltip the consumer put on it has to keep winning over the row's.
|
|
174
|
+
applyRowTooltip(this, [this._prefixEl, this._textEl]);
|
|
175
|
+
this._applyLineClamping();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Apply CSS line-clamping based on title-lines/subtitle-lines attributes. */
|
|
179
|
+
private _applyLineClamping(): void {
|
|
180
|
+
applyRowLineClamp({
|
|
181
|
+
titleEl: this._titleEl,
|
|
182
|
+
subtitleEl: this._subtitleEl,
|
|
183
|
+
textContainerEl: this._titleEl.parentElement!,
|
|
184
|
+
getTitleLines: () => parseRowLinesAttribute(this.getAttribute('title-lines')),
|
|
185
|
+
getSubtitleLines: () => parseRowLinesAttribute(this.getAttribute('subtitle-lines')),
|
|
186
|
+
});
|
|
163
187
|
}
|
|
164
188
|
}
|
|
165
189
|
|
|
@@ -48,6 +48,28 @@
|
|
|
48
48
|
// C does for the only layout this element has, it matches the three sibling tab lists,
|
|
49
49
|
// and it leaves ArrowUp/ArrowDown to the page instead of swallowing a scroll.
|
|
50
50
|
//
|
|
51
|
+
// SENSITIVITY HAS TWO DOORS, and both are the GTK ones. `GtkWidget:sensitive` on the GROUP
|
|
52
|
+
// is the `sensitive` attribute, read by VALUE rather than by presence because the property
|
|
53
|
+
// is TRUE by default and `<adw-toggle-group>` must not read as insensitive (`src/attributes.ts`).
|
|
54
|
+
// `AdwToggle:enabled` is per toggle.
|
|
55
|
+
//
|
|
56
|
+
// PER-TOGGLE `enabled` IS REACHABLE, and the issue's warning does not close it. `add_toggle`
|
|
57
|
+
// spends it at add time — `gtk_widget_set_sensitive (toggle->button, toggle->enabled)`
|
|
58
|
+
// (adw-toggle-group.c:871) — and `adw_toggle_set_enabled` applies it to that same button
|
|
59
|
+
// afterwards (:1663), so nothing about the order is load-bearing. What makes the door awkward
|
|
60
|
+
// is not the order but the TYPE: `AdwToggle` is not a widget and has no `set_sensitive`, so
|
|
61
|
+
// `add_toggle` is the only public way in and it wants an `AdwToggle` this element never
|
|
62
|
+
// builds. Here the door is the rendered `<button>` — which is exactly what `add_toggle` spends
|
|
63
|
+
// the flag on — so the element reads `enabled` where `add_toggle` would have read it and lands
|
|
64
|
+
// in the same state. One consequence is ported with it: an insensitive toggle cannot be the
|
|
65
|
+
// active one (`set_active_toggle`, :720), which is also what makes a disabled `<button>`
|
|
66
|
+
// agree with the selection rather than sitting checked and unreachable.
|
|
67
|
+
//
|
|
68
|
+
// The roving walk is FILTERED, which `roving-focus.ts` requires of every caller: a disabled
|
|
69
|
+
// `<button>` cannot take focus, and leaving one in strands the user on a `focus()` the browser
|
|
70
|
+
// refuses. `status/open-todos/adwaita-web.md` made adding that filter the price of the
|
|
71
|
+
// attribute; this is that change.
|
|
72
|
+
//
|
|
51
73
|
// Reference: refs/libadwaita/src/adw-toggle-group.c (AdwToggleGroup behaviour)
|
|
52
74
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_toggle-group.scss
|
|
53
75
|
// Reference: refs/adwaita-web/adwaita-web/scss/_toggle_group.scss
|
|
@@ -58,6 +80,7 @@
|
|
|
58
80
|
|
|
59
81
|
import { ToggleGroupState, keptToggles, toggleIndexOfName } from '@gjsify/adwaita-core';
|
|
60
82
|
|
|
83
|
+
import { booleanAttribute } from '../attributes.js';
|
|
61
84
|
import { createGtkImage } from './gtk-image.js';
|
|
62
85
|
import { attachRovingFocus } from './roving-focus.js';
|
|
63
86
|
|
|
@@ -67,8 +90,24 @@ export class AdwToggle extends HTMLElement {
|
|
|
67
90
|
// icon-only toggle has no other text — `tooltip` is what NAMES it, so it is not a
|
|
68
91
|
// decoration. It produces no disabled or hidden button, which is the invariant
|
|
69
92
|
// `keyboard-operable.spec.ts` pins this list for.
|
|
93
|
+
//
|
|
94
|
+
// `enabled` is the one that DOES change the invariant, and the pinned list is what
|
|
95
|
+
// forced the roving filter to land in the same change.
|
|
70
96
|
static get observedAttributes() {
|
|
71
|
-
return ['label', 'icon-name', 'tooltip'];
|
|
97
|
+
return ['label', 'icon-name', 'tooltip', 'enabled'];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
|
|
101
|
+
if (name === 'enabled' && oldValue !== newValue) {
|
|
102
|
+
// Notify the parent group that this toggle's enabled state changed.
|
|
103
|
+
this.dispatchEvent(
|
|
104
|
+
new CustomEvent('toggle-enabled-changed', {
|
|
105
|
+
bubbles: true,
|
|
106
|
+
composed: true,
|
|
107
|
+
detail: { enabled: newValue !== 'false' },
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
72
111
|
}
|
|
73
112
|
}
|
|
74
113
|
|
|
@@ -82,9 +121,32 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
82
121
|
private _stateAttr: 'aria-checked' | 'aria-selected' = 'aria-checked';
|
|
83
122
|
/** Each toggle's `name`, in order — what `active-name` resolves against. */
|
|
84
123
|
private _names: (string | null)[] = [];
|
|
124
|
+
/** `AdwToggle:enabled` per rendered button, in order — `add_toggle` spends this at add time. */
|
|
125
|
+
private _enabled: boolean[] = [];
|
|
126
|
+
/** The authored <adw-toggle> elements, kept to observe live `enabled` changes. */
|
|
127
|
+
private _toggles: AdwToggle[] = [];
|
|
85
128
|
|
|
86
129
|
static get observedAttributes() {
|
|
87
|
-
return ['active', 'active-name', 'flat', 'round'];
|
|
130
|
+
return ['active', 'active-name', 'flat', 'round', 'sensitive'];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* `GtkWidget:sensitive` — whether the whole group is insensitive.
|
|
135
|
+
*
|
|
136
|
+
* TRUE unless the attribute says `false`, because the property defaults to TRUE and a
|
|
137
|
+
* boolean attribute read by PRESENCE would leave every group on a page insensitive.
|
|
138
|
+
*/
|
|
139
|
+
get sensitive(): boolean {
|
|
140
|
+
return booleanAttribute(this.getAttribute('sensitive'), true);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
set sensitive(value: boolean) {
|
|
144
|
+
this.setAttribute('sensitive', value ? 'true' : 'false');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Whether the toggle at `index` is enabled (`AdwToggle:enabled`) — false once disabled. */
|
|
148
|
+
isToggleEnabled(index: number): boolean {
|
|
149
|
+
return this._enabled[index] ?? false;
|
|
88
150
|
}
|
|
89
151
|
|
|
90
152
|
/** Zero-based index of the active toggle. */
|
|
@@ -159,11 +221,15 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
159
221
|
// in the C.
|
|
160
222
|
this._innerEl.setAttribute('role', 'none');
|
|
161
223
|
|
|
224
|
+
this._toggles = toggles;
|
|
162
225
|
this._buttons = toggles.map((toggle, index) => {
|
|
163
226
|
const btn = document.createElement('button');
|
|
164
227
|
btn.type = 'button';
|
|
165
228
|
btn.className = 'adw-toggle';
|
|
166
229
|
btn.setAttribute('role', toggleRole);
|
|
230
|
+
// `add_toggle` reads `enabled` while building the button (:871), and a toggle
|
|
231
|
+
// with no `enabled` attribute is the TRUE default (:513).
|
|
232
|
+
this._enabled[index] = booleanAttribute(toggle.getAttribute('enabled'), true);
|
|
167
233
|
|
|
168
234
|
const label = toggle.getAttribute('label') ?? '';
|
|
169
235
|
const icon = toggle.getAttribute('icon-name') ?? '';
|
|
@@ -190,6 +256,21 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
190
256
|
this._innerEl.append(...this._buttons);
|
|
191
257
|
this.replaceChildren(this._innerEl);
|
|
192
258
|
|
|
259
|
+
// Observe live `enabled` changes on the authored <adw-toggle> elements.
|
|
260
|
+
// Upstream's `adw_toggle_set_enabled` is a live setter (:1663); the port
|
|
261
|
+
// snapshots at connect, so a later `toggle.setAttribute('enabled', 'false')`
|
|
262
|
+
// had no effect. The `AdwToggle` element fires `toggle-enabled-changed`
|
|
263
|
+
// from its attributeChangedCallback; we update `_enabled` and re-render.
|
|
264
|
+
for (let i = 0; i < this._toggles.length; i++) {
|
|
265
|
+
this._toggles[i].addEventListener('toggle-enabled-changed', (event: Event) => {
|
|
266
|
+
const { enabled } = (event as CustomEvent<{ enabled: boolean }>).detail;
|
|
267
|
+
if (this._enabled[i] !== enabled) {
|
|
268
|
+
this._enabled[i] = enabled;
|
|
269
|
+
this._render();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
193
274
|
// Hand the segments to the headless state machine (it needs the count to
|
|
194
275
|
// bound the selection), then seed the active index from the attribute.
|
|
195
276
|
this._state.setLabels(
|
|
@@ -203,16 +284,13 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
203
284
|
this._applyActiveName();
|
|
204
285
|
this._render();
|
|
205
286
|
|
|
206
|
-
// No `
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
// untestable. `AdwToggle.observedAttributes` is pinned in
|
|
210
|
-
// `keyboard-operable.spec.ts` so the day it grows, this decision has to be
|
|
211
|
-
// revisited instead of silently becoming a focus trap.
|
|
287
|
+
// No `hidden` filter, because no `<adw-toggle>` attribute can produce one — and now
|
|
288
|
+
// there IS a `disabled` one, so the filter `roving-focus.ts` asks of every caller is
|
|
289
|
+
// here. `status/open-todos/adwaita-web.md` made that the price of `enabled`.
|
|
212
290
|
attachRovingFocus({
|
|
213
291
|
host: this,
|
|
214
292
|
orientation: 'horizontal',
|
|
215
|
-
items: () => this._buttons,
|
|
293
|
+
items: () => this._buttons.filter((btn) => !btn.disabled),
|
|
216
294
|
// Same path a click takes, so an arrow key cannot drift from a press.
|
|
217
295
|
select: (item) => this._selectIndex(this._buttons.findIndex((btn) => btn === item)),
|
|
218
296
|
});
|
|
@@ -229,7 +307,7 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
229
307
|
this._applyActiveName();
|
|
230
308
|
return;
|
|
231
309
|
}
|
|
232
|
-
// flat
|
|
310
|
+
// `sensitive`, flat and round: styling and per-button state, both `_render`.
|
|
233
311
|
this._render();
|
|
234
312
|
}
|
|
235
313
|
|
|
@@ -253,6 +331,10 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
253
331
|
}
|
|
254
332
|
|
|
255
333
|
private _selectIndex(index: number): void {
|
|
334
|
+
// `set_active_toggle` refuses a toggle that is not enabled (:720), so an insensitive
|
|
335
|
+
// one can never become the active one — the check is the core state machine's guard
|
|
336
|
+
// plus this one, in C's order.
|
|
337
|
+
if (!this._enabled[index]) return;
|
|
256
338
|
// The core state machine guards the no-op/out-of-range cases and notifies
|
|
257
339
|
// the subscriber (which re-renders) only on a real change.
|
|
258
340
|
if (!this._state.setSelected(index)) return;
|
|
@@ -266,14 +348,41 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
266
348
|
this.classList.toggle('flat', this.hasAttribute('flat'));
|
|
267
349
|
this.classList.toggle('round', this.hasAttribute('round'));
|
|
268
350
|
const active = this._state.selected;
|
|
351
|
+
const sensitive = this.sensitive;
|
|
352
|
+
// The group node's own `:disabled` rule (_toggle-group.scss:16) needs the UA's
|
|
353
|
+
// `:disabled`, which a custom element never is — `_expander.scss` records why, and
|
|
354
|
+
// writes the rule against the attribute for the same reason.
|
|
355
|
+
this.toggleAttribute('disabled', !sensitive);
|
|
269
356
|
this._buttons.forEach((btn, index) => {
|
|
270
357
|
const isActive = index === active;
|
|
271
358
|
btn.classList.toggle('active', isActive);
|
|
272
359
|
btn.setAttribute(this._stateAttr, String(isActive));
|
|
360
|
+
// `GtkWidget:sensitive` on the GROUP reaches every child in GTK, because
|
|
361
|
+
// `gtk_widget_is_sensitive` ANDs the parent chain; a `<button disabled>` is the
|
|
362
|
+
// browser's half of that, and it is also what takes the toggle out of the tab
|
|
363
|
+
// order and out of the roving walk.
|
|
364
|
+
btn.disabled = !sensitive || !this._enabled[index];
|
|
273
365
|
// The roving tabindex: Tab enters on the ACTIVE toggle, the way
|
|
274
366
|
// `adw_toggle_group_grab_focus` grabs it, and leaves the group from there.
|
|
275
367
|
btn.tabIndex = isActive ? 0 : -1;
|
|
276
368
|
});
|
|
369
|
+
// An insensitive toggle can never BE the active one, so an active-but-disabled toggle
|
|
370
|
+
// — `active="1"` authored next to `<adw-toggle enabled="false">` — would be a
|
|
371
|
+
// checked radio nothing can reach. Upstream cannot express the pair at all
|
|
372
|
+
// (`set_active_toggle` clears the selection instead, :720), so the selection moves
|
|
373
|
+
// to the first enabled toggle, which is the one Tab then enters on.
|
|
374
|
+
if (!this._enabled[active]) this._activateFirstEnabled();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Select the first enabled toggle, if there is one — the C's "no active" repair. */
|
|
378
|
+
private _activateFirstEnabled(): void {
|
|
379
|
+
const first = this._enabled.findIndex((enabled) => enabled);
|
|
380
|
+
if (first === -1) return;
|
|
381
|
+
this._state.setSelected(first);
|
|
382
|
+
this.setAttribute('active', String(first));
|
|
383
|
+
// The state change came from _render, not user interaction, but the active
|
|
384
|
+
// index changed — emit notify::active so consumers stay in sync.
|
|
385
|
+
this.dispatchEvent(new CustomEvent('notify::active', { bubbles: true, detail: { active: first } }));
|
|
277
386
|
}
|
|
278
387
|
}
|
|
279
388
|
|
|
@@ -50,9 +50,17 @@
|
|
|
50
50
|
import { ViewStackState } from '@gjsify/adwaita-core';
|
|
51
51
|
import type { AdwViewStackPageInfo as CorePageInfo, ViewStackStateChange } from '@gjsify/adwaita-core';
|
|
52
52
|
|
|
53
|
+
import type { DispatchedSignals } from '../signals.js';
|
|
54
|
+
|
|
53
55
|
export type AdwViewStackPageInfo = CorePageInfo<HTMLElement>;
|
|
54
56
|
|
|
55
57
|
export class AdwViewStack extends HTMLElement {
|
|
58
|
+
/** The GTK signals this element dispatches, each with the DOM event it arrives as (ADR 0093). */
|
|
59
|
+
static readonly signals: DispatchedSignals = {
|
|
60
|
+
'notify::visible-child': 'notify::visible-child',
|
|
61
|
+
'notify::visible-child-name': 'notify::visible-child-name',
|
|
62
|
+
};
|
|
63
|
+
|
|
56
64
|
private readonly _state = new ViewStackState<HTMLElement>();
|
|
57
65
|
private _initialized = false;
|
|
58
66
|
|
|
@@ -255,6 +263,12 @@ export class AdwViewStack extends HTMLElement {
|
|
|
255
263
|
},
|
|
256
264
|
}),
|
|
257
265
|
);
|
|
266
|
+
this.dispatchEvent(
|
|
267
|
+
new CustomEvent('notify::visible-child-name', {
|
|
268
|
+
bubbles: true,
|
|
269
|
+
detail: { 'visible-child-name': change.name },
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
258
272
|
}
|
|
259
273
|
|
|
260
274
|
private _applyVisibility(): void {
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
// width, height — the size REQUEST in px, the one `gtk_widget_set_size_request
|
|
21
21
|
// (GTK_WIDGET (self), 360, 200)` makes in adw_window_init (C:344). Absent, the
|
|
22
22
|
// element keeps that default; an explicit attribute overrides it.
|
|
23
|
+
// default-width, default-height — `Gtk.Window:default-width` / `:default-height`, the size a
|
|
24
|
+
// `.blp` authors (`default-width: 1100`). A page has no window manager to open the window at
|
|
25
|
+
// that size, so it IS the box's size here, and it beats the request where both are present.
|
|
23
26
|
//
|
|
24
27
|
// Properties: `content` (the Adw.Window:content child), `breakpoints`,
|
|
25
28
|
// `currentBreakpoint` (Adw.Window:current-breakpoint), `dialogs` and
|
|
@@ -51,7 +54,7 @@ export class AdwWindow extends HTMLElement {
|
|
|
51
54
|
private _openDialogs: HTMLElement[] = [];
|
|
52
55
|
|
|
53
56
|
static get observedAttributes() {
|
|
54
|
-
return ['width', 'height'];
|
|
57
|
+
return ['width', 'height', 'default-width', 'default-height'];
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
/** The content area — the Adw.Window:content property. */
|
|
@@ -128,7 +131,9 @@ export class AdwWindow extends HTMLElement {
|
|
|
128
131
|
// The size request is read from the ATTRIBUTE, so it is applied on every
|
|
129
132
|
// change — including one that arrived before the element was connected, which
|
|
130
133
|
// is the ordinary case for markup already sitting in a document.
|
|
131
|
-
if (name === 'width' || name === 'height')
|
|
134
|
+
if (name === 'width' || name === 'height' || name === 'default-width' || name === 'default-height') {
|
|
135
|
+
this._applySizeRequest();
|
|
136
|
+
}
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
private _buildOnce() {
|
|
@@ -182,8 +187,8 @@ export class AdwWindow extends HTMLElement {
|
|
|
182
187
|
|
|
183
188
|
/** The window's size request — GTK's, not CSS's, so an absent attribute means the default. */
|
|
184
189
|
private _applySizeRequest() {
|
|
185
|
-
const width = this.getAttribute('width');
|
|
186
|
-
const height = this.getAttribute('height');
|
|
190
|
+
const width = this.getAttribute('default-width') ?? this.getAttribute('width');
|
|
191
|
+
const height = this.getAttribute('default-height') ?? this.getAttribute('height');
|
|
187
192
|
this.style.width = width ? `${width}px` : `${DEFAULT_WIDTH}px`;
|
|
188
193
|
this.style.height = height ? `${height}px` : `${DEFAULT_HEIGHT}px`;
|
|
189
194
|
}
|
package/src/elements/checks.ts
CHANGED
|
@@ -87,6 +87,8 @@
|
|
|
87
87
|
|
|
88
88
|
import { RadioGroupState, resolveCheckState } from '@gjsify/adwaita-core';
|
|
89
89
|
|
|
90
|
+
import type { DispatchedSignals } from '../signals.js';
|
|
91
|
+
|
|
90
92
|
/**
|
|
91
93
|
* The document's radio groups. ONE registry, as global as a bare
|
|
92
94
|
* `<input type="radio" name="x">` outside a form is — two independent groups therefore
|
|
@@ -124,6 +126,15 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
124
126
|
this.toggleAttribute('checked', !!value);
|
|
125
127
|
}
|
|
126
128
|
|
|
129
|
+
/** `Gtk.CheckButton:active`, the GObject name for {@link checked}. */
|
|
130
|
+
get active(): boolean {
|
|
131
|
+
return this.checked;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
set active(value: boolean) {
|
|
135
|
+
this.checked = value;
|
|
136
|
+
}
|
|
137
|
+
|
|
127
138
|
/** Whether the control is in the third, inconsistent state. */
|
|
128
139
|
get indeterminate(): boolean {
|
|
129
140
|
return this.hasAttribute('indeterminate');
|
|
@@ -202,10 +213,17 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
202
213
|
this._label.textContent = this.getAttribute('label') ?? '';
|
|
203
214
|
}
|
|
204
215
|
this.render();
|
|
216
|
+
if (name === 'active') {
|
|
217
|
+
// The GObject spelling a projected `.blp` writes; `checked` stays the state.
|
|
218
|
+
this.checked = this.hasAttribute('active');
|
|
219
|
+
}
|
|
205
220
|
if (name === 'checked') {
|
|
206
221
|
this.dispatchEvent(
|
|
207
222
|
new CustomEvent('notify::checked', { bubbles: true, detail: { checked: this.checked } }),
|
|
208
223
|
);
|
|
224
|
+
// `toggled` fires on every change of `active`, programmatic included.
|
|
225
|
+
this.dispatchEvent(new CustomEvent('toggled', { bubbles: true }));
|
|
226
|
+
this.dispatchEvent(new CustomEvent('notify::active', { bubbles: true, detail: { active: this.checked } }));
|
|
209
227
|
}
|
|
210
228
|
}
|
|
211
229
|
|
|
@@ -227,8 +245,14 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
227
245
|
|
|
228
246
|
/** The Adwaita checkbox — `checkbutton > check` as a custom element. */
|
|
229
247
|
export class GtkCheckButton extends AdwCheckBase {
|
|
248
|
+
/** The GTK signals this element dispatches, each with the DOM event it arrives as (ADR 0093). */
|
|
249
|
+
static readonly signals: DispatchedSignals = {
|
|
250
|
+
toggled: 'toggled',
|
|
251
|
+
'notify::active': 'notify::active',
|
|
252
|
+
};
|
|
253
|
+
|
|
230
254
|
static get observedAttributes() {
|
|
231
|
-
return ['checked', 'indeterminate', 'disabled', 'label'];
|
|
255
|
+
return ['checked', 'active', 'indeterminate', 'disabled', 'label'];
|
|
232
256
|
}
|
|
233
257
|
|
|
234
258
|
protected get inputType(): 'checkbox' {
|
|
@@ -26,8 +26,11 @@
|
|
|
26
26
|
|
|
27
27
|
import type { AdwSlot } from '../slotted-children.js';
|
|
28
28
|
import { GtkWindow } from './gtk-window.js';
|
|
29
|
+
import { defineWindowApplication } from '../application.js';
|
|
29
30
|
|
|
30
31
|
export class GtkApplicationWindow extends GtkWindow {
|
|
32
|
+
/** `Gtk.Window:application`: setting it is `add_window` (ADR 0098), set by `defineWindowApplication`. */
|
|
33
|
+
declare application: object | null;
|
|
31
34
|
private _menubar: HTMLDivElement | null = null;
|
|
32
35
|
private _menubarObserver: MutationObserver | null = null;
|
|
33
36
|
|
|
@@ -106,3 +109,5 @@ export class GtkApplicationWindow extends GtkWindow {
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
customElements.define('gtk-application-window', GtkApplicationWindow);
|
|
112
|
+
|
|
113
|
+
defineWindowApplication(GtkApplicationWindow.prototype);
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
// Events:
|
|
48
48
|
// `notify::selected` (CustomEvent, bubbles, detail = { selected }) — the index
|
|
49
49
|
// changed (mirrors the Gtk.DropDown `notify::selected` GObject signal).
|
|
50
|
+
// `notify::selected-item` (CustomEvent, bubbles, detail = { 'selected-item' }) — fired
|
|
51
|
+
// right after `notify::selected`, on the same real change.
|
|
50
52
|
// `change` (CustomEvent, bubbles, detail = { index, value, label }) —
|
|
51
53
|
// a convenience event carrying the full selection. Fires ONLY on a user
|
|
52
54
|
// pick — `ComboState`'s `interactive` flag is what tells the two apart.
|
|
@@ -69,6 +71,7 @@ import { ComboState, normalizeComboOptions, parseListModel } from '@gjsify/adwai
|
|
|
69
71
|
import type { AdwComboOption, AdwListItemsChanged, AdwListModelInput } from '@gjsify/adwaita-core';
|
|
70
72
|
|
|
71
73
|
import { bindSlottedChildren } from '../slotted-children.js';
|
|
74
|
+
import type { DispatchedSignals } from '../signals.js';
|
|
72
75
|
import { stringListSlot } from '../string-list-slot.js';
|
|
73
76
|
|
|
74
77
|
import { createGtkImage } from './gtk-image.js';
|
|
@@ -81,6 +84,12 @@ import { createGtkImage } from './gtk-image.js';
|
|
|
81
84
|
export type GtkDropDownOption = AdwComboOption;
|
|
82
85
|
|
|
83
86
|
export class GtkDropDown extends HTMLElement {
|
|
87
|
+
/** The GTK signals this element dispatches, each with the DOM event it arrives as (ADR 0093). */
|
|
88
|
+
static readonly signals: DispatchedSignals = {
|
|
89
|
+
'notify::selected': 'notify::selected',
|
|
90
|
+
'notify::selected-item': 'notify::selected-item',
|
|
91
|
+
};
|
|
92
|
+
|
|
84
93
|
private _buttonEl!: HTMLButtonElement;
|
|
85
94
|
private _labelEl!: HTMLSpanElement;
|
|
86
95
|
private _popoverEl!: GtkPopover;
|
|
@@ -242,6 +251,13 @@ export class GtkDropDown extends HTMLElement {
|
|
|
242
251
|
// `notify::selected` mirrors GObject property-notify: EVERY change,
|
|
243
252
|
// programmatic included.
|
|
244
253
|
this.dispatchEvent(new CustomEvent('notify::selected', { bubbles: true, detail: { selected: index } }));
|
|
254
|
+
// `gtk_drop_down_set_selected` notifies `selected-item` right after `selected`.
|
|
255
|
+
this.dispatchEvent(
|
|
256
|
+
new CustomEvent('notify::selected-item', {
|
|
257
|
+
bubbles: true,
|
|
258
|
+
detail: { 'selected-item': option ?? null },
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
245
261
|
// `change` mirrors the DOM <select> contract: ONLY a user-initiated change. A
|
|
246
262
|
// programmatic assignment stays silent, just as native `select.value = x` fires
|
|
247
263
|
// no `change`, so consumers can set the value without guarding against it.
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
import { ENTRY_ROW_MAX_LENGTH_LIMIT, clampEntryText, entryTextLength } from '@gjsify/adwaita-core';
|
|
24
24
|
|
|
25
|
+
import type { DispatchedSignals } from '../signals.js';
|
|
26
|
+
|
|
25
27
|
export class GtkEntry extends HTMLElement {
|
|
26
28
|
/**
|
|
27
29
|
* `protected`, not `private`: `GtkPasswordEntry` and `GtkSearchEntry` subclass this
|
|
@@ -32,9 +34,18 @@ export class GtkEntry extends HTMLElement {
|
|
|
32
34
|
protected _input!: HTMLInputElement;
|
|
33
35
|
private _initialized = false;
|
|
34
36
|
private _maxLength = 0;
|
|
37
|
+
/** The text last announced, so `changed` fires on a real change only — as `gtk_editable_changed` does. */
|
|
38
|
+
private _announced = '';
|
|
39
|
+
|
|
40
|
+
/** The GTK signals this element dispatches, each with the DOM event it arrives as (ADR 0093). */
|
|
41
|
+
static readonly signals: DispatchedSignals = {
|
|
42
|
+
changed: 'changed',
|
|
43
|
+
'notify::text': 'notify::text',
|
|
44
|
+
activate: 'activate',
|
|
45
|
+
};
|
|
35
46
|
|
|
36
47
|
static get observedAttributes() {
|
|
37
|
-
return ['value', 'placeholder-text', 'placeholder', 'width-request', 'type', 'disabled', 'maxlength'];
|
|
48
|
+
return ['value', 'text', 'placeholder-text', 'placeholder', 'width-request', 'type', 'disabled', 'maxlength'];
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
get value(): string {
|
|
@@ -43,8 +54,28 @@ export class GtkEntry extends HTMLElement {
|
|
|
43
54
|
|
|
44
55
|
set value(v: string) {
|
|
45
56
|
const clamped = clampEntryText(v ?? '', this._maxLength);
|
|
46
|
-
if (this._input)
|
|
47
|
-
|
|
57
|
+
if (this._input) {
|
|
58
|
+
this._input.value = clamped;
|
|
59
|
+
this._announceText();
|
|
60
|
+
} else this.setAttribute('value', clamped);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** `Gtk.Editable:text`, the GObject name for {@link value}. */
|
|
64
|
+
get text(): string {
|
|
65
|
+
return this.value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
set text(v: string) {
|
|
69
|
+
this.value = v;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** `changed` then `notify::text`, once per real change of the text, typed or set from code. */
|
|
73
|
+
private _announceText(): void {
|
|
74
|
+
const text = this._input.value;
|
|
75
|
+
if (text === this._announced) return;
|
|
76
|
+
this._announced = text;
|
|
77
|
+
this.dispatchEvent(new CustomEvent('changed', { bubbles: true }));
|
|
78
|
+
this.dispatchEvent(new CustomEvent('notify::text', { bubbles: true, detail: { text } }));
|
|
48
79
|
}
|
|
49
80
|
|
|
50
81
|
/** `Gtk.Entry:max-length` — 0 means unlimited. Counted in CODE POINTS. */
|
|
@@ -56,7 +87,10 @@ export class GtkEntry extends HTMLElement {
|
|
|
56
87
|
this._maxLength = Number.isFinite(value)
|
|
57
88
|
? Math.min(ENTRY_ROW_MAX_LENGTH_LIMIT, Math.max(0, Math.trunc(value)))
|
|
58
89
|
: 0;
|
|
59
|
-
if (this._input)
|
|
90
|
+
if (this._input) {
|
|
91
|
+
this._input.value = clampEntryText(this._input.value, this._maxLength);
|
|
92
|
+
this._announceText();
|
|
93
|
+
}
|
|
60
94
|
}
|
|
61
95
|
|
|
62
96
|
/** `Gtk.Entry:text-length` — code points, not UTF-16 units. */
|
|
@@ -92,7 +126,7 @@ export class GtkEntry extends HTMLElement {
|
|
|
92
126
|
const input = document.createElement('input');
|
|
93
127
|
input.className = 'adw-entry';
|
|
94
128
|
input.type = this.getAttribute('type') || 'text';
|
|
95
|
-
input.value = clampEntryText(this.getAttribute('value') ?? '', this._maxLength);
|
|
129
|
+
input.value = clampEntryText(this.getAttribute('value') ?? this.getAttribute('text') ?? '', this._maxLength);
|
|
96
130
|
input.placeholder = this.placeholderAttribute();
|
|
97
131
|
this.applyWidthRequest();
|
|
98
132
|
input.disabled = this.hasAttribute('disabled');
|
|
@@ -101,6 +135,7 @@ export class GtkEntry extends HTMLElement {
|
|
|
101
135
|
input.addEventListener('input', () => {
|
|
102
136
|
const clamped = clampEntryText(input.value, this._maxLength);
|
|
103
137
|
if (clamped !== input.value) input.value = clamped;
|
|
138
|
+
this._announceText();
|
|
104
139
|
});
|
|
105
140
|
input.addEventListener('keydown', (e) => {
|
|
106
141
|
if (e.key === 'Enter') {
|
|
@@ -109,6 +144,7 @@ export class GtkEntry extends HTMLElement {
|
|
|
109
144
|
});
|
|
110
145
|
|
|
111
146
|
this._input = input;
|
|
147
|
+
this._announced = input.value;
|
|
112
148
|
this.replaceChildren(input);
|
|
113
149
|
}
|
|
114
150
|
|
|
@@ -129,8 +165,10 @@ export class GtkEntry extends HTMLElement {
|
|
|
129
165
|
return;
|
|
130
166
|
}
|
|
131
167
|
if (!this._input) return;
|
|
132
|
-
if (name === 'value'
|
|
133
|
-
|
|
168
|
+
if (name === 'value' || name === 'text') {
|
|
169
|
+
this._input.value = clampEntryText(value ?? '', this._maxLength);
|
|
170
|
+
this._announceText();
|
|
171
|
+
} else if (name === 'placeholder-text' || name === 'placeholder')
|
|
134
172
|
this._input.placeholder = this.placeholderAttribute();
|
|
135
173
|
else if (name === 'type') this._input.type = value || 'text';
|
|
136
174
|
else if (name === 'disabled') this._input.disabled = value !== null;
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
normalizeMenuModel,
|
|
35
35
|
parseMenuModel,
|
|
36
36
|
} from '@gjsify/adwaita-core';
|
|
37
|
+
import { activateElementMenuItem, menuActionsOfElement } from '../application.js';
|
|
37
38
|
import type { AdwMenuActions, AdwMenuInput, AdwMenuModel, SplitButtonDirection } from '@gjsify/adwaita-core';
|
|
38
39
|
|
|
39
40
|
import { PopoverMenuView } from './popover-menu.js';
|
|
@@ -197,6 +198,7 @@ export class GtkMenuButton extends HTMLElement {
|
|
|
197
198
|
if (item === null) return;
|
|
198
199
|
this._popoverEl.popdown();
|
|
199
200
|
this._buttonEl.focus();
|
|
201
|
+
activateElementMenuItem(this, item);
|
|
200
202
|
this.dispatchEvent(
|
|
201
203
|
new CustomEvent('menu-item-activated', {
|
|
202
204
|
bubbles: true,
|
|
@@ -246,7 +248,10 @@ export class GtkMenuButton extends HTMLElement {
|
|
|
246
248
|
private _onPopoverToggled(open: boolean): void {
|
|
247
249
|
this.classList.toggle('active', open);
|
|
248
250
|
this._buttonEl.setAttribute('aria-expanded', String(open));
|
|
249
|
-
if (open)
|
|
251
|
+
if (open) {
|
|
252
|
+
this._refreshActions();
|
|
253
|
+
this._menuView.focusableRows[0]?.focus();
|
|
254
|
+
}
|
|
250
255
|
// A dismissal returns the popup to its top page: a menu that reopens inside a
|
|
251
256
|
// submenu is one the reader cannot get out of.
|
|
252
257
|
else this._menuView.reset();
|
|
@@ -270,6 +275,12 @@ export class GtkMenuButton extends HTMLElement {
|
|
|
270
275
|
this._renderMenu();
|
|
271
276
|
}
|
|
272
277
|
|
|
278
|
+
/** The registry's answer, unless the application wrote `actions` itself (ADR 0097 § 2). */
|
|
279
|
+
private _refreshActions(): void {
|
|
280
|
+
this._menuView.setActions(this._actions ?? menuActionsOfElement(this, this._model));
|
|
281
|
+
this._menuView.render();
|
|
282
|
+
}
|
|
283
|
+
|
|
273
284
|
private _renderMenu(): void {
|
|
274
285
|
this._menuView.setMenuModel(this._model);
|
|
275
286
|
this._menuView.setTitle(this.getAttribute('menu-title') ?? this._menuTitle);
|