@gjsify/adwaita-web 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/adwaita-web.css +2742 -263
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/elements/adw-about-dialog.d.ts +77 -0
- package/lib/types/elements/adw-action-row.d.ts +15 -0
- package/lib/types/elements/adw-alert-dialog.d.ts +80 -0
- package/lib/types/elements/adw-avatar.d.ts +9 -0
- package/lib/types/elements/adw-banner.d.ts +9 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +35 -0
- package/lib/types/elements/adw-button-content.d.ts +10 -0
- package/lib/types/elements/adw-button-row.d.ts +11 -0
- package/lib/types/elements/adw-button.d.ts +11 -0
- package/lib/types/elements/adw-carousel.d.ts +64 -0
- package/lib/types/elements/adw-clamp.d.ts +6 -0
- package/lib/types/elements/adw-combo-row.d.ts +3 -0
- package/lib/types/elements/adw-entry-row.d.ts +10 -0
- package/lib/types/elements/adw-entry.d.ts +11 -0
- package/lib/types/elements/adw-expander-row.d.ts +26 -0
- package/lib/types/elements/adw-header-bar.d.ts +4 -0
- package/lib/types/elements/adw-inline-view-switcher.d.ts +30 -0
- package/lib/types/elements/adw-navigation-split-view.d.ts +15 -0
- package/lib/types/elements/adw-navigation-view.d.ts +66 -0
- package/lib/types/elements/adw-password-entry-row.d.ts +16 -0
- package/lib/types/elements/adw-preferences-dialog.d.ts +36 -0
- package/lib/types/elements/adw-preferences-group.d.ts +8 -0
- package/lib/types/elements/adw-sidebar.d.ts +29 -0
- package/lib/types/elements/adw-spin-row.d.ts +3 -0
- package/lib/types/elements/adw-spinner.d.ts +6 -0
- package/lib/types/elements/adw-split-button.d.ts +18 -0
- package/lib/types/elements/adw-status-page.d.ts +11 -0
- package/lib/types/elements/adw-switch-row.d.ts +3 -0
- package/lib/types/elements/adw-tab-view.d.ts +24 -0
- package/lib/types/elements/adw-toast-overlay.d.ts +19 -4
- package/lib/types/elements/adw-toggle-group.d.ts +19 -0
- package/lib/types/elements/adw-toolbar-view.d.ts +13 -0
- package/lib/types/elements/adw-view-switcher.d.ts +29 -0
- package/lib/types/elements/adw-window-title.d.ts +9 -0
- package/lib/types/elements/adw-wrap-box.d.ts +13 -0
- package/lib/types/index.d.ts +29 -0
- package/lib/types/styles.generated.d.ts +1 -0
- package/package.json +5 -5
- package/scss/_about_dialog.scss +257 -0
- package/scss/_action_row.scss +65 -0
- package/scss/_alert_dialog.scss +151 -0
- package/scss/_avatar.scss +31 -0
- package/scss/_banner.scss +46 -0
- package/scss/_bottom_sheet.scss +113 -0
- package/scss/_button.scss +168 -0
- package/scss/_button_content.scss +48 -0
- package/scss/_button_row.scss +100 -0
- package/scss/_carousel.scss +130 -0
- package/scss/_clamp.scss +11 -0
- package/scss/_entry.scss +49 -0
- package/scss/_entry_row.scss +54 -0
- package/scss/_expander_row.scss +150 -0
- package/scss/_headerbar.scss +1 -1
- package/scss/_inline_view_switcher.scss +174 -0
- package/scss/_navigation_split_view.scss +48 -0
- package/scss/_navigation_view.scss +80 -0
- package/scss/_overlay_split_view.scss +19 -0
- package/scss/_password_entry_row.scss +67 -0
- package/scss/_preferences.scss +26 -3
- package/scss/_preferences_dialog.scss +154 -0
- package/scss/_row.scss +31 -0
- package/scss/_sidebar.scss +213 -0
- package/scss/_spin_row.scss +0 -1
- package/scss/_spinner.scss +28 -0
- package/scss/_split_button.scss +222 -0
- package/scss/_status_page.scss +40 -0
- package/scss/_switch_row.scss +0 -1
- package/scss/_tab_view.scss +193 -0
- package/scss/_theme.scss +12 -0
- package/scss/_toast.scss +122 -12
- package/scss/_toggle_group.scss +124 -0
- package/scss/_toolbar_view.scss +31 -0
- package/scss/_variables.scss +19 -0
- package/scss/_view_switcher.scss +133 -0
- package/scss/_window_title.scss +33 -0
- package/scss/_wrap_box.scss +20 -0
- package/scss/adwaita-skin.scss +38 -0
- package/src/elements/adw-about-dialog.ts +629 -0
- package/src/elements/adw-action-row.ts +82 -0
- package/src/elements/adw-alert-dialog.ts +385 -0
- package/src/elements/adw-avatar.ts +108 -0
- package/src/elements/adw-banner.ts +57 -0
- package/src/elements/adw-bottom-sheet.ts +205 -0
- package/src/elements/adw-button-content.ts +69 -0
- package/src/elements/adw-button-row.ts +75 -0
- package/src/elements/adw-button.ts +81 -0
- package/src/elements/adw-carousel.ts +321 -0
- package/src/elements/adw-clamp.ts +28 -0
- package/src/elements/adw-combo-row.ts +32 -20
- package/src/elements/adw-entry-row.ts +69 -0
- package/src/elements/adw-entry.ts +61 -0
- package/src/elements/adw-expander-row.ts +167 -0
- package/src/elements/adw-header-bar.ts +21 -8
- package/src/elements/adw-inline-view-switcher.ts +222 -0
- package/src/elements/adw-navigation-split-view.ts +89 -0
- package/src/elements/adw-navigation-view.ts +344 -0
- package/src/elements/adw-password-entry-row.ts +121 -0
- package/src/elements/adw-preferences-dialog.ts +240 -0
- package/src/elements/adw-preferences-group.ts +79 -12
- package/src/elements/adw-sidebar.ts +238 -0
- package/src/elements/adw-spin-row.ts +28 -14
- package/src/elements/adw-spinner.ts +31 -0
- package/src/elements/adw-split-button.ts +206 -0
- package/src/elements/adw-status-page.ts +66 -0
- package/src/elements/adw-switch-row.ts +29 -23
- package/src/elements/adw-tab-view.ts +184 -0
- package/src/elements/adw-toast-overlay.ts +98 -19
- package/src/elements/adw-toggle-group.ts +126 -0
- package/src/elements/adw-toolbar-view.ts +60 -0
- package/src/elements/adw-view-switcher.ts +200 -0
- package/src/elements/adw-window-title.ts +48 -0
- package/src/elements/adw-wrap-box.ts +139 -0
- package/src/index.js +1 -1
- package/src/index.ts +46 -3
- package/src/styles.generated.js +2976 -0
- package/src/styles2.generated.js +2976 -0
- package/style.css.d.ts +17 -4
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// <adw-expander-row> — A boxed-list row that discloses nested rows when expanded,
|
|
2
|
+
// with an optional enable switch. Mirrors Adw.ExpanderRow.
|
|
3
|
+
// Attributes: title, subtitle, expanded (boolean), enable-expansion (boolean),
|
|
4
|
+
// show-enable-switch (boolean).
|
|
5
|
+
// Slots: slot="prefix" / slot="suffix" widgets sit in the header row beside the
|
|
6
|
+
// title (before the enable switch + disclosure chevron), like Adw.ExpanderRow's
|
|
7
|
+
// add_prefix / add_suffix; any other child <adw-*-row> is moved into the
|
|
8
|
+
// disclosed content listbox.
|
|
9
|
+
// Properties: expanded, enableExpansion (get/set, reflect to attributes).
|
|
10
|
+
// Events: notify::expanded (CustomEvent), notify::enable-expansion (CustomEvent)
|
|
11
|
+
// — mirror Adw.ExpanderRow's GObject signal names so the same code path drives
|
|
12
|
+
// both the web and GTK renderers.
|
|
13
|
+
// Reference: refs/adwaita-web/adwaita-web/scss/_expander_row.scss
|
|
14
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_expander-row.scss
|
|
15
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
16
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
17
|
+
|
|
18
|
+
export class AdwExpanderRow extends HTMLElement {
|
|
19
|
+
private _headerEl!: HTMLDivElement;
|
|
20
|
+
private _titleEl!: HTMLSpanElement;
|
|
21
|
+
private _subtitleEl!: HTMLSpanElement;
|
|
22
|
+
private _enableSwitch!: HTMLInputElement;
|
|
23
|
+
private _enableLabel!: HTMLLabelElement;
|
|
24
|
+
private _chevronEl!: HTMLSpanElement;
|
|
25
|
+
private _contentEl!: HTMLDivElement;
|
|
26
|
+
private _prefixEl!: HTMLDivElement;
|
|
27
|
+
private _suffixEl!: HTMLDivElement;
|
|
28
|
+
private _initialized = false;
|
|
29
|
+
|
|
30
|
+
static get observedAttributes() {
|
|
31
|
+
return ['title', 'subtitle', 'expanded', 'enable-expansion', 'show-enable-switch'];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get expanded(): boolean {
|
|
35
|
+
return this.hasAttribute('expanded');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set expanded(value: boolean) {
|
|
39
|
+
if (value) this.setAttribute('expanded', '');
|
|
40
|
+
else this.removeAttribute('expanded');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get enableExpansion(): boolean {
|
|
44
|
+
return !this.hasAttribute('enable-expansion') || this.getAttribute('enable-expansion') !== 'false';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
set enableExpansion(value: boolean) {
|
|
48
|
+
this.setAttribute('enable-expansion', value ? 'true' : 'false');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The disclosed content section — append nested rows here imperatively. */
|
|
52
|
+
get contentSection(): HTMLDivElement {
|
|
53
|
+
return this._contentEl;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The header suffix section — append controls here imperatively. */
|
|
57
|
+
get suffixSection(): HTMLDivElement {
|
|
58
|
+
return this._suffixEl;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The header prefix section — append icons/widgets here imperatively. */
|
|
62
|
+
get prefixSection(): HTMLDivElement {
|
|
63
|
+
return this._prefixEl;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
connectedCallback() {
|
|
67
|
+
if (this._initialized) return;
|
|
68
|
+
this._initialized = true;
|
|
69
|
+
|
|
70
|
+
// Slotted prefix/suffix widgets go in the header; everything else is a
|
|
71
|
+
// nested row to disclose.
|
|
72
|
+
const prefixChildren = Array.from(this.querySelectorAll(':scope > [slot="prefix"]'));
|
|
73
|
+
const suffixChildren = Array.from(this.querySelectorAll(':scope > [slot="suffix"]'));
|
|
74
|
+
const claimed = new Set<Node>([...prefixChildren, ...suffixChildren]);
|
|
75
|
+
const rows = Array.from(this.childNodes).filter((node) => !claimed.has(node));
|
|
76
|
+
|
|
77
|
+
// Header row — looks like an action row: a text column, an optional
|
|
78
|
+
// enable switch, and the disclosure chevron.
|
|
79
|
+
this._headerEl = document.createElement('div');
|
|
80
|
+
this._headerEl.className = 'adw-expander-row-header';
|
|
81
|
+
|
|
82
|
+
const textEl = document.createElement('div');
|
|
83
|
+
textEl.className = 'adw-row-text';
|
|
84
|
+
this._titleEl = document.createElement('span');
|
|
85
|
+
this._titleEl.className = 'adw-row-title';
|
|
86
|
+
this._subtitleEl = document.createElement('span');
|
|
87
|
+
this._subtitleEl.className = 'adw-row-subtitle';
|
|
88
|
+
textEl.append(this._titleEl, this._subtitleEl);
|
|
89
|
+
|
|
90
|
+
// Optional enable switch (the same toggle Adw.ExpanderRow shows when
|
|
91
|
+
// show-enable-switch is set). Clicking it does NOT toggle the disclosure.
|
|
92
|
+
this._enableLabel = document.createElement('label');
|
|
93
|
+
this._enableLabel.className = 'adw-switch adw-expander-row-enable-switch';
|
|
94
|
+
this._enableSwitch = document.createElement('input');
|
|
95
|
+
this._enableSwitch.type = 'checkbox';
|
|
96
|
+
this._enableSwitch.checked = this.enableExpansion;
|
|
97
|
+
const slider = document.createElement('span');
|
|
98
|
+
slider.className = 'adw-switch-slider';
|
|
99
|
+
this._enableLabel.append(this._enableSwitch, slider);
|
|
100
|
+
|
|
101
|
+
this._chevronEl = document.createElement('span');
|
|
102
|
+
this._chevronEl.className = 'adw-icon adw-icon--go-down adw-expander-row-chevron';
|
|
103
|
+
|
|
104
|
+
// Header prefix / suffix widget slots (Adw.ExpanderRow add_prefix/add_suffix).
|
|
105
|
+
this._prefixEl = document.createElement('div');
|
|
106
|
+
this._prefixEl.className = 'adw-expander-row-prefix';
|
|
107
|
+
for (const child of prefixChildren) this._prefixEl.appendChild(child);
|
|
108
|
+
|
|
109
|
+
this._suffixEl = document.createElement('div');
|
|
110
|
+
this._suffixEl.className = 'adw-expander-row-suffix';
|
|
111
|
+
for (const child of suffixChildren) this._suffixEl.appendChild(child);
|
|
112
|
+
|
|
113
|
+
this._headerEl.append(this._prefixEl, textEl, this._suffixEl, this._enableLabel, this._chevronEl);
|
|
114
|
+
|
|
115
|
+
// Disclosed content — a nested listbox holding the rows.
|
|
116
|
+
this._contentEl = document.createElement('div');
|
|
117
|
+
this._contentEl.className = 'adw-expander-row-content';
|
|
118
|
+
for (const row of rows) this._contentEl.appendChild(row);
|
|
119
|
+
|
|
120
|
+
this.replaceChildren(this._headerEl, this._contentEl);
|
|
121
|
+
|
|
122
|
+
this._headerEl.addEventListener('click', (event) => {
|
|
123
|
+
// Clicks on the enable switch toggle expansion-enable, not disclosure.
|
|
124
|
+
if (this._enableLabel.contains(event.target as Node)) return;
|
|
125
|
+
this.expanded = !this.expanded;
|
|
126
|
+
this.dispatchEvent(
|
|
127
|
+
new CustomEvent('notify::expanded', { bubbles: true, detail: { expanded: this.expanded } }),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
this._enableSwitch.addEventListener('change', () => {
|
|
132
|
+
this.setAttribute('enable-expansion', this._enableSwitch.checked ? 'true' : 'false');
|
|
133
|
+
this.dispatchEvent(
|
|
134
|
+
new CustomEvent('notify::enable-expansion', {
|
|
135
|
+
bubbles: true,
|
|
136
|
+
detail: { enableExpansion: this._enableSwitch.checked },
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
this._render();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
attributeChangedCallback() {
|
|
145
|
+
if (this._initialized) this._render();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private _render() {
|
|
149
|
+
const title = this.getAttribute('title') ?? '';
|
|
150
|
+
const subtitle = this.getAttribute('subtitle') ?? '';
|
|
151
|
+
this._titleEl.textContent = title;
|
|
152
|
+
this._subtitleEl.textContent = subtitle;
|
|
153
|
+
this._subtitleEl.hidden = subtitle.length === 0;
|
|
154
|
+
|
|
155
|
+
const showSwitch = this.hasAttribute('show-enable-switch');
|
|
156
|
+
this._enableLabel.hidden = !showSwitch;
|
|
157
|
+
this._enableSwitch.checked = this.enableExpansion;
|
|
158
|
+
|
|
159
|
+
this._prefixEl.hidden = this._prefixEl.childElementCount === 0;
|
|
160
|
+
this._suffixEl.hidden = this._suffixEl.childElementCount === 0;
|
|
161
|
+
|
|
162
|
+
this.classList.toggle('expanded', this.expanded);
|
|
163
|
+
this._contentEl.classList.toggle('expanded', this.expanded);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
customElements.define('adw-expander-row', AdwExpanderRow);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
export class AdwHeaderBar extends HTMLElement {
|
|
7
7
|
private _initialized = false;
|
|
8
8
|
private _startEl: HTMLDivElement | null = null;
|
|
9
|
+
private _centerEl: HTMLDivElement | null = null;
|
|
9
10
|
private _endEl: HTMLDivElement | null = null;
|
|
10
11
|
|
|
11
12
|
/** The start (left) section container — append buttons/widgets here. */
|
|
@@ -13,6 +14,12 @@ export class AdwHeaderBar extends HTMLElement {
|
|
|
13
14
|
return this._startEl;
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/** The center (title) section — holds the `title` text or any `slot="center"`
|
|
18
|
+
* widget (the equivalent of Adw.HeaderBar's title-widget, e.g. a URL entry). */
|
|
19
|
+
get centerSection(): HTMLDivElement | null {
|
|
20
|
+
return this._centerEl;
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
/** The end (right) section container — append buttons/widgets here. */
|
|
17
24
|
get endSection(): HTMLDivElement | null {
|
|
18
25
|
return this._endEl;
|
|
@@ -26,6 +33,7 @@ export class AdwHeaderBar extends HTMLElement {
|
|
|
26
33
|
|
|
27
34
|
// Capture any pre-existing slotted children before clearing
|
|
28
35
|
const startChildren = Array.from(this.querySelectorAll(':scope > [slot="start"]'));
|
|
36
|
+
const centerChildren = Array.from(this.querySelectorAll(':scope > [slot="center"]'));
|
|
29
37
|
const endChildren = Array.from(this.querySelectorAll(':scope > [slot="end"]'));
|
|
30
38
|
|
|
31
39
|
// Start section
|
|
@@ -33,13 +41,18 @@ export class AdwHeaderBar extends HTMLElement {
|
|
|
33
41
|
this._startEl.className = 'adw-header-bar-start';
|
|
34
42
|
for (const child of startChildren) this._startEl.appendChild(child);
|
|
35
43
|
|
|
36
|
-
// Center section
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
// Center section — a `slot="center"` widget (title-widget) wins over the
|
|
45
|
+
// plain `title` text.
|
|
46
|
+
this._centerEl = document.createElement('div');
|
|
47
|
+
this._centerEl.className = 'adw-header-bar-center';
|
|
48
|
+
if (centerChildren.length > 0) {
|
|
49
|
+
for (const child of centerChildren) this._centerEl.appendChild(child);
|
|
50
|
+
} else {
|
|
51
|
+
const titleEl = document.createElement('span');
|
|
52
|
+
titleEl.className = 'adw-header-bar-title';
|
|
53
|
+
titleEl.textContent = title;
|
|
54
|
+
this._centerEl.appendChild(titleEl);
|
|
55
|
+
}
|
|
43
56
|
|
|
44
57
|
// End section
|
|
45
58
|
this._endEl = document.createElement('div');
|
|
@@ -47,7 +60,7 @@ export class AdwHeaderBar extends HTMLElement {
|
|
|
47
60
|
for (const child of endChildren) this._endEl.appendChild(child);
|
|
48
61
|
|
|
49
62
|
// Replace all children atomically
|
|
50
|
-
this.replaceChildren(this._startEl,
|
|
63
|
+
this.replaceChildren(this._startEl, this._centerEl, this._endEl);
|
|
51
64
|
}
|
|
52
65
|
}
|
|
53
66
|
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// <adw-inline-view-switcher> — A compact, toolbar-friendly view switcher (the
|
|
2
|
+
// web counterpart of Adw.InlineViewSwitcher). It pairs a toggle-group-style
|
|
3
|
+
// switcher bar with a stack of pages: each declared <adw-view-stack-page> child
|
|
4
|
+
// (with a `title` and/or `icon-name` attribute plus arbitrary content) becomes a
|
|
5
|
+
// toggle in the bar and a panel in the stack, and exactly one page is visible at
|
|
6
|
+
// a time. The toggles can show labels, icons, or both via `display-mode`, just
|
|
7
|
+
// like the native widget's AdwInlineViewSwitcherDisplayMode.
|
|
8
|
+
// Attributes:
|
|
9
|
+
// active (zero-based index of the visible page, default 0)
|
|
10
|
+
// display-mode (labels | icons | both, default both) — what the toggles show
|
|
11
|
+
// flat / round (mirroring the Adw.InlineViewSwitcher `.flat` / `.round` style
|
|
12
|
+
// classes, which it inherits from Adw.ToggleGroup)
|
|
13
|
+
// Events:
|
|
14
|
+
// `notify::active` (CustomEvent, bubbles, `detail = { active }`) when the
|
|
15
|
+
// visible page changes — mirrors selecting a page in the paired Adw.ViewStack.
|
|
16
|
+
// `notify::display-mode` (CustomEvent, bubbles, `detail = { displayMode }`)
|
|
17
|
+
// when the display mode changes — mirrors the Adw.InlineViewSwitcher
|
|
18
|
+
// `display-mode` GObject property.
|
|
19
|
+
// Reference: refs/libadwaita/src/adw-inline-view-switcher.c (AdwInlineViewSwitcher behaviour)
|
|
20
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_toggle-group.scss (inline-view-switcher > toggle-group)
|
|
21
|
+
// Reference: refs/adwaita-web/adwaita-web/scss/_viewswitcher.scss
|
|
22
|
+
// Copyright (c) 2023 Maximiliano Sandoval / 2024 GNOME Foundation Inc. (libadwaita). LGPLv2.1+.
|
|
23
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
24
|
+
|
|
25
|
+
/** The three display modes, mirroring AdwInlineViewSwitcherDisplayMode. */
|
|
26
|
+
const DISPLAY_MODES = ['labels', 'icons', 'both'] as const;
|
|
27
|
+
type DisplayMode = (typeof DISPLAY_MODES)[number];
|
|
28
|
+
|
|
29
|
+
/** A single page. Children of <adw-inline-view-switcher>; consumed at connect time. */
|
|
30
|
+
export class AdwViewStackPage extends HTMLElement {
|
|
31
|
+
static get observedAttributes() {
|
|
32
|
+
return ['title', 'icon-name'];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class AdwInlineViewSwitcher extends HTMLElement {
|
|
37
|
+
private _groupEl!: HTMLDivElement;
|
|
38
|
+
private _pagesEl!: HTMLDivElement;
|
|
39
|
+
private _toggles: HTMLButtonElement[] = [];
|
|
40
|
+
private _pages: HTMLDivElement[] = [];
|
|
41
|
+
private _active = 0;
|
|
42
|
+
private _displayMode: DisplayMode = 'both';
|
|
43
|
+
private _initialized = false;
|
|
44
|
+
|
|
45
|
+
static get observedAttributes() {
|
|
46
|
+
return ['active', 'display-mode', 'flat', 'round'];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Zero-based index of the visible page. */
|
|
50
|
+
get active(): number {
|
|
51
|
+
return this._active;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
set active(value: number) {
|
|
55
|
+
this.setAttribute('active', String(value));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** What the toggles display: 'labels', 'icons' or 'both'. */
|
|
59
|
+
get displayMode(): DisplayMode {
|
|
60
|
+
return this._displayMode;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
set displayMode(value: DisplayMode) {
|
|
64
|
+
this.setAttribute('display-mode', value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
connectedCallback() {
|
|
68
|
+
if (this._initialized) return;
|
|
69
|
+
this._initialized = true;
|
|
70
|
+
|
|
71
|
+
// Snapshot the declared <adw-view-stack-page> children before we take
|
|
72
|
+
// over the subtree — their title / icon-name + content become the
|
|
73
|
+
// rendered toggles and panels.
|
|
74
|
+
const pages = Array.from(this.querySelectorAll('adw-view-stack-page')) as AdwViewStackPage[];
|
|
75
|
+
|
|
76
|
+
// The switcher bar — a toggle-group of one toggle per page.
|
|
77
|
+
this._groupEl = document.createElement('div');
|
|
78
|
+
this._groupEl.className = 'adw-inline-view-switcher-group';
|
|
79
|
+
this._groupEl.setAttribute('role', 'tablist');
|
|
80
|
+
|
|
81
|
+
this._pagesEl = document.createElement('div');
|
|
82
|
+
this._pagesEl.className = 'adw-inline-view-switcher-pages';
|
|
83
|
+
|
|
84
|
+
this._toggles = [];
|
|
85
|
+
this._pages = [];
|
|
86
|
+
|
|
87
|
+
pages.forEach((page, index) => {
|
|
88
|
+
const title = page.getAttribute('title') ?? '';
|
|
89
|
+
const icon = (page.getAttribute('icon-name') ?? '').replace(/-symbolic$/, '');
|
|
90
|
+
|
|
91
|
+
const toggle = document.createElement('button');
|
|
92
|
+
toggle.type = 'button';
|
|
93
|
+
toggle.className = 'adw-inline-view-switcher-toggle';
|
|
94
|
+
toggle.setAttribute('role', 'tab');
|
|
95
|
+
// The toggle stores both title + icon; _render() shows the parts the
|
|
96
|
+
// current display mode calls for (mirrors libadwaita rebuilding the
|
|
97
|
+
// toggle child on a display-mode change).
|
|
98
|
+
toggle.dataset.title = title;
|
|
99
|
+
toggle.dataset.icon = icon;
|
|
100
|
+
|
|
101
|
+
const iconEl = document.createElement('span');
|
|
102
|
+
iconEl.className = `adw-icon${icon ? ` adw-icon--${icon}` : ''} adw-inline-view-switcher-icon`;
|
|
103
|
+
iconEl.setAttribute('aria-hidden', 'true'); // decorative (mask-image only)
|
|
104
|
+
toggle.appendChild(iconEl);
|
|
105
|
+
|
|
106
|
+
const labelEl = document.createElement('span');
|
|
107
|
+
labelEl.className = 'adw-inline-view-switcher-label';
|
|
108
|
+
labelEl.textContent = title;
|
|
109
|
+
toggle.appendChild(labelEl);
|
|
110
|
+
|
|
111
|
+
toggle.addEventListener('click', () => this._selectIndex(index));
|
|
112
|
+
this._groupEl.appendChild(toggle);
|
|
113
|
+
this._toggles.push(toggle);
|
|
114
|
+
|
|
115
|
+
// The page content area — the declared element's children are moved
|
|
116
|
+
// into a panel that the switcher toggles visible.
|
|
117
|
+
const panel = document.createElement('div');
|
|
118
|
+
panel.className = 'adw-inline-view-switcher-page';
|
|
119
|
+
panel.setAttribute('role', 'tabpanel');
|
|
120
|
+
for (const child of Array.from(page.childNodes)) panel.appendChild(child);
|
|
121
|
+
this._pagesEl.appendChild(panel);
|
|
122
|
+
this._pages.push(panel);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this.replaceChildren(this._groupEl, this._pagesEl);
|
|
126
|
+
|
|
127
|
+
this._active = this._readActiveAttr();
|
|
128
|
+
this._displayMode = this._readDisplayModeAttr();
|
|
129
|
+
this._render();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
attributeChangedCallback(name: string) {
|
|
133
|
+
if (!this._initialized) return;
|
|
134
|
+
if (name === 'active') {
|
|
135
|
+
const next = this._readActiveAttr();
|
|
136
|
+
if (next !== this._active) {
|
|
137
|
+
this._active = next;
|
|
138
|
+
this._render();
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (name === 'display-mode') {
|
|
143
|
+
const next = this._readDisplayModeAttr();
|
|
144
|
+
if (next !== this._displayMode) {
|
|
145
|
+
this._displayMode = next;
|
|
146
|
+
this._render();
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// flat / round are styling-only.
|
|
151
|
+
this._render();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private _readActiveAttr(): number {
|
|
155
|
+
const raw = Number.parseInt(this.getAttribute('active') ?? '0', 10);
|
|
156
|
+
if (Number.isNaN(raw)) return 0;
|
|
157
|
+
const max = Math.max(0, this._pages.length - 1);
|
|
158
|
+
return Math.min(Math.max(raw, 0), max);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private _readDisplayModeAttr(): DisplayMode {
|
|
162
|
+
const raw = this.getAttribute('display-mode') ?? 'both';
|
|
163
|
+
return (DISPLAY_MODES as readonly string[]).includes(raw) ? (raw as DisplayMode) : 'both';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private _selectIndex(index: number): void {
|
|
167
|
+
if (index === this._active) return;
|
|
168
|
+
this._active = index;
|
|
169
|
+
// Keep the attribute in sync without re-entering via the guarded
|
|
170
|
+
// attributeChangedCallback (value already matches, so it no-ops).
|
|
171
|
+
this.setAttribute('active', String(index));
|
|
172
|
+
this._render();
|
|
173
|
+
this.dispatchEvent(new CustomEvent('notify::active', { bubbles: true, detail: { active: index } }));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private _render(): void {
|
|
177
|
+
this.classList.toggle('flat', this.hasAttribute('flat'));
|
|
178
|
+
this.classList.toggle('round', this.hasAttribute('round'));
|
|
179
|
+
|
|
180
|
+
// The bar carries the active display mode as a class — mirrors
|
|
181
|
+
// libadwaita's inline-view-switcher > toggle-group.{labels,icons,both}.
|
|
182
|
+
for (const mode of DISPLAY_MODES) this._groupEl.classList.toggle(mode, mode === this._displayMode);
|
|
183
|
+
|
|
184
|
+
const showIcon = this._displayMode !== 'labels';
|
|
185
|
+
const showLabel = this._displayMode !== 'icons';
|
|
186
|
+
|
|
187
|
+
this._toggles.forEach((toggle, index) => {
|
|
188
|
+
const isActive = index === this._active;
|
|
189
|
+
toggle.classList.toggle('active', isActive);
|
|
190
|
+
toggle.setAttribute('aria-selected', String(isActive));
|
|
191
|
+
toggle.tabIndex = isActive ? 0 : -1;
|
|
192
|
+
|
|
193
|
+
const iconEl = toggle.querySelector('.adw-inline-view-switcher-icon') as HTMLElement | null;
|
|
194
|
+
const labelEl = toggle.querySelector('.adw-inline-view-switcher-label') as HTMLElement | null;
|
|
195
|
+
const hasIcon = (toggle.dataset.icon ?? '').length > 0;
|
|
196
|
+
const title = toggle.dataset.title ?? '';
|
|
197
|
+
|
|
198
|
+
if (iconEl) iconEl.hidden = !showIcon || !hasIcon;
|
|
199
|
+
if (labelEl) labelEl.hidden = !showLabel || title.length === 0;
|
|
200
|
+
|
|
201
|
+
// In icons-only mode the toggle has no visible text — surface the
|
|
202
|
+
// title as an accessible name + a hover tooltip (the native widget
|
|
203
|
+
// sets the toggle tooltip to the page title in ICONS mode).
|
|
204
|
+
if (!showLabel && title) {
|
|
205
|
+
toggle.setAttribute('aria-label', title);
|
|
206
|
+
toggle.title = title;
|
|
207
|
+
} else {
|
|
208
|
+
toggle.removeAttribute('aria-label');
|
|
209
|
+
toggle.removeAttribute('title');
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
this._pages.forEach((panel, index) => {
|
|
214
|
+
const isActive = index === this._active;
|
|
215
|
+
panel.classList.toggle('active-view', isActive);
|
|
216
|
+
panel.hidden = !isActive;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
customElements.define('adw-view-stack-page', AdwViewStackPage);
|
|
222
|
+
customElements.define('adw-inline-view-switcher', AdwInlineViewSwitcher);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// <adw-navigation-split-view> — A docked sidebar beside content. Both panes are
|
|
2
|
+
// visible when expanded; when `collapsed` it shows one pane at a time
|
|
3
|
+
// (`show-content` decides which), mirroring Adw.NavigationSplitView's
|
|
4
|
+
// push-navigation on narrow widths.
|
|
5
|
+
// Attributes: collapsed, show-content, min-sidebar-width, max-sidebar-width.
|
|
6
|
+
// Slots: slot="sidebar", slot="content".
|
|
7
|
+
// Reference: refs/adwaita-web/adwaita-web/docs/widgets/navigationsplitview.md
|
|
8
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_sidebars.scss
|
|
9
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
10
|
+
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
11
|
+
|
|
12
|
+
export class AdwNavigationSplitView extends HTMLElement {
|
|
13
|
+
private _sidebarEl!: HTMLDivElement;
|
|
14
|
+
private _contentEl!: HTMLDivElement;
|
|
15
|
+
private _initialized = false;
|
|
16
|
+
|
|
17
|
+
static get observedAttributes() {
|
|
18
|
+
return ['collapsed', 'show-content', 'min-sidebar-width', 'max-sidebar-width'];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get collapsed(): boolean {
|
|
22
|
+
return this.hasAttribute('collapsed');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
set collapsed(v: boolean) {
|
|
26
|
+
this.toggleAttribute('collapsed', v);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** When collapsed, whether the content pane (true) or sidebar (false) shows. */
|
|
30
|
+
get showContent(): boolean {
|
|
31
|
+
return this.hasAttribute('show-content');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
set showContent(v: boolean) {
|
|
35
|
+
this.toggleAttribute('show-content', v);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
connectedCallback() {
|
|
39
|
+
if (this._initialized) return;
|
|
40
|
+
this._initialized = true;
|
|
41
|
+
|
|
42
|
+
const sidebarChildren = Array.from(this.querySelectorAll(':scope > [slot="sidebar"]'));
|
|
43
|
+
const contentChildren = Array.from(this.querySelectorAll(':scope > [slot="content"]'));
|
|
44
|
+
|
|
45
|
+
this._sidebarEl = document.createElement('div');
|
|
46
|
+
this._sidebarEl.className = 'adw-nsv-sidebar';
|
|
47
|
+
for (const child of sidebarChildren) this._sidebarEl.appendChild(child);
|
|
48
|
+
|
|
49
|
+
this._contentEl = document.createElement('div');
|
|
50
|
+
this._contentEl.className = 'adw-nsv-content';
|
|
51
|
+
for (const child of contentChildren) this._contentEl.appendChild(child);
|
|
52
|
+
|
|
53
|
+
this.replaceChildren(this._sidebarEl, this._contentEl);
|
|
54
|
+
this._syncWidth();
|
|
55
|
+
this._syncClasses();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
attributeChangedCallback(name: string) {
|
|
59
|
+
if (!this._initialized) return;
|
|
60
|
+
if (name === 'min-sidebar-width' || name === 'max-sidebar-width') this._syncWidth();
|
|
61
|
+
else {
|
|
62
|
+
this._syncClasses();
|
|
63
|
+
// Collapsing flips whether the sidebar is width-capped, so re-apply.
|
|
64
|
+
this._syncWidth();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private _syncWidth() {
|
|
69
|
+
// When collapsed the visible pane fills the whole view, so the sidebar's
|
|
70
|
+
// min/max width caps must not apply (an inline cap would otherwise win
|
|
71
|
+
// over any stylesheet rule and leave a dead strip beside the pane).
|
|
72
|
+
if (this.collapsed) {
|
|
73
|
+
this._sidebarEl.style.minWidth = '';
|
|
74
|
+
this._sidebarEl.style.maxWidth = '';
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const min = this.getAttribute('min-sidebar-width');
|
|
78
|
+
const max = this.getAttribute('max-sidebar-width');
|
|
79
|
+
this._sidebarEl.style.minWidth = min ? `${parseFloat(min)}px` : '';
|
|
80
|
+
this._sidebarEl.style.maxWidth = max ? `${parseFloat(max)}px` : '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private _syncClasses() {
|
|
84
|
+
this.classList.toggle('collapsed', this.collapsed);
|
|
85
|
+
this.classList.toggle('show-content', this.showContent);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
customElements.define('adw-navigation-split-view', AdwNavigationSplitView);
|