@gjsify/adwaita-web 0.31.0 → 0.32.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/dist/adwaita-web.css +786 -272
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/adw-about-dialog.spec.d.ts +1 -0
- package/lib/types/adw-action-rows.spec.d.ts +1 -0
- package/lib/types/adw-avatar.spec.d.ts +1 -0
- package/lib/types/adw-banner.spec.d.ts +1 -0
- package/lib/types/adw-button-content.spec.d.ts +1 -0
- package/lib/types/adw-carousel.spec.d.ts +1 -0
- package/lib/types/adw-checks.spec.d.ts +1 -0
- package/lib/types/adw-header-bar.spec.d.ts +1 -0
- package/lib/types/adw-icon.spec.d.ts +1 -0
- package/lib/types/adw-progress-bar.spec.d.ts +1 -0
- package/lib/types/adw-sidebar.spec.d.ts +1 -0
- package/lib/types/adw-switch.spec.d.ts +1 -0
- package/lib/types/adw-wrap-box.spec.d.ts +1 -0
- package/lib/types/bottom-sheet.spec.d.ts +1 -0
- package/lib/types/breakpoints.d.ts +22 -0
- package/lib/types/breakpoints.spec.d.ts +1 -0
- package/lib/types/chrome.spec.d.ts +1 -0
- package/lib/types/elements/adw-about-dialog.d.ts +47 -8
- package/lib/types/elements/adw-action-row.d.ts +35 -0
- package/lib/types/elements/adw-avatar.d.ts +11 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +33 -4
- package/lib/types/elements/adw-button-content.d.ts +12 -0
- package/lib/types/elements/adw-button-row.d.ts +10 -1
- package/lib/types/elements/adw-carousel.d.ts +111 -8
- package/lib/types/elements/adw-checks.d.ts +57 -0
- package/lib/types/elements/adw-clamp.d.ts +4 -1
- package/lib/types/elements/adw-data-grid.d.ts +11 -31
- package/lib/types/elements/adw-drop-down.d.ts +17 -20
- package/lib/types/elements/adw-entry-row.d.ts +93 -3
- package/lib/types/elements/adw-expander-row.d.ts +7 -1
- package/lib/types/elements/adw-header-bar.d.ts +15 -0
- package/lib/types/elements/adw-icon.d.ts +25 -0
- package/lib/types/elements/adw-inline-view-switcher.d.ts +37 -11
- package/lib/types/elements/adw-menu-button.d.ts +6 -6
- package/lib/types/elements/adw-navigation-split-view.d.ts +6 -0
- package/lib/types/elements/adw-navigation-view.d.ts +63 -20
- package/lib/types/elements/adw-overlay-split-view.d.ts +38 -0
- package/lib/types/elements/adw-password-entry-row.d.ts +19 -10
- package/lib/types/elements/adw-popover.d.ts +58 -0
- package/lib/types/elements/adw-preferences-dialog.d.ts +57 -2
- package/lib/types/elements/adw-preferences-group.d.ts +31 -0
- package/lib/types/elements/adw-progress-bar.d.ts +38 -0
- package/lib/types/elements/adw-sidebar.d.ts +37 -5
- package/lib/types/elements/adw-split-button.d.ts +53 -7
- package/lib/types/elements/adw-switch-row.d.ts +11 -2
- package/lib/types/elements/adw-switch.d.ts +15 -0
- package/lib/types/elements/adw-tab-view.d.ts +129 -12
- package/lib/types/elements/adw-toolbar-view.d.ts +5 -0
- package/lib/types/elements/adw-view-stack.d.ts +27 -19
- package/lib/types/elements/adw-view-switcher-bar.d.ts +23 -7
- package/lib/types/elements/adw-view-switcher.d.ts +22 -11
- package/lib/types/elements/adw-window-title.d.ts +3 -1
- package/lib/types/elements/view-switcher-dom.d.ts +44 -0
- package/lib/types/entry-rows.spec.d.ts +1 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/navigation-view.spec.d.ts +1 -0
- package/lib/types/preferences-search.d.ts +20 -0
- package/lib/types/preferences.spec.d.ts +1 -0
- package/lib/types/split-button.spec.d.ts +1 -0
- package/lib/types/split-views.spec.d.ts +1 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/lib/types/tab-view.spec.d.ts +1 -0
- package/lib/types/view-stack.spec.d.ts +1 -0
- package/lib/types/view-switcher.spec.d.ts +1 -0
- package/package.json +6 -6
- package/scss/_banner.scss +13 -0
- package/scss/_bottom_sheet.scss +5 -6
- package/scss/_button.scss +3 -26
- package/scss/_button_content.scss +28 -0
- package/scss/_checks.scss +228 -0
- package/scss/_clamp.scss +9 -3
- package/scss/_data_grid.scss +15 -7
- package/scss/_drop_down.scss +12 -55
- package/scss/_expander_row.scss +5 -50
- package/scss/_icon.scss +51 -0
- package/scss/_icons.generated.scss +6 -0
- package/scss/_inline_view_switcher.scss +34 -0
- package/scss/_labels.scss +135 -0
- package/scss/_menu_button.scss +10 -70
- package/scss/_popover.scss +186 -0
- package/scss/_preferences.scss +29 -0
- package/scss/_preferences_dialog.scss +17 -0
- package/scss/_progress_bar.scss +161 -0
- package/scss/_reset.scss +18 -7
- package/scss/_spinner.scss +7 -3
- package/scss/_split_button.scss +6 -50
- package/scss/_switch.scss +90 -0
- package/scss/_switch_row.scss +6 -52
- package/scss/_tab_view.scss +64 -0
- package/scss/_theme.scss +10 -0
- package/scss/_toolbar_view.scss +74 -0
- package/scss/_variables.scss +34 -0
- package/scss/_view_switcher.scss +40 -0
- package/scss/_view_switcher_bar.scss +37 -0
- package/scss/_wrap_box.scss +56 -6
- package/scss/adwaita-skin.scss +23 -0
- package/src/adw-about-dialog.spec.ts +350 -0
- package/src/adw-action-rows.spec.ts +342 -0
- package/src/adw-avatar.spec.ts +139 -0
- package/src/adw-banner.spec.ts +152 -0
- package/src/adw-button-content.spec.ts +183 -0
- package/src/adw-button-row.spec.ts +12 -6
- package/src/adw-carousel.spec.ts +389 -0
- package/src/adw-checks.spec.ts +368 -0
- package/src/adw-drop-down.spec.ts +123 -0
- package/src/adw-header-bar.spec.ts +83 -0
- package/src/adw-icon.spec.ts +167 -0
- package/src/adw-progress-bar.spec.ts +173 -0
- package/src/adw-sidebar.spec.ts +332 -0
- package/src/adw-switch.spec.ts +242 -0
- package/src/adw-tab-view.spec.ts +12 -3
- package/src/adw-view-switcher-bar.spec.ts +61 -13
- package/src/adw-wrap-box.spec.ts +194 -0
- package/src/bottom-sheet.spec.ts +325 -0
- package/src/breakpoints.spec.ts +147 -0
- package/src/breakpoints.ts +67 -0
- package/src/chrome.spec.ts +301 -0
- package/src/elements/adw-about-dialog.ts +223 -163
- package/src/elements/adw-action-row.ts +120 -10
- package/src/elements/adw-avatar.ts +48 -54
- package/src/elements/adw-banner.ts +46 -10
- package/src/elements/adw-bottom-sheet.ts +145 -60
- package/src/elements/adw-button-content.ts +107 -33
- package/src/elements/adw-button-row.ts +61 -37
- package/src/elements/adw-button.ts +5 -7
- package/src/elements/adw-carousel.ts +467 -105
- package/src/elements/adw-checks.ts +299 -0
- package/src/elements/adw-clamp.ts +80 -10
- package/src/elements/adw-combo-row.ts +15 -7
- package/src/elements/adw-data-grid.ts +43 -91
- package/src/elements/adw-drop-down.ts +117 -128
- package/src/elements/adw-entry-row.ts +404 -36
- package/src/elements/adw-expander-row.ts +61 -29
- package/src/elements/adw-header-bar.ts +31 -6
- package/src/elements/adw-icon.ts +148 -0
- package/src/elements/adw-inline-view-switcher.ts +240 -129
- package/src/elements/adw-menu-button.ts +93 -70
- package/src/elements/adw-navigation-split-view.ts +48 -7
- package/src/elements/adw-navigation-view.ts +286 -148
- package/src/elements/adw-overlay-split-view.ts +164 -14
- package/src/elements/adw-password-entry-row.ts +111 -89
- package/src/elements/adw-popover.ts +283 -0
- package/src/elements/adw-preferences-dialog.ts +120 -21
- package/src/elements/adw-preferences-group.ts +128 -22
- package/src/elements/adw-progress-bar.ts +189 -0
- package/src/elements/adw-sidebar.ts +301 -142
- package/src/elements/adw-spin-row.ts +11 -5
- package/src/elements/adw-spinner.ts +23 -8
- package/src/elements/adw-split-button.ts +371 -92
- package/src/elements/adw-status-page.ts +9 -9
- package/src/elements/adw-switch-row.ts +100 -30
- package/src/elements/adw-switch.ts +129 -0
- package/src/elements/adw-tab-view.ts +682 -129
- package/src/elements/adw-toggle-group.ts +5 -7
- package/src/elements/adw-toolbar-view.ts +84 -21
- package/src/elements/adw-view-stack.ts +162 -85
- package/src/elements/adw-view-switcher-bar.ts +163 -81
- package/src/elements/adw-view-switcher.ts +231 -112
- package/src/elements/adw-window-title.ts +44 -11
- package/src/elements/adw-wrap-box.ts +131 -35
- package/src/elements/view-switcher-dom.ts +108 -0
- package/src/entry-rows.spec.ts +391 -0
- package/src/index.ts +23 -0
- package/src/navigation-view.spec.ts +435 -0
- package/src/preferences-search.ts +135 -0
- package/src/preferences.spec.ts +250 -0
- package/src/source-view/adw-source-view.ts +2 -5
- package/src/split-button.spec.ts +470 -0
- package/src/split-views.spec.ts +309 -0
- package/src/styles.generated.ts +1 -1
- package/src/tab-view.spec.ts +451 -0
- package/src/test.browser.mts +49 -0
- package/src/view-stack.spec.ts +272 -0
- package/src/view-switcher.spec.ts +446 -0
|
@@ -5,6 +5,20 @@
|
|
|
5
5
|
// real Adwaita-styled popover positioned under the button; the popover is
|
|
6
6
|
// dismissed on an outside click or Escape and supports arrow-key navigation.
|
|
7
7
|
//
|
|
8
|
+
// The popover is `<adw-popover>` and the dismissal/keyboard machine is
|
|
9
|
+
// `@gjsify/adwaita-core`'s (ADR 0004). This element used to build both by hand,
|
|
10
|
+
// as did `<adw-drop-down>` and `<adw-split-button>` — three surfaces that
|
|
11
|
+
// disagreed with libadwaita and with each other on radius, shadow and padding,
|
|
12
|
+
// and two copies of the same `(current ± 1 + n) % n` arithmetic. This element
|
|
13
|
+
// keeps only what is a menu button: the icon, the item model, the title.
|
|
14
|
+
//
|
|
15
|
+
// THE ICON WENT THE SAME WAY, and it is where the drift actually bit: this
|
|
16
|
+
// element interpolated `class="adw-icon adw-menu-button-icon adw-icon--<name>"`
|
|
17
|
+
// while `<adw-split-button>` — alone among six copies — first checked the name
|
|
18
|
+
// was one CSS token. So `icon-name="a b"` shipped a stray `b` class here, and so
|
|
19
|
+
// did every JSON menu entry's `icon`. Both are `<adw-icon>` now, and the guard
|
|
20
|
+
// is `normalizeIconName`'s.
|
|
21
|
+
//
|
|
8
22
|
// Attributes:
|
|
9
23
|
// icon-name — symbolic icon (no `-symbolic` suffix), default `open-menu`.
|
|
10
24
|
// menu-title — optional heading shown atop the popover.
|
|
@@ -12,6 +26,10 @@
|
|
|
12
26
|
// disabled — boolean; a disabled button does not open the menu.
|
|
13
27
|
// flat — boolean style class (default: flat, like the header app menu).
|
|
14
28
|
// circular — boolean style class (round 34×34 button).
|
|
29
|
+
// direction — none | up | down | left | right (default down); where the
|
|
30
|
+
// popover opens. `none` behaves as `down`, which is
|
|
31
|
+
// Gtk.MenuButton's own rule, not ours — see
|
|
32
|
+
// {@link menuButtonPopupDirection}.
|
|
15
33
|
// Properties (mirroring Adw.MenuButton / the NS twin):
|
|
16
34
|
// menuItems — the menu entries ({ id?, label, icon? }[]) (get/set).
|
|
17
35
|
// menuTitle — the popover heading (get/set).
|
|
@@ -26,6 +44,21 @@
|
|
|
26
44
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
27
45
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
28
46
|
|
|
47
|
+
import { isSplitButtonDirection, menuButtonPopupDirection } from '@gjsify/adwaita-core';
|
|
48
|
+
import type { SplitButtonDirection } from '@gjsify/adwaita-core';
|
|
49
|
+
|
|
50
|
+
// SIDE-EFFECT import, deliberately separate from the type import below: it is
|
|
51
|
+
// what guarantees `adw-popover` is defined before this module's own
|
|
52
|
+
// `customElements.define` can upgrade a server-rendered `<adw-menu-button>` and
|
|
53
|
+
// build one. A combined `import { AdwPopover }` would NOT do it — the binding is
|
|
54
|
+
// only ever used in type position here, and this package compiles without
|
|
55
|
+
// `verbatimModuleSyntax`, so TypeScript would elide the whole statement and take
|
|
56
|
+
// the registration with it.
|
|
57
|
+
import './adw-popover.js';
|
|
58
|
+
import type { AdwPopover } from './adw-popover.js';
|
|
59
|
+
|
|
60
|
+
import { createAdwIcon } from './adw-icon.js';
|
|
61
|
+
|
|
29
62
|
/** A menu entry. `id` defaults to `label` on activation; `icon` is optional. */
|
|
30
63
|
export interface AdwMenuItem {
|
|
31
64
|
id?: string;
|
|
@@ -33,31 +66,38 @@ export interface AdwMenuItem {
|
|
|
33
66
|
icon?: string;
|
|
34
67
|
}
|
|
35
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Where the popover sits, per `GtkArrowType`. `menuButtonPopupDirection` folds
|
|
71
|
+
* `none` onto `down` (adw-split-button.c:415 — a pass-through to
|
|
72
|
+
* `gtk_menu_button_set_direction`, so it is GtkMenuButton's rule); this table is
|
|
73
|
+
* only the direction→CSS-axis mapping, which is a renderer fact.
|
|
74
|
+
*
|
|
75
|
+
* `left`/`right` become `start`/`end` because the surface is placed with
|
|
76
|
+
* logical properties, so it follows the writing direction the way GTK's
|
|
77
|
+
* `:dir(rtl)` popovers do.
|
|
78
|
+
*/
|
|
79
|
+
const POPOVER_POSITIONS = {
|
|
80
|
+
down: 'bottom',
|
|
81
|
+
up: 'top',
|
|
82
|
+
left: 'start',
|
|
83
|
+
right: 'end',
|
|
84
|
+
} as const;
|
|
85
|
+
|
|
36
86
|
export class AdwMenuButton extends HTMLElement {
|
|
37
87
|
private _buttonEl!: HTMLButtonElement;
|
|
38
|
-
private _popoverEl!:
|
|
88
|
+
private _popoverEl!: AdwPopover;
|
|
39
89
|
private _items: AdwMenuItem[] = [];
|
|
40
90
|
private _menuTitle = '';
|
|
41
|
-
private _active = false;
|
|
42
91
|
private _initialized = false;
|
|
43
92
|
private _itemButtons: HTMLButtonElement[] = [];
|
|
44
|
-
private _onDocumentPointerDown = (event: Event): void => {
|
|
45
|
-
if (!this.contains(event.target as Node)) this._setActive(false);
|
|
46
|
-
};
|
|
47
|
-
private _onDocumentKeyDown = (event: KeyboardEvent): void => {
|
|
48
|
-
if (event.key === 'Escape') {
|
|
49
|
-
this._setActive(false);
|
|
50
|
-
this._buttonEl.focus();
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
93
|
|
|
54
94
|
static get observedAttributes() {
|
|
55
|
-
return ['icon-name', 'menu-title', 'menu', 'disabled', 'flat', 'circular'];
|
|
95
|
+
return ['icon-name', 'menu-title', 'menu', 'disabled', 'flat', 'circular', 'direction'];
|
|
56
96
|
}
|
|
57
97
|
|
|
58
98
|
/** Whether the popover menu is currently open. */
|
|
59
99
|
get active(): boolean {
|
|
60
|
-
return this.
|
|
100
|
+
return this._popoverEl?.open ?? false;
|
|
61
101
|
}
|
|
62
102
|
|
|
63
103
|
/** The menu entries. Setting rebuilds the popover. */
|
|
@@ -80,6 +120,16 @@ export class AdwMenuButton extends HTMLElement {
|
|
|
80
120
|
this.setAttribute('menu-title', this._menuTitle);
|
|
81
121
|
}
|
|
82
122
|
|
|
123
|
+
/** Where the popover opens, and (via core) which way `none` folds. */
|
|
124
|
+
get direction(): SplitButtonDirection {
|
|
125
|
+
const value = this.getAttribute('direction');
|
|
126
|
+
return isSplitButtonDirection(value) ? value : 'down';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
set direction(value: SplitButtonDirection) {
|
|
130
|
+
this.setAttribute('direction', value);
|
|
131
|
+
}
|
|
132
|
+
|
|
83
133
|
connectedCallback() {
|
|
84
134
|
if (this._initialized) return;
|
|
85
135
|
this._initialized = true;
|
|
@@ -92,17 +142,23 @@ export class AdwMenuButton extends HTMLElement {
|
|
|
92
142
|
this._buttonEl.setAttribute('aria-haspopup', 'menu');
|
|
93
143
|
this._buttonEl.setAttribute('aria-expanded', 'false');
|
|
94
144
|
|
|
95
|
-
this._popoverEl = document.createElement('
|
|
96
|
-
this._popoverEl.
|
|
145
|
+
this._popoverEl = document.createElement('adw-popover') as AdwPopover;
|
|
146
|
+
this._popoverEl.classList.add('adw-menu-button-popover');
|
|
97
147
|
this._popoverEl.setAttribute('role', 'menu');
|
|
98
|
-
|
|
99
|
-
|
|
148
|
+
// `gtk_menu_button_set_menu_model` builds a GtkPopoverMenu, whose CSS
|
|
149
|
+
// node is `popover.menu` — the surface _menus.scss styles throughout.
|
|
150
|
+
this._popoverEl.setAttribute('menu', '');
|
|
151
|
+
this._popoverEl.setAttribute('align', 'end'); // the header-end app menu idiom
|
|
100
152
|
|
|
101
153
|
this.replaceChildren(this._buttonEl, this._popoverEl);
|
|
154
|
+
this._popoverEl.anchor = this._buttonEl;
|
|
155
|
+
this._popoverEl.subscribe((open) => this._onPopoverToggled(open));
|
|
102
156
|
|
|
103
157
|
this._buttonEl.addEventListener('click', () => {
|
|
104
158
|
if (this.hasAttribute('disabled')) return;
|
|
105
|
-
|
|
159
|
+
// Nothing to show is not a menu — never open an empty popover.
|
|
160
|
+
if (!this._popoverEl.open && this._items.length === 0) return;
|
|
161
|
+
this._popoverEl.open = !this._popoverEl.open;
|
|
106
162
|
});
|
|
107
163
|
|
|
108
164
|
// Seed items from the `menu` attribute if the property was not set.
|
|
@@ -114,11 +170,6 @@ export class AdwMenuButton extends HTMLElement {
|
|
|
114
170
|
this._render();
|
|
115
171
|
}
|
|
116
172
|
|
|
117
|
-
disconnectedCallback() {
|
|
118
|
-
document.removeEventListener('pointerdown', this._onDocumentPointerDown);
|
|
119
|
-
document.removeEventListener('keydown', this._onDocumentKeyDown);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
173
|
attributeChangedCallback(name: string) {
|
|
123
174
|
if (!this._initialized || !this._buttonEl) return;
|
|
124
175
|
if (name === 'menu') {
|
|
@@ -143,52 +194,26 @@ export class AdwMenuButton extends HTMLElement {
|
|
|
143
194
|
}
|
|
144
195
|
}
|
|
145
196
|
|
|
146
|
-
private
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this.
|
|
151
|
-
this._popoverEl.hidden = !active;
|
|
152
|
-
this.classList.toggle('active', active);
|
|
153
|
-
this._buttonEl.setAttribute('aria-expanded', String(active));
|
|
154
|
-
if (active) {
|
|
155
|
-
document.addEventListener('pointerdown', this._onDocumentPointerDown);
|
|
156
|
-
document.addEventListener('keydown', this._onDocumentKeyDown);
|
|
157
|
-
// Move focus into the menu for keyboard navigation.
|
|
158
|
-
this._itemButtons[0]?.focus();
|
|
159
|
-
} else {
|
|
160
|
-
document.removeEventListener('pointerdown', this._onDocumentPointerDown);
|
|
161
|
-
document.removeEventListener('keydown', this._onDocumentKeyDown);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private _onMenuKeyDown(event: KeyboardEvent): void {
|
|
166
|
-
const buttons = this._itemButtons;
|
|
167
|
-
if (buttons.length === 0) return;
|
|
168
|
-
const current = buttons.indexOf(document.activeElement as HTMLButtonElement);
|
|
169
|
-
let next = -1;
|
|
170
|
-
if (event.key === 'ArrowDown') next = (current + 1) % buttons.length;
|
|
171
|
-
else if (event.key === 'ArrowUp') next = (current - 1 + buttons.length) % buttons.length;
|
|
172
|
-
else if (event.key === 'Home') next = 0;
|
|
173
|
-
else if (event.key === 'End') next = buttons.length - 1;
|
|
174
|
-
else return;
|
|
175
|
-
event.preventDefault();
|
|
176
|
-
buttons[next]?.focus();
|
|
197
|
+
private _onPopoverToggled(open: boolean): void {
|
|
198
|
+
this.classList.toggle('active', open);
|
|
199
|
+
this._buttonEl.setAttribute('aria-expanded', String(open));
|
|
200
|
+
// Move focus into the menu for keyboard navigation.
|
|
201
|
+
if (open) this._itemButtons[0]?.focus();
|
|
177
202
|
}
|
|
178
203
|
|
|
179
204
|
private _render(): void {
|
|
180
205
|
const disabled = this.hasAttribute('disabled');
|
|
181
206
|
this.classList.toggle('disabled', disabled);
|
|
182
207
|
this._buttonEl.disabled = disabled;
|
|
208
|
+
if (disabled) this._popoverEl.popdown();
|
|
183
209
|
this.classList.toggle('flat', this.hasAttribute('flat'));
|
|
184
210
|
this.classList.toggle('circular', this.hasAttribute('circular'));
|
|
211
|
+
this._popoverEl.position = POPOVER_POSITIONS[menuButtonPopupDirection(this.direction)];
|
|
185
212
|
|
|
186
|
-
const icon = (this.getAttribute('icon-name') ?? 'open-menu').replace(/-symbolic$/, '');
|
|
187
213
|
this._buttonEl.replaceChildren();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this._buttonEl.appendChild(iconEl);
|
|
214
|
+
this._buttonEl.appendChild(
|
|
215
|
+
createAdwIcon(this.getAttribute('icon-name') ?? 'open-menu', 'adw-menu-button-icon'),
|
|
216
|
+
);
|
|
192
217
|
this._buttonEl.setAttribute('aria-label', this.getAttribute('menu-title') || 'Menu');
|
|
193
218
|
|
|
194
219
|
this._renderMenu();
|
|
@@ -201,7 +226,7 @@ export class AdwMenuButton extends HTMLElement {
|
|
|
201
226
|
const title = this.getAttribute('menu-title') ?? this._menuTitle;
|
|
202
227
|
if (title) {
|
|
203
228
|
const titleEl = document.createElement('div');
|
|
204
|
-
titleEl.className = 'adw-menu-button-title';
|
|
229
|
+
titleEl.className = 'adw-popover-title adw-menu-button-title';
|
|
205
230
|
titleEl.textContent = title;
|
|
206
231
|
this._popoverEl.appendChild(titleEl);
|
|
207
232
|
}
|
|
@@ -209,24 +234,22 @@ export class AdwMenuButton extends HTMLElement {
|
|
|
209
234
|
for (const [index, entry] of this._items.entries()) {
|
|
210
235
|
const item = document.createElement('button');
|
|
211
236
|
item.type = 'button';
|
|
212
|
-
item.className = 'adw-menu-button-item';
|
|
237
|
+
item.className = 'adw-popover-item adw-menu-button-item';
|
|
213
238
|
item.setAttribute('role', 'menuitem');
|
|
214
239
|
item.tabIndex = -1;
|
|
215
240
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
item.appendChild(iconEl);
|
|
222
|
-
}
|
|
241
|
+
// An entry with no USABLE icon gets no icon node at all, rather than
|
|
242
|
+
// an empty 16px box — the element itself answers whether the name
|
|
243
|
+
// resolved, so the `-symbolic`-only and bad-token cases agree.
|
|
244
|
+
const entryIcon = createAdwIcon(entry.icon ?? null, 'adw-menu-button-item-icon');
|
|
245
|
+
if (entryIcon.resolvedIconName !== '') item.appendChild(entryIcon);
|
|
223
246
|
const labelEl = document.createElement('span');
|
|
224
247
|
labelEl.className = 'adw-menu-button-item-label';
|
|
225
248
|
labelEl.textContent = entry.label;
|
|
226
249
|
item.appendChild(labelEl);
|
|
227
250
|
|
|
228
251
|
item.addEventListener('click', () => {
|
|
229
|
-
this.
|
|
252
|
+
this._popoverEl.popdown();
|
|
230
253
|
this._buttonEl.focus();
|
|
231
254
|
this.dispatchEvent(
|
|
232
255
|
new CustomEvent('menu-item-activated', {
|
|
@@ -240,7 +263,7 @@ export class AdwMenuButton extends HTMLElement {
|
|
|
240
263
|
}
|
|
241
264
|
|
|
242
265
|
// A menu-button with no entries can never open.
|
|
243
|
-
if (this._items.length === 0) this.
|
|
266
|
+
if (this._items.length === 0) this._popoverEl.popdown();
|
|
244
267
|
}
|
|
245
268
|
}
|
|
246
269
|
|
|
@@ -2,20 +2,27 @@
|
|
|
2
2
|
// visible when expanded; when `collapsed` it shows one pane at a time
|
|
3
3
|
// (`show-content` decides which), mirroring Adw.NavigationSplitView's
|
|
4
4
|
// push-navigation on narrow widths.
|
|
5
|
-
// Attributes: collapsed, show-content, min-sidebar-width, max-sidebar-width
|
|
5
|
+
// Attributes: collapsed, show-content, min-sidebar-width, max-sidebar-width,
|
|
6
|
+
// breakpoint (an Adwaita condition, e.g. "max-width: 720px", that collapses
|
|
7
|
+
// the view by itself — the counterpart to an Adw.Breakpoint add_setter()).
|
|
6
8
|
// Slots: slot="sidebar", slot="content".
|
|
7
9
|
// Reference: refs/adwaita-web/adwaita-web/docs/widgets/navigationsplitview.md
|
|
8
10
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_sidebars.scss
|
|
9
11
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
10
12
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
11
13
|
|
|
14
|
+
import { DEFAULT_MAX_SIDEBAR_WIDTH, DEFAULT_MIN_SIDEBAR_WIDTH, resolveSidebarBounds } from '@gjsify/adwaita-core';
|
|
15
|
+
|
|
16
|
+
import { bindBreakpointSetter } from '../breakpoints.js';
|
|
17
|
+
|
|
12
18
|
export class AdwNavigationSplitView extends HTMLElement {
|
|
13
19
|
private _sidebarEl!: HTMLDivElement;
|
|
14
20
|
private _contentEl!: HTMLDivElement;
|
|
15
21
|
private _initialized = false;
|
|
22
|
+
private _disposeBreakpoint: (() => void) | undefined;
|
|
16
23
|
|
|
17
24
|
static get observedAttributes() {
|
|
18
|
-
return ['collapsed', 'show-content', 'min-sidebar-width', 'max-sidebar-width'];
|
|
25
|
+
return ['collapsed', 'show-content', 'min-sidebar-width', 'max-sidebar-width', 'breakpoint'];
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
get collapsed(): boolean {
|
|
@@ -53,11 +60,18 @@ export class AdwNavigationSplitView extends HTMLElement {
|
|
|
53
60
|
this.replaceChildren(this._sidebarEl, this._contentEl);
|
|
54
61
|
this._syncWidth();
|
|
55
62
|
this._syncClasses();
|
|
63
|
+
this._syncBreakpoint();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
disconnectedCallback() {
|
|
67
|
+
this._disposeBreakpoint?.();
|
|
68
|
+
this._disposeBreakpoint = undefined;
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
attributeChangedCallback(name: string) {
|
|
59
72
|
if (!this._initialized) return;
|
|
60
|
-
if (name === '
|
|
73
|
+
if (name === 'breakpoint') this._syncBreakpoint();
|
|
74
|
+
else if (name === 'min-sidebar-width' || name === 'max-sidebar-width') this._syncWidth();
|
|
61
75
|
else {
|
|
62
76
|
this._syncClasses();
|
|
63
77
|
// Collapsing flips whether the sidebar is width-capped, so re-apply.
|
|
@@ -74,16 +88,43 @@ export class AdwNavigationSplitView extends HTMLElement {
|
|
|
74
88
|
this._sidebarEl.style.maxWidth = '';
|
|
75
89
|
return;
|
|
76
90
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
// An ABSENT attribute used to mean "no bound at all", where
|
|
92
|
+
// Adw.NavigationSplitView always has 180 / 280
|
|
93
|
+
// (adw-navigation-split-view.c min-sidebar-width / max-sidebar-width).
|
|
94
|
+
// The core also normalises the pair — libadwaita never lets max fall
|
|
95
|
+
// below min, and CSS resolves that conflict the other way round.
|
|
96
|
+
const bounds = resolveSidebarBounds(
|
|
97
|
+
{
|
|
98
|
+
minSidebarWidth: this._widthAttr('min-sidebar-width', DEFAULT_MIN_SIDEBAR_WIDTH),
|
|
99
|
+
maxSidebarWidth: this._widthAttr('max-sidebar-width', DEFAULT_MAX_SIDEBAR_WIDTH),
|
|
100
|
+
},
|
|
101
|
+
0,
|
|
102
|
+
{ ceil: true },
|
|
103
|
+
);
|
|
104
|
+
this._sidebarEl.style.minWidth = `${bounds.min}px`;
|
|
105
|
+
this._sidebarEl.style.maxWidth = `${bounds.max}px`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** A pixel attribute, falling back to the widget's own default. */
|
|
109
|
+
private _widthAttr(name: string, fallback: number): number {
|
|
110
|
+
const raw = this.getAttribute(name);
|
|
111
|
+
if (raw === null) return fallback;
|
|
112
|
+
const value = parseFloat(raw);
|
|
113
|
+
return Number.isFinite(value) ? value : fallback;
|
|
81
114
|
}
|
|
82
115
|
|
|
83
116
|
private _syncClasses() {
|
|
84
117
|
this.classList.toggle('collapsed', this.collapsed);
|
|
85
118
|
this.classList.toggle('show-content', this.showContent);
|
|
86
119
|
}
|
|
120
|
+
|
|
121
|
+
/** (Re)bind the `breakpoint` condition to `collapsed`. */
|
|
122
|
+
private _syncBreakpoint() {
|
|
123
|
+
this._disposeBreakpoint?.();
|
|
124
|
+
this._disposeBreakpoint = bindBreakpointSetter(this, this.getAttribute('breakpoint'), (active) => {
|
|
125
|
+
this.collapsed = active;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
customElements.define('adw-navigation-split-view', AdwNavigationSplitView);
|