@gjsify/adwaita-web 0.42.0 → 0.44.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 +27 -0
- package/lib/types/adw-alert-dialog.spec.d.ts +1 -0
- package/lib/types/elements/adw-alert-dialog.d.ts +12 -0
- package/lib/types/elements/adw-carousel.d.ts +8 -0
- package/lib/types/elements/adw-entry-row.d.ts +5 -7
- package/lib/types/elements/adw-header-bar.d.ts +7 -0
- package/lib/types/elements/adw-navigation-split-view.d.ts +9 -0
- package/lib/types/elements/adw-preferences-group.d.ts +18 -9
- package/lib/types/elements/swipe-drag.d.ts +63 -0
- package/lib/types/slotted-children.d.ts +65 -0
- package/lib/types/slotted-children.spec.d.ts +1 -0
- package/package.json +6 -6
- package/src/adw-alert-dialog.spec.ts +146 -0
- package/src/adw-header-bar.spec.ts +4 -2
- package/src/elements/adw-action-row.ts +8 -6
- package/src/elements/adw-alert-dialog.ts +99 -23
- package/src/elements/adw-carousel.ts +86 -5
- package/src/elements/adw-dialog.ts +5 -6
- package/src/elements/adw-entry-row.ts +33 -34
- package/src/elements/adw-expander-row.ts +19 -15
- package/src/elements/adw-header-bar.ts +39 -20
- package/src/elements/adw-navigation-split-view.ts +31 -8
- package/src/elements/adw-overlay-split-view.ts +11 -12
- package/src/elements/adw-preferences-dialog.ts +7 -8
- package/src/elements/adw-preferences-group.ts +37 -36
- package/src/elements/adw-status-page.ts +10 -4
- package/src/elements/adw-toast-overlay.ts +6 -4
- package/src/elements/adw-toolbar-view.ts +16 -11
- package/src/elements/swipe-drag.ts +450 -0
- package/src/slotted-children.spec.ts +227 -0
- package/src/slotted-children.ts +212 -0
- package/src/test.browser.mts +4 -0
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
} from '@gjsify/adwaita-core';
|
|
37
37
|
|
|
38
38
|
import { bindBreakpointSetter } from '../breakpoints.js';
|
|
39
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
39
40
|
|
|
40
41
|
/** Detail of the `navigation-push` / `navigation-pop` delegation event. */
|
|
41
42
|
export interface NavigationDelegateDetail {
|
|
@@ -142,18 +143,24 @@ export class AdwNavigationSplitView extends HTMLElement {
|
|
|
142
143
|
if (this._initialized) return;
|
|
143
144
|
this._initialized = true;
|
|
144
145
|
|
|
145
|
-
const sidebarChildren = Array.from(this.querySelectorAll(':scope > [slot="sidebar"]'));
|
|
146
|
-
const contentChildren = Array.from(this.querySelectorAll(':scope > [slot="content"]'));
|
|
147
|
-
|
|
148
146
|
this._sidebarEl = document.createElement('div');
|
|
149
147
|
this._sidebarEl.className = 'adw-nsv-sidebar';
|
|
150
|
-
for (const child of sidebarChildren) this._sidebarEl.appendChild(child);
|
|
151
148
|
|
|
152
149
|
this._contentEl = document.createElement('div');
|
|
153
150
|
this._contentEl.className = 'adw-nsv-content';
|
|
154
|
-
for (const child of contentChildren) this._contentEl.appendChild(child);
|
|
155
151
|
|
|
156
|
-
|
|
152
|
+
// Both panes stay LIVE. `adw_navigation_split_view_set_sidebar` is a property
|
|
153
|
+
// setter, callable whenever, so a pane appended after connect has to reach the
|
|
154
|
+
// pane box AND the state that decides the stack — which is what `_mountPane` does
|
|
155
|
+
// on the way. `src/slotted-children.ts` has the incident.
|
|
156
|
+
bindSlottedChildren(
|
|
157
|
+
this,
|
|
158
|
+
[
|
|
159
|
+
{ name: 'sidebar', into: this._sidebarEl },
|
|
160
|
+
{ name: 'content', into: this._contentEl },
|
|
161
|
+
],
|
|
162
|
+
(_node, slot) => this._mountPane(slot.name === 'sidebar' ? 'sidebar' : 'content'),
|
|
163
|
+
).install(this._sidebarEl, this._contentEl);
|
|
157
164
|
|
|
158
165
|
// Parse-time attributes are the CONSTRUCTION properties, as in the overlay view:
|
|
159
166
|
// applying them as sequential setters emits a transition for a state the markup
|
|
@@ -175,14 +182,30 @@ export class AdwNavigationSplitView extends HTMLElement {
|
|
|
175
182
|
// Which children EXIST decides the stack, so the panes are mounted into the state
|
|
176
183
|
// and not merely into the DOM — a lone child stays visible whatever
|
|
177
184
|
// `show-content` says, which two CSS classes cannot express.
|
|
178
|
-
this.
|
|
179
|
-
this.
|
|
185
|
+
this._mountPane('sidebar');
|
|
186
|
+
this._mountPane('content');
|
|
180
187
|
this._state.subscribe(() => {
|
|
181
188
|
this._reflectShowContent();
|
|
182
189
|
this._syncClasses();
|
|
183
190
|
});
|
|
184
191
|
}
|
|
185
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Mount whatever a pane box currently HOLDS into the state — `null` while it is empty.
|
|
195
|
+
*
|
|
196
|
+
* Re-mounting an unchanged pane re-emits the plan, because the state compares the page
|
|
197
|
+
* ref by identity. Both subscribers are idempotent reflections (an attribute and a
|
|
198
|
+
* class list), so a second child in the same pane costs a repaint decision and never a
|
|
199
|
+
* navigation event.
|
|
200
|
+
*/
|
|
201
|
+
private _mountPane(pane: 'sidebar' | 'content'): void {
|
|
202
|
+
const box = pane === 'sidebar' ? this._sidebarEl : this._contentEl;
|
|
203
|
+
const children = Array.from(box.children);
|
|
204
|
+
const page = children.length > 0 ? { tag: this._tagOf(children) } : null;
|
|
205
|
+
if (pane === 'sidebar') this._state.setSidebar(page);
|
|
206
|
+
else this._state.setContent(page);
|
|
207
|
+
}
|
|
208
|
+
|
|
186
209
|
/** `Adw.NavigationPage:tag` off the slotted pane, or `null` when untagged. */
|
|
187
210
|
private _tagOf(children: readonly Element[]): string | null {
|
|
188
211
|
for (const child of children) {
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from '@gjsify/adwaita-core';
|
|
27
27
|
|
|
28
28
|
import { bindBreakpointSetter } from '../breakpoints.js';
|
|
29
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* The reveal spring, as a CSS-timed animator: libadwaita animates with a spring
|
|
@@ -173,28 +174,26 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
173
174
|
if (this._initialized) return;
|
|
174
175
|
this._initialized = true;
|
|
175
176
|
|
|
176
|
-
const sidebarChildren = Array.from(this.querySelectorAll('[slot="sidebar"]'));
|
|
177
|
-
const contentChildren = Array.from(this.querySelectorAll('[slot="content"]'));
|
|
178
|
-
const unslotted = Array.from(this.childNodes).filter(
|
|
179
|
-
(n) => !sidebarChildren.includes(n as Element) && !contentChildren.includes(n as Element),
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
this.replaceChildren();
|
|
183
|
-
|
|
184
177
|
this._sidebarEl = document.createElement('div');
|
|
185
178
|
this._sidebarEl.className = 'adw-osv-sidebar';
|
|
186
|
-
sidebarChildren.forEach((c) => this._sidebarEl.appendChild(c));
|
|
187
179
|
|
|
188
180
|
this._contentEl = document.createElement('div');
|
|
189
181
|
this._contentEl.className = 'adw-osv-content';
|
|
190
|
-
contentChildren.forEach((c) => this._contentEl.appendChild(c));
|
|
191
|
-
unslotted.forEach((c) => this._contentEl.appendChild(c));
|
|
192
182
|
|
|
193
183
|
this._backdropEl = document.createElement('div');
|
|
194
184
|
this._backdropEl.className = 'adw-osv-backdrop';
|
|
195
185
|
this._backdropEl.addEventListener('click', () => this.hideSidebar());
|
|
196
186
|
|
|
197
|
-
|
|
187
|
+
// Both panes stay LIVE, and the unnamed slot is the content — the
|
|
188
|
+
// Adw.OverlaySplitView buildable default. `src/slotted-children.ts` has the
|
|
189
|
+
// incident. The routing also stops at the DIRECT children, which the two
|
|
190
|
+
// selectors here did not: `[slot="sidebar"]` matched at any depth, so a nested
|
|
191
|
+
// split view's sidebar was stolen into the outer one's pane.
|
|
192
|
+
bindSlottedChildren(this, [
|
|
193
|
+
{ name: 'sidebar', into: this._sidebarEl },
|
|
194
|
+
{ name: 'content', into: this._contentEl },
|
|
195
|
+
{ into: this._contentEl },
|
|
196
|
+
]).install(this._sidebarEl, this._contentEl, this._backdropEl);
|
|
198
197
|
|
|
199
198
|
// The attributes present at parse time ARE the construction options —
|
|
200
199
|
// `Adw.OverlaySplitView` is built with its properties, not built and then
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
import type { PreferencesSearchResult, SearchPreferencesOptions } from '@gjsify/adwaita-core';
|
|
52
52
|
import { searchPreferencesDom } from '../preferences-search.js';
|
|
53
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
53
54
|
import { AdwModalSurface } from './modal-surface.js';
|
|
54
55
|
|
|
55
56
|
const DEFAULT_CONTENT_WIDTH = 640;
|
|
@@ -207,12 +208,6 @@ export class AdwPreferencesDialog extends HTMLElement {
|
|
|
207
208
|
if (this._initialized) return;
|
|
208
209
|
this._initialized = true;
|
|
209
210
|
|
|
210
|
-
// The page ELEMENTS move into the body intact — their title / name / icon-name
|
|
211
|
-
// are what a search result subtitle and a view switcher read — and any unwrapped
|
|
212
|
-
// children follow them (Adw.PreferencesDialog's buildable default adds a child
|
|
213
|
-
// as a page).
|
|
214
|
-
const declared = Array.from(this.childNodes);
|
|
215
|
-
|
|
216
211
|
// Clicking the scrim dismisses the dialog, or raises close-attempt when locked.
|
|
217
212
|
this._scrimEl = document.createElement('div');
|
|
218
213
|
this._scrimEl.className = 'adw-preferences-dialog-scrim';
|
|
@@ -253,12 +248,16 @@ export class AdwPreferencesDialog extends HTMLElement {
|
|
|
253
248
|
|
|
254
249
|
this._pagesEl = document.createElement('div');
|
|
255
250
|
this._pagesEl.className = 'adw-preferences-dialog-pages';
|
|
256
|
-
for (const child of declared) this._pagesEl.appendChild(child);
|
|
257
251
|
this._bodyEl.appendChild(this._pagesEl);
|
|
258
252
|
|
|
259
253
|
this._dialogEl.append(header, this._bodyEl);
|
|
260
254
|
|
|
261
|
-
|
|
255
|
+
// The page ELEMENTS move into the body intact — their title / name / icon-name are
|
|
256
|
+
// what a search result subtitle and a view switcher read — and any unwrapped child
|
|
257
|
+
// follows them (the Adw.PreferencesDialog buildable default adds a child as a
|
|
258
|
+
// page). LIVE, because `adw_preferences_dialog_add` works at any point in the
|
|
259
|
+
// dialog's life. `src/slotted-children.ts` has the incident.
|
|
260
|
+
bindSlottedChildren(this, [{ into: this._pagesEl }]).install(this._scrimEl, this._dialogEl);
|
|
262
261
|
|
|
263
262
|
// The role, `aria-modal`, Escape (Adw.Dialog's shortcut → `maybe_close_cb`), the
|
|
264
263
|
// Tab trap and the return-focus.
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
|
|
25
25
|
import { derivePreferencesGroupHeader } from '@gjsify/adwaita-core';
|
|
26
26
|
|
|
27
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
28
|
+
|
|
27
29
|
/** A child that asked to sit in the header rather than in the boxed list. */
|
|
28
30
|
function isHeaderSuffix(node: Node): boolean {
|
|
29
31
|
return node instanceof Element && node.getAttribute('slot') === 'header-suffix';
|
|
@@ -36,6 +38,12 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
36
38
|
private _suffixEl!: HTMLDivElement;
|
|
37
39
|
private _listboxEl!: HTMLDivElement;
|
|
38
40
|
private _initialized = false;
|
|
41
|
+
/**
|
|
42
|
+
* Watches the two BOXES, not the host: the routing of a host child is
|
|
43
|
+
* `src/slotted-children.ts`'s job now, and what is left here is the header derivation,
|
|
44
|
+
* whose inputs are the row count and whether a suffix exists. A removal straight out of
|
|
45
|
+
* the listbox changes both and reaches no other callback.
|
|
46
|
+
*/
|
|
39
47
|
private _observer: MutationObserver | null = null;
|
|
40
48
|
|
|
41
49
|
static get observedAttributes() {
|
|
@@ -55,10 +63,11 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
55
63
|
* `adw_preferences_group_add`: a row goes into the boxed list, a `header-suffix`
|
|
56
64
|
* child into the header.
|
|
57
65
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
66
|
+
* Kept as a method as well as a slot, because both spellings occur —
|
|
67
|
+
* `group.addRow(row)` in code, `group.append(row)` from a framework's rendering — and a
|
|
68
|
+
* bare child of the host would sit outside the card, invisible to the row count that
|
|
69
|
+
* decides whether the card is painted at all. The `append` spelling is routed by
|
|
70
|
+
* `src/slotted-children.ts`, which every other slotted element now shares.
|
|
62
71
|
*/
|
|
63
72
|
addRow(child: Node): void {
|
|
64
73
|
if (isHeaderSuffix(child)) this._suffixEl.appendChild(child);
|
|
@@ -81,15 +90,11 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
81
90
|
|
|
82
91
|
connectedCallback() {
|
|
83
92
|
if (this._initialized) {
|
|
84
|
-
this.
|
|
93
|
+
this._observeBoxes();
|
|
85
94
|
return;
|
|
86
95
|
}
|
|
87
96
|
this._initialized = true;
|
|
88
97
|
|
|
89
|
-
const children = Array.from(this.childNodes);
|
|
90
|
-
const suffixChildren = children.filter(isHeaderSuffix);
|
|
91
|
-
const rowChildren = children.filter((child) => !suffixChildren.includes(child));
|
|
92
|
-
|
|
93
98
|
this._headerEl = document.createElement('div');
|
|
94
99
|
this._headerEl.className = 'adw-preferences-group-header';
|
|
95
100
|
|
|
@@ -106,26 +111,31 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
106
111
|
|
|
107
112
|
this._suffixEl = document.createElement('div');
|
|
108
113
|
this._suffixEl.className = 'adw-preferences-group-suffix';
|
|
109
|
-
for (const child of suffixChildren) this._suffixEl.appendChild(child);
|
|
110
114
|
|
|
111
115
|
this._headerEl.append(labels, this._suffixEl);
|
|
112
116
|
|
|
113
117
|
this._listboxEl = document.createElement('div');
|
|
114
118
|
this._listboxEl.className = 'adw-preferences-group-listbox';
|
|
115
|
-
for (const child of rowChildren) this._listboxEl.appendChild(child);
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
bindSlottedChildren(
|
|
121
|
+
this,
|
|
122
|
+
[{ name: 'header-suffix', into: this._suffixEl }, { into: this._listboxEl }],
|
|
123
|
+
// Redundant while this group is connected — the listbox record covers it — and
|
|
124
|
+
// the only thing that covers it while it is DETACHED, where `_observeBoxes` is
|
|
125
|
+
// deliberately off. A row appended to a parked group must be right when it
|
|
126
|
+
// comes back, not one derivation behind.
|
|
127
|
+
() => this._renderHeader(),
|
|
128
|
+
).install(this._headerEl, this._listboxEl);
|
|
118
129
|
// adw-preferences-group.c:319 — GTK_ACCESSIBLE_ROLE_GROUP. A GROUP, not a list:
|
|
119
130
|
// that is why the rows inside carry no `listitem` role, which outside a list
|
|
120
131
|
// would be worse than none.
|
|
121
132
|
this.setAttribute('role', 'group');
|
|
122
133
|
|
|
123
|
-
this.
|
|
124
|
-
this._observeHost();
|
|
134
|
+
this._observeBoxes();
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
disconnectedCallback() {
|
|
128
|
-
// The observer holds this element alive through the
|
|
138
|
+
// The observer holds this element alive through the boxes it watches; drop it
|
|
129
139
|
// with the connection that created it, not at some later "cleanup".
|
|
130
140
|
this._observer?.disconnect();
|
|
131
141
|
this._observer = null;
|
|
@@ -136,35 +146,26 @@ export class AdwPreferencesGroup extends HTMLElement {
|
|
|
136
146
|
}
|
|
137
147
|
|
|
138
148
|
/**
|
|
139
|
-
*
|
|
140
|
-
* `group.append(row)` behaves like `adw_preferences_group_add`.
|
|
149
|
+
* Re-derive the header whenever the two boxes change, in EITHER direction.
|
|
141
150
|
*
|
|
142
151
|
* C gets this for free: `gtk_widget_observe_children` on the listbox drives
|
|
143
|
-
* `update_listbox_visibility` through `items-changed`. A DOM element needs the
|
|
144
|
-
*
|
|
152
|
+
* `update_listbox_visibility` through `items-changed`. A DOM element needs the observer
|
|
153
|
+
* for the same rule to hold over time rather than only at connect. Adoption is not what
|
|
154
|
+
* this watches — a row appended to the host reaches the listbox through the shared slot
|
|
155
|
+
* routing, and lands here as the listbox record it causes. A row REMOVED straight out
|
|
156
|
+
* of the listbox reaches no other callback at all, and it still changes the row count.
|
|
145
157
|
*/
|
|
146
|
-
private
|
|
158
|
+
private _observeBoxes(): void {
|
|
147
159
|
if (this._observer) return;
|
|
148
|
-
this._observer = new MutationObserver((
|
|
149
|
-
for (const record of records) {
|
|
150
|
-
// Only the HOST's own children need re-homing. Records from the listbox are
|
|
151
|
-
// the CONSEQUENCE of that move; re-adopting them would append each row a
|
|
152
|
-
// second time and reorder the list.
|
|
153
|
-
if (record.target !== this) continue;
|
|
154
|
-
for (const node of record.addedNodes) {
|
|
155
|
-
if (!(node instanceof Element)) continue;
|
|
156
|
-
if (node === this._headerEl || node === this._listboxEl) continue;
|
|
157
|
-
this.addRow(node);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// A removal straight out of the listbox still changes the row count.
|
|
161
|
-
this._renderHeader();
|
|
162
|
-
});
|
|
163
|
-
this._observer.observe(this, { childList: true });
|
|
160
|
+
this._observer = new MutationObserver(() => this._renderHeader());
|
|
164
161
|
this._observer.observe(this._listboxEl, { childList: true });
|
|
165
162
|
// The suffix too: `hasHeaderSuffix` is an INPUT to the header derivation, so a
|
|
166
163
|
// suffix appended after connect left the header hidden on a group with no title.
|
|
167
164
|
this._observer.observe(this._suffixEl, { childList: true });
|
|
165
|
+
// Arming and deriving are ONE act, as in `src/empty-sections.ts`: a group that was
|
|
166
|
+
// parked, filled and re-attached would otherwise come back showing the header state
|
|
167
|
+
// it had when it left.
|
|
168
|
+
this._renderHeader();
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
private _renderHeader() {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { stringIsNotEmpty } from '@gjsify/adwaita-core';
|
|
11
11
|
|
|
12
12
|
import { bindEmptySections } from '../empty-sections.js';
|
|
13
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
13
14
|
import { type AdwIcon, createAdwIcon } from './adw-icon.js';
|
|
14
15
|
|
|
15
16
|
export class AdwStatusPage extends HTMLElement {
|
|
@@ -27,8 +28,6 @@ export class AdwStatusPage extends HTMLElement {
|
|
|
27
28
|
if (this._initialized) return;
|
|
28
29
|
this._initialized = true;
|
|
29
30
|
|
|
30
|
-
const children = Array.from(this.childNodes);
|
|
31
|
-
|
|
32
31
|
this._iconEl = createAdwIcon(null, 'adw-status-page-icon');
|
|
33
32
|
|
|
34
33
|
this._titleEl = document.createElement('span');
|
|
@@ -39,9 +38,16 @@ export class AdwStatusPage extends HTMLElement {
|
|
|
39
38
|
|
|
40
39
|
this._childEl = document.createElement('div');
|
|
41
40
|
this._childEl.className = 'adw-status-page-child';
|
|
42
|
-
for (const child of children) this._childEl.appendChild(child);
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
// `Adw.StatusPage:child` is a property, so the action a status page offers can be
|
|
43
|
+
// set at any point — a button appended after connect has to land in the child box,
|
|
44
|
+
// not beside it. `src/slotted-children.ts` has the incident.
|
|
45
|
+
bindSlottedChildren(this, [{ into: this._childEl }]).install(
|
|
46
|
+
this._iconEl,
|
|
47
|
+
this._titleEl,
|
|
48
|
+
this._descEl,
|
|
49
|
+
this._childEl,
|
|
50
|
+
);
|
|
45
51
|
// `_render` only ever runs from `attributeChangedCallback`, which an action
|
|
46
52
|
// appended into the child slot after connect does not trigger.
|
|
47
53
|
bindEmptySections(this._childEl);
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import { AdwToast, AdwToastQueue } from '@gjsify/adwaita-core';
|
|
21
21
|
|
|
22
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
22
23
|
import { createAdwIcon } from './adw-icon.js';
|
|
23
24
|
import type { ToastScheduler, ToastTimerHandle } from '@gjsify/adwaita-core';
|
|
24
25
|
|
|
@@ -86,16 +87,17 @@ export class AdwToastOverlay extends HTMLElement {
|
|
|
86
87
|
if (this._initialized) return;
|
|
87
88
|
this._initialized = true;
|
|
88
89
|
|
|
89
|
-
// The
|
|
90
|
-
//
|
|
90
|
+
// The children become the wrapped content (Adw.ToastOverlay:child); the toast
|
|
91
|
+
// container is layered over the bottom edge. LIVE, because the overlay is the
|
|
92
|
+
// outermost widget of a window and the app it wraps is very often appended to it
|
|
93
|
+
// after it exists. `src/slotted-children.ts` has the incident.
|
|
91
94
|
const content = document.createElement('div');
|
|
92
95
|
content.className = 'adw-toast-overlay-content';
|
|
93
|
-
for (const child of Array.from(this.childNodes)) content.appendChild(child);
|
|
94
96
|
|
|
95
97
|
this._toasts = document.createElement('div');
|
|
96
98
|
this._toasts.className = 'adw-toast-overlay-toasts';
|
|
97
99
|
|
|
98
|
-
this.
|
|
100
|
+
bindSlottedChildren(this, [{ into: content }]).install(content, this._toasts);
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
/**
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
} from '@gjsify/adwaita-core';
|
|
33
33
|
import { bindEmptySections } from '../empty-sections.js';
|
|
34
34
|
import { AdwScrollShading } from '../scroll-shading.js';
|
|
35
|
+
import { bindSlottedChildren } from '../slotted-children.js';
|
|
35
36
|
|
|
36
37
|
export class AdwToolbarView extends HTMLElement {
|
|
37
38
|
private _topEl!: HTMLDivElement;
|
|
@@ -71,31 +72,35 @@ export class AdwToolbarView extends HTMLElement {
|
|
|
71
72
|
if (!this._initialized) {
|
|
72
73
|
this._initialized = true;
|
|
73
74
|
|
|
74
|
-
const topChildren = Array.from(this.querySelectorAll(':scope > [slot="top"]'));
|
|
75
|
-
const bottomChildren = Array.from(this.querySelectorAll(':scope > [slot="bottom"]'));
|
|
76
|
-
const contentChildren = Array.from(this.childNodes).filter(
|
|
77
|
-
(n) => !topChildren.includes(n as Element) && !bottomChildren.includes(n as Element),
|
|
78
|
-
);
|
|
79
|
-
|
|
80
75
|
this._topEl = document.createElement('div');
|
|
81
76
|
this._topEl.className = 'adw-toolbar-view-top';
|
|
82
|
-
for (const child of topChildren) this._topEl.appendChild(child);
|
|
83
77
|
|
|
84
78
|
this._contentEl = document.createElement('div');
|
|
85
79
|
this._contentEl.className = 'adw-toolbar-view-content';
|
|
86
|
-
for (const child of contentChildren) this._contentEl.appendChild(child);
|
|
87
80
|
|
|
88
81
|
this._bottomEl = document.createElement('div');
|
|
89
82
|
this._bottomEl.className = 'adw-toolbar-view-bottom';
|
|
90
|
-
|
|
83
|
+
|
|
84
|
+
// `topBar` invites an imperative append, and a DECLARED `slot="top"` bar has to
|
|
85
|
+
// reach the same box whenever it is written — including after connect, which a
|
|
86
|
+
// snapshot here cannot see. The unnamed slot is the content, the
|
|
87
|
+
// Adw.ToolbarView buildable default. `src/slotted-children.ts` has the incident.
|
|
88
|
+
bindSlottedChildren(this, [
|
|
89
|
+
{ name: 'top', into: this._topEl },
|
|
90
|
+
{ name: 'bottom', into: this._bottomEl },
|
|
91
|
+
{ into: this._contentEl },
|
|
92
|
+
]).install(this._topEl, this._contentEl, this._bottomEl);
|
|
91
93
|
|
|
92
94
|
// A bar appended imperatively — which `topBar` invites — is a childList
|
|
93
95
|
// change, not an attribute one, so the empty/non-empty decision cannot be a
|
|
94
96
|
// one-shot read here. The ResizeObserver below then sees the bar go 0 → 48
|
|
95
97
|
// and the undershoot classes follow on their own.
|
|
98
|
+
//
|
|
99
|
+
// AFTER the slot routing, and that order is load-bearing: this derives once
|
|
100
|
+
// SYNCHRONOUSLY, so on empty boxes it hides both and only un-hides them a
|
|
101
|
+
// microtask later — long after `_syncClasses` has measured a declared bar at
|
|
102
|
+
// `offsetHeight` 0 and dropped the undershoot classes for good.
|
|
96
103
|
bindEmptySections(this._topEl, this._bottomEl);
|
|
97
|
-
|
|
98
|
-
this.replaceChildren(this._topEl, this._contentEl, this._bottomEl);
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
// `update_undershoots` runs from size_allocate, so the classes have to
|