@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.
Files changed (46) hide show
  1. package/dist/adwaita-web.css +118 -55
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/elements/adw-combo-row.d.ts +10 -0
  4. package/lib/types/elements/adw-inline-view-switcher.d.ts +37 -1
  5. package/lib/types/elements/adw-navigation-split-view.d.ts +42 -0
  6. package/lib/types/elements/adw-overlay-split-view.d.ts +56 -0
  7. package/lib/types/elements/adw-spinner.d.ts +23 -0
  8. package/lib/types/elements/adw-view-stack.d.ts +12 -0
  9. package/lib/types/elements/adw-view-switcher-bar.d.ts +7 -5
  10. package/lib/types/elements/adw-view-switcher.d.ts +55 -3
  11. package/lib/types/elements/adw-wrap-box.d.ts +58 -3
  12. package/lib/types/styles.generated.d.ts +1 -1
  13. package/package.json +6 -6
  14. package/scss/_combo_row.scss +19 -0
  15. package/scss/_navigation_split_view.scss +52 -9
  16. package/scss/_navigation_view.scss +28 -4
  17. package/scss/_overlay_split_view.scss +122 -33
  18. package/scss/_panes.scss +57 -0
  19. package/scss/_sidebar.scss +8 -1
  20. package/scss/_spinner.scss +35 -21
  21. package/scss/_tab_view.scss +12 -13
  22. package/scss/_theme.scss +9 -0
  23. package/scss/_variables.scss +18 -1
  24. package/scss/_wrap_box.scss +9 -0
  25. package/scss/adwaita-skin.scss +1 -0
  26. package/src/adw-about-dialog.spec.ts +36 -0
  27. package/src/adw-avatar.spec.ts +37 -0
  28. package/src/adw-button-content.spec.ts +24 -0
  29. package/src/adw-row-state.spec.ts +49 -0
  30. package/src/adw-sidebar.spec.ts +32 -0
  31. package/src/adw-wrap-box.spec.ts +148 -1
  32. package/src/chrome.spec.ts +111 -12
  33. package/src/elements/adw-action-row.ts +11 -1
  34. package/src/elements/adw-combo-row.ts +17 -0
  35. package/src/elements/adw-inline-view-switcher.ts +91 -8
  36. package/src/elements/adw-navigation-split-view.ts +189 -14
  37. package/src/elements/adw-overlay-split-view.ts +395 -20
  38. package/src/elements/adw-spinner.ts +165 -21
  39. package/src/elements/adw-tab-view.ts +20 -2
  40. package/src/elements/adw-view-stack.ts +21 -0
  41. package/src/elements/adw-view-switcher-bar.ts +13 -5
  42. package/src/elements/adw-view-switcher.ts +103 -11
  43. package/src/elements/adw-wrap-box.ts +290 -135
  44. package/src/split-views.spec.ts +677 -2
  45. package/src/styles.generated.ts +1 -1
  46. 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
- private readonly _state = new ViewSwitcherState({ scheduler: domViewSwitcherScheduler });
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) return;
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.addEventListener('dragenter', () => this._state.dragEnter(model.pageIndex));
269
- toggle.addEventListener('dragleave', () => this._state.dragLeave(model.pageIndex));
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
- this.classList.toggle('flat', this.hasAttribute('flat'));
318
- this.classList.toggle('round', this.hasAttribute('round'));
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
- // Attributes: collapsed, show-content, min-sidebar-width, max-sidebar-width,
6
- // breakpoint (an Adwaita condition, e.g. "max-width: 720px", that collapses
7
- // the view by itself — the counterpart to an Adw.Breakpoint add_setter()).
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 { DEFAULT_MAX_SIDEBAR_WIDTH, DEFAULT_MIN_SIDEBAR_WIDTH, resolveSidebarBounds } from '@gjsify/adwaita-core';
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 ['collapsed', 'show-content', 'min-sidebar-width', 'max-sidebar-width', 'breakpoint'];
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') this._syncBreakpoint();
74
- else if (name === 'min-sidebar-width' || name === 'max-sidebar-width') this._syncWidth();
75
- else {
76
- this._syncClasses();
77
- // Collapsing flips whether the sidebar is width-capped, so re-apply.
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
- this.classList.toggle('collapsed', this.collapsed);
118
- this.classList.toggle('show-content', this.showContent);
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`. */