@gjsify/adwaita-web 0.32.0 → 0.33.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 +72 -50
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/elements/adw-combo-row.d.ts +10 -0
- package/lib/types/elements/adw-inline-view-switcher.d.ts +37 -1
- package/lib/types/elements/adw-navigation-split-view.d.ts +42 -0
- package/lib/types/elements/adw-overlay-split-view.d.ts +56 -0
- package/lib/types/elements/adw-spinner.d.ts +23 -0
- package/lib/types/elements/adw-view-stack.d.ts +12 -0
- package/lib/types/elements/adw-view-switcher-bar.d.ts +7 -5
- package/lib/types/elements/adw-view-switcher.d.ts +55 -3
- package/lib/types/elements/adw-wrap-box.d.ts +58 -3
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +6 -6
- package/scss/_combo_row.scss +19 -0
- package/scss/_navigation_split_view.scss +47 -8
- package/scss/_overlay_split_view.scss +117 -32
- package/scss/_spinner.scss +35 -21
- package/scss/_tab_view.scss +12 -13
- package/scss/_wrap_box.scss +9 -0
- package/src/adw-avatar.spec.ts +37 -0
- package/src/adw-button-content.spec.ts +24 -0
- package/src/adw-row-state.spec.ts +49 -0
- package/src/adw-wrap-box.spec.ts +148 -1
- package/src/chrome.spec.ts +111 -12
- package/src/elements/adw-action-row.ts +11 -1
- package/src/elements/adw-combo-row.ts +17 -0
- package/src/elements/adw-inline-view-switcher.ts +91 -8
- package/src/elements/adw-navigation-split-view.ts +179 -14
- package/src/elements/adw-overlay-split-view.ts +378 -20
- package/src/elements/adw-spinner.ts +165 -21
- package/src/elements/adw-tab-view.ts +20 -2
- package/src/elements/adw-view-stack.ts +21 -0
- package/src/elements/adw-view-switcher-bar.ts +13 -5
- package/src/elements/adw-view-switcher.ts +103 -11
- package/src/elements/adw-wrap-box.ts +290 -135
- package/src/split-views.spec.ts +586 -2
- package/src/styles.generated.ts +1 -1
- package/src/view-switcher.spec.ts +219 -0
|
@@ -2,31 +2,74 @@
|
|
|
2
2
|
// visible when expanded; when `collapsed` it shows one pane at a time
|
|
3
3
|
// (`show-content` decides which), mirroring Adw.NavigationSplitView's
|
|
4
4
|
// push-navigation on narrow widths.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
//
|
|
6
|
+
// Attributes: collapsed, show-content, sidebar-position, min-sidebar-width,
|
|
7
|
+
// max-sidebar-width, breakpoint (an Adwaita condition, e.g. "max-width: 720px",
|
|
8
|
+
// that collapses the view by itself — the counterpart to an Adw.Breakpoint
|
|
9
|
+
// add_setter()). A slotted pane may carry `tag`, which is
|
|
10
|
+
// `Adw.NavigationPage:tag`.
|
|
8
11
|
// Slots: slot="sidebar", slot="content".
|
|
12
|
+
//
|
|
13
|
+
// The element used to toggle two CSS classes and nothing else, so THREE rules
|
|
14
|
+
// from the C had no expression here at all: the navigation-stack ordering table
|
|
15
|
+
// (a LONE child stays visible whatever `show-content` says; with
|
|
16
|
+
// `sidebar-position: end` the CONTENT is the root page), the duplicate-tag
|
|
17
|
+
// guard, and the `navigation.push` / `navigation.pop` routing that lets a nested
|
|
18
|
+
// split view forward a push outwards. All three are `NavigationSplitViewState`
|
|
19
|
+
// in `@gjsify/adwaita-core`, held to NAVIGATION_STACK_VECTORS,
|
|
20
|
+
// NAVIGATION_SPLIT_VIEW_CRITICALS and NAVIGATION_ACTION_VECTORS — the last of
|
|
21
|
+
// which had no consumer at all before this.
|
|
22
|
+
//
|
|
9
23
|
// Reference: refs/adwaita-web/adwaita-web/docs/widgets/navigationsplitview.md
|
|
24
|
+
// Reference: refs/libadwaita/src/adw-navigation-split-view.c
|
|
10
25
|
// Reference: refs/libadwaita/src/stylesheet/widgets/_sidebars.scss
|
|
11
26
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
12
27
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
13
28
|
|
|
14
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
DEFAULT_MAX_SIDEBAR_WIDTH,
|
|
31
|
+
DEFAULT_MIN_SIDEBAR_WIDTH,
|
|
32
|
+
NavigationSplitViewState,
|
|
33
|
+
isSidebarAtVisualStart,
|
|
34
|
+
resolveSidebarBounds,
|
|
35
|
+
type AdwPackType,
|
|
36
|
+
type AdwTextDirection,
|
|
37
|
+
type NavigationActionResult,
|
|
38
|
+
} from '@gjsify/adwaita-core';
|
|
15
39
|
|
|
16
40
|
import { bindBreakpointSetter } from '../breakpoints.js';
|
|
17
41
|
|
|
42
|
+
/** Detail of the `navigation-push` / `navigation-pop` delegation event. */
|
|
43
|
+
export interface NavigationDelegateDetail {
|
|
44
|
+
/** The tag a `push` names; absent for a `pop`. */
|
|
45
|
+
tag?: string;
|
|
46
|
+
/** Set by an ancestor that handled it — the `return TRUE` of the C's routing. */
|
|
47
|
+
handled: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
18
50
|
export class AdwNavigationSplitView extends HTMLElement {
|
|
19
51
|
private _sidebarEl!: HTMLDivElement;
|
|
20
52
|
private _contentEl!: HTMLDivElement;
|
|
21
53
|
private _initialized = false;
|
|
22
54
|
private _disposeBreakpoint: (() => void) | undefined;
|
|
55
|
+
/** The ordering table, the tag guard and the action routing (ADR 0004). */
|
|
56
|
+
private _state = new NavigationSplitViewState();
|
|
57
|
+
/** Re-entrancy guard for the `show-content` reflection. */
|
|
58
|
+
private _reflecting = false;
|
|
23
59
|
|
|
24
60
|
static get observedAttributes() {
|
|
25
|
-
return [
|
|
61
|
+
return [
|
|
62
|
+
'collapsed',
|
|
63
|
+
'show-content',
|
|
64
|
+
'sidebar-position',
|
|
65
|
+
'min-sidebar-width',
|
|
66
|
+
'max-sidebar-width',
|
|
67
|
+
'breakpoint',
|
|
68
|
+
];
|
|
26
69
|
}
|
|
27
70
|
|
|
28
71
|
get collapsed(): boolean {
|
|
29
|
-
return this.hasAttribute('collapsed');
|
|
72
|
+
return this._initialized ? this._state.collapsed : this.hasAttribute('collapsed');
|
|
30
73
|
}
|
|
31
74
|
|
|
32
75
|
set collapsed(v: boolean) {
|
|
@@ -35,13 +78,49 @@ export class AdwNavigationSplitView extends HTMLElement {
|
|
|
35
78
|
|
|
36
79
|
/** When collapsed, whether the content pane (true) or sidebar (false) shows. */
|
|
37
80
|
get showContent(): boolean {
|
|
38
|
-
return this.hasAttribute('show-content');
|
|
81
|
+
return this._initialized ? this._state.showContent : this.hasAttribute('show-content');
|
|
39
82
|
}
|
|
40
83
|
|
|
41
84
|
set showContent(v: boolean) {
|
|
42
85
|
this.toggleAttribute('show-content', v);
|
|
43
86
|
}
|
|
44
87
|
|
|
88
|
+
/** Which side the sidebar is packed on — `start` (default) or `end`. */
|
|
89
|
+
get sidebarPosition(): AdwPackType {
|
|
90
|
+
return this.getAttribute('sidebar-position') === 'end' ? 'end' : 'start';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
set sidebarPosition(value: AdwPackType) {
|
|
94
|
+
this.setAttribute('sidebar-position', value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The sidebar page's tag of record, or `null`. */
|
|
98
|
+
get sidebarTag(): string | null {
|
|
99
|
+
return this._state.sidebarTag;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The content page's tag of record, or `null`. */
|
|
103
|
+
get contentTag(): string | null {
|
|
104
|
+
return this._state.contentTag;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* `navigation.push` with `tag` — `navigation_push_cb` (:644-685).
|
|
109
|
+
*
|
|
110
|
+
* An unmatched tag DELEGATES to the parent before it may become a critical,
|
|
111
|
+
* which is how nested split views forward a push outwards; on this port that
|
|
112
|
+
* is a bubbling `navigation-push` event whose `detail.handled` an ancestor
|
|
113
|
+
* sets. Returns what the routing decided.
|
|
114
|
+
*/
|
|
115
|
+
push(tag: string): NavigationActionResult {
|
|
116
|
+
return this._state.push(tag);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** `navigation.pop` — `navigation_pop_cb` (:687-702). */
|
|
120
|
+
pop(): NavigationActionResult {
|
|
121
|
+
return this._state.pop();
|
|
122
|
+
}
|
|
123
|
+
|
|
45
124
|
connectedCallback() {
|
|
46
125
|
if (this._initialized) return;
|
|
47
126
|
this._initialized = true;
|
|
@@ -58,11 +137,68 @@ export class AdwNavigationSplitView extends HTMLElement {
|
|
|
58
137
|
for (const child of contentChildren) this._contentEl.appendChild(child);
|
|
59
138
|
|
|
60
139
|
this.replaceChildren(this._sidebarEl, this._contentEl);
|
|
140
|
+
|
|
141
|
+
// Parse-time attributes are the CONSTRUCTION properties, for the same
|
|
142
|
+
// reason the overlay view treats them so: applying them as sequential
|
|
143
|
+
// setters would emit a transition for a state the markup simply declared.
|
|
144
|
+
this._state = new NavigationSplitViewState({
|
|
145
|
+
sidebarPosition: this.sidebarPosition,
|
|
146
|
+
collapsed: this.hasAttribute('collapsed'),
|
|
147
|
+
showContent: this.hasAttribute('show-content'),
|
|
148
|
+
// `g_critical` has no browser counterpart that a consumer can see;
|
|
149
|
+
// `console.error` is the one a developer reads, and the TEXT is the
|
|
150
|
+
// C's verbatim so a GTK bug report and a browser one say the same.
|
|
151
|
+
onCritical: (message) => console.error(message),
|
|
152
|
+
onDelegate: (action, tag) => {
|
|
153
|
+
const detail: NavigationDelegateDetail = { tag, handled: false };
|
|
154
|
+
this.dispatchEvent(new CustomEvent(`navigation-${action}`, { bubbles: true, detail }));
|
|
155
|
+
return detail.handled;
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
// Which children EXIST decides the stack, so the panes are mounted into
|
|
159
|
+
// the state, not merely into the DOM — a lone child stays visible
|
|
160
|
+
// whatever `show-content` says (:389-401), which two CSS classes cannot
|
|
161
|
+
// express.
|
|
162
|
+
this._state.setSidebar(sidebarChildren.length > 0 ? { tag: this._tagOf(sidebarChildren) } : null);
|
|
163
|
+
this._state.setContent(contentChildren.length > 0 ? { tag: this._tagOf(contentChildren) } : null);
|
|
164
|
+
this._state.subscribe(() => {
|
|
165
|
+
this._reflectShowContent();
|
|
166
|
+
this._syncClasses();
|
|
167
|
+
});
|
|
168
|
+
|
|
61
169
|
this._syncWidth();
|
|
62
170
|
this._syncClasses();
|
|
63
171
|
this._syncBreakpoint();
|
|
64
172
|
}
|
|
65
173
|
|
|
174
|
+
/** `Adw.NavigationPage:tag` off the slotted pane, or `null` when untagged. */
|
|
175
|
+
private _tagOf(children: readonly Element[]): string | null {
|
|
176
|
+
for (const child of children) {
|
|
177
|
+
const tag = child.getAttribute('tag');
|
|
178
|
+
if (tag !== null) return tag;
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Mirror the state's `showContent` onto the attribute, so the DOM keeps
|
|
185
|
+
* telling the truth after a `navigation.push` moved it.
|
|
186
|
+
*
|
|
187
|
+
* Guarded the way the overlay view's reflection is: writing the attribute
|
|
188
|
+
* re-enters `attributeChangedCallback`, which without the flag is a loop.
|
|
189
|
+
*/
|
|
190
|
+
private _reflectShowContent() {
|
|
191
|
+
if (this._reflecting) return;
|
|
192
|
+
const wanted = this._state.showContent;
|
|
193
|
+
if (this.hasAttribute('show-content') === wanted) return;
|
|
194
|
+
this._reflecting = true;
|
|
195
|
+
try {
|
|
196
|
+
this.toggleAttribute('show-content', wanted);
|
|
197
|
+
} finally {
|
|
198
|
+
this._reflecting = false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
66
202
|
disconnectedCallback() {
|
|
67
203
|
this._disposeBreakpoint?.();
|
|
68
204
|
this._disposeBreakpoint = undefined;
|
|
@@ -70,13 +206,20 @@ export class AdwNavigationSplitView extends HTMLElement {
|
|
|
70
206
|
|
|
71
207
|
attributeChangedCallback(name: string) {
|
|
72
208
|
if (!this._initialized) return;
|
|
73
|
-
if (name === 'breakpoint')
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
209
|
+
if (name === 'breakpoint') {
|
|
210
|
+
this._syncBreakpoint();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (name === 'min-sidebar-width' || name === 'max-sidebar-width') {
|
|
78
214
|
this._syncWidth();
|
|
215
|
+
return;
|
|
79
216
|
}
|
|
217
|
+
if (name === 'collapsed') this._state.setCollapsed(this.hasAttribute('collapsed'));
|
|
218
|
+
if (name === 'show-content') this._state.setShowContent(this.hasAttribute('show-content'));
|
|
219
|
+
if (name === 'sidebar-position') this._state.setSidebarPosition(this.sidebarPosition);
|
|
220
|
+
this._syncClasses();
|
|
221
|
+
// Collapsing flips whether the sidebar is width-capped, so re-apply.
|
|
222
|
+
this._syncWidth();
|
|
80
223
|
}
|
|
81
224
|
|
|
82
225
|
private _syncWidth() {
|
|
@@ -113,9 +256,31 @@ export class AdwNavigationSplitView extends HTMLElement {
|
|
|
113
256
|
return Number.isFinite(value) ? value : fallback;
|
|
114
257
|
}
|
|
115
258
|
|
|
259
|
+
/** The reading direction `start` / `end` resolve against (`get_start_or_end`, :220-227). */
|
|
260
|
+
private get _direction(): AdwTextDirection {
|
|
261
|
+
return getComputedStyle(this).direction === 'rtl' ? 'rtl' : 'ltr';
|
|
262
|
+
}
|
|
263
|
+
|
|
116
264
|
private _syncClasses() {
|
|
117
|
-
|
|
118
|
-
this.classList.toggle('
|
|
265
|
+
const state = this._state;
|
|
266
|
+
this.classList.toggle('collapsed', state.collapsed);
|
|
267
|
+
this.classList.toggle('show-content', state.showContent);
|
|
268
|
+
this.classList.toggle('sidebar-end', state.sidebarPosition === 'end');
|
|
269
|
+
// The VISUAL side, which under RTL is the opposite of the logical one —
|
|
270
|
+
// it decides where the pane is drawn AND which edge its divider is on.
|
|
271
|
+
this.classList.toggle(
|
|
272
|
+
'sidebar-at-visual-start',
|
|
273
|
+
isSidebarAtVisualStart(state.sidebarPosition, this._direction),
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// The STACK, not the two flags: `update_navigation_stack` (:342-405)
|
|
277
|
+
// keeps a LONE child visible whatever `show-content` says, and with
|
|
278
|
+
// `sidebar-position: end` the CONTENT is the root page — so the sidebar
|
|
279
|
+
// is PUSHED ON TOP of it rather than replacing it.
|
|
280
|
+
const { stack } = state.stack;
|
|
281
|
+
const visible = stack[stack.length - 1] ?? null;
|
|
282
|
+
if (this._sidebarEl) this._sidebarEl.dataset.paneVisible = String(!state.collapsed || visible === 'sidebar');
|
|
283
|
+
if (this._contentEl) this._contentEl.dataset.paneVisible = String(!state.collapsed || visible === 'content');
|
|
119
284
|
}
|
|
120
285
|
|
|
121
286
|
/** (Re)bind the `breakpoint` condition to `collapsed`. */
|