@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// <adw-bottom-sheet> — A content area with a sheet that slides up from the
|
|
2
|
+
// bottom edge over the content (the web counterpart of Adw.BottomSheet). The
|
|
3
|
+
// content is shown persistently; the sheet is displayed above it when `open` is
|
|
4
|
+
// set, with an overlaid drag handle by default. When `modal` is set, a dimming
|
|
5
|
+
// layer covers the content and dismisses the sheet on click (unless `can-close`
|
|
6
|
+
// is false, in which case a close attempt is signalled instead).
|
|
7
|
+
//
|
|
8
|
+
// Children are declared as <adw-bottom-sheet-content> (the persistent content)
|
|
9
|
+
// and <adw-bottom-sheet-sheet> (the slide-up sheet); both are consumed at
|
|
10
|
+
// connect time. Unslotted children fall back to the content, mirroring the
|
|
11
|
+
// AdwBottomSheet GtkBuildable default (omitting the child type sets content).
|
|
12
|
+
//
|
|
13
|
+
// Attributes:
|
|
14
|
+
// open (boolean — whether the sheet is revealed; default closed)
|
|
15
|
+
// modal (boolean — dim + block the content while open; default on)
|
|
16
|
+
// can-close (boolean — user can dismiss via the handle / dimming / Esc;
|
|
17
|
+
// default on. When off, a dismissal raises `close-attempt`
|
|
18
|
+
// instead of closing — mirrors Adw.BottomSheet:can-close)
|
|
19
|
+
// show-drag-handle (boolean — overlay the drag handle on the sheet; default on)
|
|
20
|
+
//
|
|
21
|
+
// Events:
|
|
22
|
+
// `notify::open` (CustomEvent, bubbles, `detail = { open }`) when the revealed
|
|
23
|
+
// state changes — mirrors the Adw.BottomSheet `open` GObject property.
|
|
24
|
+
// `close-attempt` (CustomEvent, bubbles) when a dismissal is attempted while
|
|
25
|
+
// `can-close` is false — mirrors the Adw.BottomSheet `close-attempt` signal.
|
|
26
|
+
//
|
|
27
|
+
// Reference: refs/libadwaita/src/adw-bottom-sheet.c (AdwBottomSheet behaviour)
|
|
28
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_bottom-sheet.scss
|
|
29
|
+
// Copyright (c) 2023-2024 GNOME Foundation Inc. (libadwaita). LGPLv2.1+.
|
|
30
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
31
|
+
|
|
32
|
+
/** The persistent content. Child of <adw-bottom-sheet>; consumed at connect time. */
|
|
33
|
+
export class AdwBottomSheetContent extends HTMLElement {}
|
|
34
|
+
|
|
35
|
+
/** The slide-up sheet. Child of <adw-bottom-sheet>; consumed at connect time. */
|
|
36
|
+
export class AdwBottomSheetSheet extends HTMLElement {}
|
|
37
|
+
|
|
38
|
+
export class AdwBottomSheet extends HTMLElement {
|
|
39
|
+
private _initialized = false;
|
|
40
|
+
private _contentEl!: HTMLDivElement;
|
|
41
|
+
private _dimmingEl!: HTMLDivElement;
|
|
42
|
+
private _sheetEl!: HTMLDivElement;
|
|
43
|
+
private _dragHandleEl!: HTMLDivElement;
|
|
44
|
+
|
|
45
|
+
static get observedAttributes() {
|
|
46
|
+
return ['open', 'modal', 'can-close', 'show-drag-handle'];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Whether the sheet is revealed. */
|
|
50
|
+
get open(): boolean {
|
|
51
|
+
return this.hasAttribute('open');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
set open(value: boolean) {
|
|
55
|
+
if (value) this.setAttribute('open', '');
|
|
56
|
+
else this.removeAttribute('open');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Whether the content is dimmed + blocked while the sheet is open. */
|
|
60
|
+
get modal(): boolean {
|
|
61
|
+
return this._boolAttr('modal');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
set modal(value: boolean) {
|
|
65
|
+
this._setBoolAttr('modal', value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Whether the user can dismiss the sheet (handle / dimming / Escape). */
|
|
69
|
+
get canClose(): boolean {
|
|
70
|
+
return this._boolAttr('can-close');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
set canClose(value: boolean) {
|
|
74
|
+
this._setBoolAttr('can-close', value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Whether the overlaid drag handle is shown. */
|
|
78
|
+
get showDragHandle(): boolean {
|
|
79
|
+
return this._boolAttr('show-drag-handle');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
set showDragHandle(value: boolean) {
|
|
83
|
+
this._setBoolAttr('show-drag-handle', value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
connectedCallback() {
|
|
87
|
+
if (this._initialized) return;
|
|
88
|
+
this._initialized = true;
|
|
89
|
+
|
|
90
|
+
// Snapshot declared children before taking over the subtree. The sheet
|
|
91
|
+
// and content are placed by slot; anything unslotted falls back to the
|
|
92
|
+
// content (the AdwBottomSheet GtkBuildable default).
|
|
93
|
+
const sheetChildren = this._collectSlot('adw-bottom-sheet-sheet', 'sheet');
|
|
94
|
+
const contentChildren = this._collectSlot('adw-bottom-sheet-content', 'content');
|
|
95
|
+
const claimed = new Set<Node>([...sheetChildren, ...contentChildren]);
|
|
96
|
+
const unslotted = Array.from(this.childNodes).filter((n) => !claimed.has(n));
|
|
97
|
+
|
|
98
|
+
// Persistent content layer.
|
|
99
|
+
this._contentEl = document.createElement('div');
|
|
100
|
+
this._contentEl.className = 'adw-bottom-sheet-content';
|
|
101
|
+
for (const child of contentChildren) this._contentEl.appendChild(child);
|
|
102
|
+
for (const child of unslotted) this._contentEl.appendChild(child);
|
|
103
|
+
|
|
104
|
+
// Dimming layer — covers the content while the sheet is open (modal).
|
|
105
|
+
// Clicking it dismisses the sheet (or raises close-attempt when locked).
|
|
106
|
+
this._dimmingEl = document.createElement('div');
|
|
107
|
+
this._dimmingEl.className = 'adw-bottom-sheet-dimming';
|
|
108
|
+
this._dimmingEl.addEventListener('click', () => this._attemptClose());
|
|
109
|
+
|
|
110
|
+
// The slide-up sheet with its overlaid drag handle.
|
|
111
|
+
this._sheetEl = document.createElement('div');
|
|
112
|
+
this._sheetEl.className = 'adw-bottom-sheet-sheet';
|
|
113
|
+
|
|
114
|
+
this._dragHandleEl = document.createElement('div');
|
|
115
|
+
this._dragHandleEl.className = 'adw-bottom-sheet-drag-handle';
|
|
116
|
+
this._dragHandleEl.setAttribute('role', 'button');
|
|
117
|
+
this._dragHandleEl.setAttribute('aria-label', 'Drag handle');
|
|
118
|
+
this._dragHandleEl.tabIndex = 0;
|
|
119
|
+
// The handle is the primary pointer affordance for closing the sheet.
|
|
120
|
+
this._dragHandleEl.addEventListener('click', () => this._attemptClose());
|
|
121
|
+
this._dragHandleEl.addEventListener('keydown', (event) => {
|
|
122
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
this._attemptClose();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const sheetBody = document.createElement('div');
|
|
129
|
+
sheetBody.className = 'adw-bottom-sheet-sheet-body';
|
|
130
|
+
for (const child of sheetChildren) sheetBody.appendChild(child);
|
|
131
|
+
|
|
132
|
+
this._sheetEl.append(this._dragHandleEl, sheetBody);
|
|
133
|
+
|
|
134
|
+
this.replaceChildren(this._contentEl, this._dimmingEl, this._sheetEl);
|
|
135
|
+
|
|
136
|
+
// Escape closes the sheet when open (mirrors AdwBottomSheet's Escape
|
|
137
|
+
// shortcut → maybe_close_cb).
|
|
138
|
+
this.addEventListener('keydown', (event) => {
|
|
139
|
+
if (event.key === 'Escape' && this.open) {
|
|
140
|
+
event.stopPropagation();
|
|
141
|
+
this._attemptClose();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this._render();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
|
|
149
|
+
if (!this._initialized) return;
|
|
150
|
+
if (oldValue === newValue) return;
|
|
151
|
+
this._render();
|
|
152
|
+
if (name === 'open') {
|
|
153
|
+
this.dispatchEvent(new CustomEvent('notify::open', { bubbles: true, detail: { open: this.open } }));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private _attemptClose(): void {
|
|
158
|
+
if (!this.open) return;
|
|
159
|
+
if (!this.canClose) {
|
|
160
|
+
this.dispatchEvent(new CustomEvent('close-attempt', { bubbles: true }));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Keep the attribute in sync without re-entering the guarded callback
|
|
164
|
+
// (it no-ops on an unchanged value, but the notify::open fires from the
|
|
165
|
+
// attribute write below).
|
|
166
|
+
this.open = false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private _render(): void {
|
|
170
|
+
this.classList.toggle('open', this.open);
|
|
171
|
+
this.classList.toggle('modal', this.modal);
|
|
172
|
+
this.classList.toggle('has-drag-handle', this.showDragHandle);
|
|
173
|
+
|
|
174
|
+
// The dimming layer is only meaningful while modal; it fades in with the
|
|
175
|
+
// sheet and is non-interactive when closed.
|
|
176
|
+
this._dimmingEl.classList.toggle('visible', this.open && this.modal);
|
|
177
|
+
this._dragHandleEl.hidden = !this.showDragHandle;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Read a boolean attribute that defaults to `true` when absent. */
|
|
181
|
+
private _boolAttr(name: string): boolean {
|
|
182
|
+
const value = this.getAttribute(name);
|
|
183
|
+
// Absent attribute → default on; explicit `="false"` → off.
|
|
184
|
+
if (value === null) return name === 'modal' || name === 'can-close' || name === 'show-drag-handle';
|
|
185
|
+
return value !== 'false';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private _setBoolAttr(name: string, value: boolean): void {
|
|
189
|
+
if (value) this.setAttribute(name, '');
|
|
190
|
+
else this.setAttribute(name, 'false');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Move declared slot children out of the light-DOM tree into a list. */
|
|
194
|
+
private _collectSlot(tag: string, slot: string): Node[] {
|
|
195
|
+
const byElement = Array.from(this.querySelectorAll(`:scope > ${tag}`)).flatMap((el) =>
|
|
196
|
+
Array.from(el.childNodes),
|
|
197
|
+
);
|
|
198
|
+
const bySlotAttr = Array.from(this.querySelectorAll(`:scope > [slot="${slot}"]`));
|
|
199
|
+
return [...byElement, ...bySlotAttr];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
customElements.define('adw-bottom-sheet-content', AdwBottomSheetContent);
|
|
204
|
+
customElements.define('adw-bottom-sheet-sheet', AdwBottomSheetSheet);
|
|
205
|
+
customElements.define('adw-bottom-sheet', AdwBottomSheet);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// <adw-button-content> — a box-like child pairing a symbolic icon with a label,
|
|
2
|
+
// intended as the child of an <adw-button> (or Gtk.Button / MenuButton /
|
|
3
|
+
// SplitButton in libadwaita) when it needs both an icon and a label. With
|
|
4
|
+
// `can-shrink` the label ellipsizes instead of forcing the button wider.
|
|
5
|
+
// Attributes: icon-name (symbolic name, e.g. "folder-download"; empty hides the
|
|
6
|
+
// icon), label (empty hides the label), can-shrink (boolean — label
|
|
7
|
+
// ellipsizes when set).
|
|
8
|
+
// Events: `notify::icon-name`, `notify::label`, `notify::can-shrink`
|
|
9
|
+
// (CustomEvent, bubbles) mirroring the AdwButtonContent GObject notify signals
|
|
10
|
+
// the native story observes.
|
|
11
|
+
// Reference: refs/libadwaita/src/adw-button-content.c (AdwButtonContent)
|
|
12
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_buttons.scss (buttoncontent)
|
|
13
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
14
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
15
|
+
|
|
16
|
+
export class AdwButtonContent extends HTMLElement {
|
|
17
|
+
private _boxEl!: HTMLDivElement;
|
|
18
|
+
private _iconEl!: HTMLSpanElement;
|
|
19
|
+
private _labelEl!: HTMLSpanElement;
|
|
20
|
+
private _initialized = false;
|
|
21
|
+
|
|
22
|
+
static get observedAttributes() {
|
|
23
|
+
return ['icon-name', 'label', 'can-shrink'];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
connectedCallback() {
|
|
27
|
+
if (this._initialized) return;
|
|
28
|
+
this._initialized = true;
|
|
29
|
+
|
|
30
|
+
// CSS node mirrors libadwaita's `buttoncontent > box > {image, label}`.
|
|
31
|
+
this._boxEl = document.createElement('div');
|
|
32
|
+
this._boxEl.className = 'adw-button-content-box';
|
|
33
|
+
|
|
34
|
+
this._iconEl = document.createElement('span');
|
|
35
|
+
// The image node is GTK_ACCESSIBLE_ROLE_PRESENTATION in libadwaita —
|
|
36
|
+
// it is decorative, the label carries the accessible name.
|
|
37
|
+
this._iconEl.setAttribute('aria-hidden', 'true');
|
|
38
|
+
|
|
39
|
+
this._labelEl = document.createElement('span');
|
|
40
|
+
this._labelEl.className = 'adw-button-content-label';
|
|
41
|
+
|
|
42
|
+
this._boxEl.append(this._iconEl, this._labelEl);
|
|
43
|
+
this.replaceChildren(this._boxEl);
|
|
44
|
+
this._render();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
attributeChangedCallback(name: string) {
|
|
48
|
+
if (!this._initialized) return;
|
|
49
|
+
this._render();
|
|
50
|
+
// Mirror the AdwButtonContent notify::<prop> signals the native story
|
|
51
|
+
// listens to (icon-name / label / can-shrink).
|
|
52
|
+
this.dispatchEvent(new CustomEvent(`notify::${name}`, { bubbles: true }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private _render() {
|
|
56
|
+
const icon = this.getAttribute('icon-name') ?? '';
|
|
57
|
+
this._iconEl.className = icon ? `adw-icon adw-icon--${icon}` : '';
|
|
58
|
+
this._iconEl.hidden = icon.length === 0;
|
|
59
|
+
|
|
60
|
+
const label = this.getAttribute('label') ?? '';
|
|
61
|
+
this._labelEl.textContent = label;
|
|
62
|
+
this._labelEl.hidden = label.length === 0;
|
|
63
|
+
|
|
64
|
+
// can-shrink → ellipsize the label (Gtk.Label PANGO_ELLIPSIZE_END).
|
|
65
|
+
this.classList.toggle('can-shrink', this.hasAttribute('can-shrink'));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
customElements.define('adw-button-content', AdwButtonContent);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// <adw-button-row> — A boxed-list row that behaves like a button: a centered
|
|
2
|
+
// title with an optional start icon, spanning the full width of the list. The
|
|
3
|
+
// `suggested-action` / `destructive-action` style classes recolor it like the
|
|
4
|
+
// matching Adwaita button variants.
|
|
5
|
+
// Attributes: title, start-icon-name (symbolic name, e.g. "list-add"),
|
|
6
|
+
// activatable (default on — matches Adw.ButtonRow).
|
|
7
|
+
// Events: `activated` (CustomEvent, bubbles) when the row is clicked — mirrors
|
|
8
|
+
// the Adw.ButtonRow `activated` GObject signal.
|
|
9
|
+
// Reference: refs/libadwaita/src/adw-button-row.ui (AdwButtonRow template)
|
|
10
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_lists.scss (row.button)
|
|
11
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
12
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
13
|
+
|
|
14
|
+
export class AdwButtonRow extends HTMLElement {
|
|
15
|
+
private _contentsEl!: HTMLDivElement;
|
|
16
|
+
private _startIconEl!: HTMLSpanElement;
|
|
17
|
+
private _titleEl!: HTMLSpanElement;
|
|
18
|
+
private _initialized = false;
|
|
19
|
+
|
|
20
|
+
static get observedAttributes() {
|
|
21
|
+
return ['title', 'start-icon-name', 'activatable'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
connectedCallback() {
|
|
25
|
+
if (this._initialized) return;
|
|
26
|
+
this._initialized = true;
|
|
27
|
+
|
|
28
|
+
// Adw.ButtonRow is activatable by default; only an explicit
|
|
29
|
+
// activatable="false" turns it off.
|
|
30
|
+
if (!this.hasAttribute('activatable')) {
|
|
31
|
+
this.setAttribute('activatable', '');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this._contentsEl = document.createElement('div');
|
|
35
|
+
this._contentsEl.className = 'adw-button-row-contents';
|
|
36
|
+
|
|
37
|
+
this._startIconEl = document.createElement('span');
|
|
38
|
+
this._startIconEl.setAttribute('aria-hidden', 'true');
|
|
39
|
+
|
|
40
|
+
this._titleEl = document.createElement('span');
|
|
41
|
+
this._titleEl.className = 'adw-button-row-title';
|
|
42
|
+
|
|
43
|
+
this._contentsEl.append(this._startIconEl, this._titleEl);
|
|
44
|
+
this.replaceChildren(this._contentsEl);
|
|
45
|
+
this._render();
|
|
46
|
+
|
|
47
|
+
this.addEventListener('click', () => {
|
|
48
|
+
if (this._isActivatable()) {
|
|
49
|
+
this.dispatchEvent(new CustomEvent('activated', { bubbles: true }));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
attributeChangedCallback() {
|
|
55
|
+
if (this._initialized) this._render();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private _isActivatable(): boolean {
|
|
59
|
+
return this.getAttribute('activatable') !== 'false';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private _render() {
|
|
63
|
+
const title = this.getAttribute('title') ?? '';
|
|
64
|
+
this._titleEl.textContent = title;
|
|
65
|
+
this._titleEl.hidden = title.length === 0;
|
|
66
|
+
|
|
67
|
+
const icon = this.getAttribute('start-icon-name') ?? '';
|
|
68
|
+
this._startIconEl.className = icon ? `adw-icon adw-icon--${icon}` : '';
|
|
69
|
+
this._startIconEl.hidden = icon.length === 0;
|
|
70
|
+
|
|
71
|
+
this.classList.toggle('activatable', this._isActivatable());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
customElements.define('adw-button-row', AdwButtonRow);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// <adw-button> — Adwaita button.
|
|
2
|
+
// Attributes: icon (symbolic name, e.g. "go-previous" / "view-refresh"),
|
|
3
|
+
// label, tooltip, disabled, and the boolean variant flags
|
|
4
|
+
// flat / suggested / destructive / circular / pill.
|
|
5
|
+
// Renders an inner <button class="adw-button …">; `click` bubbles to the host,
|
|
6
|
+
// so `adwButton.addEventListener('click', …)` works.
|
|
7
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_buttons.scss
|
|
8
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
9
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
10
|
+
|
|
11
|
+
const VARIANT_CLASSES: Record<string, string> = {
|
|
12
|
+
flat: 'flat',
|
|
13
|
+
suggested: 'suggested-action',
|
|
14
|
+
destructive: 'destructive-action',
|
|
15
|
+
circular: 'circular',
|
|
16
|
+
pill: 'pill',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class AdwButton extends HTMLElement {
|
|
20
|
+
private _button!: HTMLButtonElement;
|
|
21
|
+
private _label = '';
|
|
22
|
+
private _initialized = false;
|
|
23
|
+
|
|
24
|
+
static get observedAttributes() {
|
|
25
|
+
return ['icon', 'label', 'tooltip', 'disabled', 'flat', 'suggested', 'destructive', 'circular', 'pill'];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The inner native button (for focus/imperative access). */
|
|
29
|
+
get button(): HTMLButtonElement {
|
|
30
|
+
return this._button;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
connectedCallback() {
|
|
34
|
+
if (this._initialized) return;
|
|
35
|
+
this._initialized = true;
|
|
36
|
+
// Capture any inline text as the label before we take over the subtree.
|
|
37
|
+
this._label = (this.getAttribute('label') ?? this.textContent ?? '').trim();
|
|
38
|
+
this._button = document.createElement('button');
|
|
39
|
+
this.replaceChildren(this._button);
|
|
40
|
+
this._render();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
attributeChangedCallback() {
|
|
44
|
+
if (this._initialized) this._render();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private _render() {
|
|
48
|
+
const btn = this._button;
|
|
49
|
+
btn.className = 'adw-button';
|
|
50
|
+
for (const [attr, cls] of Object.entries(VARIANT_CLASSES)) {
|
|
51
|
+
if (this.hasAttribute(attr)) btn.classList.add(cls);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const icon = this.getAttribute('icon');
|
|
55
|
+
const label = (this.getAttribute('label') ?? this._label).trim();
|
|
56
|
+
if (icon && !label) btn.classList.add('icon-only');
|
|
57
|
+
|
|
58
|
+
btn.replaceChildren();
|
|
59
|
+
if (icon) {
|
|
60
|
+
const span = document.createElement('span');
|
|
61
|
+
span.className = `adw-icon adw-icon--${icon}`;
|
|
62
|
+
span.setAttribute('aria-hidden', 'true'); // decorative (mask-image only, no text)
|
|
63
|
+
btn.appendChild(span);
|
|
64
|
+
}
|
|
65
|
+
if (label) btn.appendChild(document.createTextNode(label));
|
|
66
|
+
|
|
67
|
+
const tooltip = this.getAttribute('tooltip');
|
|
68
|
+
btn.title = tooltip ?? '';
|
|
69
|
+
// An icon-only button has no text content, so screen readers would
|
|
70
|
+
// announce it as unlabeled. Give it an accessible name — prefer the
|
|
71
|
+
// tooltip, fall back to the symbolic icon name (WCAG 4.1.2).
|
|
72
|
+
if (icon && !label) {
|
|
73
|
+
btn.setAttribute('aria-label', tooltip ?? icon);
|
|
74
|
+
} else {
|
|
75
|
+
btn.removeAttribute('aria-label');
|
|
76
|
+
}
|
|
77
|
+
btn.disabled = this.hasAttribute('disabled');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
customElements.define('adw-button', AdwButton);
|