@gjsify/adwaita-web 0.10.0 → 0.12.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 +5 -5
- package/dist/adwaita-web.css +2742 -263
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/elements/adw-about-dialog.d.ts +77 -0
- package/lib/types/elements/adw-action-row.d.ts +15 -0
- package/lib/types/elements/adw-alert-dialog.d.ts +80 -0
- package/lib/types/elements/adw-avatar.d.ts +9 -0
- package/lib/types/elements/adw-banner.d.ts +9 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +35 -0
- package/lib/types/elements/adw-button-content.d.ts +10 -0
- package/lib/types/elements/adw-button-row.d.ts +11 -0
- package/lib/types/elements/adw-button.d.ts +11 -0
- package/lib/types/elements/adw-carousel.d.ts +64 -0
- package/lib/types/elements/adw-clamp.d.ts +6 -0
- package/lib/types/elements/adw-combo-row.d.ts +3 -0
- package/lib/types/elements/adw-entry-row.d.ts +10 -0
- package/lib/types/elements/adw-entry.d.ts +11 -0
- package/lib/types/elements/adw-expander-row.d.ts +26 -0
- package/lib/types/elements/adw-header-bar.d.ts +4 -0
- package/lib/types/elements/adw-inline-view-switcher.d.ts +30 -0
- package/lib/types/elements/adw-navigation-split-view.d.ts +15 -0
- package/lib/types/elements/adw-navigation-view.d.ts +66 -0
- package/lib/types/elements/adw-password-entry-row.d.ts +16 -0
- package/lib/types/elements/adw-preferences-dialog.d.ts +36 -0
- package/lib/types/elements/adw-preferences-group.d.ts +8 -0
- package/lib/types/elements/adw-sidebar.d.ts +29 -0
- package/lib/types/elements/adw-spin-row.d.ts +3 -0
- package/lib/types/elements/adw-spinner.d.ts +6 -0
- package/lib/types/elements/adw-split-button.d.ts +18 -0
- package/lib/types/elements/adw-status-page.d.ts +11 -0
- package/lib/types/elements/adw-switch-row.d.ts +3 -0
- package/lib/types/elements/adw-tab-view.d.ts +24 -0
- package/lib/types/elements/adw-toast-overlay.d.ts +19 -4
- package/lib/types/elements/adw-toggle-group.d.ts +19 -0
- package/lib/types/elements/adw-toolbar-view.d.ts +13 -0
- package/lib/types/elements/adw-view-switcher.d.ts +29 -0
- package/lib/types/elements/adw-window-title.d.ts +9 -0
- package/lib/types/elements/adw-wrap-box.d.ts +13 -0
- package/lib/types/index.d.ts +29 -0
- package/lib/types/styles.generated.d.ts +1 -0
- package/package.json +5 -5
- package/scss/_about_dialog.scss +257 -0
- package/scss/_action_row.scss +65 -0
- package/scss/_alert_dialog.scss +151 -0
- package/scss/_avatar.scss +31 -0
- package/scss/_banner.scss +46 -0
- package/scss/_bottom_sheet.scss +113 -0
- package/scss/_button.scss +168 -0
- package/scss/_button_content.scss +48 -0
- package/scss/_button_row.scss +100 -0
- package/scss/_carousel.scss +130 -0
- package/scss/_clamp.scss +11 -0
- package/scss/_entry.scss +49 -0
- package/scss/_entry_row.scss +54 -0
- package/scss/_expander_row.scss +150 -0
- package/scss/_headerbar.scss +1 -1
- package/scss/_inline_view_switcher.scss +174 -0
- package/scss/_navigation_split_view.scss +48 -0
- package/scss/_navigation_view.scss +80 -0
- package/scss/_overlay_split_view.scss +19 -0
- package/scss/_password_entry_row.scss +67 -0
- package/scss/_preferences.scss +26 -3
- package/scss/_preferences_dialog.scss +154 -0
- package/scss/_row.scss +31 -0
- package/scss/_sidebar.scss +213 -0
- package/scss/_spin_row.scss +0 -1
- package/scss/_spinner.scss +28 -0
- package/scss/_split_button.scss +222 -0
- package/scss/_status_page.scss +40 -0
- package/scss/_switch_row.scss +0 -1
- package/scss/_tab_view.scss +193 -0
- package/scss/_theme.scss +12 -0
- package/scss/_toast.scss +122 -12
- package/scss/_toggle_group.scss +124 -0
- package/scss/_toolbar_view.scss +31 -0
- package/scss/_variables.scss +19 -0
- package/scss/_view_switcher.scss +133 -0
- package/scss/_window_title.scss +33 -0
- package/scss/_wrap_box.scss +20 -0
- package/scss/adwaita-skin.scss +38 -0
- package/src/elements/adw-about-dialog.ts +629 -0
- package/src/elements/adw-action-row.ts +82 -0
- package/src/elements/adw-alert-dialog.ts +385 -0
- package/src/elements/adw-avatar.ts +108 -0
- package/src/elements/adw-banner.ts +57 -0
- package/src/elements/adw-bottom-sheet.ts +205 -0
- package/src/elements/adw-button-content.ts +69 -0
- package/src/elements/adw-button-row.ts +75 -0
- package/src/elements/adw-button.ts +81 -0
- package/src/elements/adw-carousel.ts +321 -0
- package/src/elements/adw-clamp.ts +28 -0
- package/src/elements/adw-combo-row.ts +32 -20
- package/src/elements/adw-entry-row.ts +69 -0
- package/src/elements/adw-entry.ts +61 -0
- package/src/elements/adw-expander-row.ts +167 -0
- package/src/elements/adw-header-bar.ts +21 -8
- package/src/elements/adw-inline-view-switcher.ts +222 -0
- package/src/elements/adw-navigation-split-view.ts +89 -0
- package/src/elements/adw-navigation-view.ts +344 -0
- package/src/elements/adw-password-entry-row.ts +121 -0
- package/src/elements/adw-preferences-dialog.ts +240 -0
- package/src/elements/adw-preferences-group.ts +79 -12
- package/src/elements/adw-sidebar.ts +238 -0
- package/src/elements/adw-spin-row.ts +28 -14
- package/src/elements/adw-spinner.ts +31 -0
- package/src/elements/adw-split-button.ts +206 -0
- package/src/elements/adw-status-page.ts +66 -0
- package/src/elements/adw-switch-row.ts +29 -23
- package/src/elements/adw-tab-view.ts +184 -0
- package/src/elements/adw-toast-overlay.ts +98 -19
- package/src/elements/adw-toggle-group.ts +126 -0
- package/src/elements/adw-toolbar-view.ts +60 -0
- package/src/elements/adw-view-switcher.ts +200 -0
- package/src/elements/adw-window-title.ts +48 -0
- package/src/elements/adw-wrap-box.ts +139 -0
- package/src/index.js +1 -1
- package/src/index.ts +46 -3
- package/src/styles.generated.js +2976 -0
- package/src/styles2.generated.js +2976 -0
- package/style.css.d.ts +17 -4
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
// <adw-spin-row> — Row with a
|
|
2
|
-
// Attributes: title, min, max, step, value
|
|
1
|
+
// <adw-spin-row> — Row with a title/subtitle and a numeric spin control (+/− buttons).
|
|
2
|
+
// Attributes: title, subtitle, min, max, step, value
|
|
3
3
|
// Events: notify::value (CustomEvent, mirrors GJS GObject signal naming)
|
|
4
4
|
// Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
|
|
5
5
|
// Copyright (c) 2025 csm. MIT License.
|
|
6
|
-
// Modifications: Reimplemented as Web Component for @gjsify/adwaita-web
|
|
6
|
+
// Modifications: Reimplemented as Web Component for @gjsify/adwaita-web;
|
|
7
|
+
// title/subtitle text column added to match Adw.SpinRow.
|
|
7
8
|
|
|
8
9
|
export class AdwSpinRow extends HTMLElement {
|
|
9
10
|
private _input!: HTMLInputElement;
|
|
11
|
+
private _titleEl!: HTMLSpanElement;
|
|
12
|
+
private _subtitleEl!: HTMLSpanElement;
|
|
10
13
|
private _min = 0;
|
|
11
14
|
private _max = 100;
|
|
12
15
|
private _step = 1;
|
|
@@ -14,7 +17,7 @@ export class AdwSpinRow extends HTMLElement {
|
|
|
14
17
|
private _initialized = false;
|
|
15
18
|
|
|
16
19
|
static get observedAttributes() {
|
|
17
|
-
return ['value', 'min', 'max', 'step'];
|
|
20
|
+
return ['title', 'subtitle', 'value', 'min', 'max', 'step'];
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
get value(): number {
|
|
@@ -36,34 +39,34 @@ export class AdwSpinRow extends HTMLElement {
|
|
|
36
39
|
if (this._initialized) return;
|
|
37
40
|
this._initialized = true;
|
|
38
41
|
|
|
39
|
-
const title = this.getAttribute('title') || '';
|
|
40
42
|
this._min = parseFloat(this.getAttribute('min') || '0');
|
|
41
43
|
this._max = parseFloat(this.getAttribute('max') || '100');
|
|
42
44
|
this._step = parseFloat(this.getAttribute('step') || '1');
|
|
43
45
|
this._value = parseFloat(this.getAttribute('value') || String(this._min));
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const text = document.createElement('div');
|
|
48
|
+
text.className = 'adw-row-text';
|
|
49
|
+
this._titleEl = document.createElement('span');
|
|
50
|
+
this._titleEl.className = 'adw-row-title';
|
|
51
|
+
this._subtitleEl = document.createElement('span');
|
|
52
|
+
this._subtitleEl.className = 'adw-row-subtitle';
|
|
53
|
+
text.append(this._titleEl, this._subtitleEl);
|
|
49
54
|
|
|
50
55
|
// Spin control container
|
|
51
56
|
const control = document.createElement('div');
|
|
52
57
|
control.className = 'adw-spin-control';
|
|
53
58
|
|
|
54
|
-
// Decrement button
|
|
55
59
|
const decBtn = document.createElement('button');
|
|
56
60
|
decBtn.className = 'adw-spin-dec';
|
|
57
61
|
decBtn.textContent = '−';
|
|
58
62
|
decBtn.addEventListener('click', () => this._adjust(-this._step));
|
|
59
63
|
|
|
60
|
-
// Value input
|
|
61
64
|
const input = document.createElement('input');
|
|
62
65
|
input.type = 'text';
|
|
63
66
|
input.value = this._formatValue(this._value);
|
|
64
67
|
input.addEventListener('change', () => {
|
|
65
68
|
const parsed = parseFloat(input.value);
|
|
66
|
-
if (!isNaN(parsed)) {
|
|
69
|
+
if (!Number.isNaN(parsed)) {
|
|
67
70
|
this.value = parsed;
|
|
68
71
|
this._emitChange();
|
|
69
72
|
} else {
|
|
@@ -71,19 +74,23 @@ export class AdwSpinRow extends HTMLElement {
|
|
|
71
74
|
}
|
|
72
75
|
});
|
|
73
76
|
|
|
74
|
-
// Increment button
|
|
75
77
|
const incBtn = document.createElement('button');
|
|
76
78
|
incBtn.className = 'adw-spin-inc';
|
|
77
79
|
incBtn.textContent = '+';
|
|
78
80
|
incBtn.addEventListener('click', () => this._adjust(this._step));
|
|
79
81
|
|
|
80
82
|
control.append(decBtn, input, incBtn);
|
|
81
|
-
this.
|
|
83
|
+
this.replaceChildren(text, control);
|
|
82
84
|
this._input = input;
|
|
85
|
+
this._renderText();
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
attributeChangedCallback(name: string, _old: string | null, val: string | null) {
|
|
86
89
|
if (!this._initialized) return;
|
|
90
|
+
if (name === 'title' || name === 'subtitle') {
|
|
91
|
+
this._renderText();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
87
94
|
const num = parseFloat(val || '0');
|
|
88
95
|
switch (name) {
|
|
89
96
|
case 'value':
|
|
@@ -104,6 +111,13 @@ export class AdwSpinRow extends HTMLElement {
|
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
|
|
114
|
+
private _renderText() {
|
|
115
|
+
this._titleEl.textContent = this.getAttribute('title') ?? '';
|
|
116
|
+
const subtitle = this.getAttribute('subtitle') ?? '';
|
|
117
|
+
this._subtitleEl.textContent = subtitle;
|
|
118
|
+
this._subtitleEl.hidden = subtitle.length === 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
private _adjust(delta: number) {
|
|
108
122
|
this.value = this._value + delta;
|
|
109
123
|
this._emitChange();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// <adw-spinner> — A continuously-spinning loading indicator.
|
|
2
|
+
// Attributes: size (px, default 24).
|
|
3
|
+
// Reference: refs/adwaita-web/adwaita-web/scss/_spinner.scss
|
|
4
|
+
// Reference: refs/libadwaita (Adw.Spinner)
|
|
5
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
6
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
7
|
+
|
|
8
|
+
export class AdwSpinner extends HTMLElement {
|
|
9
|
+
static get observedAttributes() {
|
|
10
|
+
return ['size'];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
connectedCallback() {
|
|
14
|
+
this._sync();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
attributeChangedCallback() {
|
|
18
|
+
this._sync();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private _sync() {
|
|
22
|
+
const size = parseFloat(this.getAttribute('size') || '24');
|
|
23
|
+
this.style.width = `${size}px`;
|
|
24
|
+
this.style.height = `${size}px`;
|
|
25
|
+
// Scale the ring thickness with the diameter (libadwaita's arc thickens
|
|
26
|
+
// with size); clamp so tiny spinners stay visible.
|
|
27
|
+
this.style.borderWidth = `${Math.max(2, Math.round(size / 12))}px`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
customElements.define('adw-spinner', AdwSpinner);
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// <adw-split-button> — Adwaita split button: a primary action button paired
|
|
2
|
+
// with an attached dropdown that opens a menu of related actions. The two
|
|
3
|
+
// halves are separated by a thin divider; the .suggested-action /
|
|
4
|
+
// .destructive-action / .flat style classes recolor the whole control like the
|
|
5
|
+
// matching Adwaita button variants.
|
|
6
|
+
// Attributes: label, icon-name (symbolic name, e.g. "document-save"; an icon
|
|
7
|
+
// without a label collapses the action half to icon-only), tooltip,
|
|
8
|
+
// dropdown-tooltip, disabled, and the boolean variant flags
|
|
9
|
+
// suggested / destructive / flat.
|
|
10
|
+
// Menu entries are provided as a JSON array on the `menu` attribute, each
|
|
11
|
+
// entry `{ "label": "Save as…", "action": "app.save-as" }`.
|
|
12
|
+
// Events (all CustomEvent, bubbles — mirror the Adw.SplitButton GObject signals):
|
|
13
|
+
// `clicked` — the primary action half was activated.
|
|
14
|
+
// `notify::active` — the dropdown menu was opened/closed (detail.active).
|
|
15
|
+
// `menu-activated` — a menu entry was chosen (detail.action / detail.label).
|
|
16
|
+
// Reference: refs/libadwaita/src/adw-split-button.c (AdwSplitButton)
|
|
17
|
+
// Reference: refs/adwaita-web/adwaita-web/scss/_split_button.scss
|
|
18
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
19
|
+
// Copyright (c) 2025 csm (adwaita-web). MIT License.
|
|
20
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
21
|
+
|
|
22
|
+
const VARIANT_CLASSES: Record<string, string> = {
|
|
23
|
+
flat: 'flat',
|
|
24
|
+
suggested: 'suggested-action',
|
|
25
|
+
destructive: 'destructive-action',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface MenuEntry {
|
|
29
|
+
label: string;
|
|
30
|
+
action?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class AdwSplitButton extends HTMLElement {
|
|
34
|
+
private _actionEl!: HTMLButtonElement;
|
|
35
|
+
private _dropdownEl!: HTMLButtonElement;
|
|
36
|
+
private _menuEl!: HTMLDivElement;
|
|
37
|
+
private _label = '';
|
|
38
|
+
private _active = false;
|
|
39
|
+
private _initialized = false;
|
|
40
|
+
private _onDocumentPointerDown = (event: Event): void => {
|
|
41
|
+
if (!this.contains(event.target as Node)) this._setActive(false);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
static get observedAttributes() {
|
|
45
|
+
return [
|
|
46
|
+
'label',
|
|
47
|
+
'icon-name',
|
|
48
|
+
'tooltip',
|
|
49
|
+
'dropdown-tooltip',
|
|
50
|
+
'menu',
|
|
51
|
+
'disabled',
|
|
52
|
+
'flat',
|
|
53
|
+
'suggested',
|
|
54
|
+
'destructive',
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Whether the dropdown menu is currently open. */
|
|
59
|
+
get active(): boolean {
|
|
60
|
+
return this._active;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
connectedCallback() {
|
|
64
|
+
if (this._initialized) return;
|
|
65
|
+
this._initialized = true;
|
|
66
|
+
|
|
67
|
+
// Capture any inline text as the label before we take over the subtree.
|
|
68
|
+
this._label = (this.getAttribute('label') ?? this.textContent ?? '').trim();
|
|
69
|
+
|
|
70
|
+
this.classList.add('adw-split-button');
|
|
71
|
+
|
|
72
|
+
this._actionEl = document.createElement('button');
|
|
73
|
+
this._actionEl.type = 'button';
|
|
74
|
+
this._actionEl.className = 'adw-split-button-action';
|
|
75
|
+
|
|
76
|
+
this._dropdownEl = document.createElement('button');
|
|
77
|
+
this._dropdownEl.type = 'button';
|
|
78
|
+
this._dropdownEl.className = 'adw-split-button-dropdown';
|
|
79
|
+
const arrow = document.createElement('span');
|
|
80
|
+
arrow.className = 'adw-icon adw-icon--go-down';
|
|
81
|
+
arrow.setAttribute('aria-hidden', 'true');
|
|
82
|
+
this._dropdownEl.appendChild(arrow);
|
|
83
|
+
|
|
84
|
+
this._menuEl = document.createElement('div');
|
|
85
|
+
this._menuEl.className = 'adw-split-button-menu';
|
|
86
|
+
this._menuEl.hidden = true;
|
|
87
|
+
|
|
88
|
+
this.replaceChildren(this._actionEl, this._dropdownEl, this._menuEl);
|
|
89
|
+
|
|
90
|
+
this._actionEl.addEventListener('click', () => {
|
|
91
|
+
if (this.hasAttribute('disabled')) return;
|
|
92
|
+
this.dispatchEvent(new CustomEvent('clicked', { bubbles: true }));
|
|
93
|
+
});
|
|
94
|
+
this._dropdownEl.addEventListener('click', () => {
|
|
95
|
+
if (this.hasAttribute('disabled')) return;
|
|
96
|
+
this._setActive(!this._active);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
this._render();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
disconnectedCallback() {
|
|
103
|
+
document.removeEventListener('pointerdown', this._onDocumentPointerDown);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
attributeChangedCallback() {
|
|
107
|
+
if (this._initialized) this._render();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private _setActive(active: boolean): void {
|
|
111
|
+
if (this._active === active) return;
|
|
112
|
+
this._active = active;
|
|
113
|
+
this._menuEl.hidden = !active;
|
|
114
|
+
this.classList.toggle('active', active);
|
|
115
|
+
this._dropdownEl.setAttribute('aria-expanded', String(active));
|
|
116
|
+
if (active) {
|
|
117
|
+
document.addEventListener('pointerdown', this._onDocumentPointerDown);
|
|
118
|
+
} else {
|
|
119
|
+
document.removeEventListener('pointerdown', this._onDocumentPointerDown);
|
|
120
|
+
}
|
|
121
|
+
this.dispatchEvent(new CustomEvent('notify::active', { bubbles: true, detail: { active } }));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private _parseMenu(): MenuEntry[] {
|
|
125
|
+
const raw = this.getAttribute('menu');
|
|
126
|
+
if (!raw) return [];
|
|
127
|
+
try {
|
|
128
|
+
const parsed: unknown = JSON.parse(raw);
|
|
129
|
+
if (!Array.isArray(parsed)) return [];
|
|
130
|
+
return parsed
|
|
131
|
+
.filter((entry): entry is MenuEntry => typeof entry === 'object' && entry !== null && 'label' in entry)
|
|
132
|
+
.map((entry) => ({ label: String(entry.label), action: entry.action }));
|
|
133
|
+
} catch {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private _render() {
|
|
139
|
+
const disabled = this.hasAttribute('disabled');
|
|
140
|
+
this.classList.toggle('disabled', disabled);
|
|
141
|
+
this._actionEl.disabled = disabled;
|
|
142
|
+
this._dropdownEl.disabled = disabled;
|
|
143
|
+
|
|
144
|
+
this.classList.remove('flat', 'suggested-action', 'destructive-action');
|
|
145
|
+
for (const [attr, cls] of Object.entries(VARIANT_CLASSES)) {
|
|
146
|
+
if (this.hasAttribute(attr)) this.classList.add(cls);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// --- Primary action half ---
|
|
150
|
+
const icon = this.getAttribute('icon-name');
|
|
151
|
+
const label = (this.getAttribute('label') ?? this._label).trim();
|
|
152
|
+
this._actionEl.classList.toggle('icon-only', Boolean(icon) && !label);
|
|
153
|
+
|
|
154
|
+
this._actionEl.replaceChildren();
|
|
155
|
+
if (icon) {
|
|
156
|
+
const span = document.createElement('span');
|
|
157
|
+
span.className = `adw-icon adw-icon--${icon}`;
|
|
158
|
+
span.setAttribute('aria-hidden', 'true');
|
|
159
|
+
this._actionEl.appendChild(span);
|
|
160
|
+
}
|
|
161
|
+
if (label) this._actionEl.appendChild(document.createTextNode(label));
|
|
162
|
+
|
|
163
|
+
const tooltip = this.getAttribute('tooltip');
|
|
164
|
+
this._actionEl.title = tooltip ?? '';
|
|
165
|
+
// An icon-only action half has no text, so give it an accessible name —
|
|
166
|
+
// prefer the tooltip, fall back to the symbolic icon name (WCAG 4.1.2).
|
|
167
|
+
if (icon && !label) {
|
|
168
|
+
this._actionEl.setAttribute('aria-label', tooltip ?? icon);
|
|
169
|
+
} else {
|
|
170
|
+
this._actionEl.removeAttribute('aria-label');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --- Dropdown half ---
|
|
174
|
+
const dropdownTooltip = this.getAttribute('dropdown-tooltip');
|
|
175
|
+
this._dropdownEl.title = dropdownTooltip ?? '';
|
|
176
|
+
this._dropdownEl.setAttribute('aria-label', dropdownTooltip ?? 'Menu');
|
|
177
|
+
this._dropdownEl.setAttribute('aria-haspopup', 'menu');
|
|
178
|
+
this._dropdownEl.setAttribute('aria-expanded', String(this._active));
|
|
179
|
+
|
|
180
|
+
// --- Menu ---
|
|
181
|
+
const entries = this._parseMenu();
|
|
182
|
+
this._menuEl.replaceChildren();
|
|
183
|
+
this._menuEl.setAttribute('role', 'menu');
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
const item = document.createElement('button');
|
|
186
|
+
item.type = 'button';
|
|
187
|
+
item.className = 'adw-split-button-menu-item';
|
|
188
|
+
item.setAttribute('role', 'menuitem');
|
|
189
|
+
item.textContent = entry.label;
|
|
190
|
+
item.addEventListener('click', () => {
|
|
191
|
+
this._setActive(false);
|
|
192
|
+
this.dispatchEvent(
|
|
193
|
+
new CustomEvent('menu-activated', {
|
|
194
|
+
bubbles: true,
|
|
195
|
+
detail: { label: entry.label, action: entry.action },
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
this._menuEl.appendChild(item);
|
|
200
|
+
}
|
|
201
|
+
// A split button with no menu entries hides its (empty) popover entirely.
|
|
202
|
+
if (entries.length === 0) this._setActive(false);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
customElements.define('adw-split-button', AdwSplitButton);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// <adw-status-page> — A centered empty/placeholder state: a large symbolic
|
|
2
|
+
// icon, a title, a description and an optional action (slotted child).
|
|
3
|
+
// Attributes: icon (symbolic name, with or without -symbolic), title, description.
|
|
4
|
+
// Reference: refs/adwaita-web/adwaita-web/scss/_status_page.scss
|
|
5
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_status-page.scss
|
|
6
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
7
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
8
|
+
|
|
9
|
+
export class AdwStatusPage extends HTMLElement {
|
|
10
|
+
private _iconEl!: HTMLSpanElement;
|
|
11
|
+
private _titleEl!: HTMLSpanElement;
|
|
12
|
+
private _descEl!: HTMLSpanElement;
|
|
13
|
+
private _childEl!: HTMLDivElement;
|
|
14
|
+
private _initialized = false;
|
|
15
|
+
|
|
16
|
+
static get observedAttributes() {
|
|
17
|
+
return ['icon', 'title', 'description'];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
connectedCallback() {
|
|
21
|
+
if (this._initialized) return;
|
|
22
|
+
this._initialized = true;
|
|
23
|
+
|
|
24
|
+
const children = Array.from(this.childNodes);
|
|
25
|
+
|
|
26
|
+
this._iconEl = document.createElement('span');
|
|
27
|
+
this._iconEl.className = 'adw-status-page-icon adw-icon';
|
|
28
|
+
this._iconEl.setAttribute('aria-hidden', 'true');
|
|
29
|
+
|
|
30
|
+
this._titleEl = document.createElement('span');
|
|
31
|
+
this._titleEl.className = 'adw-status-page-title';
|
|
32
|
+
|
|
33
|
+
this._descEl = document.createElement('span');
|
|
34
|
+
this._descEl.className = 'adw-status-page-description';
|
|
35
|
+
|
|
36
|
+
this._childEl = document.createElement('div');
|
|
37
|
+
this._childEl.className = 'adw-status-page-child';
|
|
38
|
+
for (const child of children) this._childEl.appendChild(child);
|
|
39
|
+
|
|
40
|
+
this.replaceChildren(this._iconEl, this._titleEl, this._descEl, this._childEl);
|
|
41
|
+
this._render();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
attributeChangedCallback() {
|
|
45
|
+
if (this._initialized) this._render();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private _render() {
|
|
49
|
+
const icon = (this.getAttribute('icon') ?? '').replace(/-symbolic$/, '');
|
|
50
|
+
// Swap the icon mask class (keep the base + size class).
|
|
51
|
+
this._iconEl.className = `adw-status-page-icon adw-icon${icon ? ` adw-icon--${icon}` : ''}`;
|
|
52
|
+
this._iconEl.hidden = icon.length === 0;
|
|
53
|
+
|
|
54
|
+
const title = this.getAttribute('title') ?? '';
|
|
55
|
+
this._titleEl.textContent = title;
|
|
56
|
+
this._titleEl.hidden = title.length === 0;
|
|
57
|
+
|
|
58
|
+
const description = this.getAttribute('description') ?? '';
|
|
59
|
+
this._descEl.textContent = description;
|
|
60
|
+
this._descEl.hidden = description.length === 0;
|
|
61
|
+
|
|
62
|
+
this._childEl.hidden = this._childEl.childElementCount === 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
customElements.define('adw-status-page', AdwStatusPage);
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
// <adw-switch-row> — Row with a
|
|
2
|
-
// Attributes: title, active (boolean)
|
|
1
|
+
// <adw-switch-row> — Row with a title/subtitle and a trailing toggle switch.
|
|
2
|
+
// Attributes: title, subtitle, active (boolean)
|
|
3
3
|
// Events: notify::active (CustomEvent, mirrors GJS GObject signal naming)
|
|
4
4
|
// Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
|
|
5
5
|
// Copyright (c) 2025 csm. MIT License.
|
|
6
|
-
// Modifications: Reimplemented as Web Component for @gjsify/adwaita-web
|
|
6
|
+
// Modifications: Reimplemented as Web Component for @gjsify/adwaita-web;
|
|
7
|
+
// title/subtitle text column added to match Adw.SwitchRow.
|
|
7
8
|
|
|
8
9
|
export class AdwSwitchRow extends HTMLElement {
|
|
9
10
|
private _checkbox!: HTMLInputElement;
|
|
11
|
+
private _titleEl!: HTMLSpanElement;
|
|
12
|
+
private _subtitleEl!: HTMLSpanElement;
|
|
10
13
|
private _initialized = false;
|
|
11
14
|
|
|
12
15
|
static get observedAttributes() {
|
|
13
|
-
return ['active'];
|
|
16
|
+
return ['title', 'subtitle', 'active'];
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
get active(): boolean {
|
|
@@ -26,30 +29,24 @@ export class AdwSwitchRow extends HTMLElement {
|
|
|
26
29
|
if (this._initialized) return;
|
|
27
30
|
this._initialized = true;
|
|
28
31
|
|
|
29
|
-
const
|
|
30
|
-
|
|
32
|
+
const text = document.createElement('div');
|
|
33
|
+
text.className = 'adw-row-text';
|
|
34
|
+
this._titleEl = document.createElement('span');
|
|
35
|
+
this._titleEl.className = 'adw-row-title';
|
|
36
|
+
this._subtitleEl = document.createElement('span');
|
|
37
|
+
this._subtitleEl.className = 'adw-row-subtitle';
|
|
38
|
+
text.append(this._titleEl, this._subtitleEl);
|
|
31
39
|
|
|
32
|
-
// Title
|
|
33
|
-
const titleEl = document.createElement('span');
|
|
34
|
-
titleEl.className = 'adw-row-title';
|
|
35
|
-
titleEl.textContent = title;
|
|
36
|
-
|
|
37
|
-
// Switch label with hidden checkbox + slider
|
|
38
40
|
const label = document.createElement('label');
|
|
39
41
|
label.className = 'adw-switch';
|
|
40
|
-
|
|
41
42
|
const input = document.createElement('input');
|
|
42
43
|
input.type = 'checkbox';
|
|
43
|
-
input.checked =
|
|
44
|
-
|
|
44
|
+
input.checked = this.hasAttribute('active');
|
|
45
45
|
const slider = document.createElement('span');
|
|
46
46
|
slider.className = 'adw-switch-slider';
|
|
47
|
+
label.append(input, slider);
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
label.appendChild(slider);
|
|
50
|
-
|
|
51
|
-
this.appendChild(titleEl);
|
|
52
|
-
this.appendChild(label);
|
|
49
|
+
this.replaceChildren(text, label);
|
|
53
50
|
|
|
54
51
|
this._checkbox = input;
|
|
55
52
|
this._checkbox.addEventListener('change', () => {
|
|
@@ -61,12 +58,21 @@ export class AdwSwitchRow extends HTMLElement {
|
|
|
61
58
|
}),
|
|
62
59
|
);
|
|
63
60
|
});
|
|
61
|
+
|
|
62
|
+
this._renderText();
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
attributeChangedCallback(name: string, _old: string | null, value: string | null) {
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
if (!this._initialized) return;
|
|
67
|
+
if (name === 'active') this._checkbox.checked = value !== null;
|
|
68
|
+
else this._renderText();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private _renderText() {
|
|
72
|
+
this._titleEl.textContent = this.getAttribute('title') ?? '';
|
|
73
|
+
const subtitle = this.getAttribute('subtitle') ?? '';
|
|
74
|
+
this._subtitleEl.textContent = subtitle;
|
|
75
|
+
this._subtitleEl.hidden = subtitle.length === 0;
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
78
|
|