@gjsify/adwaita-web 0.51.1 → 0.52.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 +255 -27
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/blueprint-layout.spec.d.ts +1 -0
- package/lib/types/blueprint-markup.spec.d.ts +4 -0
- package/lib/types/blueprint-tree.spec.d.ts +1 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +31 -13
- package/lib/types/elements/adw-carousel.d.ts +5 -0
- package/lib/types/elements/adw-header-bar.d.ts +15 -0
- package/lib/types/elements/adw-inline-view-switcher.d.ts +1 -0
- package/lib/types/elements/adw-navigation-view.d.ts +1 -0
- package/lib/types/elements/adw-sidebar.d.ts +10 -0
- package/lib/types/elements/adw-tab-view.d.ts +1 -0
- package/lib/types/elements/adw-toggle-group.d.ts +11 -0
- package/lib/types/elements/gtk-action-bar.d.ts +19 -0
- package/lib/types/elements/gtk-box.d.ts +17 -0
- package/lib/types/elements/gtk-button.d.ts +2 -0
- package/lib/types/elements/gtk-label.d.ts +122 -0
- package/lib/types/gio-menu.spec.d.ts +1 -0
- package/lib/types/gtk-box.spec.d.ts +1 -0
- package/lib/types/gtk-label.spec.d.ts +1 -0
- package/lib/types/gtk-value-doors.spec.d.ts +1 -0
- package/lib/types/index.d.ts +4 -1
- package/lib/types/namespace/gio.d.ts +1 -0
- package/lib/types/namespace/gtk.d.ts +5 -0
- package/lib/types/shared-tree-builder.d.ts +64 -0
- package/lib/types/source-view/asm6502.d.ts +1 -20
- package/lib/types/source-view/index.d.ts +3 -2
- package/lib/types/source-view/theme.d.ts +1 -1
- package/lib/types/string-list-slot.d.ts +4 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/lib/types/tags.spec.d.ts +1 -0
- package/lib/types/value-lists.spec.d.ts +1 -0
- package/package.json +8 -6
- package/scss/_action_bar.scss +51 -0
- package/scss/_banner.scss +8 -1
- package/scss/_bottom_sheet.scss +57 -0
- package/scss/_box.scss +23 -0
- package/scss/_button.scss +29 -7
- package/scss/_button_content.scss +6 -1
- package/scss/_button_row.scss +5 -2
- package/scss/_carousel.scss +6 -0
- package/scss/_labels.scss +126 -12
- package/scss/_navigation_split_view.scss +18 -0
- package/scss/_overlay_split_view.scss +5 -0
- package/scss/_reset.scss +29 -4
- package/scss/_split_button.scss +5 -2
- package/scss/_status_page.scss +31 -17
- package/scss/_theme.scss +11 -3
- package/scss/_toolbar_view.scss +10 -0
- package/scss/_variables.scss +1 -0
- package/scss/_widget.scss +55 -0
- package/scss/_window_title.scss +9 -0
- package/scss/adwaita-skin.scss +8 -2
- package/src/adw-banner.spec.ts +16 -0
- package/src/adw-button-row.spec.ts +8 -0
- package/src/adw-header-bar.spec.ts +17 -0
- package/src/blueprint-layout.spec.ts +223 -0
- package/src/blueprint-markup.spec.ts +155 -0
- package/src/blueprint-tree.spec.ts +174 -0
- package/src/bottom-sheet.spec.ts +166 -1
- package/src/chrome.spec.ts +5 -2
- package/src/elements/adw-bottom-sheet.ts +166 -59
- package/src/elements/adw-carousel.ts +10 -5
- package/src/elements/adw-clamp.ts +12 -0
- package/src/elements/adw-combo-row.ts +13 -4
- package/src/elements/adw-header-bar.ts +46 -3
- package/src/elements/adw-inline-view-switcher.ts +11 -0
- package/src/elements/adw-navigation-view.ts +10 -0
- package/src/elements/adw-sidebar.ts +22 -0
- package/src/elements/adw-spin-row.ts +44 -5
- package/src/elements/adw-status-page.ts +12 -6
- package/src/elements/adw-tab-view.ts +20 -3
- package/src/elements/adw-toggle-group.ts +45 -4
- package/src/elements/adw-toolbar-view.ts +9 -0
- package/src/elements/gtk-action-bar.ts +90 -0
- package/src/elements/gtk-box.ts +87 -0
- package/src/elements/gtk-button.ts +60 -13
- package/src/elements/gtk-drop-down.ts +10 -1
- package/src/elements/gtk-label.ts +415 -0
- package/src/gio-menu.spec.ts +125 -0
- package/src/globals.d.ts +7 -0
- package/src/gtk-box.spec.ts +168 -0
- package/src/gtk-button.spec.ts +123 -1
- package/src/gtk-label.spec.ts +595 -0
- package/src/gtk-value-doors.spec.ts +104 -0
- package/src/index.ts +14 -1
- package/src/keyboard-operable.spec.ts +2 -1
- package/src/namespace/gio.ts +19 -0
- package/src/namespace/gtk.ts +14 -0
- package/src/shared-tree-builder.ts +268 -0
- package/src/shared-trees.spec.ts +296 -32
- package/src/source-view/adw-source-view.ts +3 -3
- package/src/source-view/asm6502.ts +11 -191
- package/src/source-view/index.ts +17 -11
- package/src/source-view/theme.ts +16 -16
- package/src/split-button.spec.ts +7 -0
- package/src/string-list-slot.ts +21 -0
- package/src/styles.generated.ts +1 -1
- package/src/tags.spec.ts +51 -0
- package/src/test.browser.mts +18 -0
- package/src/value-lists.spec.ts +93 -0
- package/lib/types/source-view/hex.d.ts +0 -21
- package/src/source-view/hex.ts +0 -37
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
// the NativeScript row and the React Native widget take. The same markup door
|
|
17
17
|
// `<adw-combo-row model=…>` opens for its list, on the same reasoning.
|
|
18
18
|
//
|
|
19
|
+
// A `.blp` writes the range as an OBJECT, `adjustment: Adjustment { upper: 20; }`, and the
|
|
20
|
+
// shared-tree builder hands that over as a `<gtk-adjustment slot="adjustment" upper="20">`
|
|
21
|
+
// child. The slot consumes it: the child is data, read for the six `Gtk.Adjustment`
|
|
22
|
+
// properties under their GObject spelling, then dropped — GtkBuilder leaves no adjustment
|
|
23
|
+
// in the widget tree either. It REPLACES the range rather than merging into it, because
|
|
24
|
+
// `adjustment:` sets the property to a whole new object: an unwritten field takes the
|
|
25
|
+
// adjustment's own default, not what the row held (`ADJUSTMENT_AUTHORED_VECTORS`).
|
|
26
|
+
//
|
|
19
27
|
// Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
|
|
20
28
|
// Copyright (c) 2025 csm. MIT License.
|
|
21
29
|
// Modifications: Reimplemented as Web Component for @gjsify/adwaita-web;
|
|
@@ -24,6 +32,29 @@
|
|
|
24
32
|
|
|
25
33
|
import { SpinState, deriveRowLabels, normalizeAdjustment, parseAdjustment } from '@gjsify/adwaita-core';
|
|
26
34
|
import type { AdwAdjustment, AdwAdjustmentInput } from '@gjsify/adwaita-core';
|
|
35
|
+
import { attributeOf } from '@gjsify/adwaita-core/tags';
|
|
36
|
+
|
|
37
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
38
|
+
|
|
39
|
+
/** `Gtk.Adjustment`'s six properties, as `AdwAdjustmentInput` names them. */
|
|
40
|
+
const ADJUSTMENT_FIELDS = ['value', 'lower', 'upper', 'stepIncrement', 'pageIncrement', 'pageSize'] as const;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The range a `<gtk-adjustment>` child authors: every field written as a number, under its
|
|
44
|
+
* GObject attribute spelling (`step-increment`). An absent or non-numeric field is not
|
|
45
|
+
* authored, so it cannot overwrite what the row already holds — the same contract
|
|
46
|
+
* `parseAdjustment` gives the JSON attribute.
|
|
47
|
+
*/
|
|
48
|
+
function authoredAdjustment(node: Node): AdwAdjustmentInput {
|
|
49
|
+
const input: Record<string, number> = {};
|
|
50
|
+
if (!(node instanceof Element)) return input;
|
|
51
|
+
for (const field of ADJUSTMENT_FIELDS) {
|
|
52
|
+
const raw = node.getAttribute(attributeOf(field));
|
|
53
|
+
const value = raw === null ? Number.NaN : Number.parseFloat(raw);
|
|
54
|
+
if (Number.isFinite(value)) input[field] = value;
|
|
55
|
+
}
|
|
56
|
+
return input;
|
|
57
|
+
}
|
|
27
58
|
|
|
28
59
|
export class AdwSpinRow extends HTMLElement {
|
|
29
60
|
private _input!: HTMLInputElement;
|
|
@@ -73,10 +104,6 @@ export class AdwSpinRow extends HTMLElement {
|
|
|
73
104
|
// rather than against whatever a partly-seeded state held.
|
|
74
105
|
const range = normalizeAdjustment(parseAdjustment(this.getAttribute('adjustment')));
|
|
75
106
|
this._state.configure(range);
|
|
76
|
-
const authoredValue = this.getAttribute('value');
|
|
77
|
-
this._state.setValue(
|
|
78
|
-
this._roundToStep(authoredValue === null ? range.value : Number.parseFloat(authoredValue)),
|
|
79
|
-
);
|
|
80
107
|
|
|
81
108
|
const text = document.createElement('div');
|
|
82
109
|
text.className = 'adw-row-text';
|
|
@@ -113,7 +140,19 @@ export class AdwSpinRow extends HTMLElement {
|
|
|
113
140
|
incBtn.addEventListener('click', () => this._adjust(this._state.adjustment.stepIncrement));
|
|
114
141
|
|
|
115
142
|
control.append(decBtn, input, incBtn);
|
|
116
|
-
|
|
143
|
+
// The install reads an authored `<gtk-adjustment>` into the range; the authored
|
|
144
|
+
// `value` is applied after it, against the range it was authored beside.
|
|
145
|
+
bindSlottedChildren(this, [
|
|
146
|
+
{
|
|
147
|
+
name: 'adjustment',
|
|
148
|
+
consume: (node) => this._state.configure(normalizeAdjustment(authoredAdjustment(node))),
|
|
149
|
+
},
|
|
150
|
+
]).install(text, control);
|
|
151
|
+
const authoredValue = this.getAttribute('value');
|
|
152
|
+
this._state.setValue(
|
|
153
|
+
this._roundToStep(authoredValue === null ? this._state.value : Number.parseFloat(authoredValue)),
|
|
154
|
+
);
|
|
155
|
+
input.value = this._formatValue(this._state.value);
|
|
117
156
|
this._input = input;
|
|
118
157
|
|
|
119
158
|
// Every core change (a set, or a re-clamp after a bound moved) refreshes
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// <adw-status-page> — A centered empty/placeholder state: a large symbolic
|
|
2
2
|
// icon, a title, a description and an optional action (slotted child).
|
|
3
|
-
// Attributes: icon (symbolic name, with or without -symbolic)
|
|
3
|
+
// Attributes: icon (symbolic name, with or without -symbolic) or icon-name, the GObject
|
|
4
|
+
// spelling a projected `.blp` writes; title, description.
|
|
4
5
|
// Reference: refs/adwaita-web/adwaita-web/scss/_status_page.scss
|
|
5
6
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_misc.scss (AdwStatusPage)
|
|
6
7
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
@@ -21,7 +22,7 @@ export class AdwStatusPage extends HTMLElement {
|
|
|
21
22
|
private _initialized = false;
|
|
22
23
|
|
|
23
24
|
static get observedAttributes() {
|
|
24
|
-
return ['icon', 'title', 'description'];
|
|
25
|
+
return ['icon', 'icon-name', 'title', 'description'];
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
connectedCallback() {
|
|
@@ -41,8 +42,12 @@ export class AdwStatusPage extends HTMLElement {
|
|
|
41
42
|
|
|
42
43
|
// `Adw.StatusPage:child` is a property, so the action a status page offers can be
|
|
43
44
|
// set at any point — a button appended after connect has to land in the child box,
|
|
44
|
-
// not beside it. `src/slotted-children.ts` has the incident.
|
|
45
|
-
|
|
45
|
+
// not beside it. `src/slotted-children.ts` has the incident. NAMED beside the
|
|
46
|
+
// default for the same reason `adw-toolbar-view.ts` names `content` beside ITS
|
|
47
|
+
// default: an authored `child: …` carries `slot: 'child'`
|
|
48
|
+
// (`shared-tree-builder.ts`'s `refuseUnknownSlots`), a name the bare default claim
|
|
49
|
+
// does not answer to, so the placement was refused sight unseen until this line.
|
|
50
|
+
bindSlottedChildren(this, [{ name: 'child', into: this._childEl }, { into: this._childEl }]).install(
|
|
46
51
|
this._iconEl,
|
|
47
52
|
this._titleEl,
|
|
48
53
|
this._descEl,
|
|
@@ -59,15 +64,16 @@ export class AdwStatusPage extends HTMLElement {
|
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
private _render() {
|
|
67
|
+
const icon = this.getAttribute('icon') ?? this.getAttribute('icon-name');
|
|
62
68
|
// The element swaps the mask class and keeps the size class.
|
|
63
|
-
this._iconEl.iconName =
|
|
69
|
+
this._iconEl.iconName = icon;
|
|
64
70
|
// `has_image`: `paintable || (icon_name && icon_name[0])` — a name that was GIVEN
|
|
65
71
|
// shows the image whether or not the theme can resolve it, and GTK draws
|
|
66
72
|
// `image-missing` in it. Reading `resolvedIconName` instead hid the slot for every
|
|
67
73
|
// name that is not one CSS token, a branch the C does not have.
|
|
68
74
|
// Reference: refs/libadwaita/src/adw-status-page.c:88 (has_image), bound from
|
|
69
75
|
// `<binding name="visible">` in adw-status-page.ui:27-31.
|
|
70
|
-
this._iconEl.hidden = !stringIsNotEmpty(
|
|
76
|
+
this._iconEl.hidden = !stringIsNotEmpty(icon);
|
|
71
77
|
|
|
72
78
|
// `string_is_not_empty` from the core, not a local `.length === 0`: the same
|
|
73
79
|
// closure the NativeScript port binds its labels to, and the reason a title of
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
import { TabViewState, tabCloseVisible, tabIconState, tabTooltip, tabsRevealed } from '@gjsify/adwaita-core';
|
|
49
49
|
import type { AdwTabPageSpec, AdwTabPageState, TabViewPagesChange, TabViewSelectionChange } from '@gjsify/adwaita-core';
|
|
50
50
|
|
|
51
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
51
52
|
import { type GtkImage, createGtkImage } from './gtk-image.js';
|
|
52
53
|
|
|
53
54
|
export type AdwTabViewPage = AdwTabPageState<HTMLElement>;
|
|
@@ -71,6 +72,13 @@ export class AdwTabPage extends HTMLElement {
|
|
|
71
72
|
return PAGE_ATTRIBUTES;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
connectedCallback(): void {
|
|
76
|
+
// `AdwTabPage:child` is a PROPERTY, so a `.blp`'s `child: …` authors `slot="child"`.
|
|
77
|
+
// The page's children already ARE its panel content, so this only enrols the
|
|
78
|
+
// name, for the reason `adw-clamp.ts` gives for not calling `.install()`.
|
|
79
|
+
bindSlottedChildren(this, [{ name: 'child', into: this }]);
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
attributeChangedCallback(name: string, _old: string | null, value: string | null) {
|
|
75
83
|
// `closest` still finds the view after adoption: the element is moved
|
|
76
84
|
// into the view's page container, not replaced by a wrapper.
|
|
@@ -194,10 +202,19 @@ export class AdwTabView extends HTMLElement {
|
|
|
194
202
|
const declaredSelection = this.getAttribute('selected-page');
|
|
195
203
|
|
|
196
204
|
// Snapshot the declared pages, then take over the subtree. The elements
|
|
197
|
-
// themselves become the panels, so they must survive replaceChildren.
|
|
198
|
-
|
|
205
|
+
// themselves become the panels, so they must survive replaceChildren. A child
|
|
206
|
+
// that is not an `<adw-tab-page>` is a page too, untitled and around itself, as
|
|
207
|
+
// `adw_tab_view_append` makes one of a bare widget (adw-tab-view.c:2884) — it was
|
|
208
|
+
// dropped by the `replaceChildren` before.
|
|
209
|
+
const declared = Array.from(this.children) as HTMLElement[];
|
|
199
210
|
this.replaceChildren(this._barEl, this._pagesEl);
|
|
200
|
-
for (const
|
|
211
|
+
for (const el of declared) {
|
|
212
|
+
// By NAME, not `instanceof`: this view is defined before `<adw-tab-page>`, so
|
|
213
|
+
// markup parsed before the module loaded reaches here with its pages not yet
|
|
214
|
+
// upgraded — and `_adoptDeclaredPage` reads only attributes, which they have.
|
|
215
|
+
if (el.localName === 'adw-tab-page') this._adoptDeclaredPage(el as AdwTabPage);
|
|
216
|
+
else this.appendPage({ id: this._nextId(), content: el });
|
|
217
|
+
}
|
|
201
218
|
|
|
202
219
|
if (declaredSelection !== null) this._applySelectedAttribute(declaredSelection);
|
|
203
220
|
this._applyBarVisibility();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// <adw-toggle-group> — the web counterpart of Adw.ToggleGroup: a linked set of toggle
|
|
2
2
|
// buttons where exactly one is active. Toggles are `<adw-toggle>` children carrying
|
|
3
|
-
// `label` and/or `icon-name
|
|
3
|
+
// `label` and/or `icon-name` and an optional `name`, which the group's `active-name`
|
|
4
|
+
// selects by (read at connect, like the rest). The `flat` / `round` attributes mirror the upstream
|
|
4
5
|
// `.flat` / `.round` style classes. `notify::active` (CustomEvent, bubbles, detail
|
|
5
6
|
// `{ active }`) mirrors the `active` GObject property.
|
|
6
7
|
//
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
// selection state machine composed from @gjsify/adwaita-core, the icon node
|
|
55
56
|
// from <gtk-image>.
|
|
56
57
|
|
|
57
|
-
import { ToggleGroupState } from '@gjsify/adwaita-core';
|
|
58
|
+
import { ToggleGroupState, keptToggles, toggleIndexOfName } from '@gjsify/adwaita-core';
|
|
58
59
|
|
|
59
60
|
import { createGtkImage } from './gtk-image.js';
|
|
60
61
|
import { attachRovingFocus } from './roving-focus.js';
|
|
@@ -74,9 +75,11 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
74
75
|
private _initialized = false;
|
|
75
76
|
/** `aria-checked` under `radio`, `aria-selected` under `tab` — decided at connect. */
|
|
76
77
|
private _stateAttr: 'aria-checked' | 'aria-selected' = 'aria-checked';
|
|
78
|
+
/** Each toggle's `name`, in order — what `active-name` resolves against. */
|
|
79
|
+
private _names: (string | null)[] = [];
|
|
77
80
|
|
|
78
81
|
static get observedAttributes() {
|
|
79
|
-
return ['active', 'flat', 'round'];
|
|
82
|
+
return ['active', 'active-name', 'flat', 'round'];
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
/** Zero-based index of the active toggle. */
|
|
@@ -88,13 +91,31 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
88
91
|
this.setAttribute('active', String(value));
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
/** `Adw.ToggleGroup:active-name` — the `name` of the active toggle, or `null`. */
|
|
95
|
+
get activeName(): string | null {
|
|
96
|
+
return this._names[this._state.selected] ?? null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
set activeName(value: string | null) {
|
|
100
|
+
if (value === null) this.removeAttribute('active-name');
|
|
101
|
+
else this.setAttribute('active-name', value);
|
|
102
|
+
}
|
|
103
|
+
|
|
91
104
|
connectedCallback() {
|
|
92
105
|
if (this._initialized) return;
|
|
93
106
|
this._initialized = true;
|
|
94
107
|
|
|
95
108
|
// Snapshot the declared <adw-toggle> children before we take over the
|
|
96
109
|
// subtree — their label / icon-name become the rendered buttons.
|
|
97
|
-
|
|
110
|
+
// A second toggle claiming a name is refused whole, as `add_toggle` refuses it
|
|
111
|
+
// (adw-toggle-group.c:849) — the core's rule, not this element's.
|
|
112
|
+
const authored = Array.from(this.querySelectorAll('adw-toggle')) as AdwToggle[];
|
|
113
|
+
const toggles = keptToggles(authored.map((el) => ({ el, name: el.getAttribute('name') }))).map(
|
|
114
|
+
(toggle) => toggle.el,
|
|
115
|
+
);
|
|
116
|
+
if (toggles.length < authored.length) {
|
|
117
|
+
console.warn('[adw-toggle-group] a toggle name already exists; the later toggle is not added');
|
|
118
|
+
}
|
|
98
119
|
|
|
99
120
|
// `gtk_widget_class_set_accessible_role` sets a CLASS DEFAULT that an instance's
|
|
100
121
|
// construct-time `accessible-role` overrides, and `add_toggle` then reads
|
|
@@ -159,8 +180,12 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
159
180
|
this._state.setLabels(
|
|
160
181
|
toggles.map((toggle) => toggle.getAttribute('label') || toggle.getAttribute('icon-name') || ''),
|
|
161
182
|
);
|
|
183
|
+
this._names = toggles.map((toggle) => toggle.getAttribute('name'));
|
|
162
184
|
this._state.subscribe(() => this._render());
|
|
163
185
|
this._state.setSelected(this._readActiveAttr());
|
|
186
|
+
// After `active`, and after the toggles are in, as `parser_finished` applies it
|
|
187
|
+
// (adw-toggle-group.c:1247): a group whose markup names both ends on the name.
|
|
188
|
+
this._applyActiveName();
|
|
164
189
|
this._render();
|
|
165
190
|
|
|
166
191
|
// No `disabled`/`hidden` filter, because no `<adw-toggle>` attribute can produce
|
|
@@ -185,10 +210,26 @@ export class AdwToggleGroup extends HTMLElement {
|
|
|
185
210
|
this._state.setSelected(this._readActiveAttr());
|
|
186
211
|
return;
|
|
187
212
|
}
|
|
213
|
+
if (name === 'active-name') {
|
|
214
|
+
this._applyActiveName();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
188
217
|
// flat / round are styling-only.
|
|
189
218
|
this._render();
|
|
190
219
|
}
|
|
191
220
|
|
|
221
|
+
/**
|
|
222
|
+
* `adw_toggle_group_set_active_name`: the toggle carrying the name becomes active, and
|
|
223
|
+
* a name nothing carries leaves the selection where it was — the C's `g_critical` and
|
|
224
|
+
* return (:2001). Which toggle a name means is the core's answer, not this element's.
|
|
225
|
+
*/
|
|
226
|
+
private _applyActiveName(): void {
|
|
227
|
+
const name = this.getAttribute('active-name');
|
|
228
|
+
if (name === null) return;
|
|
229
|
+
const index = toggleIndexOfName(this._names, name);
|
|
230
|
+
if (index !== -1) this._state.setSelected(index);
|
|
231
|
+
}
|
|
232
|
+
|
|
192
233
|
private _readActiveAttr(): number {
|
|
193
234
|
const raw = Number.parseInt(this.getAttribute('active') ?? '0', 10);
|
|
194
235
|
if (Number.isNaN(raw)) return 0;
|
|
@@ -85,9 +85,18 @@ export class AdwToolbarView extends HTMLElement {
|
|
|
85
85
|
// reach the same box whenever it is written — including after connect, which a
|
|
86
86
|
// snapshot here cannot see. The unnamed slot is the content, the
|
|
87
87
|
// Adw.ToolbarView buildable default. `src/slotted-children.ts` has the incident.
|
|
88
|
+
//
|
|
89
|
+
// `content` IS NAMED BESIDE THAT DEFAULT, and it is the same destination rather
|
|
90
|
+
// than a fourth one: `Adw.ToolbarView:content` is the property a source that
|
|
91
|
+
// spells the placement writes it at, and GTK reaches this box through either —
|
|
92
|
+
// the buildable default for a bare child, `adw_toolbar_view_set_content` for a
|
|
93
|
+
// named one. A tree authored at the property position (a Blueprint
|
|
94
|
+
// `content: …`) carried a name this element had no destination for, so the
|
|
95
|
+
// child stayed a stray sibling of all three boxes.
|
|
88
96
|
bindSlottedChildren(this, [
|
|
89
97
|
{ name: 'top', into: this._topEl },
|
|
90
98
|
{ name: 'bottom', into: this._bottomEl },
|
|
99
|
+
{ name: 'content', into: this._contentEl },
|
|
91
100
|
{ into: this._contentEl },
|
|
92
101
|
]).install(this._topEl, this._contentEl, this._bottomEl);
|
|
93
102
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// <gtk-action-bar> — GTK's bottom bar: widgets packed from the start, from the end, and
|
|
2
|
+
// one in the centre.
|
|
3
|
+
//
|
|
4
|
+
// NAMED FOR THE LIBRARY THAT OWNS THE GTYPE (ADR 0034 clause 1): `Gtk.ActionBar` is GTK's,
|
|
5
|
+
// and libadwaita only styles it (`actionbar > revealer > box` in `_toolbars.scss`).
|
|
6
|
+
//
|
|
7
|
+
// THE PLACEMENT IS GtkBuildable's (gtkactionbar.c:220): `[start]` packs from the start,
|
|
8
|
+
// `[end]` packs from the end, `[center]` is the centre widget, and a child with no type
|
|
9
|
+
// packs from the start. There is no `center-widget` slot: GTK has `set_center_widget` but
|
|
10
|
+
// no property of that name, and `Gtk.Builder` refuses a `.blp` that writes one. Packing
|
|
11
|
+
// from the end is `gtk_box_insert_child_after (end_box, child, NULL)` — a PREPEND — so the
|
|
12
|
+
// first `[end]` child sits nearest the edge and each later one lands in front of it,
|
|
13
|
+
// exactly as `adw_header_bar_pack_end` does.
|
|
14
|
+
//
|
|
15
|
+
// `revealed` defaults to TRUE, as `GtkActionBar:revealed` does: only `revealed="false"`
|
|
16
|
+
// hides the bar.
|
|
17
|
+
//
|
|
18
|
+
// Reference: refs/gtk/gtk/gtkactionbar.c (the buildable child types, pack_start/pack_end)
|
|
19
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_toolbars.scss (GtkActionBar)
|
|
20
|
+
// Copyright (c) The GTK Team. LGPLv2.1+.
|
|
21
|
+
|
|
22
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
23
|
+
|
|
24
|
+
export class GtkActionBar extends HTMLElement {
|
|
25
|
+
private _initialized = false;
|
|
26
|
+
private _startEl: HTMLDivElement | null = null;
|
|
27
|
+
private _centerEl: HTMLDivElement | null = null;
|
|
28
|
+
private _endEl: HTMLDivElement | null = null;
|
|
29
|
+
|
|
30
|
+
static get observedAttributes() {
|
|
31
|
+
return ['revealed'];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `GtkActionBar:revealed` — whether the bar shows its contents. */
|
|
35
|
+
get revealed(): boolean {
|
|
36
|
+
return this.getAttribute('revealed') !== 'false';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set revealed(value: boolean) {
|
|
40
|
+
this.setAttribute('revealed', value ? 'true' : 'false');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The start box — what `pack_start` appends to. */
|
|
44
|
+
get startSection(): HTMLDivElement | null {
|
|
45
|
+
return this._startEl;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The centre — what `gtk_action_bar_set_center_widget` sets. */
|
|
49
|
+
get centerSection(): HTMLDivElement | null {
|
|
50
|
+
return this._centerEl;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The end box — what `pack_end` prepends to. */
|
|
54
|
+
get endSection(): HTMLDivElement | null {
|
|
55
|
+
return this._endEl;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
connectedCallback() {
|
|
59
|
+
if (this._initialized) return;
|
|
60
|
+
this._initialized = true;
|
|
61
|
+
|
|
62
|
+
this._startEl = document.createElement('div');
|
|
63
|
+
this._startEl.className = 'adw-action-bar-start';
|
|
64
|
+
this._centerEl = document.createElement('div');
|
|
65
|
+
this._centerEl.className = 'adw-action-bar-center';
|
|
66
|
+
this._endEl = document.createElement('div');
|
|
67
|
+
this._endEl.className = 'adw-action-bar-end';
|
|
68
|
+
const endEl = this._endEl;
|
|
69
|
+
|
|
70
|
+
bindSlottedChildren(this, [
|
|
71
|
+
{ name: 'start', into: this._startEl },
|
|
72
|
+
{ name: 'center', into: this._centerEl },
|
|
73
|
+
{ name: 'end', consume: (node) => endEl.prepend(node) },
|
|
74
|
+
// An untyped child packs from the start (gtkactionbar.c:233).
|
|
75
|
+
{ into: this._startEl },
|
|
76
|
+
]).install(this._startEl, this._centerEl, this._endEl);
|
|
77
|
+
|
|
78
|
+
this._renderRevealed();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
attributeChangedCallback() {
|
|
82
|
+
if (this._initialized) this._renderRevealed();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private _renderRevealed(): void {
|
|
86
|
+
this.hidden = !this.revealed;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
customElements.define('gtk-action-bar', GtkActionBar);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// <gtk-box> — GTK's one-axis container: children in a row or a column, a fixed gap
|
|
2
|
+
// between them.
|
|
3
|
+
//
|
|
4
|
+
// NAMED FOR THE LIBRARY THAT OWNS THE GTYPE (ADR 0034 clause 1): libadwaita ships no box,
|
|
5
|
+
// it styles what a caller puts in one. Without this element a Blueprint layout mounted
|
|
6
|
+
// through `mountSharedTree` realised every `Gtk.Box` as an unknown inline element, so a
|
|
7
|
+
// vertical stack of preference groups laid out as one run of inline boxes.
|
|
8
|
+
//
|
|
9
|
+
// NO CORE STATE MACHINE (ADR 0004, trivial behaviour): the stacking is flexbox's. What is
|
|
10
|
+
// shared is how an authored value becomes the property — `normalizeBoxSpacing` and
|
|
11
|
+
// `normalizeBoxOrientation` in `@gjsify/adwaita-core`, which the NativeScript `GtkBox`
|
|
12
|
+
// reads too. The gap is the flex `gap`, which is GTK's spacing exactly: between children
|
|
13
|
+
// only, never at the box's own edges.
|
|
14
|
+
//
|
|
15
|
+
// `homogeneous` gives every child an equal share along the axis (`flex: 1 1 0`, in
|
|
16
|
+
// `_box.scss`). A child's minimum still holds, as in `gtk_box_layout`'s homogeneous
|
|
17
|
+
// allocation, because flex never shrinks an item below its min-content size.
|
|
18
|
+
//
|
|
19
|
+
// Children are not moved or wrapped: the element IS the flex container, so a child in the
|
|
20
|
+
// light DOM is a child of the layout, which is what `gtk_box_append` means.
|
|
21
|
+
//
|
|
22
|
+
// Reference: refs/gtk/gtk/gtkbox.c (the three properties, the `box` CSS name)
|
|
23
|
+
// Copyright (c) The GTK Team. LGPLv2.1+.
|
|
24
|
+
|
|
25
|
+
import { normalizeBoxOrientation, normalizeBoxSpacing, type BoxOrientation } from '@gjsify/adwaita-core';
|
|
26
|
+
|
|
27
|
+
/** The attributes that carry a property — also the `notify::` roster. */
|
|
28
|
+
const PROPERTY_ATTRIBUTES = ['orientation', 'spacing', 'homogeneous'] as const;
|
|
29
|
+
|
|
30
|
+
export class GtkBox extends HTMLElement {
|
|
31
|
+
static get observedAttributes() {
|
|
32
|
+
return [...PROPERTY_ATTRIBUTES];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** `GtkOrientable:orientation` — `horizontal` (the default) or `vertical`. */
|
|
36
|
+
get orientation(): BoxOrientation {
|
|
37
|
+
return normalizeBoxOrientation(this.getAttribute('orientation'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
set orientation(value: BoxOrientation) {
|
|
41
|
+
this.setAttribute('orientation', value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `Gtk.Box:spacing` — the gap between children, in px. Defaults to 0. */
|
|
45
|
+
get spacing(): number {
|
|
46
|
+
return normalizeBoxSpacing(this.getAttribute('spacing'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set spacing(value: number) {
|
|
50
|
+
this.setAttribute('spacing', String(value));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** `Gtk.Box:homogeneous` — whether every child gets the same size along the axis. */
|
|
54
|
+
get homogeneous(): boolean {
|
|
55
|
+
return this.hasAttribute('homogeneous');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set homogeneous(value: boolean) {
|
|
59
|
+
this.toggleAttribute('homogeneous', !!value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
connectedCallback() {
|
|
63
|
+
this._sync();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
attributeChangedCallback(name: string, old: string | null, value: string | null) {
|
|
67
|
+
this._sync();
|
|
68
|
+
const next = this._normalized(name, value);
|
|
69
|
+
if (next === this._normalized(name, old)) return;
|
|
70
|
+
this.dispatchEvent(new CustomEvent(`notify::${name}`, { bubbles: true, detail: { [name]: next } }));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private _normalized(name: string, raw: string | null): string | number | boolean {
|
|
74
|
+
if (name === 'orientation') return normalizeBoxOrientation(raw);
|
|
75
|
+
if (name === 'spacing') return normalizeBoxSpacing(raw);
|
|
76
|
+
return raw !== null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private _sync(): void {
|
|
80
|
+
// Inline, from the NORMALISED values, so an unknown nick or a negative spacing
|
|
81
|
+
// lays out as the GTK default rather than whatever the raw attribute says.
|
|
82
|
+
this.style.flexDirection = this.orientation === 'vertical' ? 'column' : 'row';
|
|
83
|
+
this.style.gap = `${this.spacing}px`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
customElements.define('gtk-box', GtkBox);
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
// <gtk-button> — Adwaita button.
|
|
2
|
-
// Attributes: icon (symbolic name, e.g. "go-previous" / "view-refresh"),
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// Attributes: icon (symbolic name, e.g. "go-previous" / "view-refresh"), or
|
|
3
|
+
// icon-name, the GObject spelling a projected `.blp` writes; label, tooltip or
|
|
4
|
+
// tooltip-text (the `.blp` spelling again),
|
|
5
|
+
// disabled, and the boolean variant flags flat / suggested / destructive /
|
|
6
|
+
// circular / pill. The GTK style classes on the host (`class="pill"`, which is
|
|
7
|
+
// what a `.blp`'s `styles ["pill"]` becomes) select the same classes.
|
|
5
8
|
// Renders an inner <button class="adw-button …">; `click` bubbles to the host,
|
|
6
|
-
// so `adwButton.addEventListener('click', …)` works.
|
|
9
|
+
// so `adwButton.addEventListener('click', …)` works. An element child — what a
|
|
10
|
+
// `.blp`'s `child: Adw.ButtonContent { … }` becomes — is `Gtk.Button:child` and
|
|
11
|
+
// replaces the icon and the label inside that inner button.
|
|
7
12
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_buttons.scss
|
|
8
13
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
9
14
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web; the
|
|
@@ -11,6 +16,7 @@
|
|
|
11
16
|
|
|
12
17
|
import { buttonStyleClasses } from '@gjsify/adwaita-core';
|
|
13
18
|
|
|
19
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
14
20
|
import { createGtkImage } from './gtk-image.js';
|
|
15
21
|
|
|
16
22
|
/** The boolean attributes that select a style class; the mapping lives in the core. */
|
|
@@ -20,9 +26,24 @@ export class GtkButton extends HTMLElement {
|
|
|
20
26
|
private _button!: HTMLButtonElement;
|
|
21
27
|
private _label = '';
|
|
22
28
|
private _initialized = false;
|
|
29
|
+
/** `Gtk.Button:child`, or `null` while the icon and the label fill the button. */
|
|
30
|
+
private _child: Element | null = null;
|
|
23
31
|
|
|
24
32
|
static get observedAttributes() {
|
|
25
|
-
return [
|
|
33
|
+
return [
|
|
34
|
+
'icon',
|
|
35
|
+
'icon-name',
|
|
36
|
+
'label',
|
|
37
|
+
'tooltip',
|
|
38
|
+
'tooltip-text',
|
|
39
|
+
'disabled',
|
|
40
|
+
'flat',
|
|
41
|
+
'suggested',
|
|
42
|
+
'destructive',
|
|
43
|
+
'circular',
|
|
44
|
+
'pill',
|
|
45
|
+
'class',
|
|
46
|
+
];
|
|
26
47
|
}
|
|
27
48
|
|
|
28
49
|
/** The inner native button (for focus/imperative access). */
|
|
@@ -33,10 +54,28 @@ export class GtkButton extends HTMLElement {
|
|
|
33
54
|
connectedCallback() {
|
|
34
55
|
if (this._initialized) return;
|
|
35
56
|
this._initialized = true;
|
|
36
|
-
// Capture any inline text as the label before we take over the subtree.
|
|
37
|
-
|
|
57
|
+
// Capture any inline text as the label before we take over the subtree. Only the
|
|
58
|
+
// host's own text: an element child is `child`, and its text is not a label.
|
|
59
|
+
const inlineText = Array.from(this.childNodes)
|
|
60
|
+
.filter((node) => node.nodeType === Node.TEXT_NODE)
|
|
61
|
+
.map((node) => node.textContent ?? '')
|
|
62
|
+
.join('');
|
|
63
|
+
this._label = (this.getAttribute('label') ?? inlineText).trim();
|
|
38
64
|
this._button = document.createElement('button');
|
|
39
|
-
|
|
65
|
+
// An authored `child: …` names the `child` slot, and a bare element child means the
|
|
66
|
+
// same thing, as GtkBuildable's untyped `<child>` does. Consumed rather than moved:
|
|
67
|
+
// `_render` rebuilds the inner button's content, so it is `_render` that places it.
|
|
68
|
+
// Stray text is claimed by nothing and leaves with the host's old children.
|
|
69
|
+
bindSlottedChildren(this, [
|
|
70
|
+
{
|
|
71
|
+
name: 'child',
|
|
72
|
+
claims: (node) => node instanceof Element,
|
|
73
|
+
consume: (node) => {
|
|
74
|
+
this._child = node as Element;
|
|
75
|
+
this._render();
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
]).install(this._button);
|
|
40
79
|
this._render();
|
|
41
80
|
}
|
|
42
81
|
|
|
@@ -50,17 +89,25 @@ export class GtkButton extends HTMLElement {
|
|
|
50
89
|
// The attribute → class mapping is `@gjsify/adwaita-core`'s, so this element
|
|
51
90
|
// and the NativeScript one cannot disagree about which classes exist —
|
|
52
91
|
// `circular` was in this table and missing from that one.
|
|
53
|
-
|
|
92
|
+
// The host's own classes go through the same table: a built `.blp` spells the
|
|
93
|
+
// style as GTK does, a class on the widget, and reading only the attributes
|
|
94
|
+
// rendered its pill and flat buttons as plain ones.
|
|
95
|
+
const styles = [...STYLE_ATTRIBUTES.filter((attr) => this.hasAttribute(attr)), ...this.classList];
|
|
96
|
+
btn.classList.add(...buttonStyleClasses(styles));
|
|
54
97
|
|
|
55
|
-
const
|
|
56
|
-
const
|
|
98
|
+
const child = this._child;
|
|
99
|
+
const icon = child ? null : (this.getAttribute('icon') ?? this.getAttribute('icon-name'));
|
|
100
|
+
const label = child ? '' : (this.getAttribute('label') ?? this._label).trim();
|
|
57
101
|
if (icon && !label) btn.classList.add('icon-only');
|
|
102
|
+
// GTK's `.text-button`: what `gtk_button_set_label` stamps on a label-only button.
|
|
103
|
+
if (label && !icon) btn.classList.add('text-button');
|
|
58
104
|
|
|
59
|
-
btn.replaceChildren();
|
|
105
|
+
if (child) btn.replaceChildren(child);
|
|
106
|
+
else btn.replaceChildren();
|
|
60
107
|
if (icon) btn.appendChild(createGtkImage(icon));
|
|
61
108
|
if (label) btn.appendChild(document.createTextNode(label));
|
|
62
109
|
|
|
63
|
-
const tooltip = this.getAttribute('tooltip');
|
|
110
|
+
const tooltip = this.getAttribute('tooltip') ?? this.getAttribute('tooltip-text');
|
|
64
111
|
btn.title = tooltip ?? '';
|
|
65
112
|
// An icon-only button has no text content, so screen readers would
|
|
66
113
|
// announce it as unlabeled. Give it an accessible name — prefer the
|
|
@@ -68,6 +68,9 @@ import type { GtkPopover } from './gtk-popover.js';
|
|
|
68
68
|
import { ComboState, normalizeComboOptions, parseListModel } from '@gjsify/adwaita-core';
|
|
69
69
|
import type { AdwComboOption, AdwListItemsChanged, AdwListModelInput } from '@gjsify/adwaita-core';
|
|
70
70
|
|
|
71
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
72
|
+
import { stringListSlot } from '../string-list-slot.js';
|
|
73
|
+
|
|
71
74
|
import { createGtkImage } from './gtk-image.js';
|
|
72
75
|
|
|
73
76
|
/**
|
|
@@ -176,6 +179,13 @@ export class GtkDropDown extends HTMLElement {
|
|
|
176
179
|
this._listEl.className = 'adw-drop-down-list';
|
|
177
180
|
this._popoverEl.appendChild(this._listEl);
|
|
178
181
|
|
|
182
|
+
// A `<gtk-string-list slot="model">` child is the list a `.blp` authored (ADR 0072),
|
|
183
|
+
// consumed first so the attribute seed below sees a model and leaves it alone.
|
|
184
|
+
bindSlottedChildren(this, [stringListSlot((model) => this._state.setModel(model))]).install(
|
|
185
|
+
this._buttonEl,
|
|
186
|
+
this._popoverEl,
|
|
187
|
+
);
|
|
188
|
+
|
|
179
189
|
// Seed options BEFORE anything subscribes, so building the initial DOM below is
|
|
180
190
|
// not driven by a change notification.
|
|
181
191
|
if (this._state.count === 0) this._state.setModel(parseListModel(this.getAttribute('model')));
|
|
@@ -186,7 +196,6 @@ export class GtkDropDown extends HTMLElement {
|
|
|
186
196
|
// assignment reaches the splice rather than a rebuild.
|
|
187
197
|
this._state.subscribeItems((change) => this._applyItemsChanged(change));
|
|
188
198
|
|
|
189
|
-
this.replaceChildren(this._buttonEl, this._popoverEl);
|
|
190
199
|
this._popoverEl.anchor = this._buttonEl;
|
|
191
200
|
this._popoverEl.subscribe((open) => this._onPopoverToggled(open));
|
|
192
201
|
// Type-ahead is the ONE key class the shared popover deliberately leaves to the
|