@gjsify/adwaita-web 0.35.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
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// <adw-shortcut-label> — a keyboard shortcut, drawn as keycaps.
|
|
2
|
+
// Attributes: accelerator (`<Control>C`, `<Shift>A Home`, `<Alt>1...9`,
|
|
3
|
+
// `Control_L&Control_R`, `<Control>C+<Control>X`), disabled-text (the
|
|
4
|
+
// placeholder shown when `accelerator` is empty).
|
|
5
|
+
// Events: `notify::accelerator`, `notify::disabled-text` (CustomEvent, bubbles),
|
|
6
|
+
// mirroring the AdwShortcutLabel GObject notify signals.
|
|
7
|
+
//
|
|
8
|
+
// The whole parse — four nesting levels and the fixed keycap order — is
|
|
9
|
+
// `@gjsify/adwaita-core`'s `parseShortcutLabel` (ADR 0004). This element only
|
|
10
|
+
// turns its nodes into DOM, which is the split that keeps a second renderer from
|
|
11
|
+
// re-deriving the grammar and drifting.
|
|
12
|
+
//
|
|
13
|
+
// TEXT, NOT MARKUP. The C builds pango markup: `<` for a literal `<`, and
|
|
14
|
+
// `%s <small><b>%s</b></small>` for the `L`/`R` subscript of a sided modifier
|
|
15
|
+
// (adw-shortcut-label.c:195, :253-266). Core returns `{ label, sideMarker }`
|
|
16
|
+
// instead, so the escaping is `textContent`'s job here and the subscript is a
|
|
17
|
+
// real `<sub>` — nothing parses a markup string back apart.
|
|
18
|
+
//
|
|
19
|
+
// DIRECTION IS READ, NOT ASSUMED. `parse_sequence` picks its arrow from the
|
|
20
|
+
// widget's text direction (:439), so the element resolves its own computed
|
|
21
|
+
// direction at render time rather than defaulting to LTR.
|
|
22
|
+
//
|
|
23
|
+
// Reference: refs/libadwaita/src/adw-shortcut-label.c
|
|
24
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_shortcuts-dialog.scss:33-58
|
|
25
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
26
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
27
|
+
|
|
28
|
+
import { parseShortcutLabel } from '@gjsify/adwaita-core';
|
|
29
|
+
|
|
30
|
+
export class AdwShortcutLabel extends HTMLElement {
|
|
31
|
+
private _initialized = false;
|
|
32
|
+
|
|
33
|
+
static get observedAttributes() {
|
|
34
|
+
return ['accelerator', 'disabled-text'];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
connectedCallback() {
|
|
38
|
+
if (!this._initialized) {
|
|
39
|
+
this._initialized = true;
|
|
40
|
+
// `GTK_ACCESSIBLE_ROLE_LABEL` (:88). The accessible NAME is set from
|
|
41
|
+
// the parse below, because the keycaps read as a pile of single
|
|
42
|
+
// letters otherwise.
|
|
43
|
+
if (!this.hasAttribute('role')) this.setAttribute('role', 'img');
|
|
44
|
+
}
|
|
45
|
+
this._render();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
attributeChangedCallback(name: string) {
|
|
49
|
+
if (!this._initialized) return;
|
|
50
|
+
this._render();
|
|
51
|
+
this.dispatchEvent(new CustomEvent(`notify::${name}`, { bubbles: true }));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get accelerator(): string {
|
|
55
|
+
return this.getAttribute('accelerator') ?? '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set accelerator(value: string) {
|
|
59
|
+
this.setAttribute('accelerator', value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get disabledText(): string {
|
|
63
|
+
return this.getAttribute('disabled-text') ?? '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
set disabledText(value: string) {
|
|
67
|
+
this.setAttribute('disabled-text', value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private _render() {
|
|
71
|
+
const { nodes, accessibleLabel, error } = parseShortcutLabel(this.accelerator, {
|
|
72
|
+
disabledText: this.disabledText,
|
|
73
|
+
direction: this._direction(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.replaceChildren(...nodes.map((node) => this._nodeElement(node)));
|
|
77
|
+
this.setAttribute('aria-label', accessibleLabel);
|
|
78
|
+
|
|
79
|
+
// `g_warning ("Failed to parse %s, part of accelerator '%s'")` (:532).
|
|
80
|
+
// The partial render is upstream's too — it keeps what it built.
|
|
81
|
+
if (error) {
|
|
82
|
+
console.warn(`adw-shortcut-label: failed to parse ${error}, part of accelerator '${this.accelerator}'`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The rendered direction, which is what the sequence arrow follows.
|
|
88
|
+
*
|
|
89
|
+
* `getComputedStyle` is the only thing that sees an inherited `dir` — the
|
|
90
|
+
* attribute alone is set on an ancestor most of the time. A detached element
|
|
91
|
+
* has no computed style to read, so the attribute is the fallback.
|
|
92
|
+
*/
|
|
93
|
+
private _direction(): 'ltr' | 'rtl' {
|
|
94
|
+
const computed = this.isConnected ? getComputedStyle(this).direction : '';
|
|
95
|
+
if (computed === 'rtl' || computed === 'ltr') return computed;
|
|
96
|
+
return this.closest('[dir="rtl"]') ? 'rtl' : 'ltr';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private _nodeElement(node: ReturnType<typeof parseShortcutLabel>['nodes'][number]): HTMLElement {
|
|
100
|
+
if (node.kind === 'separator' || node.kind === 'disabled') {
|
|
101
|
+
// `dim_label` (:359-368) — both the separators and the placeholder,
|
|
102
|
+
// which upstream tells apart only by position (a placeholder is the
|
|
103
|
+
// only child). The extra class on the placeholder is this port's:
|
|
104
|
+
// `.dimmed` still applies, and a consumer can now style the
|
|
105
|
+
// "no shortcut" state without matching on child count.
|
|
106
|
+
const label = document.createElement('span');
|
|
107
|
+
label.className = node.kind === 'disabled' ? 'dimmed adw-shortcut-label-disabled' : 'dimmed';
|
|
108
|
+
label.textContent = node.text;
|
|
109
|
+
return label;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// One combination is one BOX of keycaps, LTR even in an RTL context
|
|
113
|
+
// (:375-380): the modifier order is a property of the shortcut, not of
|
|
114
|
+
// the surrounding text.
|
|
115
|
+
const box = document.createElement('span');
|
|
116
|
+
box.className = 'adw-shortcut-label-keys';
|
|
117
|
+
box.setAttribute('dir', 'ltr');
|
|
118
|
+
|
|
119
|
+
for (const key of node.keys) {
|
|
120
|
+
const cap = document.createElement('span');
|
|
121
|
+
cap.className = 'keycap';
|
|
122
|
+
cap.textContent = key.label;
|
|
123
|
+
|
|
124
|
+
if (key.sideMarker) {
|
|
125
|
+
const marker = document.createElement('sub');
|
|
126
|
+
marker.className = 'adw-shortcut-label-side';
|
|
127
|
+
marker.textContent = key.sideMarker;
|
|
128
|
+
cap.append(marker);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
box.append(cap);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return box;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
customElements.define('adw-shortcut-label', AdwShortcutLabel);
|
|
@@ -7,16 +7,15 @@
|
|
|
7
7
|
// chevron arrow on each row (the AdwPreferencesPage look the native widget
|
|
8
8
|
// switches to when collapsed).
|
|
9
9
|
//
|
|
10
|
-
// The BEHAVIOUR — the flat index space, the out-of-range rule, which section
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// the C source — see `src/adw-sidebar.spec.ts`.
|
|
10
|
+
// The BEHAVIOUR — the flat index space, the out-of-range rule, which section headers
|
|
11
|
+
// render, the selection-vs-activation split, the filter and the empty state — is
|
|
12
|
+
// HEADLESS and lives in `@gjsify/adwaita-core` (ADR 0004) as {@link SidebarState}; this
|
|
13
|
+
// element only builds DOM for it and re-emits its changes as events, pinned to the C
|
|
14
|
+
// source by the shared vectors in `@gjsify/adwaita-core/conformance` (see
|
|
15
|
+
// `src/adw-sidebar.spec.ts`). Three rules a hand-written copy gets wrong: an
|
|
16
|
+
// out-of-range `selected` DROPS the selection rather than clamping to the last row,
|
|
17
|
+
// section headers are keyed off the rows that actually RENDER and not the declaration
|
|
18
|
+
// index, and the row list stays live after the first connect.
|
|
20
19
|
//
|
|
21
20
|
// Sub-elements (declared as children, consumed at connect time — mirrors
|
|
22
21
|
// adw-tab-page in adw-tab-view):
|
|
@@ -59,12 +58,10 @@ import {
|
|
|
59
58
|
|
|
60
59
|
import { createAdwIcon } from './adw-icon.js';
|
|
61
60
|
|
|
62
|
-
// The sidebar consumes its declared children and drops them from the tree, so a
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// (adw-sidebar.c:1393-1421). Before this, `observedAttributes` was declared on
|
|
67
|
-
// both child elements and no `attributeChangedCallback` existed to answer it.
|
|
61
|
+
// The sidebar consumes its declared children and drops them from the tree, so a later
|
|
62
|
+
// `setAttribute` on one cannot find its sidebar with `closest()`. These keep the link —
|
|
63
|
+
// the web stand-in for the `g_object_bind_property` / `notify::icon-name` bindings that
|
|
64
|
+
// keep a live AdwSidebarItem's labels in sync.
|
|
68
65
|
const itemBindings = new WeakMap<AdwSidebarItem, { sidebar: AdwSidebar; spec: AdwSidebarItemSpec }>();
|
|
69
66
|
const sectionBindings = new WeakMap<AdwSidebarSection, { sidebar: AdwSidebar; spec: AdwSidebarSectionSpec }>();
|
|
70
67
|
|
|
@@ -116,7 +113,6 @@ export class AdwSidebar extends HTMLElement {
|
|
|
116
113
|
return ['mode', 'selected'];
|
|
117
114
|
}
|
|
118
115
|
|
|
119
|
-
/** Zero-based flat index of the selected item, or -1 for no selection. */
|
|
120
116
|
get selected(): number {
|
|
121
117
|
return this._state.selected;
|
|
122
118
|
}
|
|
@@ -130,7 +126,6 @@ export class AdwSidebar extends HTMLElement {
|
|
|
130
126
|
return this._state.selectedItem;
|
|
131
127
|
}
|
|
132
128
|
|
|
133
|
-
/** 'sidebar' (flat navigation list) or 'page' (boxed lists). */
|
|
134
129
|
get mode(): 'sidebar' | 'page' {
|
|
135
130
|
return this._state.mode;
|
|
136
131
|
}
|
|
@@ -171,10 +166,10 @@ export class AdwSidebar extends HTMLElement {
|
|
|
171
166
|
// asked to hear about, and `setSections` runs the 0 → n auto-select.
|
|
172
167
|
this._state.setSections(this._readDeclaredSections());
|
|
173
168
|
this._state.setMode(this._readModeAttr());
|
|
174
|
-
// A declared `selected` wins over that auto-select. libadwaita
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
169
|
+
// A declared `selected` wins over that auto-select. libadwaita lets the
|
|
170
|
+
// auto-select clobber a builder-set property (items arrive after properties, and
|
|
171
|
+
// `items_changed_cb` then selects 0); for a declarative element the authored
|
|
172
|
+
// attribute has to survive its own children.
|
|
178
173
|
if (this.hasAttribute('selected')) this._state.setSelected(this._readSelectedAttr());
|
|
179
174
|
|
|
180
175
|
this._state.subscribe((change) => this._onSelectionChanged(change));
|
|
@@ -190,8 +185,8 @@ export class AdwSidebar extends HTMLElement {
|
|
|
190
185
|
|
|
191
186
|
if (name === 'selected') {
|
|
192
187
|
this._state.setSelected(this._readSelectedAttr());
|
|
193
|
-
// Normalise even when nothing moved, so `selected="5"` on a 3-item
|
|
194
|
-
//
|
|
188
|
+
// Normalise even when nothing moved, so `selected="5"` on a 3-item sidebar
|
|
189
|
+
// does not keep claiming 5 while the property reads -1.
|
|
195
190
|
this._reflectSelected();
|
|
196
191
|
return;
|
|
197
192
|
}
|
|
@@ -202,9 +197,9 @@ export class AdwSidebar extends HTMLElement {
|
|
|
202
197
|
}
|
|
203
198
|
|
|
204
199
|
/**
|
|
205
|
-
* Re-derive after a declared `<adw-sidebar-item>` / `<adw-sidebar-section>`
|
|
206
|
-
*
|
|
207
|
-
*
|
|
200
|
+
* Re-derive after a declared `<adw-sidebar-item>` / `<adw-sidebar-section>` attribute
|
|
201
|
+
* changed. The item model keeps its shape, so the selection cannot move — exactly
|
|
202
|
+
* like the GObject property bindings this stands in for.
|
|
208
203
|
*/
|
|
209
204
|
refresh(): void {
|
|
210
205
|
if (!this._initialized) return;
|
|
@@ -212,7 +207,6 @@ export class AdwSidebar extends HTMLElement {
|
|
|
212
207
|
this._rebuild();
|
|
213
208
|
}
|
|
214
209
|
|
|
215
|
-
/** Snapshot the declared children into specs, and bind them for live updates. */
|
|
216
210
|
private _readDeclaredSections(): AdwSidebarSectionSpec[] {
|
|
217
211
|
const sections: AdwSidebarSectionSpec[] = [];
|
|
218
212
|
|
|
@@ -245,8 +239,8 @@ export class AdwSidebar extends HTMLElement {
|
|
|
245
239
|
|
|
246
240
|
/**
|
|
247
241
|
* `parseFloat`, not `parseInt`: the core rejects a fractional position, and
|
|
248
|
-
* truncating `"1.5"` to row 1 here would hide that from it. An unparseable
|
|
249
|
-
*
|
|
242
|
+
* truncating `"1.5"` to row 1 here would hide that from it. An unparseable value is
|
|
243
|
+
* NaN, which the core turns into "no selection".
|
|
250
244
|
*/
|
|
251
245
|
private _readSelectedAttr(): number {
|
|
252
246
|
return Number.parseFloat(this.getAttribute('selected') ?? '');
|
|
@@ -269,13 +263,11 @@ export class AdwSidebar extends HTMLElement {
|
|
|
269
263
|
);
|
|
270
264
|
}
|
|
271
265
|
|
|
272
|
-
/** Click handler — the core applies the selection, then `activated` fires. */
|
|
273
266
|
private _activate(index: number): void {
|
|
274
267
|
if (!this._state.activate(index).activated) return;
|
|
275
268
|
this.dispatchEvent(new CustomEvent('activated', { bubbles: true, detail: { index } }));
|
|
276
269
|
}
|
|
277
270
|
|
|
278
|
-
/** Rebuild the rendered rows + headers from the core's derived model. */
|
|
279
271
|
private _rebuild(): void {
|
|
280
272
|
const headers = new Map<number, SidebarHeaderSpec>();
|
|
281
273
|
for (const header of this._state.headers) headers.set(header.sectionIndex, header);
|
|
@@ -335,9 +327,9 @@ export class AdwSidebar extends HTMLElement {
|
|
|
335
327
|
row.type = 'button';
|
|
336
328
|
row.className = 'adw-sidebar-item';
|
|
337
329
|
row.setAttribute('role', 'option');
|
|
338
|
-
// `visible` / `enabled` are bound to the row's `visible` / `sensitive`
|
|
339
|
-
//
|
|
340
|
-
//
|
|
330
|
+
// `visible` / `enabled` are bound to the row's `visible` / `sensitive` in
|
|
331
|
+
// `create_row`: the row still EXISTS and still owns its flat index, it just does
|
|
332
|
+
// not show or respond.
|
|
341
333
|
row.hidden = item.visible === false;
|
|
342
334
|
row.disabled = item.enabled === false;
|
|
343
335
|
|
|
@@ -362,8 +354,8 @@ export class AdwSidebar extends HTMLElement {
|
|
|
362
354
|
|
|
363
355
|
row.appendChild(textEl);
|
|
364
356
|
|
|
365
|
-
// Page mode adds a trailing chevron on every row, the way the
|
|
366
|
-
//
|
|
357
|
+
// Page mode adds a trailing chevron on every row, the way the boxed-list
|
|
358
|
+
// AdwActionRow carries a `go-next-symbolic` arrow.
|
|
367
359
|
row.appendChild(createAdwIcon('go-next', 'adw-sidebar-item-arrow'));
|
|
368
360
|
|
|
369
361
|
row.addEventListener('click', () => this._activate(flat.index));
|
|
@@ -379,8 +371,8 @@ export class AdwSidebar extends HTMLElement {
|
|
|
379
371
|
|
|
380
372
|
private _applySelection(): void {
|
|
381
373
|
const selected = this._state.selected;
|
|
382
|
-
// Page mode tracks the selection but never paints it
|
|
383
|
-
//
|
|
374
|
+
// Page mode tracks the selection but never paints it: its rows are plain
|
|
375
|
+
// boxed-list AdwActionRows with no selected state.
|
|
384
376
|
const paint = this._state.selectionVisible;
|
|
385
377
|
|
|
386
378
|
this._rows.forEach((row, position) => {
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
// <adw-spin-row> —
|
|
2
|
-
// Attributes: title, subtitle, min, max, step, value
|
|
3
|
-
// Events: notify::value (CustomEvent, mirrors GJS GObject signal naming)
|
|
1
|
+
// <adw-spin-row> — row with a title/subtitle and a numeric spin control (+/− buttons).
|
|
4
2
|
//
|
|
5
|
-
// The ADJUSTMENT state machine (value/min/max/step plus the clamp-on-every-
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// the adjustment) and the `notify::value` event.
|
|
11
|
-
// `@gjsify/adwaita-nativescript` composes the same state machine, so both ports
|
|
12
|
-
// share one behaviour.
|
|
3
|
+
// The ADJUSTMENT state machine (value/min/max/step plus the clamp-on-every-mutation and
|
|
4
|
+
// the change detection) is HEADLESS and lives in `@gjsify/adwaita-core` (ADR 0004) as
|
|
5
|
+
// {@link SpinState}; this element keeps only the DOM half — the −/input/+ control, the
|
|
6
|
+
// step-precision display (the `Adw.SpinRow:digits` concern, which belongs to the widget
|
|
7
|
+
// rather than to the adjustment) and the `notify::value` event.
|
|
13
8
|
//
|
|
14
9
|
// Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
|
|
15
10
|
// Copyright (c) 2025 csm. MIT License.
|
|
@@ -61,7 +56,6 @@ export class AdwSpinRow extends HTMLElement {
|
|
|
61
56
|
this._subtitleEl.className = 'adw-row-subtitle';
|
|
62
57
|
text.append(this._titleEl, this._subtitleEl);
|
|
63
58
|
|
|
64
|
-
// Spin control container
|
|
65
59
|
const control = document.createElement('div');
|
|
66
60
|
control.className = 'adw-spin-control';
|
|
67
61
|
|
|
@@ -123,8 +117,7 @@ export class AdwSpinRow extends HTMLElement {
|
|
|
123
117
|
}
|
|
124
118
|
|
|
125
119
|
private _renderText() {
|
|
126
|
-
// The `string_is_not_empty` label rule, from core —
|
|
127
|
-
// six hand-rolled copies, all of which omitted the TITLE half.
|
|
120
|
+
// The `string_is_not_empty` label rule, from core — it hides the TITLE too.
|
|
128
121
|
const labels = deriveRowLabels({
|
|
129
122
|
title: this.getAttribute('title'),
|
|
130
123
|
subtitle: this.getAttribute('subtitle'),
|
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
// <adw-spinner> — A continuously-spinning loading indicator.
|
|
2
2
|
// Attributes: size (px; the BOX, floored at the measured minimum 16).
|
|
3
3
|
//
|
|
4
|
-
// THE BOX AND THE RING ARE DIFFERENT THINGS. `adw_spinner_measure`
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// `
|
|
8
|
-
// the paintable, which caps only `radius` (adw-spinner-paintable.c:343) and
|
|
9
|
-
// still centres on the box (:349-351). So `size="200"` occupies 200px of layout
|
|
10
|
-
// and draws a 64px ring in the middle. This element used to BE the ring, which
|
|
11
|
-
// is why its suite asserted the opposite of the core's `SPINNER_SIZE_VECTORS`.
|
|
4
|
+
// THE BOX AND THE RING ARE DIFFERENT THINGS. `adw_spinner_measure` reports MIN_SIZE as
|
|
5
|
+
// both minimum and natural and has no upper bound, while `adw_spinner_snapshot` hands
|
|
6
|
+
// the widget's real size to the paintable, which caps only `radius` and still centres on
|
|
7
|
+
// the box. So `size="200"` occupies 200px of layout and draws a 64px ring in the middle.
|
|
12
8
|
//
|
|
13
|
-
// THE ARC IS NOT A ROTATING QUARTER-CIRCLE. It extends, overlaps, contracts and
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// keyframes cannot express it: a `border-top-color` chase is a fixed 90 degrees,
|
|
19
|
-
// which is what this element drew for its whole life.
|
|
9
|
+
// THE ARC IS NOT A ROTATING QUARTER-CIRCLE. It extends, overlaps, contracts and idles on
|
|
10
|
+
// an ease-in-out-sine while the figure turns; the arithmetic is `@gjsify/adwaita-core`'s
|
|
11
|
+
// `spinnerArc`, shared with the vectors that judge it. That needs a per-frame value, so
|
|
12
|
+
// this element draws an SVG arc and advances it from one module-level ticker. CSS
|
|
13
|
+
// keyframes cannot express it — a `border-top-color` chase is a fixed 90 degrees.
|
|
20
14
|
//
|
|
21
|
-
// IT KEEPS SPINNING UNDER REDUCED MOTION
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// `@media (prefers-reduced-motion: reduce) { animation: none }`
|
|
15
|
+
// IT KEEPS SPINNING UNDER REDUCED MOTION, deliberately: a frozen busy indicator reads as
|
|
16
|
+
// a hang, which is why `adw_spinner_paintable_set_widget` calls
|
|
17
|
+
// `adw_animation_set_follow_enable_animations_setting (…, FALSE)`. Do not reintroduce a
|
|
18
|
+
// `@media (prefers-reduced-motion: reduce) { animation: none }` rule for it.
|
|
25
19
|
//
|
|
26
20
|
// Reference: refs/libadwaita/src/adw-spinner.c (MIN_SIZE, adw_spinner_measure, the a11y role)
|
|
27
21
|
// Reference: refs/libadwaita/src/adw-spinner-paintable.c (the animation + geometry)
|
|
@@ -42,10 +36,9 @@ const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
|
42
36
|
/**
|
|
43
37
|
* Every mapped spinner, driven by ONE `requestAnimationFrame` loop.
|
|
44
38
|
*
|
|
45
|
-
* The C animates per widget off the frame clock, which costs nothing extra
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* the loop must not exist at all while none are mapped.
|
|
39
|
+
* The C animates per widget off the frame clock, which costs nothing extra because the
|
|
40
|
+
* clock is already ticking. A browser has no such clock, so the equivalent is one shared
|
|
41
|
+
* rAF: N spinners must not mean N loops, and the loop must not exist while none are mapped.
|
|
49
42
|
*/
|
|
50
43
|
const mapped = new Set<AdwSpinner>();
|
|
51
44
|
let frameHandle = 0;
|
|
@@ -76,8 +69,8 @@ export class AdwSpinner extends HTMLElement {
|
|
|
76
69
|
|
|
77
70
|
connectedCallback() {
|
|
78
71
|
this._build();
|
|
79
|
-
// `widget_map_cb`
|
|
80
|
-
//
|
|
72
|
+
// `widget_map_cb` plays the animation on MAP, not on construction: an off-screen
|
|
73
|
+
// spinner burns nothing.
|
|
81
74
|
mapped.add(this);
|
|
82
75
|
this._origin = null;
|
|
83
76
|
startTicking();
|
|
@@ -92,11 +85,9 @@ export class AdwSpinner extends HTMLElement {
|
|
|
92
85
|
}
|
|
93
86
|
|
|
94
87
|
/**
|
|
95
|
-
* Draw the frame for `now`, in `requestAnimationFrame` timebase.
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* method — and because a test needs to place the spinner at a known moment
|
|
99
|
-
* without waiting for real frames.
|
|
88
|
+
* Draw the frame for `now`, in `requestAnimationFrame` timebase. Public because the
|
|
89
|
+
* shared ticker is a module-level function, and because a test needs to place the
|
|
90
|
+
* spinner at a known moment without waiting for real frames.
|
|
100
91
|
*/
|
|
101
92
|
drawAt(now: number): void {
|
|
102
93
|
if (this._origin === null) this._origin = now;
|
|
@@ -107,19 +98,18 @@ export class AdwSpinner extends HTMLElement {
|
|
|
107
98
|
if (this._svg) return;
|
|
108
99
|
const svg = document.createElementNS(SVG_NS, 'svg');
|
|
109
100
|
svg.setAttribute('fill', 'none');
|
|
110
|
-
// The ring is decorative; the ROLE is on the host
|
|
101
|
+
// The ring is decorative; the ROLE is on the host.
|
|
111
102
|
svg.setAttribute('aria-hidden', 'true');
|
|
112
103
|
|
|
113
104
|
const track = document.createElementNS(SVG_NS, 'circle');
|
|
114
105
|
track.setAttribute('stroke', 'currentColor');
|
|
115
|
-
// `CIRCLE_OPACITY` of the WIDGET's colour
|
|
116
|
-
//
|
|
106
|
+
// C's `CIRCLE_OPACITY` of the WIDGET's colour, not a hardcoded grey, so the track
|
|
107
|
+
// follows the text colour everywhere.
|
|
117
108
|
track.setAttribute('stroke-opacity', String(ADW_SPINNER_TRACK_OPACITY));
|
|
118
109
|
|
|
119
110
|
const arc = document.createElementNS(SVG_NS, 'circle');
|
|
120
111
|
arc.setAttribute('stroke', 'currentColor');
|
|
121
|
-
// `GSK_LINE_CAP_ROUND
|
|
122
|
-
// `border-top-color` ends were square-cut, visible from 48px up.
|
|
112
|
+
// C's `GSK_LINE_CAP_ROUND`; square-cut ends are visible from 48px up.
|
|
123
113
|
arc.setAttribute('stroke-linecap', 'round');
|
|
124
114
|
|
|
125
115
|
svg.append(track, arc);
|
|
@@ -128,9 +118,8 @@ export class AdwSpinner extends HTMLElement {
|
|
|
128
118
|
this._arc = arc;
|
|
129
119
|
this.replaceChildren(svg);
|
|
130
120
|
|
|
131
|
-
// `gtk_widget_class_set_accessible_role (…, PROGRESS_BAR)`
|
|
132
|
-
//
|
|
133
|
-
// either found ZERO hits before this: screen readers announced nothing.
|
|
121
|
+
// C's `gtk_widget_class_set_accessible_role (…, PROGRESS_BAR)` plus
|
|
122
|
+
// `GTK_ACCESSIBLE_STATE_BUSY, TRUE`; without both, screen readers announce nothing.
|
|
134
123
|
this.setAttribute('role', 'progressbar');
|
|
135
124
|
this.setAttribute('aria-busy', 'true');
|
|
136
125
|
|
|
@@ -154,8 +143,7 @@ export class AdwSpinner extends HTMLElement {
|
|
|
154
143
|
svg.setAttribute('height', String(diameter));
|
|
155
144
|
svg.setAttribute('viewBox', `0 0 ${diameter} ${diameter}`);
|
|
156
145
|
|
|
157
|
-
// The stroke is centred on the path, so the circle sits half a stroke in
|
|
158
|
-
// — `radius - line_width / 2` (adw-spinner-paintable.c:364).
|
|
146
|
+
// The stroke is centred on the path, so the circle sits half a stroke in.
|
|
159
147
|
const radius = (diameter - lineWidth) / 2;
|
|
160
148
|
this._radius = radius;
|
|
161
149
|
this._circumference = 2 * Math.PI * radius;
|
|
@@ -180,9 +168,9 @@ export class AdwSpinner extends HTMLElement {
|
|
|
180
168
|
const { end, length } = spinnerArc(progress);
|
|
181
169
|
const drawn = length * this._radius;
|
|
182
170
|
arc.setAttribute('stroke-dasharray', `${drawn} ${Math.max(this._circumference - drawn, 0)}`);
|
|
183
|
-
// An SVG circle's path starts at 3 o'clock and runs clockwise,
|
|
184
|
-
//
|
|
185
|
-
//
|
|
171
|
+
// An SVG circle's path starts at 3 o'clock and runs clockwise, the same origin and
|
|
172
|
+
// direction as the C's angles, so a radian is a path distance and the dash only
|
|
173
|
+
// has to be shifted to `end`.
|
|
186
174
|
arc.setAttribute('stroke-dashoffset', String(-end * this._radius));
|
|
187
175
|
}
|
|
188
176
|
}
|