@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
|
@@ -1,32 +1,28 @@
|
|
|
1
|
-
// <adw-checkbox> and <adw-radio> — the two form-control primitives, in ONE
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// promptly drifted apart.
|
|
1
|
+
// <adw-checkbox> and <adw-radio> — the two form-control primitives, in ONE module,
|
|
2
|
+
// for the same reason `scss/_checks.scss` is one partial: upstream merges them
|
|
3
|
+
// (`refs/libadwaita/src/stylesheet/widgets/_checks.scss` has no `_radio.scss` beside
|
|
4
|
+
// it), and everything but the corner radius, the glyph and the group is shared.
|
|
5
|
+
// Splitting them, as `refs/adwaita-web` does, is what lets the two drift apart.
|
|
7
6
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// ADR 0004 calls out as too trivial to model. What IS core is the pair of
|
|
12
|
-
// pieces a second renderer would otherwise re-invent:
|
|
7
|
+
// The checkbox has no core class: it is one boolean plus a third state, which ADR 0004
|
|
8
|
+
// calls out as too trivial to model. What IS core is the pair a second renderer would
|
|
9
|
+
// otherwise re-invent:
|
|
13
10
|
//
|
|
14
11
|
// - {@link RadioGroupState} — group exclusivity. The browser gives the inner
|
|
15
12
|
// `<input type="radio" name="…">` exclusivity for free, and STOPS THERE: it
|
|
16
|
-
// unchecks the sibling INPUT and leaves the sibling `<adw-radio>` host's
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// - {@link resolveCheckState} — indeterminate outranks checked,
|
|
21
|
-
//
|
|
13
|
+
// unchecks the sibling INPUT and leaves the sibling `<adw-radio>` host's `checked`
|
|
14
|
+
// attribute — the published state, and the selector `_checks.scss` paints from —
|
|
15
|
+
// stale, so a group left to the browser draws two selected radios. The shared
|
|
16
|
+
// state is what repaints the loser.
|
|
17
|
+
// - {@link resolveCheckState} — indeterminate outranks checked, read off the cascade
|
|
18
|
+
// in libadwaita's `_checks.scss`, and what `aria-checked` needs.
|
|
22
19
|
//
|
|
23
|
-
//
|
|
24
|
-
// `
|
|
25
|
-
// group
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// HTML pre-click activation step, not a reading of GTK.
|
|
20
|
+
// NOT DERIVED FROM GTK. `GtkCheckButton` is a GTK widget and libadwaita has no
|
|
21
|
+
// `adw-checkbox.c`, so its group semantics (`gtk_check_button_set_group`, whether a
|
|
22
|
+
// fresh group starts with a member active) and the exact meaning of
|
|
23
|
+
// `GtkCheckButton:inconsistent` are not guessed at. Where a rule was needed the HTML
|
|
24
|
+
// one is used and cited as such — clearing `indeterminate` on activation is the HTML
|
|
25
|
+
// pre-click activation step, not a reading of GTK.
|
|
30
26
|
//
|
|
31
27
|
// NAMING. The state attribute is `checked`, not GtkCheckButton's `active`:
|
|
32
28
|
// these elements wrap a real `<input>`, `checked` is the spelling every author
|
|
@@ -67,8 +63,8 @@ import { RadioGroupState, resolveCheckState } from '@gjsify/adwaita-core';
|
|
|
67
63
|
|
|
68
64
|
/**
|
|
69
65
|
* The document's radio groups. ONE registry, as global as a bare
|
|
70
|
-
* `<input type="radio" name="x">` outside a form is — two independent groups
|
|
71
|
-
*
|
|
66
|
+
* `<input type="radio" name="x">` outside a form is — two independent groups therefore
|
|
67
|
+
* need two names.
|
|
72
68
|
*/
|
|
73
69
|
const radioGroups = new RadioGroupState();
|
|
74
70
|
|
|
@@ -85,15 +81,13 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
85
81
|
/** What a click / Space / arrow key on the control means for this kind. */
|
|
86
82
|
protected abstract activate(): void;
|
|
87
83
|
|
|
88
|
-
/** Whether the control is checked. */
|
|
89
84
|
get checked(): boolean {
|
|
90
85
|
return this.hasAttribute('checked');
|
|
91
86
|
}
|
|
92
87
|
|
|
93
88
|
set checked(value: boolean) {
|
|
94
|
-
// `toggleAttribute` with a force flag is a no-op when the attribute
|
|
95
|
-
//
|
|
96
|
-
// nothing and the notify never runs.
|
|
89
|
+
// `toggleAttribute` with a force flag is a no-op when the attribute already
|
|
90
|
+
// holds that state, so re-setting the current value never notifies.
|
|
97
91
|
this.toggleAttribute('checked', !!value);
|
|
98
92
|
}
|
|
99
93
|
|
|
@@ -106,7 +100,6 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
106
100
|
this.toggleAttribute('indeterminate', !!value);
|
|
107
101
|
}
|
|
108
102
|
|
|
109
|
-
/** Whether the control is inert. */
|
|
110
103
|
get disabled(): boolean {
|
|
111
104
|
return this.hasAttribute('disabled');
|
|
112
105
|
}
|
|
@@ -115,7 +108,6 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
115
108
|
this.toggleAttribute('disabled', !!value);
|
|
116
109
|
}
|
|
117
110
|
|
|
118
|
-
/** The label text beside the indicator. */
|
|
119
111
|
get label(): string {
|
|
120
112
|
return this.getAttribute('label') ?? this._label?.textContent ?? '';
|
|
121
113
|
}
|
|
@@ -131,8 +123,8 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
131
123
|
}
|
|
132
124
|
this._initialized = true;
|
|
133
125
|
|
|
134
|
-
// Captured BEFORE the element takes over its own children
|
|
135
|
-
//
|
|
126
|
+
// Captured BEFORE the element takes over its own children, so a server-rendered
|
|
127
|
+
// `<adw-checkbox>Enable</adw-checkbox>` keeps its text.
|
|
136
128
|
const authored = [...this.childNodes];
|
|
137
129
|
|
|
138
130
|
this._input = document.createElement('input');
|
|
@@ -154,9 +146,9 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
154
146
|
// and a direct programmatic `input.click()` both surface as `change`.
|
|
155
147
|
this._input.addEventListener('change', () => this.activate());
|
|
156
148
|
|
|
157
|
-
// The indicator and the label cover the control; the input is 0x0, so
|
|
158
|
-
//
|
|
159
|
-
//
|
|
149
|
+
// The indicator and the label cover the control; the input is 0x0, so every
|
|
150
|
+
// pointer click lands on one of them. A click that DID reach the input is already
|
|
151
|
+
// handled above — activating again would undo it.
|
|
160
152
|
this.addEventListener('click', (event) => {
|
|
161
153
|
if (this.disabled) return;
|
|
162
154
|
if (event.target === this._input) return;
|
|
@@ -184,19 +176,16 @@ abstract class AdwCheckBase extends HTMLElement {
|
|
|
184
176
|
}
|
|
185
177
|
}
|
|
186
178
|
|
|
187
|
-
/** Extra wiring for a subclass on connect. */
|
|
188
179
|
protected onConnected(): void {}
|
|
189
180
|
|
|
190
|
-
/** Extra teardown for a subclass on disconnect. */
|
|
191
181
|
protected onDisconnected(): void {}
|
|
192
182
|
|
|
193
183
|
protected render(): void {
|
|
194
184
|
const state = resolveCheckState(this.checked, this.indeterminate);
|
|
195
185
|
this._input.checked = state === 'checked';
|
|
196
186
|
this._input.indeterminate = state === 'indeterminate';
|
|
197
|
-
// Explicit, because HTML has a native mixed CHECKBOX (the property
|
|
198
|
-
//
|
|
199
|
-
// the third state for both.
|
|
187
|
+
// Explicit, because HTML has a native mixed CHECKBOX (the property above) but no
|
|
188
|
+
// mixed radio — this is the one spelling that announces the third state for both.
|
|
200
189
|
this._input.setAttribute('aria-checked', state === 'indeterminate' ? 'mixed' : String(state === 'checked'));
|
|
201
190
|
this._input.disabled = this.disabled;
|
|
202
191
|
this._label.hidden = this._label.childNodes.length === 0;
|
|
@@ -214,10 +203,9 @@ export class AdwCheckbox extends AdwCheckBase {
|
|
|
214
203
|
}
|
|
215
204
|
|
|
216
205
|
protected activate(): void {
|
|
217
|
-
// HTML's pre-click activation steps clear `indeterminate` before the
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
// behaviour is not verifiable in this tree.
|
|
206
|
+
// HTML's pre-click activation steps clear `indeterminate` before the toggle, so a
|
|
207
|
+
// user can always reach a definite state. That rule is the HTML spec's, not a
|
|
208
|
+
// reading of `GtkCheckButton:inconsistent`.
|
|
221
209
|
this.indeterminate = false;
|
|
222
210
|
this.checked = !this.checked;
|
|
223
211
|
}
|
|
@@ -235,7 +223,6 @@ export class AdwRadio extends AdwCheckBase {
|
|
|
235
223
|
return 'radio';
|
|
236
224
|
}
|
|
237
225
|
|
|
238
|
-
/** The group this member belongs to. */
|
|
239
226
|
get name(): string {
|
|
240
227
|
return this.getAttribute('name') ?? '';
|
|
241
228
|
}
|
|
@@ -245,8 +232,8 @@ export class AdwRadio extends AdwCheckBase {
|
|
|
245
232
|
}
|
|
246
233
|
|
|
247
234
|
/**
|
|
248
|
-
* This member's value. Falls back to the label text so a group authored
|
|
249
|
-
*
|
|
235
|
+
* This member's value. Falls back to the label text so a group authored without
|
|
236
|
+
* values still has members the group state can tell apart.
|
|
250
237
|
*/
|
|
251
238
|
get value(): string {
|
|
252
239
|
return this.getAttribute('value') ?? this.label.trim();
|
|
@@ -258,17 +245,17 @@ export class AdwRadio extends AdwCheckBase {
|
|
|
258
245
|
|
|
259
246
|
protected activate(): void {
|
|
260
247
|
this.indeterminate = false;
|
|
261
|
-
// An UNNAMED radio is not in a group — HTML forms a radio button group
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
248
|
+
// An UNNAMED radio is not in a group — HTML forms a radio button group only from
|
|
249
|
+
// a non-empty `name`, so a nameless one checks itself and never unchecks anything.
|
|
250
|
+
// Without this guard every nameless radio in the document would join one `''`
|
|
251
|
+
// group and fight over it.
|
|
265
252
|
if (this.name === '') {
|
|
266
253
|
this.checked = true;
|
|
267
254
|
return;
|
|
268
255
|
}
|
|
269
|
-
// The pick goes through the shared state, never straight onto this
|
|
270
|
-
//
|
|
271
|
-
//
|
|
256
|
+
// The pick goes through the shared state, never straight onto this element: the
|
|
257
|
+
// member that LOSES the selection is a different element and only the state knows
|
|
258
|
+
// which one it is.
|
|
272
259
|
radioGroups.select(this.name, this.value);
|
|
273
260
|
}
|
|
274
261
|
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
// <adw-clamp> — Constrains its children to a maximum width and centers them.
|
|
2
2
|
// Attributes: maximum-size (px, default 600), tightening-threshold (px, default 400).
|
|
3
3
|
//
|
|
4
|
-
// The clamp is NOT a `max-width
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// widget produce the same width for the same viewport.
|
|
4
|
+
// The clamp is NOT a `max-width`: Adwaita eases the child from "all the available width"
|
|
5
|
+
// into the cap across a tightening region and stamps a `small`/`medium`/`large` class on
|
|
6
|
+
// it. The arithmetic lives in `@gjsify/adwaita-core` (`clampAllocate`), so this element and
|
|
7
|
+
// the NativeScript widget produce the same width for the same viewport.
|
|
9
8
|
//
|
|
10
|
-
// The cap
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
9
|
+
// The cap sits on the CHILDREN, not on this element, which has to keep reporting the
|
|
10
|
+
// AVAILABLE width — that is what the curve is evaluated against. An element that caps
|
|
11
|
+
// itself reports the capped width back to its own ResizeObserver, which is a feedback
|
|
12
|
+
// loop, not a measurement.
|
|
14
13
|
//
|
|
15
14
|
// `childMin` is passed as 0 rather than measured. libadwaita's
|
|
16
15
|
// `lower = MAX (…, child_min)` says a child is never squeezed below its own
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
// <adw-combo-row> —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// The native <select> is stretched invisibly over the row so clicking anywhere opens it.
|
|
1
|
+
// <adw-combo-row> — row with a title/subtitle and a dropdown select. `items` is a JSON
|
|
2
|
+
// `string[]`, `selected` an index, and the native <select> is stretched invisibly over the
|
|
3
|
+
// row so clicking anywhere opens it.
|
|
5
4
|
//
|
|
6
|
-
// The SELECTION state machine (the options list, the two-way index↔value mapping,
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// inline value label and the `notify::selected` event.
|
|
11
|
-
// `@gjsify/adwaita-nativescript` composes the same state machine, so both ports
|
|
12
|
-
// share one behaviour.
|
|
5
|
+
// The SELECTION state machine (the options list, the two-way index↔value mapping, the
|
|
6
|
+
// empty/out-of-range guards and the programmatic-vs-interactive notify split) is HEADLESS
|
|
7
|
+
// and lives in `@gjsify/adwaita-core` (ADR 0004) as {@link ComboState}; this element keeps
|
|
8
|
+
// only the DOM half — the <select>, the inline value label and `notify::selected`.
|
|
13
9
|
//
|
|
14
10
|
// Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
|
|
15
11
|
// Copyright (c) 2025 csm. MIT License.
|
|
@@ -45,10 +41,9 @@ export class AdwComboRow extends HTMLElement {
|
|
|
45
41
|
this._initialized = true;
|
|
46
42
|
|
|
47
43
|
const items: string[] = JSON.parse(this.getAttribute('items') || '[]');
|
|
48
|
-
// Seed the headless state BEFORE subscribing, so building the initial DOM
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
// two selectors accepted different option vocabularies.
|
|
44
|
+
// Seed the headless state BEFORE subscribing, so building the initial DOM below is
|
|
45
|
+
// not driven by a change notification. The string→descriptor mapping is core's, so
|
|
46
|
+
// this row and `<adw-drop-down>` accept one option vocabulary.
|
|
52
47
|
this._state.setOptions(normalizeComboOptions(items));
|
|
53
48
|
this._state.setSelectedIndex(parseInt(this.getAttribute('selected') || '0', 10));
|
|
54
49
|
const selectedIdx = this._state.selectedIndex;
|
|
@@ -61,12 +56,10 @@ export class AdwComboRow extends HTMLElement {
|
|
|
61
56
|
this._subtitleEl.className = 'adw-row-subtitle';
|
|
62
57
|
text.append(this._titleEl, this._subtitleEl);
|
|
63
58
|
|
|
64
|
-
// Visible selected value display
|
|
65
59
|
this._valueEl = document.createElement('span');
|
|
66
60
|
this._valueEl.className = 'adw-row-value';
|
|
67
61
|
this._valueEl.textContent = this._state.selectedLabel;
|
|
68
62
|
|
|
69
|
-
// Hidden select overlaying the entire row
|
|
70
63
|
const select = document.createElement('select');
|
|
71
64
|
items.forEach((item, i) => {
|
|
72
65
|
const option = document.createElement('option');
|
|
@@ -118,13 +111,13 @@ export class AdwComboRow extends HTMLElement {
|
|
|
118
111
|
}
|
|
119
112
|
|
|
120
113
|
/**
|
|
121
|
-
* `model_changed
|
|
122
|
-
*
|
|
114
|
+
* `model_changed`: one option or none is not a choice, so the arrow goes and the row
|
|
115
|
+
* stops being activatable.
|
|
123
116
|
*
|
|
124
|
-
* Both halves have to be expressed
|
|
125
|
-
* `.adw-row-value::after` mask in the stylesheet, and DISABLING the overlaid
|
|
126
|
-
*
|
|
127
|
-
*
|
|
117
|
+
* Both halves have to be expressed — `data-presents-chooser` gates the
|
|
118
|
+
* `.adw-row-value::after` mask in the stylesheet, and DISABLING the overlaid `<select>`
|
|
119
|
+
* is what makes the row inert. Hiding the arrow alone leaves a row that still opens a
|
|
120
|
+
* one-entry popup on click.
|
|
128
121
|
*/
|
|
129
122
|
private _syncChooser() {
|
|
130
123
|
const presents = this._state.presentsChooser;
|
|
@@ -133,8 +126,7 @@ export class AdwComboRow extends HTMLElement {
|
|
|
133
126
|
}
|
|
134
127
|
|
|
135
128
|
private _renderText() {
|
|
136
|
-
// The `string_is_not_empty` label rule, from core —
|
|
137
|
-
// six hand-rolled copies, all of which omitted the TITLE half.
|
|
129
|
+
// The `string_is_not_empty` label rule, from core — it hides the TITLE too.
|
|
138
130
|
const labels = deriveRowLabels({
|
|
139
131
|
title: this.getAttribute('title'),
|
|
140
132
|
subtitle: this.getAttribute('subtitle'),
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
// <adw-data-grid> — A slim, Adwaita-styled ALIGNED data grid: the web mirror of
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// accounting row emphasis a statement needs: section headers, subtotals and a
|
|
9
|
-
// bold final total.
|
|
1
|
+
// <adw-data-grid> — A slim, Adwaita-styled ALIGNED data grid: the web mirror of the
|
|
2
|
+
// native Gtk.Grid an accounting app uses for its financial statements (BWA / P&L,
|
|
3
|
+
// invoice line-items). DELIBERATELY NOT a heavyweight sortable Gtk.ColumnView /
|
|
4
|
+
// TreeView — boxed lists (<adw-action-row> inside <adw-preferences-group>) stay the
|
|
5
|
+
// default for record lists. Reach for this ONLY for genuinely tabular numeric data
|
|
6
|
+
// where columns of figures must line up, with the accounting row emphasis a statement
|
|
7
|
+
// needs: section headers, subtotals and a bold final total.
|
|
10
8
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// <adw-drop-down>) — there are no clickable-sortable column headers here.
|
|
9
|
+
// PRESENTATIONAL ONLY: layout, alignment and emphasis, never formatting or sorting.
|
|
10
|
+
// Cell values are PRE-FORMATTED strings (locale currency formatting stays in the app,
|
|
11
|
+
// e.g. `1.234,50 €`); sorting is a data op driven externally, so there are no
|
|
12
|
+
// clickable-sortable column headers here.
|
|
16
13
|
//
|
|
17
14
|
// Columns (the `columns` property, or a JSON `columns` attribute):
|
|
18
15
|
// { key, label?, align?, width?, flex?, monospace?, numeric? }
|
|
@@ -51,13 +48,12 @@
|
|
|
51
48
|
// Responsive: the grid scrolls horizontally within its OWN container on narrow
|
|
52
49
|
// viewports (it never overflows the page); it sits inside an <adw-card>/boxed
|
|
53
50
|
// surface and follows light + dark via the adwaita-web CSS variables.
|
|
54
|
-
// The derivations are HEADLESS and live in `@gjsify/adwaita-core` (ADR 0004):
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// `DATA_GRID_*_VECTORS` is the table both ports are held to.
|
|
51
|
+
// The derivations are HEADLESS and live in `@gjsify/adwaita-core` (ADR 0004): the
|
|
52
|
+
// column tracks, the cell classes, the two normalisers, the cell text and the activation
|
|
53
|
+
// rule. `@gjsify/adwaita-nativescript` renders the same widget over `GridLayout`
|
|
54
|
+
// `ItemSpec`s — NativeScript has no subgrid — so the TRACK rule is a renderer-neutral
|
|
55
|
+
// descriptor and only the mapping onto `grid-template-columns` stays here. Both ports
|
|
56
|
+
// are held to `@gjsify/adwaita-core/conformance`'s `DATA_GRID_*_VECTORS`.
|
|
61
57
|
// Reference: Gtk.Grid usage in the Buchhaltung BWA view (native financial grid).
|
|
62
58
|
// Reference: refs/libadwaita/src/stylesheet/_colors.scss (separator / card tokens).
|
|
63
59
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
@@ -94,7 +90,6 @@ export class AdwDataGrid extends HTMLElement {
|
|
|
94
90
|
return ['columns', 'rows', 'caption', 'interactive'];
|
|
95
91
|
}
|
|
96
92
|
|
|
97
|
-
/** The column descriptors. Setting rebuilds the grid. */
|
|
98
93
|
get columns(): AdwDataGridColumn[] {
|
|
99
94
|
return this._columns;
|
|
100
95
|
}
|
|
@@ -104,7 +99,6 @@ export class AdwDataGrid extends HTMLElement {
|
|
|
104
99
|
if (this._initialized) this._render();
|
|
105
100
|
}
|
|
106
101
|
|
|
107
|
-
/** The row objects. Setting rebuilds the body. */
|
|
108
102
|
get rows(): AdwDataGridRow[] {
|
|
109
103
|
return this._rows;
|
|
110
104
|
}
|
|
@@ -124,7 +118,6 @@ export class AdwDataGrid extends HTMLElement {
|
|
|
124
118
|
else this.removeAttribute('caption');
|
|
125
119
|
}
|
|
126
120
|
|
|
127
|
-
/** Whether every `normal` row is clickable (element-level flag). */
|
|
128
121
|
get interactive(): boolean {
|
|
129
122
|
return this.hasAttribute('interactive');
|
|
130
123
|
}
|
|
@@ -176,8 +169,6 @@ export class AdwDataGrid extends HTMLElement {
|
|
|
176
169
|
void value;
|
|
177
170
|
}
|
|
178
171
|
|
|
179
|
-
// ── internals ──────────────────────────────────────────────────────────
|
|
180
|
-
|
|
181
172
|
private _parseJsonAttr<T>(name: string, coerce: (v: unknown) => T): T {
|
|
182
173
|
const raw = this.getAttribute(name);
|
|
183
174
|
if (!raw) return coerce(undefined);
|
|
@@ -195,13 +186,11 @@ export class AdwDataGrid extends HTMLElement {
|
|
|
195
186
|
}
|
|
196
187
|
|
|
197
188
|
/**
|
|
198
|
-
* The CSS `grid-template-columns` value derived from the column descriptors.
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* `0px` must keep its unit to round-trip through the CSSOM — is
|
|
204
|
-
* `dataGridTrackTemplate`.
|
|
189
|
+
* The CSS `grid-template-columns` value derived from the column descriptors. The
|
|
190
|
+
* DERIVATION is core's (`dataGridTracks`), because NativeScript needs the same one for
|
|
191
|
+
* its `ItemSpec`s and can do nothing with a CSS string; the mapping onto CSS text —
|
|
192
|
+
* including the `minmax(0px, 1fr)` slack track whose `0px` must keep its unit to
|
|
193
|
+
* round-trip through the CSSOM — is `dataGridTrackTemplate`.
|
|
205
194
|
*/
|
|
206
195
|
private _trackTemplate(): string {
|
|
207
196
|
return dataGridTrackTemplate(dataGridTracks(this._columns));
|
|
@@ -220,7 +209,6 @@ export class AdwDataGrid extends HTMLElement {
|
|
|
220
209
|
const columnCount = this._columns.length;
|
|
221
210
|
if (columnCount === 0) return;
|
|
222
211
|
|
|
223
|
-
// Header row (dim, small-caps) — column labels, aligned per column.
|
|
224
212
|
const header = document.createElement('div');
|
|
225
213
|
header.className = 'adw-data-grid-row header';
|
|
226
214
|
header.setAttribute('role', 'row');
|
|
@@ -242,8 +230,8 @@ export class AdwDataGrid extends HTMLElement {
|
|
|
242
230
|
row.setAttribute('role', 'row');
|
|
243
231
|
|
|
244
232
|
if (variant === 'section') {
|
|
245
|
-
// A section header spans all columns; its text is the first
|
|
246
|
-
//
|
|
233
|
+
// A section header spans all columns; its text is the first column's
|
|
234
|
+
// value (the section title).
|
|
247
235
|
const firstKey = this._columns[0]?.key ?? '';
|
|
248
236
|
const cell = document.createElement('div');
|
|
249
237
|
cell.className = 'adw-data-grid-cell align-start section-cell';
|
|
@@ -261,8 +249,8 @@ export class AdwDataGrid extends HTMLElement {
|
|
|
261
249
|
}
|
|
262
250
|
}
|
|
263
251
|
|
|
264
|
-
// A row is clickable only when it is a plain data row AND either the
|
|
265
|
-
//
|
|
252
|
+
// A row is clickable only when it is a plain data row AND either the element
|
|
253
|
+
// opts everything in or the row opts itself in.
|
|
266
254
|
if (dataGridRowInteractive(variant, rowData.interactive, elementInteractive)) {
|
|
267
255
|
row.classList.add('interactive');
|
|
268
256
|
row.tabIndex = 0;
|
|
@@ -1,44 +1,23 @@
|
|
|
1
|
-
// <adw-dialog> —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// free from AdwBreakpoint on GTK. There is no Adw.Breakpoint primitive on the
|
|
6
|
-
// web, so the switch is a CSS media query keyed on the `presentation-mode`.
|
|
1
|
+
// <adw-dialog> — the web counterpart of Adw.Dialog: arbitrary content in a floating
|
|
2
|
+
// card over a full-cover scrim on wide viewports, re-anchored as a bottom sheet
|
|
3
|
+
// (full-width, rounded top corners) on narrow ones. The adaptive switch is a CSS
|
|
4
|
+
// media query keyed on `presentation-mode`, not an AdwBreakpoint object.
|
|
7
5
|
//
|
|
8
6
|
// Unlike the specialised dialogs (<adw-alert-dialog>, <adw-preferences-dialog>,
|
|
9
|
-
// <adw-about-dialog>) this one imposes no content model of its own: its default
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// It is the generic base those specialised dialogs could later be built on.
|
|
7
|
+
// <adw-about-dialog>) this one imposes no content model of its own: its default slot
|
|
8
|
+
// holds any markup, and the flat header bar (title + trailing close button) renders
|
|
9
|
+
// only when a `title` is set or `show-header` is used.
|
|
13
10
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// (and stays reusable — close hides, it does not detach).
|
|
11
|
+
// Starts hidden; `present()` (or the `open` attribute) reveals it and `close()`
|
|
12
|
+
// dismisses it — gated by `can-close`, so while locked Escape / scrim-click /
|
|
13
|
+
// close-button raise `close-attempt` instead. Presenting traps focus inside the
|
|
14
|
+
// dialog and returns it to the previously-focused element on close. A DISCONNECTED
|
|
15
|
+
// element is attached to `document.body` on present (the Adw.Dialog.present(parent)
|
|
16
|
+
// idiom); one already in the DOM is revealed in place and stays reusable — close
|
|
17
|
+
// hides, it does not detach.
|
|
22
18
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// open (boolean — whether the dialog is revealed; default hidden)
|
|
26
|
-
// can-close (boolean — user may dismiss; default on. `="false"` locks it
|
|
27
|
-
// so a dismissal raises `close-attempt` — Adw.Dialog:can-close)
|
|
28
|
-
// content-width (max content width in px — Adw.Dialog:content-width; default 360)
|
|
29
|
-
// content-height (max content height in px — Adw.Dialog:content-height; default unset)
|
|
30
|
-
// presentation-mode (`auto` | `floating` | `bottom-sheet`; default `auto` —
|
|
31
|
-
// Adw.Dialog:presentation-mode. `auto` = floating when wide,
|
|
32
|
-
// bottom sheet when narrow)
|
|
33
|
-
// show-header (boolean — render the header bar even without a title)
|
|
34
|
-
//
|
|
35
|
-
// Properties: open, title, canClose, contentWidth, contentHeight, presentationMode.
|
|
36
|
-
// Methods: present(), close(), forceClose().
|
|
37
|
-
// Events:
|
|
38
|
-
// `notify::open` (CustomEvent, bubbles, detail = { open }) — revealed state changed.
|
|
39
|
-
// `closed` (CustomEvent, bubbles) — the dialog finished closing (Adw.Dialog::closed).
|
|
40
|
-
// `close-attempt` (CustomEvent, bubbles) — a dismissal was attempted while locked
|
|
41
|
-
// (Adw.Dialog::close-attempt).
|
|
19
|
+
// Events, all bubbling CustomEvents: `notify::open` (detail `{ open }`), `closed`
|
|
20
|
+
// (Adw.Dialog::closed), `close-attempt` (Adw.Dialog::close-attempt).
|
|
42
21
|
//
|
|
43
22
|
// Reference: refs/libadwaita/src/adw-dialog.c (present/close/can-close, Escape, focus)
|
|
44
23
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_dialogs.scss (floating sheet + bottom sheet)
|
|
@@ -51,7 +30,6 @@ export type AdwDialogPresentationMode = 'auto' | 'floating' | 'bottom-sheet';
|
|
|
51
30
|
const DEFAULT_CONTENT_WIDTH = 360;
|
|
52
31
|
const PRESENTATION_MODES: readonly AdwDialogPresentationMode[] = ['auto', 'floating', 'bottom-sheet'];
|
|
53
32
|
|
|
54
|
-
// Selector matching the tabbable elements a focus trap must cycle through.
|
|
55
33
|
const FOCUSABLE_SELECTOR = [
|
|
56
34
|
'a[href]',
|
|
57
35
|
'button:not([disabled])',
|
|
@@ -163,25 +141,24 @@ export class AdwDialog extends HTMLElement {
|
|
|
163
141
|
|
|
164
142
|
connectedCallback() {
|
|
165
143
|
if (this._initialized) {
|
|
166
|
-
// A re-attach (
|
|
167
|
-
//
|
|
144
|
+
// A re-attach (present() on a previously-connected dialog) must not rebuild
|
|
145
|
+
// the subtree — the DOM already exists.
|
|
168
146
|
return;
|
|
169
147
|
}
|
|
170
148
|
this._initialized = true;
|
|
171
149
|
|
|
172
150
|
this.classList.add('adw-dialog');
|
|
173
151
|
|
|
174
|
-
//
|
|
152
|
+
// Snapshot before the subtree is replaced: everything the author placed inside
|
|
153
|
+
// becomes the dialog content.
|
|
175
154
|
const contentChildren = Array.from(this.childNodes);
|
|
176
155
|
|
|
177
|
-
// Full-cover scrim — a flex container that centres the box; clicking it
|
|
178
|
-
// (outside the box) attempts to dismiss the dialog.
|
|
179
156
|
this._scrimEl = document.createElement('div');
|
|
180
157
|
this._scrimEl.className = 'adw-dialog-scrim';
|
|
181
158
|
this._scrimEl.addEventListener('click', () => this._attemptClose());
|
|
182
159
|
|
|
183
|
-
//
|
|
184
|
-
//
|
|
160
|
+
// Focusable so the box receives keys even when the content has none, and clicks
|
|
161
|
+
// inside must not reach the scrim's dismiss handler.
|
|
185
162
|
this._boxEl = document.createElement('div');
|
|
186
163
|
this._boxEl.className = 'adw-dialog-box';
|
|
187
164
|
this._boxEl.setAttribute('role', 'dialog');
|
|
@@ -190,8 +167,6 @@ export class AdwDialog extends HTMLElement {
|
|
|
190
167
|
this._boxEl.addEventListener('click', (event) => event.stopPropagation());
|
|
191
168
|
this._boxEl.addEventListener('keydown', (event) => this._onBoxKeyDown(event));
|
|
192
169
|
|
|
193
|
-
// Optional flat header bar (title + trailing close button), strictly
|
|
194
|
-
// centred like an AdwHeaderBar.
|
|
195
170
|
this._headerEl = document.createElement('div');
|
|
196
171
|
this._headerEl.className = 'adw-dialog-header';
|
|
197
172
|
|
|
@@ -208,14 +183,12 @@ export class AdwDialog extends HTMLElement {
|
|
|
208
183
|
this._closeBtn.type = 'button';
|
|
209
184
|
this._closeBtn.className = 'adw-dialog-close';
|
|
210
185
|
this._closeBtn.setAttribute('aria-label', 'Close');
|
|
211
|
-
//
|
|
212
|
-
// glyph is drawn in CSS (same approach as <adw-preferences-dialog>).
|
|
186
|
+
// The "×" glyph is drawn in CSS (same approach as <adw-preferences-dialog>).
|
|
213
187
|
this._closeBtn.addEventListener('click', () => this._attemptClose());
|
|
214
188
|
trailing.appendChild(this._closeBtn);
|
|
215
189
|
|
|
216
190
|
this._headerEl.append(leading, this._titleEl, trailing);
|
|
217
191
|
|
|
218
|
-
// The content area — the author's markup.
|
|
219
192
|
this._contentEl = document.createElement('div');
|
|
220
193
|
this._contentEl.className = 'adw-dialog-content';
|
|
221
194
|
for (const child of contentChildren) this._contentEl.appendChild(child);
|
|
@@ -238,8 +211,6 @@ export class AdwDialog extends HTMLElement {
|
|
|
238
211
|
}
|
|
239
212
|
}
|
|
240
213
|
|
|
241
|
-
// ── internals ──────────────────────────────────────────────────────────
|
|
242
|
-
|
|
243
214
|
/** A dismissal was requested: honour `can-close`, else raise close-attempt. */
|
|
244
215
|
private _attemptClose(): void {
|
|
245
216
|
if (!this.open) return;
|
|
@@ -306,20 +277,18 @@ export class AdwDialog extends HTMLElement {
|
|
|
306
277
|
const title = this.title;
|
|
307
278
|
this._titleEl.textContent = title;
|
|
308
279
|
|
|
309
|
-
// The header shows when there is a title or the author forces it.
|
|
310
280
|
const hasHeader = title.length > 0 || this.hasAttribute('show-header');
|
|
311
281
|
this._headerEl.hidden = !hasHeader;
|
|
312
282
|
|
|
313
|
-
//
|
|
283
|
+
// The CSS reads both of these: the dataset picks floating vs bottom sheet, the
|
|
284
|
+
// custom properties carry the size constraints.
|
|
314
285
|
this.dataset.presentation = this.presentationMode;
|
|
315
286
|
|
|
316
|
-
// Size constraints as CSS custom properties consumed by the box.
|
|
317
287
|
this._boxEl.style.setProperty('--adw-dialog-content-width', `${this.contentWidth}px`);
|
|
318
288
|
const height = this.contentHeight;
|
|
319
289
|
if (height > 0) this._boxEl.style.setProperty('--adw-dialog-content-height', `${height}px`);
|
|
320
290
|
else this._boxEl.style.removeProperty('--adw-dialog-content-height');
|
|
321
291
|
|
|
322
|
-
// A locked dialog's close button has no effect; reflect that.
|
|
323
292
|
const locked = !this.canClose;
|
|
324
293
|
this._closeBtn.disabled = locked;
|
|
325
294
|
this._closeBtn.classList.toggle('locked', locked);
|