@gjsify/adwaita-web 0.32.0 → 0.34.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 +118 -55
- 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 +52 -9
- package/scss/_navigation_view.scss +28 -4
- package/scss/_overlay_split_view.scss +122 -33
- package/scss/_panes.scss +57 -0
- package/scss/_sidebar.scss +8 -1
- package/scss/_spinner.scss +35 -21
- package/scss/_tab_view.scss +12 -13
- package/scss/_theme.scss +9 -0
- package/scss/_variables.scss +18 -1
- package/scss/_wrap_box.scss +9 -0
- package/scss/adwaita-skin.scss +1 -0
- package/src/adw-about-dialog.spec.ts +36 -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-sidebar.spec.ts +32 -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 +189 -14
- package/src/elements/adw-overlay-split-view.ts +395 -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 +677 -2
- package/src/styles.generated.ts +1 -1
- package/src/view-switcher.spec.ts +219 -0
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
isInlineViewSwitcherDisplayMode,
|
|
54
54
|
toggleIndexForPage,
|
|
55
55
|
} from '@gjsify/adwaita-core';
|
|
56
|
+
import type { ViewSwitcherScheduler } from '@gjsify/adwaita-core';
|
|
56
57
|
import type {
|
|
57
58
|
AdwInlineViewSwitcherDisplayMode,
|
|
58
59
|
AdwViewSwitcherPage,
|
|
@@ -75,21 +76,43 @@ export class AdwViewStackPage extends HTMLElement {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
export class AdwInlineViewSwitcher extends HTMLElement {
|
|
78
|
-
|
|
79
|
+
/**
|
|
80
|
+
* The dwell timer, injectable.
|
|
81
|
+
*
|
|
82
|
+
* It used to be `new ViewSwitcherState({ scheduler: domViewSwitcherScheduler })`
|
|
83
|
+
* inline, which left NOWHERE to hand a clock in — so
|
|
84
|
+
* `VIEW_SWITCHER_DRAG_VECTORS` was driven by the core suite alone, and the
|
|
85
|
+
* conformance header claimed all three suites drove it. `scheduler` is a
|
|
86
|
+
* property so a test can replace it before the element connects; the DOM one
|
|
87
|
+
* is the default, exactly as before.
|
|
88
|
+
*/
|
|
89
|
+
scheduler: ViewSwitcherScheduler = domViewSwitcherScheduler;
|
|
90
|
+
private _stateInstance: ViewSwitcherState | null = null;
|
|
91
|
+
|
|
92
|
+
/** Built on first access, so a `scheduler` set before connect is the one used. */
|
|
93
|
+
private get _state(): ViewSwitcherState {
|
|
94
|
+
if (!this._stateInstance) {
|
|
95
|
+
this._stateInstance = new ViewSwitcherState({ scheduler: this.scheduler });
|
|
96
|
+
this._stateInstance.subscribe((change) => this._onStateChange(change));
|
|
97
|
+
}
|
|
98
|
+
return this._stateInstance;
|
|
99
|
+
}
|
|
79
100
|
private _groupEl!: HTMLDivElement;
|
|
80
101
|
private _pagesEl!: HTMLDivElement;
|
|
81
102
|
private _panels: HTMLDivElement[] = [];
|
|
82
103
|
private _toggles: HTMLButtonElement[] = [];
|
|
83
104
|
private _displayMode: AdwInlineViewSwitcherDisplayMode = 'labels';
|
|
84
105
|
private _initialized = false;
|
|
106
|
+
/** The page ELEMENTS, kept because this element detaches them from the tree. */
|
|
107
|
+
private _declared: HTMLElement[] = [];
|
|
108
|
+
private _pageObserver: MutationObserver | undefined;
|
|
85
109
|
|
|
86
110
|
static get observedAttributes() {
|
|
87
|
-
return ['active', 'display-mode', 'flat', 'round'];
|
|
111
|
+
return ['active', 'display-mode', 'flat', 'round', 'osd'];
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
constructor() {
|
|
91
115
|
super();
|
|
92
|
-
this._state.subscribe((change) => this._onStateChange(change));
|
|
93
116
|
}
|
|
94
117
|
|
|
95
118
|
/** Zero-based index of the visible PAGE, `-1` when nothing is selected. */
|
|
@@ -147,7 +170,13 @@ export class AdwInlineViewSwitcher extends HTMLElement {
|
|
|
147
170
|
}
|
|
148
171
|
|
|
149
172
|
connectedCallback() {
|
|
150
|
-
if (this._initialized)
|
|
173
|
+
if (this._initialized) {
|
|
174
|
+
// Re-entering a document — same reason as `adw-view-switcher.ts`:
|
|
175
|
+
// the page observer is dropped on disconnect, so it has to be
|
|
176
|
+
// rebuilt here or a moved switcher goes deaf permanently.
|
|
177
|
+
this._watchPages();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
151
180
|
|
|
152
181
|
// `:scope >` rather than a subtree scan — `<adw-view-stack>` uses the
|
|
153
182
|
// same tag for its pages, so an unscoped query adopted a nested stack's.
|
|
@@ -176,6 +205,8 @@ export class AdwInlineViewSwitcher extends HTMLElement {
|
|
|
176
205
|
this._displayMode = this._readDisplayModeAttr();
|
|
177
206
|
this._initialized = true;
|
|
178
207
|
|
|
208
|
+
this._declared = declared;
|
|
209
|
+
this._watchPages();
|
|
179
210
|
this._state.setPages(declared.map((pageEl) => readSwitcherPage(pageEl)));
|
|
180
211
|
if (declaredActive !== null) {
|
|
181
212
|
const index = Number.parseInt(declaredActive, 10);
|
|
@@ -186,6 +217,37 @@ export class AdwInlineViewSwitcher extends HTMLElement {
|
|
|
186
217
|
|
|
187
218
|
disconnectedCallback() {
|
|
188
219
|
this._state.cancelDrag();
|
|
220
|
+
this._pageObserver?.disconnect();
|
|
221
|
+
this._pageObserver = undefined;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Re-read every declared page and push it into the state.
|
|
226
|
+
*
|
|
227
|
+
* Driven by {@link _watchPages}, which is what makes a runtime
|
|
228
|
+
* `badge-number` or `icon-name` change reach the toggle — C rebinds on every
|
|
229
|
+
* page `notify` (adw-view-switcher.c:184-193) and this element did not.
|
|
230
|
+
*/
|
|
231
|
+
refreshPages(): void {
|
|
232
|
+
if (!this._initialized) return;
|
|
233
|
+
this._state.setPages(this._declared.map((pageEl) => readSwitcherPage(pageEl)));
|
|
234
|
+
this._rebuildToggles();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Watch the declared pages for attribute changes — the DOM's `notify::` on
|
|
239
|
+
* `AdwViewStackPage`, which C binds per page (adw-view-switcher.c:184-193).
|
|
240
|
+
*
|
|
241
|
+
* A `MutationObserver` rather than an `attributeChangedCallback`: this
|
|
242
|
+
* element consumes the page elements at connect and `replaceChildren`s them
|
|
243
|
+
* out of the tree, so a detached page cannot find its owner. It delivers on
|
|
244
|
+
* a MICROTASK where GTK's notify is synchronous — invisible to a user, and
|
|
245
|
+
* nothing here reads the toggle back inside the setter.
|
|
246
|
+
*/
|
|
247
|
+
private _watchPages(): void {
|
|
248
|
+
this._pageObserver?.disconnect();
|
|
249
|
+
this._pageObserver = new MutationObserver(() => this.refreshPages());
|
|
250
|
+
for (const pageEl of this._declared) this._pageObserver.observe(pageEl, { attributes: true });
|
|
189
251
|
}
|
|
190
252
|
|
|
191
253
|
attributeChangedCallback(name: string) {
|
|
@@ -265,8 +327,21 @@ export class AdwInlineViewSwitcher extends HTMLElement {
|
|
|
265
327
|
toggle.addEventListener('click', () => {
|
|
266
328
|
this._state.setSelected(model.pageIndex);
|
|
267
329
|
});
|
|
268
|
-
toggle
|
|
269
|
-
|
|
330
|
+
// The `relatedTarget` test is load-bearing: a toggle holds an icon,
|
|
331
|
+
// a label and an indicator, and the DOM fires `dragleave` +
|
|
332
|
+
// `dragenter` when the pointer crosses BETWEEN them — which cleared
|
|
333
|
+
// the dwell timer and re-armed a fresh 500ms, so a drag that merely
|
|
334
|
+
// slid across the toggle never switched the page. C has one
|
|
335
|
+
// `GtkDropControllerMotion` per BUTTON widget
|
|
336
|
+
// (adw-view-switcher-button.c:79-96), which has no internal edges.
|
|
337
|
+
toggle.addEventListener('dragenter', (event) => {
|
|
338
|
+
if (toggle.contains((event as DragEvent).relatedTarget as Node | null)) return;
|
|
339
|
+
this._state.dragEnter(model.pageIndex);
|
|
340
|
+
});
|
|
341
|
+
toggle.addEventListener('dragleave', (event) => {
|
|
342
|
+
if (toggle.contains((event as DragEvent).relatedTarget as Node | null)) return;
|
|
343
|
+
this._state.dragLeave(model.pageIndex);
|
|
344
|
+
});
|
|
270
345
|
|
|
271
346
|
this._groupEl.appendChild(toggle);
|
|
272
347
|
return toggle;
|
|
@@ -314,8 +389,16 @@ export class AdwInlineViewSwitcher extends HTMLElement {
|
|
|
314
389
|
}
|
|
315
390
|
|
|
316
391
|
private _applyStyleClasses(): void {
|
|
317
|
-
|
|
318
|
-
|
|
392
|
+
// `css_classes_changed_cb` (adw-inline-view-switcher.c:528-545) forwards
|
|
393
|
+
// THREE classes onto the toggle group, not two: `flat`, `round` and
|
|
394
|
+
// `osd`. This element carried the first two and only onto the host, so an
|
|
395
|
+
// `osd` inline switcher — the one in a floating overlay, where the class
|
|
396
|
+
// exists to make it legible — styled nothing at all.
|
|
397
|
+
for (const name of ['flat', 'round', 'osd'] as const) {
|
|
398
|
+
const present = this.hasAttribute(name);
|
|
399
|
+
this.classList.toggle(name, present);
|
|
400
|
+
this._groupEl?.classList.toggle(name, present);
|
|
401
|
+
}
|
|
319
402
|
// The group carries the display-mode class, mirroring libadwaita's
|
|
320
403
|
// inline-view-switcher > toggle-group.{labels,icons,both} (:826-850).
|
|
321
404
|
for (const mode of INLINE_VIEW_SWITCHER_DISPLAY_MODES) {
|
|
@@ -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,41 @@ 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 PANE STYLE CLASSES, and only while docked. libadwaita builds the
|
|
277
|
+
// two `.sidebar-pane` / `.content-pane` bins in the UNCOLLAPSED branch
|
|
278
|
+
// alone (:583-608); collapsed, both pages move into one AdwNavigationView
|
|
279
|
+
// and neither bin exists (:552-568) — so a collapsed sidebar is a full
|
|
280
|
+
// window page on the WINDOW background, not a sidebar-coloured one.
|
|
281
|
+
// Painting it `--sidebar-bg-color` at every width, as this did, is the
|
|
282
|
+
// same defect the overlay view has with `.overlay-pane`.
|
|
283
|
+
this._sidebarEl?.classList.toggle('sidebar-pane', !state.collapsed);
|
|
284
|
+
this._contentEl?.classList.toggle('content-pane', !state.collapsed);
|
|
285
|
+
|
|
286
|
+
// The STACK, not the two flags: `update_navigation_stack` (:342-405)
|
|
287
|
+
// keeps a LONE child visible whatever `show-content` says, and with
|
|
288
|
+
// `sidebar-position: end` the CONTENT is the root page — so the sidebar
|
|
289
|
+
// is PUSHED ON TOP of it rather than replacing it.
|
|
290
|
+
const { stack } = state.stack;
|
|
291
|
+
const visible = stack[stack.length - 1] ?? null;
|
|
292
|
+
if (this._sidebarEl) this._sidebarEl.dataset.paneVisible = String(!state.collapsed || visible === 'sidebar');
|
|
293
|
+
if (this._contentEl) this._contentEl.dataset.paneVisible = String(!state.collapsed || visible === 'content');
|
|
119
294
|
}
|
|
120
295
|
|
|
121
296
|
/** (Re)bind the `breakpoint` condition to `collapsed`. */
|