@gjsify/adwaita-web 0.36.0 → 0.37.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 +433 -33
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
- package/lib/types/breakpoints.d.ts +10 -12
- package/lib/types/elements/adw-about-dialog.d.ts +19 -36
- package/lib/types/elements/adw-action-row.d.ts +9 -12
- package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
- package/lib/types/elements/adw-avatar.d.ts +1 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
- package/lib/types/elements/adw-button-content.d.ts +3 -4
- package/lib/types/elements/adw-button-row.d.ts +1 -1
- package/lib/types/elements/adw-carousel.d.ts +28 -49
- package/lib/types/elements/adw-checks.d.ts +2 -8
- package/lib/types/elements/adw-combo-row.d.ts +6 -6
- package/lib/types/elements/adw-data-grid.d.ts +5 -10
- package/lib/types/elements/adw-drop-down.d.ts +3 -11
- package/lib/types/elements/adw-entry-row.d.ts +20 -23
- package/lib/types/elements/adw-icon.d.ts +2 -4
- package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
- package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
- package/lib/types/elements/adw-navigation-view.d.ts +2 -10
- package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
- package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
- package/lib/types/elements/adw-popover.d.ts +2 -7
- package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
- package/lib/types/elements/adw-preferences-group.d.ts +12 -14
- package/lib/types/elements/adw-progress-bar.d.ts +3 -4
- package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
- package/lib/types/elements/adw-sidebar.d.ts +5 -10
- package/lib/types/elements/adw-spinner.d.ts +3 -5
- package/lib/types/elements/adw-split-button.d.ts +6 -16
- package/lib/types/elements/adw-switch-row.d.ts +3 -3
- package/lib/types/elements/adw-tab-view.d.ts +16 -26
- package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
- package/lib/types/elements/adw-view-stack.d.ts +7 -17
- package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
- package/lib/types/elements/adw-view-switcher.d.ts +18 -38
- package/lib/types/elements/adw-wrap-box.d.ts +7 -17
- package/lib/types/elements/view-switcher-dom.d.ts +10 -13
- package/lib/types/index.d.ts +1 -0
- package/lib/types/source-view/hex.d.ts +7 -13
- package/lib/types/style-classes.spec.d.ts +11 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +6 -6
- package/scss/_about_dialog.scss +3 -2
- package/scss/_action_row.scss +33 -0
- package/scss/_avatar.scss +12 -0
- package/scss/_button.scss +44 -22
- package/scss/_card.scss +11 -8
- package/scss/_checks.scss +2 -4
- package/scss/_dialog.scss +3 -2
- package/scss/_headerbar.scss +23 -0
- package/scss/_popover.scss +8 -4
- package/scss/_preferences.scss +7 -7
- package/scss/_progress_bar.scss +9 -6
- package/scss/_reset.scss +1 -1
- package/scss/_shortcut_label.scss +80 -0
- package/scss/_sidebar.scss +57 -0
- package/scss/_status_page.scss +20 -0
- package/scss/_style_classes.scss +407 -0
- package/scss/_switch.scss +4 -3
- package/scss/_variables.scss +25 -0
- package/scss/adwaita-skin.scss +12 -0
- package/src/adw-about-dialog.spec.ts +34 -49
- package/src/adw-action-rows.spec.ts +25 -33
- package/src/adw-avatar.spec.ts +51 -20
- package/src/adw-banner.spec.ts +5 -8
- package/src/adw-button-content.spec.ts +9 -12
- package/src/adw-button-row.spec.ts +2 -4
- package/src/adw-carousel.spec.ts +40 -53
- package/src/adw-checks.spec.ts +36 -47
- package/src/adw-drop-down.spec.ts +25 -32
- package/src/adw-header-bar.spec.ts +5 -7
- package/src/adw-icon.spec.ts +23 -32
- package/src/adw-progress-bar.spec.ts +10 -12
- package/src/adw-row-state.spec.ts +9 -10
- package/src/adw-shortcut-label.spec.ts +112 -0
- package/src/adw-sidebar.spec.ts +17 -24
- package/src/adw-switch.spec.ts +11 -17
- package/src/adw-tab-view.spec.ts +7 -10
- package/src/adw-toast-overlay.spec.ts +7 -11
- package/src/adw-view-switcher-bar.spec.ts +10 -14
- package/src/adw-wrap-box.spec.ts +25 -36
- package/src/bottom-sheet.spec.ts +30 -36
- package/src/breakpoints.ts +15 -18
- package/src/chrome.spec.ts +13 -21
- package/src/elements/adw-about-dialog.ts +71 -114
- package/src/elements/adw-action-row.ts +22 -37
- package/src/elements/adw-alert-dialog.ts +25 -50
- package/src/elements/adw-avatar.ts +40 -15
- package/src/elements/adw-banner.ts +14 -18
- package/src/elements/adw-bottom-sheet.ts +31 -52
- package/src/elements/adw-button-content.ts +30 -37
- package/src/elements/adw-button-row.ts +14 -24
- package/src/elements/adw-carousel.ts +87 -148
- package/src/elements/adw-checks.ts +43 -56
- package/src/elements/adw-clamp.ts +8 -9
- package/src/elements/adw-combo-row.ts +17 -25
- package/src/elements/adw-data-grid.ts +26 -38
- package/src/elements/adw-dialog.ts +25 -56
- package/src/elements/adw-drop-down.ts +31 -51
- package/src/elements/adw-entry-row.ts +39 -46
- package/src/elements/adw-expander-row.ts +30 -53
- package/src/elements/adw-icon.ts +30 -53
- package/src/elements/adw-inline-view-switcher.ts +38 -67
- package/src/elements/adw-menu-button.ts +28 -58
- package/src/elements/adw-navigation-split-view.ts +35 -47
- package/src/elements/adw-navigation-view.ts +12 -28
- package/src/elements/adw-overlay-split-view.ts +100 -175
- package/src/elements/adw-password-entry-row.ts +18 -28
- package/src/elements/adw-popover.ts +28 -44
- package/src/elements/adw-preferences-dialog.ts +41 -66
- package/src/elements/adw-preferences-group.ts +33 -46
- package/src/elements/adw-progress-bar.ts +27 -47
- package/src/elements/adw-shortcut-label.ts +138 -0
- package/src/elements/adw-sidebar.ts +31 -39
- package/src/elements/adw-spin-row.ts +7 -14
- package/src/elements/adw-spinner.ts +31 -43
- package/src/elements/adw-split-button.ts +45 -75
- package/src/elements/adw-switch-row.ts +29 -46
- package/src/elements/adw-switch.ts +28 -47
- package/src/elements/adw-tab-view.ts +62 -103
- package/src/elements/adw-toast-overlay.ts +28 -35
- package/src/elements/adw-toggle-group.ts +9 -16
- package/src/elements/adw-view-stack.ts +32 -51
- package/src/elements/adw-view-switcher-bar.ts +26 -50
- package/src/elements/adw-view-switcher.ts +60 -110
- package/src/elements/adw-window-title.ts +4 -4
- package/src/elements/adw-wrap-box.ts +33 -59
- package/src/elements/view-switcher-dom.ts +15 -22
- package/src/entry-rows.spec.ts +18 -24
- package/src/index.ts +13 -15
- package/src/navigation-view.spec.ts +7 -10
- package/src/preferences-search.ts +11 -15
- package/src/preferences.spec.ts +13 -15
- package/src/source-view/adw-source-view.ts +6 -19
- package/src/source-view/hex.ts +7 -13
- package/src/split-button.spec.ts +32 -44
- package/src/split-views.spec.ts +83 -149
- package/src/style-classes.spec.ts +181 -0
- package/src/style-isolation.spec.ts +3 -6
- package/src/styles.generated.ts +1 -1
- package/src/tab-view.spec.ts +26 -35
- package/src/test.browser.mts +4 -0
- package/src/view-stack.spec.ts +23 -34
- package/src/view-switcher.spec.ts +56 -84
|
@@ -13,19 +13,15 @@
|
|
|
13
13
|
// type-ahead (typing jumps to the next matching option).
|
|
14
14
|
//
|
|
15
15
|
// The popover is `<adw-popover>` and the dismissal/keyboard machine is
|
|
16
|
-
// `@gjsify/adwaita-core`'s (ADR 0004). So is the SELECTION: the options list,
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// different option vocabularies (this one took `{value,label}` descriptors, the
|
|
22
|
-
// row only bare strings). What stays here is what is genuinely a DROP-DOWN: the
|
|
23
|
-
// search filter and the type-ahead buffer.
|
|
16
|
+
// `@gjsify/adwaita-core`'s (ADR 0004). So is the SELECTION: the options list, the
|
|
17
|
+
// index↔value mapping, the bounds guards and the programmatic-vs-interactive split are
|
|
18
|
+
// {@link ComboState}, the same state machine `<adw-combo-row>` composes — so both
|
|
19
|
+
// selectors accept one option vocabulary. What stays here is what is genuinely a
|
|
20
|
+
// DROP-DOWN: the search filter and the type-ahead buffer.
|
|
24
21
|
//
|
|
25
|
-
// The arrow/Home/End wrap arithmetic
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// `enable-search` hit on every single open because it focuses the entry).
|
|
22
|
+
// The arrow/Home/End wrap arithmetic belongs to the popover, not here: a per-element
|
|
23
|
+
// copy is how ArrowUp from an unfocused list lands on the SECOND-TO-LAST option, which
|
|
24
|
+
// `enable-search` hits on every open because it focuses the entry.
|
|
29
25
|
//
|
|
30
26
|
// Attributes:
|
|
31
27
|
// options / items — JSON array: `["a","b"]` or `[{"value":"a","label":"A"}]`.
|
|
@@ -66,11 +62,9 @@ import type { AdwComboOption, AdwComboOptionInput } from '@gjsify/adwaita-core';
|
|
|
66
62
|
import { createAdwIcon } from './adw-icon.js';
|
|
67
63
|
|
|
68
64
|
/**
|
|
69
|
-
* A dropdown option — `value` is the stable id, `label` the shown text.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* the same selection model behind two chromes, and they had two names for one
|
|
73
|
-
* descriptor. Kept exported so the published type name does not break.
|
|
65
|
+
* A dropdown option — `value` is the stable id, `label` the shown text. An alias of
|
|
66
|
+
* core's {@link AdwComboOption}, since the drop-down and the combo row are the same
|
|
67
|
+
* selection model behind two chromes; still exported so the published name keeps working.
|
|
74
68
|
*/
|
|
75
69
|
export type AdwDropDownOption = AdwComboOption;
|
|
76
70
|
|
|
@@ -103,7 +97,6 @@ export class AdwDropDown extends HTMLElement {
|
|
|
103
97
|
if (this._initialized) this._renderAll();
|
|
104
98
|
}
|
|
105
99
|
|
|
106
|
-
/** Alias of `options` (Gtk.DropDown's model is often called "items"). */
|
|
107
100
|
get items(): AdwDropDownOption[] {
|
|
108
101
|
return this._state.options;
|
|
109
102
|
}
|
|
@@ -131,12 +124,10 @@ export class AdwDropDown extends HTMLElement {
|
|
|
131
124
|
if (index >= 0) this._selectIndex(index);
|
|
132
125
|
}
|
|
133
126
|
|
|
134
|
-
/** The selected option descriptor, or null when empty. */
|
|
135
127
|
get selectedItem(): AdwDropDownOption | null {
|
|
136
128
|
return this._state.options[this._state.selectedIndex] ?? null;
|
|
137
129
|
}
|
|
138
130
|
|
|
139
|
-
/** Whether the search entry is shown in the popover. */
|
|
140
131
|
get enableSearch(): boolean {
|
|
141
132
|
return this.hasAttribute('enable-search');
|
|
142
133
|
}
|
|
@@ -146,7 +137,6 @@ export class AdwDropDown extends HTMLElement {
|
|
|
146
137
|
else this.removeAttribute('enable-search');
|
|
147
138
|
}
|
|
148
139
|
|
|
149
|
-
/** Whether the popover is currently open. */
|
|
150
140
|
get active(): boolean {
|
|
151
141
|
return this._popoverEl?.open ?? false;
|
|
152
142
|
}
|
|
@@ -177,19 +167,17 @@ export class AdwDropDown extends HTMLElement {
|
|
|
177
167
|
this._popoverEl = document.createElement('adw-popover') as AdwPopover;
|
|
178
168
|
this._popoverEl.classList.add('adw-drop-down-popover');
|
|
179
169
|
this._popoverEl.setAttribute('role', 'listbox');
|
|
180
|
-
// A dropdown's popover is a `popover.menu` too — `dropdown { popover.menu
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
// questions, so neither is derived from the other.
|
|
170
|
+
// A dropdown's popover is a `popover.menu` too — `dropdown { popover.menu { … } }`
|
|
171
|
+
// — even though its rows are ARIA `option`s rather than `menuitem`s. The style
|
|
172
|
+
// class and the role answer different questions, so neither derives the other.
|
|
184
173
|
this._popoverEl.setAttribute('menu', '');
|
|
185
174
|
|
|
186
175
|
this._listEl = document.createElement('div');
|
|
187
176
|
this._listEl.className = 'adw-drop-down-list';
|
|
188
177
|
this._popoverEl.appendChild(this._listEl);
|
|
189
178
|
|
|
190
|
-
// Seed options
|
|
191
|
-
//
|
|
192
|
-
// driven by a change notification.
|
|
179
|
+
// Seed options BEFORE anything subscribes, so building the initial DOM below is
|
|
180
|
+
// not driven by a change notification.
|
|
193
181
|
if (this._state.count === 0) this._state.setOptions(this._parseOptionsAttr());
|
|
194
182
|
const attrSelected = Number.parseInt(this.getAttribute('selected') ?? '', 10);
|
|
195
183
|
if (this._state.hasIndex(attrSelected)) this._state.setSelectedIndex(attrSelected);
|
|
@@ -197,9 +185,9 @@ export class AdwDropDown extends HTMLElement {
|
|
|
197
185
|
this.replaceChildren(this._buttonEl, this._popoverEl);
|
|
198
186
|
this._popoverEl.anchor = this._buttonEl;
|
|
199
187
|
this._popoverEl.subscribe((open) => this._onPopoverToggled(open));
|
|
200
|
-
// Type-ahead is the ONE key class the shared popover deliberately leaves
|
|
201
|
-
//
|
|
202
|
-
//
|
|
188
|
+
// Type-ahead is the ONE key class the shared popover deliberately leaves to the
|
|
189
|
+
// renderer (`resolvePopoverKey` returns 'none' for printables), so it needs a
|
|
190
|
+
// listener of its own alongside the popover's.
|
|
203
191
|
this._popoverEl.addEventListener('keydown', (event) => this._onPopoverTypeAhead(event));
|
|
204
192
|
this._renderAll();
|
|
205
193
|
}
|
|
@@ -220,14 +208,12 @@ export class AdwDropDown extends HTMLElement {
|
|
|
220
208
|
}
|
|
221
209
|
}
|
|
222
210
|
|
|
223
|
-
// ── internals ──────────────────────────────────────────────────────────
|
|
224
|
-
|
|
225
211
|
private _parseOptionsAttr(): AdwDropDownOption[] {
|
|
226
212
|
const raw = this.getAttribute('options') ?? this.getAttribute('items');
|
|
227
213
|
if (!raw) return [];
|
|
228
214
|
try {
|
|
229
|
-
// `normalizeComboOptions` already guards a non-array, so a JSON
|
|
230
|
-
//
|
|
215
|
+
// `normalizeComboOptions` already guards a non-array, so a JSON object or
|
|
216
|
+
// scalar yields the empty model rather than a throw.
|
|
231
217
|
return normalizeComboOptions(JSON.parse(raw) as AdwComboOptionInput[]);
|
|
232
218
|
} catch {
|
|
233
219
|
return [];
|
|
@@ -237,12 +223,11 @@ export class AdwDropDown extends HTMLElement {
|
|
|
237
223
|
private _selectIndex(index: number, opts: { fromAttr?: boolean; fromUser?: boolean } = {}): void {
|
|
238
224
|
// The bounds arithmetic is `ComboState`'s; the POLICY of rejecting an
|
|
239
225
|
// out-of-range set is this element's published `selected` contract, and
|
|
240
|
-
// `<adw-combo-row>` deliberately answers it the other way
|
|
241
|
-
// `ComboState.hasIndex`.
|
|
226
|
+
// `<adw-combo-row>` deliberately answers it the other way (`ComboState.hasIndex`).
|
|
242
227
|
if (!this._state.hasIndex(index)) return;
|
|
243
|
-
// Both core setters return "did it change", which is what gates the
|
|
244
|
-
//
|
|
245
|
-
//
|
|
228
|
+
// Both core setters return "did it change", which is what gates the notify — no
|
|
229
|
+
// second copy of the current index is kept here, so `setOptions`' own label
|
|
230
|
+
// re-sync cannot masquerade as one.
|
|
246
231
|
const changed = opts.fromUser ? this._state.select(index) : this._state.setSelectedIndex(index);
|
|
247
232
|
this._updateLabel();
|
|
248
233
|
this._updateSelectedStates();
|
|
@@ -252,13 +237,12 @@ export class AdwDropDown extends HTMLElement {
|
|
|
252
237
|
}
|
|
253
238
|
if (changed) {
|
|
254
239
|
const option = this._state.options[index];
|
|
255
|
-
// `notify::selected` mirrors GObject property-notify:
|
|
240
|
+
// `notify::selected` mirrors GObject property-notify: EVERY change,
|
|
256
241
|
// programmatic included.
|
|
257
242
|
this.dispatchEvent(new CustomEvent('notify::selected', { bubbles: true, detail: { selected: index } }));
|
|
258
|
-
// `change` mirrors the DOM <select> contract:
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
// without guarding against a spurious user-style event.
|
|
243
|
+
// `change` mirrors the DOM <select> contract: ONLY a user-initiated change. A
|
|
244
|
+
// programmatic assignment stays silent, just as native `select.value = x` fires
|
|
245
|
+
// no `change`, so consumers can set the value without guarding against it.
|
|
262
246
|
if (opts.fromUser) {
|
|
263
247
|
this.dispatchEvent(
|
|
264
248
|
new CustomEvent('change', {
|
|
@@ -302,7 +286,6 @@ export class AdwDropDown extends HTMLElement {
|
|
|
302
286
|
this._typeAhead(event.key);
|
|
303
287
|
}
|
|
304
288
|
|
|
305
|
-
/** Type-ahead: accumulate keys, focus/select the next matching option. */
|
|
306
289
|
private _typeAhead(char: string): void {
|
|
307
290
|
this._typeAheadBuffer += char.toLowerCase();
|
|
308
291
|
if (this._typeAheadTimer) clearTimeout(this._typeAheadTimer);
|
|
@@ -313,7 +296,6 @@ export class AdwDropDown extends HTMLElement {
|
|
|
313
296
|
else this._selectIndex(match, { fromUser: true });
|
|
314
297
|
}
|
|
315
298
|
|
|
316
|
-
/** Filter the visible items by a search query (case-insensitive substring). */
|
|
317
299
|
private _filter(query: string): void {
|
|
318
300
|
const q = query.trim().toLowerCase();
|
|
319
301
|
this._itemButtons.forEach((button, index) => {
|
|
@@ -334,8 +316,8 @@ export class AdwDropDown extends HTMLElement {
|
|
|
334
316
|
}
|
|
335
317
|
|
|
336
318
|
private _updateLabel(): void {
|
|
337
|
-
// The setters
|
|
338
|
-
//
|
|
319
|
+
// The setters may run before connectedCallback builds the DOM — a no-op until the
|
|
320
|
+
// label element exists.
|
|
339
321
|
if (!this._labelEl) return;
|
|
340
322
|
this._labelEl.textContent = this._state.selectedLabel;
|
|
341
323
|
}
|
|
@@ -360,7 +342,6 @@ export class AdwDropDown extends HTMLElement {
|
|
|
360
342
|
this._searchEl.placeholder = 'Search…';
|
|
361
343
|
this._searchEl.setAttribute('aria-label', 'Search');
|
|
362
344
|
this._searchEl.addEventListener('input', () => this._filter(this._searchEl?.value ?? ''));
|
|
363
|
-
// Prepend before the list so it sits atop the options.
|
|
364
345
|
this._popoverEl.insertBefore(this._searchEl, this._listEl);
|
|
365
346
|
} else {
|
|
366
347
|
const existing = this._popoverEl.querySelector('.adw-drop-down-search');
|
|
@@ -378,7 +359,6 @@ export class AdwDropDown extends HTMLElement {
|
|
|
378
359
|
label.className = 'adw-drop-down-item-label';
|
|
379
360
|
label.textContent = option.label;
|
|
380
361
|
|
|
381
|
-
// A check icon marks the selected option (Gtk.DropDown's selected row).
|
|
382
362
|
const check = document.createElement('span');
|
|
383
363
|
check.className = 'adw-drop-down-item-check';
|
|
384
364
|
check.setAttribute('aria-hidden', 'true');
|
|
@@ -16,12 +16,11 @@
|
|
|
16
16
|
// activatesDefault, editing.
|
|
17
17
|
// Events:
|
|
18
18
|
// `changed` + `notify::text` — every buffer change. libadwaita implements
|
|
19
|
-
// GtkEditable through a delegate
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// `
|
|
23
|
-
// `
|
|
24
|
-
// `entry-activated` — Enter otherwise (C:256), AFTER the default was activated.
|
|
19
|
+
// GtkEditable through a delegate, so it emits BOTH spellings and so does this:
|
|
20
|
+
// code written against either runs against the other.
|
|
21
|
+
// `notify::text-length` — `on_length_changed`.
|
|
22
|
+
// `apply` — the apply button, or Enter while it shows.
|
|
23
|
+
// `entry-activated` — Enter otherwise, AFTER the default was activated.
|
|
25
24
|
//
|
|
26
25
|
// Reference: refs/libadwaita/src/adw-entry-row.c, adw-entry-row.ui
|
|
27
26
|
// Reference: refs/adwaita-web/adwaita-web/docs/widgets/entryrow.md
|
|
@@ -41,13 +40,10 @@ import {
|
|
|
41
40
|
import { type AdwIcon, createAdwIcon } from './adw-icon.js';
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
|
-
* Show/hide a rendered part.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `&[hidden] { display: none }` override in `_icon.scss` and `.adw-button`
|
|
49
|
-
* does not, so a hidden apply button would still paint. The inline `display`
|
|
50
|
-
* rides along until the entry-row SCSS pass lands.
|
|
43
|
+
* Show/hide a rendered part. `hidden` carries the semantics and is what the conformance
|
|
44
|
+
* suite reads, but author CSS beats the UA `[hidden]` rule: `.adw-icon` ships its own
|
|
45
|
+
* `&[hidden]` override in `_icon.scss` and `.adw-button` does not, so a hidden apply
|
|
46
|
+
* button would still paint without the inline `display`.
|
|
51
47
|
*/
|
|
52
48
|
function setPartVisible(part: HTMLElement, visible: boolean): void {
|
|
53
49
|
part.hidden = !visible;
|
|
@@ -93,7 +89,6 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
93
89
|
return 'adw-entry-row';
|
|
94
90
|
}
|
|
95
91
|
|
|
96
|
-
/** The editable text. */
|
|
97
92
|
get text(): string {
|
|
98
93
|
return this._state.text;
|
|
99
94
|
}
|
|
@@ -143,7 +138,6 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
143
138
|
this._state.setActivatesDefault(value);
|
|
144
139
|
}
|
|
145
140
|
|
|
146
|
-
/** Whether the embedded entry currently has focus. */
|
|
147
141
|
get editing(): boolean {
|
|
148
142
|
return this._state.editing;
|
|
149
143
|
}
|
|
@@ -166,7 +160,7 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
166
160
|
this._renderTitle();
|
|
167
161
|
|
|
168
162
|
// Subclass parts first, THEN the author's children: `AdwPasswordEntryRow`
|
|
169
|
-
// installs its peek toggle from its own `init
|
|
163
|
+
// installs its peek toggle from its own `init`, so a consumer
|
|
170
164
|
// suffix lands after it and both remain.
|
|
171
165
|
this._onConnected();
|
|
172
166
|
this._adoptAuthored();
|
|
@@ -202,9 +196,9 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
202
196
|
}
|
|
203
197
|
|
|
204
198
|
/**
|
|
205
|
-
* Add a widget after the editable area — `adw_entry_row_add_suffix
|
|
206
|
-
*
|
|
207
|
-
*
|
|
199
|
+
* Add a widget after the editable area — `adw_entry_row_add_suffix`, which APPENDS
|
|
200
|
+
* into a box, so several suffixes coexist and the password row's own peek toggle
|
|
201
|
+
* survives a consumer's.
|
|
208
202
|
*/
|
|
209
203
|
addSuffix(node: Node): void {
|
|
210
204
|
this._suffixes.append(node);
|
|
@@ -212,9 +206,9 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
212
206
|
}
|
|
213
207
|
|
|
214
208
|
/**
|
|
215
|
-
* Add a widget before the editable area — `adw_entry_row_add_prefix
|
|
216
|
-
*
|
|
217
|
-
*
|
|
209
|
+
* Add a widget before the editable area — `adw_entry_row_add_prefix`. PREPENDS, like
|
|
210
|
+
* the `gtk_box_prepend` it mirrors, so several prefixes stack in reverse call order
|
|
211
|
+
* exactly as they do in GTK.
|
|
218
212
|
*/
|
|
219
213
|
addPrefix(node: Node): void {
|
|
220
214
|
this._prefixes.prepend(node);
|
|
@@ -223,32 +217,32 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
223
217
|
|
|
224
218
|
/**
|
|
225
219
|
* Drive the trailing indicator — `adw_entry_row_set_show_indicator`
|
|
226
|
-
* (adw-entry-row-private.h
|
|
227
|
-
*
|
|
228
|
-
*
|
|
220
|
+
* (adw-entry-row-private.h). Private-to-the-library upstream, public here because
|
|
221
|
+
* `<adw-password-entry-row>` is a separate custom element rather than a C subclass
|
|
222
|
+
* with access to the private header.
|
|
229
223
|
*/
|
|
230
224
|
setShowIndicator(show: boolean): void {
|
|
231
225
|
this._state.setShowIndicator(show);
|
|
232
226
|
}
|
|
233
227
|
|
|
234
|
-
/** `adw_entry_row_set_indicator_icon_name
|
|
228
|
+
/** `adw_entry_row_set_indicator_icon_name`. */
|
|
235
229
|
setIndicatorIconName(iconName: string): void {
|
|
236
230
|
this._indicator.dataset.iconName = iconName;
|
|
237
231
|
}
|
|
238
232
|
|
|
239
|
-
/** `adw_entry_row_set_indicator_tooltip
|
|
233
|
+
/** `adw_entry_row_set_indicator_tooltip`. */
|
|
240
234
|
setIndicatorTooltip(tooltip: string): void {
|
|
241
235
|
this._indicator.title = tooltip;
|
|
242
236
|
this._indicator.setAttribute('aria-label', tooltip);
|
|
243
237
|
}
|
|
244
238
|
|
|
245
|
-
/** Apply the pending edit — the apply button click
|
|
239
|
+
/** Apply the pending edit — the apply button click. */
|
|
246
240
|
apply(): void {
|
|
247
241
|
this._state.apply();
|
|
248
242
|
this.dispatchEvent(new CustomEvent('apply', { bubbles: true }));
|
|
249
243
|
}
|
|
250
244
|
|
|
251
|
-
/** Focus the entry without selecting — `adw_entry_row_grab_focus_without_selecting
|
|
245
|
+
/** Focus the entry without selecting — `adw_entry_row_grab_focus_without_selecting`. */
|
|
252
246
|
grabFocusWithoutSelecting(): boolean {
|
|
253
247
|
this._input.focus();
|
|
254
248
|
return this._state.editing;
|
|
@@ -271,11 +265,11 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
271
265
|
this._suffixes = document.createElement('div');
|
|
272
266
|
this._suffixes.className = `${prefix}-suffixes`;
|
|
273
267
|
|
|
274
|
-
// TWO title labels, exactly like adw-entry-row.ui: `empty_title` is the
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
268
|
+
// TWO title labels, exactly like adw-entry-row.ui: `empty_title` is the large
|
|
269
|
+
// placeholder shown while the row is empty, `title` the small label above the
|
|
270
|
+
// value. GTK cross-fades them over EMPTY_ANIMATION_DURATION_MS; here it is a hard
|
|
271
|
+
// swap at the two animation endpoints, which is why the render reads
|
|
272
|
+
// `emptyTarget`.
|
|
279
273
|
this._emptyTitle = document.createElement('span');
|
|
280
274
|
this._emptyTitle.className = `adw-row-title ${prefix}-empty-title`;
|
|
281
275
|
this._title = document.createElement('span');
|
|
@@ -328,8 +322,8 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
328
322
|
this._editIcon = createAdwIcon('document-edit', 'adw-row-edit', `${prefix}-edit`);
|
|
329
323
|
this._editIcon.dataset.iconName = ENTRY_ROW_EDIT_ICON_NAME;
|
|
330
324
|
|
|
331
|
-
// Snapshot order: indicator → apply → edit icon inside the editable area
|
|
332
|
-
//
|
|
325
|
+
// Snapshot order: indicator → apply → edit icon inside the editable area, with
|
|
326
|
+
// the prefixes/suffixes boxes outside it.
|
|
333
327
|
this.replaceChildren(
|
|
334
328
|
this._prefixes,
|
|
335
329
|
this._area,
|
|
@@ -345,9 +339,8 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
345
339
|
}
|
|
346
340
|
|
|
347
341
|
/**
|
|
348
|
-
* Re-home the children the author wrote. libadwaita's buildable maps an
|
|
349
|
-
*
|
|
350
|
-
* spelling of its `type="prefix"`.
|
|
342
|
+
* Re-home the children the author wrote. libadwaita's buildable maps an untyped
|
|
343
|
+
* `<child>` to a suffix; `slot="prefix"` is the web spelling of `type="prefix"`.
|
|
351
344
|
*/
|
|
352
345
|
private _adoptAuthored(): void {
|
|
353
346
|
for (const node of this._authored) {
|
|
@@ -358,10 +351,10 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
358
351
|
}
|
|
359
352
|
|
|
360
353
|
/**
|
|
361
|
-
* `pressed_cb
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
354
|
+
* `pressed_cb`: a click landing on the row, the editable area, the indicator or
|
|
355
|
+
* either affordance box grabs focus; anything else (a suffix control, the apply
|
|
356
|
+
* button) is left alone. The pencil is `can-target=False` in the .ui, so a click on
|
|
357
|
+
* it picks the editable area — hence it focuses too.
|
|
365
358
|
*/
|
|
366
359
|
private _maybeFocus(event: MouseEvent): void {
|
|
367
360
|
const target = event.target;
|
|
@@ -379,15 +372,15 @@ export class AdwEntryRow extends HTMLElement {
|
|
|
379
372
|
}
|
|
380
373
|
}
|
|
381
374
|
|
|
382
|
-
/** `text_activated_cb`
|
|
375
|
+
/** `text_activated_cb` — exactly one of the two signals. */
|
|
383
376
|
private _activate(): void {
|
|
384
377
|
const activation = this._state.activate();
|
|
385
378
|
if (activation.signal === 'apply') {
|
|
386
379
|
this.dispatchEvent(new CustomEvent('apply', { bubbles: true }));
|
|
387
380
|
return;
|
|
388
381
|
}
|
|
389
|
-
//
|
|
390
|
-
//
|
|
382
|
+
// The default widget is activated BEFORE `entry-activated`; implicit form
|
|
383
|
+
// submission is the web's default-widget activation.
|
|
391
384
|
if (activation.activateDefault) this.closest('form')?.requestSubmit();
|
|
392
385
|
this.dispatchEvent(new CustomEvent('entry-activated', { bubbles: true }));
|
|
393
386
|
}
|
|
@@ -1,28 +1,16 @@
|
|
|
1
|
-
// <adw-expander-row> —
|
|
1
|
+
// <adw-expander-row> — a boxed-list row that discloses nested rows when expanded,
|
|
2
2
|
// with an optional enable switch. Mirrors Adw.ExpanderRow.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// Properties: expanded, enableExpansion (get/set, reflect to attributes).
|
|
10
|
-
// Events: notify::expanded (CustomEvent), notify::enable-expansion (CustomEvent)
|
|
11
|
-
// — mirror Adw.ExpanderRow's GObject signal names so the same code path drives
|
|
12
|
-
// both the web and GTK renderers.
|
|
3
|
+
//
|
|
4
|
+
// Slots: `slot="prefix"` / `slot="suffix"` children sit in the HEADER row beside the
|
|
5
|
+
// title (before the enable switch and disclosure chevron), like Adw.ExpanderRow's
|
|
6
|
+
// add_prefix/add_suffix; every other child is moved into the disclosed content
|
|
7
|
+
// listbox. The `notify::expanded` / `notify::enable-expansion` events are named after
|
|
8
|
+
// Adw.ExpanderRow's GObject signals so one code path drives the web and GTK renderers.
|
|
13
9
|
//
|
|
14
10
|
// The DISCLOSURE state (the expanded flag and its idempotent, notify-on-change
|
|
15
11
|
// transitions) is HEADLESS and lives in `@gjsify/adwaita-core` (ADR 0004) as
|
|
16
|
-
// {@link ExpanderState}; this element
|
|
17
|
-
//
|
|
18
|
-
// `notify::expanded` event. `@gjsify/adwaita-nativescript` composes the same
|
|
19
|
-
// state machine, so both ports share one behaviour.
|
|
20
|
-
//
|
|
21
|
-
// The optional enable switch is `<adw-switch>`. Both the markup and the
|
|
22
|
-
// stylesheet block behind it used to be hand-built copies of
|
|
23
|
-
// `<adw-switch-row>`'s — `_expander_row.scss:73-121` was character-for-character
|
|
24
|
-
// `_switch_row.scss:15-63` — because `.adw-switch` was nested inside
|
|
25
|
-
// `adw-switch-row {` and unusable from here.
|
|
12
|
+
// {@link ExpanderState}; this element keeps only the DOM half — header/content markup,
|
|
13
|
+
// the `expanded` attribute reflection and the event.
|
|
26
14
|
//
|
|
27
15
|
// Reference: refs/adwaita-web/adwaita-web/scss/_expander_row.scss
|
|
28
16
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_expander-row.scss
|
|
@@ -33,13 +21,12 @@
|
|
|
33
21
|
|
|
34
22
|
import { ExpanderState, deriveRowLabels } from '@gjsify/adwaita-core';
|
|
35
23
|
|
|
36
|
-
// SIDE-EFFECT
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// and take the registration with it.
|
|
24
|
+
// SIDE-EFFECT import, deliberately separate from the type import below: it guarantees
|
|
25
|
+
// `adw-switch` is defined before this module's `customElements.define` can upgrade a
|
|
26
|
+
// server-rendered `<adw-expander-row>` and build one. A combined
|
|
27
|
+
// `import { AdwSwitch }` would NOT do it — the binding is only used in type position,
|
|
28
|
+
// and this package compiles without `verbatimModuleSyntax`, so TypeScript would elide
|
|
29
|
+
// the statement and take the registration with it.
|
|
43
30
|
import './adw-switch.js';
|
|
44
31
|
import type { AdwSwitch } from './adw-switch.js';
|
|
45
32
|
import { type AdwIcon, createAdwIcon } from './adw-icon.js';
|
|
@@ -65,10 +52,10 @@ export class AdwExpanderRow extends HTMLElement {
|
|
|
65
52
|
|
|
66
53
|
constructor() {
|
|
67
54
|
super();
|
|
68
|
-
// Subscribed in the constructor
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
55
|
+
// Subscribed in the constructor, NOT connectedCallback, so a disclosure set
|
|
56
|
+
// before the element is connected still reflects to the attribute. The
|
|
57
|
+
// custom-element constructor rules allow it: nothing touches the DOM until the
|
|
58
|
+
// state actually changes.
|
|
72
59
|
this._state.subscribe((expanded) => {
|
|
73
60
|
if (expanded) this.setAttribute('expanded', '');
|
|
74
61
|
else this.removeAttribute('expanded');
|
|
@@ -115,15 +102,11 @@ export class AdwExpanderRow extends HTMLElement {
|
|
|
115
102
|
if (this._initialized) return;
|
|
116
103
|
this._initialized = true;
|
|
117
104
|
|
|
118
|
-
// Slotted prefix/suffix widgets go in the header; everything else is a
|
|
119
|
-
// nested row to disclose.
|
|
120
105
|
const prefixChildren = Array.from(this.querySelectorAll(':scope > [slot="prefix"]'));
|
|
121
106
|
const suffixChildren = Array.from(this.querySelectorAll(':scope > [slot="suffix"]'));
|
|
122
107
|
const claimed = new Set<Node>([...prefixChildren, ...suffixChildren]);
|
|
123
108
|
const rows = Array.from(this.childNodes).filter((node) => !claimed.has(node));
|
|
124
109
|
|
|
125
|
-
// Header row — looks like an action row: a text column, an optional
|
|
126
|
-
// enable switch, and the disclosure chevron.
|
|
127
110
|
this._headerEl = document.createElement('div');
|
|
128
111
|
this._headerEl.className = 'adw-expander-row-header';
|
|
129
112
|
|
|
@@ -135,15 +118,13 @@ export class AdwExpanderRow extends HTMLElement {
|
|
|
135
118
|
this._subtitleEl.className = 'adw-row-subtitle';
|
|
136
119
|
textEl.append(this._titleEl, this._subtitleEl);
|
|
137
120
|
|
|
138
|
-
//
|
|
139
|
-
// show-enable-switch is set). Clicking it does NOT toggle the disclosure.
|
|
121
|
+
// Clicking the enable switch does NOT toggle the disclosure.
|
|
140
122
|
this._enableSwitch = document.createElement('adw-switch') as AdwSwitch;
|
|
141
123
|
this._enableSwitch.classList.add('adw-expander-row-enable-switch');
|
|
142
124
|
this._enableSwitch.active = this.enableExpansion;
|
|
143
125
|
|
|
144
126
|
this._chevronEl = createAdwIcon('go-down', 'adw-expander-row-chevron');
|
|
145
127
|
|
|
146
|
-
// Header prefix / suffix widget slots (Adw.ExpanderRow add_prefix/add_suffix).
|
|
147
128
|
this._prefixEl = document.createElement('div');
|
|
148
129
|
this._prefixEl.className = 'adw-expander-row-prefix';
|
|
149
130
|
for (const child of prefixChildren) this._prefixEl.appendChild(child);
|
|
@@ -154,7 +135,6 @@ export class AdwExpanderRow extends HTMLElement {
|
|
|
154
135
|
|
|
155
136
|
this._headerEl.append(this._prefixEl, textEl, this._suffixEl, this._enableSwitch, this._chevronEl);
|
|
156
137
|
|
|
157
|
-
// Disclosed content — a nested listbox holding the rows.
|
|
158
138
|
this._contentEl = document.createElement('div');
|
|
159
139
|
this._contentEl.className = 'adw-expander-row-content';
|
|
160
140
|
for (const row of rows) this._contentEl.appendChild(row);
|
|
@@ -164,9 +144,9 @@ export class AdwExpanderRow extends HTMLElement {
|
|
|
164
144
|
this._headerEl.addEventListener('click', (event) => {
|
|
165
145
|
// Clicks on the enable switch toggle expansion-enable, not disclosure.
|
|
166
146
|
if (this._enableSwitch.contains(event.target as Node)) return;
|
|
167
|
-
// The core state machine flips
|
|
168
|
-
//
|
|
169
|
-
//
|
|
147
|
+
// The core state machine flips, reflects and re-renders; the event is emitted
|
|
148
|
+
// here so only a USER disclosure notifies — a programmatic `expanded = …`
|
|
149
|
+
// stays silent.
|
|
170
150
|
if (this._state.toggle()) {
|
|
171
151
|
this.dispatchEvent(
|
|
172
152
|
new CustomEvent('notify::expanded', { bubbles: true, detail: { expanded: this.expanded } }),
|
|
@@ -175,10 +155,9 @@ export class AdwExpanderRow extends HTMLElement {
|
|
|
175
155
|
});
|
|
176
156
|
|
|
177
157
|
this._enableSwitch.addEventListener('notify::active', (event) => {
|
|
178
|
-
// The switch's own notify
|
|
179
|
-
// `notify::enable-expansion`, and letting
|
|
180
|
-
//
|
|
181
|
-
// for the same user action on every ancestor listener.
|
|
158
|
+
// The switch's own notify stops at the row: the row publishes
|
|
159
|
+
// `notify::enable-expansion`, and letting `notify::active` past would give
|
|
160
|
+
// every ancestor listener a second, differently-named event for one action.
|
|
182
161
|
event.stopPropagation();
|
|
183
162
|
if (this._reflectingEnable) return;
|
|
184
163
|
const enabled = this._enableSwitch.active;
|
|
@@ -191,23 +170,21 @@ export class AdwExpanderRow extends HTMLElement {
|
|
|
191
170
|
);
|
|
192
171
|
});
|
|
193
172
|
|
|
194
|
-
// Seed the disclosure from the parsed markup
|
|
195
|
-
//
|
|
196
|
-
// `expanded` attribute has to be adopted here.
|
|
173
|
+
// Seed the disclosure from the parsed markup: attributeChangedCallback is guarded
|
|
174
|
+
// until the element is initialized, so a declarative `expanded` lands here.
|
|
197
175
|
this._state.setExpanded(this.hasAttribute('expanded'));
|
|
198
176
|
this._render();
|
|
199
177
|
}
|
|
200
178
|
|
|
201
179
|
attributeChangedCallback(name: string) {
|
|
202
180
|
if (!this._initialized) return;
|
|
203
|
-
// The `expanded` attribute is the declarative face of the core state.
|
|
204
181
|
if (name === 'expanded') this._state.setExpanded(this.hasAttribute('expanded'));
|
|
205
182
|
this._render();
|
|
206
183
|
}
|
|
207
184
|
|
|
208
185
|
private _render() {
|
|
209
|
-
// The `string_is_not_empty` label rule, from core —
|
|
210
|
-
//
|
|
186
|
+
// The `string_is_not_empty` label rule, from core — it hides the TITLE too, not
|
|
187
|
+
// just the subtitle.
|
|
211
188
|
const labels = deriveRowLabels({
|
|
212
189
|
title: this.getAttribute('title'),
|
|
213
190
|
subtitle: this.getAttribute('subtitle'),
|