@gjsify/adwaita-web 0.36.0 → 0.38.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 +504 -34
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/accent.d.ts +28 -0
- package/lib/types/adw-accent.spec.d.ts +2 -0
- package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
- package/lib/types/breakpoints.d.ts +10 -12
- package/lib/types/elements/adw-about-dialog.d.ts +19 -36
- package/lib/types/elements/adw-action-row.d.ts +9 -12
- package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
- package/lib/types/elements/adw-avatar.d.ts +1 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
- package/lib/types/elements/adw-button-content.d.ts +3 -4
- package/lib/types/elements/adw-button-row.d.ts +1 -1
- package/lib/types/elements/adw-carousel.d.ts +28 -49
- package/lib/types/elements/adw-checks.d.ts +2 -8
- package/lib/types/elements/adw-combo-row.d.ts +6 -6
- package/lib/types/elements/adw-data-grid.d.ts +5 -10
- package/lib/types/elements/adw-drop-down.d.ts +3 -11
- package/lib/types/elements/adw-entry-row.d.ts +20 -23
- package/lib/types/elements/adw-icon.d.ts +2 -4
- package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
- package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
- package/lib/types/elements/adw-navigation-view.d.ts +2 -10
- package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
- package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
- package/lib/types/elements/adw-popover.d.ts +2 -7
- package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
- package/lib/types/elements/adw-preferences-group.d.ts +12 -14
- package/lib/types/elements/adw-progress-bar.d.ts +3 -4
- package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
- package/lib/types/elements/adw-sidebar.d.ts +5 -10
- package/lib/types/elements/adw-spinner.d.ts +3 -5
- package/lib/types/elements/adw-split-button.d.ts +6 -16
- package/lib/types/elements/adw-switch-row.d.ts +3 -3
- package/lib/types/elements/adw-tab-view.d.ts +16 -26
- package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
- package/lib/types/elements/adw-view-stack.d.ts +7 -17
- package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
- package/lib/types/elements/adw-view-switcher.d.ts +18 -38
- package/lib/types/elements/adw-wrap-box.d.ts +7 -17
- package/lib/types/elements/view-switcher-dom.d.ts +10 -13
- package/lib/types/index.d.ts +3 -0
- package/lib/types/source-view/hex.d.ts +7 -13
- package/lib/types/style-classes.spec.d.ts +11 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +18 -7
- package/scss/_about_dialog.scss +3 -2
- package/scss/_action_row.scss +33 -0
- package/scss/_avatar.scss +12 -0
- package/scss/_button.scss +44 -22
- package/scss/_card.scss +11 -8
- package/scss/_checks.scss +2 -4
- package/scss/_dialog.scss +3 -2
- package/scss/_headerbar.scss +23 -0
- package/scss/_popover.scss +8 -4
- package/scss/_preferences.scss +7 -7
- package/scss/_progress_bar.scss +9 -6
- package/scss/_reset.scss +1 -1
- package/scss/_shortcut_label.scss +80 -0
- package/scss/_sidebar.scss +57 -0
- package/scss/_status_page.scss +20 -0
- package/scss/_style_classes.scss +407 -0
- package/scss/_switch.scss +4 -3
- package/scss/_theme.scss +20 -2
- package/scss/_variables.scss +34 -1
- package/scss/adwaita-skin.scss +12 -0
- package/src/accent.ts +74 -0
- package/src/adw-about-dialog.spec.ts +34 -49
- package/src/adw-accent.spec.ts +105 -0
- package/src/adw-action-rows.spec.ts +25 -33
- package/src/adw-avatar.spec.ts +51 -20
- package/src/adw-banner.spec.ts +5 -8
- package/src/adw-button-content.spec.ts +9 -12
- package/src/adw-button-row.spec.ts +2 -4
- package/src/adw-carousel.spec.ts +40 -53
- package/src/adw-checks.spec.ts +36 -47
- package/src/adw-drop-down.spec.ts +25 -32
- package/src/adw-header-bar.spec.ts +5 -7
- package/src/adw-icon.spec.ts +23 -32
- package/src/adw-progress-bar.spec.ts +10 -12
- package/src/adw-row-state.spec.ts +9 -10
- package/src/adw-shortcut-label.spec.ts +112 -0
- package/src/adw-sidebar.spec.ts +17 -24
- package/src/adw-switch.spec.ts +11 -17
- package/src/adw-tab-view.spec.ts +7 -10
- package/src/adw-toast-overlay.spec.ts +7 -11
- package/src/adw-view-switcher-bar.spec.ts +10 -14
- package/src/adw-wrap-box.spec.ts +25 -36
- package/src/bottom-sheet.spec.ts +30 -36
- package/src/breakpoints.ts +15 -18
- package/src/chrome.spec.ts +13 -21
- package/src/elements/adw-about-dialog.ts +71 -114
- package/src/elements/adw-action-row.ts +22 -37
- package/src/elements/adw-alert-dialog.ts +25 -50
- package/src/elements/adw-avatar.ts +40 -15
- package/src/elements/adw-banner.ts +14 -18
- package/src/elements/adw-bottom-sheet.ts +31 -52
- package/src/elements/adw-button-content.ts +30 -37
- package/src/elements/adw-button-row.ts +14 -24
- package/src/elements/adw-carousel.ts +87 -148
- package/src/elements/adw-checks.ts +43 -56
- package/src/elements/adw-clamp.ts +8 -9
- package/src/elements/adw-combo-row.ts +17 -25
- package/src/elements/adw-data-grid.ts +26 -38
- package/src/elements/adw-dialog.ts +25 -56
- package/src/elements/adw-drop-down.ts +31 -51
- package/src/elements/adw-entry-row.ts +39 -46
- package/src/elements/adw-expander-row.ts +30 -53
- package/src/elements/adw-icon.ts +30 -53
- package/src/elements/adw-inline-view-switcher.ts +38 -67
- package/src/elements/adw-menu-button.ts +28 -58
- package/src/elements/adw-navigation-split-view.ts +35 -47
- package/src/elements/adw-navigation-view.ts +12 -28
- package/src/elements/adw-overlay-split-view.ts +100 -175
- package/src/elements/adw-password-entry-row.ts +18 -28
- package/src/elements/adw-popover.ts +28 -44
- package/src/elements/adw-preferences-dialog.ts +41 -66
- package/src/elements/adw-preferences-group.ts +33 -46
- package/src/elements/adw-progress-bar.ts +27 -47
- package/src/elements/adw-shortcut-label.ts +138 -0
- package/src/elements/adw-sidebar.ts +31 -39
- package/src/elements/adw-spin-row.ts +7 -14
- package/src/elements/adw-spinner.ts +31 -43
- package/src/elements/adw-split-button.ts +45 -75
- package/src/elements/adw-switch-row.ts +29 -46
- package/src/elements/adw-switch.ts +28 -47
- package/src/elements/adw-tab-view.ts +62 -103
- package/src/elements/adw-toast-overlay.ts +28 -35
- package/src/elements/adw-toggle-group.ts +9 -16
- package/src/elements/adw-view-stack.ts +32 -51
- package/src/elements/adw-view-switcher-bar.ts +26 -50
- package/src/elements/adw-view-switcher.ts +60 -110
- package/src/elements/adw-window-title.ts +4 -4
- package/src/elements/adw-wrap-box.ts +33 -59
- package/src/elements/view-switcher-dom.ts +15 -22
- package/src/entry-rows.spec.ts +18 -24
- package/src/index.ts +24 -15
- package/src/navigation-view.spec.ts +7 -10
- package/src/preferences-search.ts +11 -15
- package/src/preferences.spec.ts +13 -15
- package/src/source-view/adw-source-view.ts +6 -19
- package/src/source-view/hex.ts +7 -13
- package/src/split-button.spec.ts +32 -44
- package/src/split-views.spec.ts +83 -149
- package/src/style-classes.spec.ts +181 -0
- package/src/style-isolation.spec.ts +3 -6
- package/src/styles.generated.ts +1 -1
- package/src/tab-view.spec.ts +26 -35
- package/src/test.browser.mts +6 -0
- package/src/view-stack.spec.ts +23 -34
- package/src/view-switcher.spec.ts +56 -84
|
@@ -28,29 +28,24 @@ import {
|
|
|
28
28
|
import { bindBreakpointSetter } from '../breakpoints.js';
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* The reveal spring, as a CSS-timed animator
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* (
|
|
35
|
-
* parameters, so this drives the progress from `requestAnimationFrame` on a
|
|
36
|
-
* critically-damped approximation and lets the element write the resulting
|
|
37
|
-
* geometry — which is the same seam the NativeScript port fills with
|
|
38
|
-
* `View.animate()`, and the same one a test fills with an instant fake.
|
|
31
|
+
* The reveal spring, as a CSS-timed animator: libadwaita animates with a spring
|
|
32
|
+
* `(1, 0.5, 500)`, which a browser cannot be handed, so this approximates it from
|
|
33
|
+
* `requestAnimationFrame`. Pluggable so the NativeScript port can substitute
|
|
34
|
+
* `View.animate()` and a test an instant fake.
|
|
39
35
|
*/
|
|
40
36
|
const CSS_SPLIT_VIEW_ANIMATOR: SplitViewAnimator = {
|
|
41
37
|
animate(request) {
|
|
42
38
|
let handle = 0;
|
|
43
39
|
let cancelled = false;
|
|
44
40
|
// 250ms is the visible settle time of libadwaita's spring at these
|
|
45
|
-
// parameters
|
|
46
|
-
//
|
|
41
|
+
// parameters. The CURVE is an approximation; the ENDPOINTS are exact,
|
|
42
|
+
// because `onDone` writes `to`.
|
|
47
43
|
const duration = 250;
|
|
48
44
|
const start = performance.now();
|
|
49
45
|
const step = (now: number) => {
|
|
50
46
|
if (cancelled) return;
|
|
51
47
|
const t = Math.min((now - start) / duration, 1);
|
|
52
|
-
// ease-out
|
|
53
|
-
// with no overshoot, which is what `clamp` asks for anyway.
|
|
48
|
+
// ease-out — the visible signature of a spring with no overshoot.
|
|
54
49
|
const eased = 1 - (1 - t) * (1 - t);
|
|
55
50
|
request.onValue(request.from + (request.to - request.from) * eased);
|
|
56
51
|
if (t < 1) {
|
|
@@ -71,12 +66,10 @@ const CSS_SPLIT_VIEW_ANIMATOR: SplitViewAnimator = {
|
|
|
71
66
|
};
|
|
72
67
|
|
|
73
68
|
/**
|
|
74
|
-
* Read a boolean attribute the way a GTK property with a TRUE default has to be
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* default — so `show-sidebar` used to start hidden while `Adw.OverlaySplitView`
|
|
79
|
-
* starts shown (adw-overlay-split-view.c:974-976). `show-sidebar="false"` is the
|
|
69
|
+
* Read a boolean attribute the way a GTK property with a TRUE default has to be read:
|
|
70
|
+
* absence means the default, not false. HTML boolean attributes are presence-only,
|
|
71
|
+
* which can only ever express a FALSE default, while
|
|
72
|
+
* `Adw.OverlaySplitView:show-sidebar` starts shown — `show-sidebar="false"` is the
|
|
80
73
|
* explicit off switch.
|
|
81
74
|
*/
|
|
82
75
|
function readTriStateAttr(element: Element, name: string, fallback: boolean): boolean {
|
|
@@ -92,10 +85,9 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
92
85
|
private _backdropEl!: HTMLDivElement;
|
|
93
86
|
private _disposeBreakpoint: (() => void) | undefined;
|
|
94
87
|
/**
|
|
95
|
-
* The
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* MEAN together lives in core, held to `OVERLAY_COLLAPSE_VECTORS`.
|
|
88
|
+
* The property interplay, shared with the NativeScript renderer (ADR 0004): the
|
|
89
|
+
* element owns the attributes and the painting, core owns what `collapsed` +
|
|
90
|
+
* `show-sidebar` + `pin-sidebar` mean together, held to `OVERLAY_COLLAPSE_VECTORS`.
|
|
99
91
|
*/
|
|
100
92
|
private _state = new OverlaySplitViewState();
|
|
101
93
|
/** Re-entrancy guard for {@link _reflectShowSidebar}. */
|
|
@@ -111,9 +103,7 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
111
103
|
/**
|
|
112
104
|
* Every attribute {@link _readAttribute} handles must be listed here — an
|
|
113
105
|
* unobserved attribute never reaches `attributeChangedCallback`, so its branch
|
|
114
|
-
* is
|
|
115
|
-
* property that suppresses the collapse coupling settable only in the markup
|
|
116
|
-
* a parser saw: `view.setAttribute('pin-sidebar', '')` did nothing at all.
|
|
106
|
+
* is dead after construction and the property is settable only in parsed markup.
|
|
117
107
|
*/
|
|
118
108
|
static get observedAttributes() {
|
|
119
109
|
return [
|
|
@@ -148,13 +138,9 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
148
138
|
else this.removeAttribute('collapsed');
|
|
149
139
|
}
|
|
150
140
|
|
|
151
|
-
// The three defaults
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
// set EXPLICITLY in their `.blp` — so an app's preference had been copied
|
|
155
|
-
// into the widget. The visible cost was in the storybook, whose whole
|
|
156
|
-
// purpose is a 1:1 comparison: the GTK story takes the real defaults and the
|
|
157
|
-
// browser story took these, so the two sides showed different sidebars.
|
|
141
|
+
// The three defaults are the widget's, not an app's taste: 180 / 280 / 0.25. A
|
|
142
|
+
// showcase that wants other numbers sets them explicitly, so the storybook's GTK
|
|
143
|
+
// and browser sides stay 1:1.
|
|
158
144
|
get minSidebarWidth(): number {
|
|
159
145
|
return parseFloat(this.getAttribute('min-sidebar-width') || String(DEFAULT_MIN_SIDEBAR_WIDTH));
|
|
160
146
|
}
|
|
@@ -177,46 +163,37 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
177
163
|
this._syncBreakpoint();
|
|
178
164
|
}
|
|
179
165
|
|
|
180
|
-
/** The DOM surgery and the state, which happen once per element lifetime. */
|
|
181
166
|
private _buildOnce() {
|
|
182
167
|
if (this._initialized) return;
|
|
183
168
|
this._initialized = true;
|
|
184
169
|
|
|
185
|
-
// Capture slotted children before rebuilding DOM
|
|
186
170
|
const sidebarChildren = Array.from(this.querySelectorAll('[slot="sidebar"]'));
|
|
187
171
|
const contentChildren = Array.from(this.querySelectorAll('[slot="content"]'));
|
|
188
|
-
// Any remaining unslotted children go to content
|
|
189
172
|
const unslotted = Array.from(this.childNodes).filter(
|
|
190
173
|
(n) => !sidebarChildren.includes(n as Element) && !contentChildren.includes(n as Element),
|
|
191
174
|
);
|
|
192
175
|
|
|
193
|
-
// Clear children safely
|
|
194
176
|
this.replaceChildren();
|
|
195
177
|
|
|
196
|
-
// Sidebar container
|
|
197
178
|
this._sidebarEl = document.createElement('div');
|
|
198
179
|
this._sidebarEl.className = 'adw-osv-sidebar';
|
|
199
180
|
sidebarChildren.forEach((c) => this._sidebarEl.appendChild(c));
|
|
200
181
|
|
|
201
|
-
// Content container
|
|
202
182
|
this._contentEl = document.createElement('div');
|
|
203
183
|
this._contentEl.className = 'adw-osv-content';
|
|
204
184
|
contentChildren.forEach((c) => this._contentEl.appendChild(c));
|
|
205
185
|
unslotted.forEach((c) => this._contentEl.appendChild(c));
|
|
206
186
|
|
|
207
|
-
// Backdrop for overlay dismiss
|
|
208
187
|
this._backdropEl = document.createElement('div');
|
|
209
188
|
this._backdropEl.className = 'adw-osv-backdrop';
|
|
210
189
|
this._backdropEl.addEventListener('click', () => this.hideSidebar());
|
|
211
190
|
|
|
212
191
|
this.append(this._sidebarEl, this._contentEl, this._backdropEl);
|
|
213
192
|
|
|
214
|
-
// Apply initial state
|
|
215
193
|
// The attributes present at parse time ARE the construction options —
|
|
216
|
-
// `Adw.OverlaySplitView` is built with its properties,
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
// explicitly asked to keep shown.
|
|
194
|
+
// `Adw.OverlaySplitView` is built with its properties, not built and then
|
|
195
|
+
// mutated. Sequential setters would fire the collapse transition and
|
|
196
|
+
// auto-hide a sidebar the markup explicitly asked to keep shown.
|
|
220
197
|
this._state = new OverlaySplitViewState({
|
|
221
198
|
collapsed: this.hasAttribute('collapsed'),
|
|
222
199
|
showSidebar: readTriStateAttr(this, 'show-sidebar', true),
|
|
@@ -226,43 +203,34 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
226
203
|
enableHideGesture: readTriStateAttr(this, 'enable-hide-gesture', true),
|
|
227
204
|
animator: CSS_SPLIT_VIEW_ANIMATOR,
|
|
228
205
|
});
|
|
229
|
-
// Every progress step repaints
|
|
230
|
-
//
|
|
206
|
+
// Every progress step repaints: the reveal is a continuum, not two end
|
|
207
|
+
// states — the five OVERLAY_SWIPE_* tables describe it.
|
|
231
208
|
this._state.subscribe(() => {
|
|
232
209
|
this._reflectShowSidebar();
|
|
233
210
|
this._syncClasses();
|
|
234
211
|
});
|
|
235
212
|
|
|
236
|
-
// `escape_shortcut_cb
|
|
237
|
-
//
|
|
238
|
-
// Bound
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
// survives detaching and is collected with the element, so there is
|
|
243
|
-
// nothing to leak — while re-adding it per connect would stack a second
|
|
244
|
-
// handler on every move. Only the two bindings that reach OUTSIDE the
|
|
245
|
-
// element (the observer below and the breakpoint's media query) need a
|
|
246
|
-
// teardown, and they are re-established on every connect.
|
|
213
|
+
// `escape_shortcut_cb`, bound on the element so it only fires while focus is
|
|
214
|
+
// inside the view and still propagates when the state declines to consume it.
|
|
215
|
+
// Bound ONCE and never removed: a listener on the element itself is collected
|
|
216
|
+
// with the element, while re-adding it per connect would stack a handler on
|
|
217
|
+
// every move. Only the bindings that reach OUTSIDE the element (the observer,
|
|
218
|
+
// the breakpoint's media query) need a teardown.
|
|
247
219
|
this.addEventListener('keydown', this._onKeyDown);
|
|
248
220
|
this._bindSwipe();
|
|
249
221
|
}
|
|
250
222
|
|
|
251
223
|
/**
|
|
252
|
-
* Everything that must be re-established every time the element enters a
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* gone for good: moving the view between parents — which is what a slideshow
|
|
258
|
-
* or a client-side route change does — killed the ResizeObserver and left
|
|
259
|
-
* `_measuredWidth` frozen at whatever it last saw, or at 0 if the view had
|
|
260
|
-
* never been visible. That is the other half of the docs-site regression.
|
|
224
|
+
* Everything that must be re-established every time the element enters a document,
|
|
225
|
+
* NOT once per lifetime: `_buildOnce` returns early on the second connect, so
|
|
226
|
+
* anything torn down in `disconnectedCallback` and rebound only there would be gone
|
|
227
|
+
* for good — moving the view between parents (a slideshow, a client-side route
|
|
228
|
+
* change) would kill the ResizeObserver and freeze `_measuredWidth`.
|
|
261
229
|
*/
|
|
262
230
|
private _bindToDocument() {
|
|
263
|
-
// The view's own box is the size source, the same one the breakpoints
|
|
264
|
-
//
|
|
265
|
-
//
|
|
231
|
+
// The view's own box is the size source, the same one the breakpoints use:
|
|
232
|
+
// the sidebar width is a FRACTION of it, so a one-shot read at connect
|
|
233
|
+
// cannot keep up.
|
|
266
234
|
this._resizeObserver?.disconnect();
|
|
267
235
|
this._resizeObserver = new ResizeObserver((entries) => {
|
|
268
236
|
const entry = entries[entries.length - 1];
|
|
@@ -273,11 +241,10 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
273
241
|
this._syncClasses();
|
|
274
242
|
});
|
|
275
243
|
this._resizeObserver.observe(this);
|
|
276
|
-
// Seed synchronously so the FIRST paint after a connect is already
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
// subtree, and `_syncGeometry` knows what to do with it.
|
|
244
|
+
// Seed synchronously so the FIRST paint after a connect is already placed:
|
|
245
|
+
// the observer's initial callback is a frame away, and on a re-connect
|
|
246
|
+
// there may be no size change for it to report at all. Zero is the honest
|
|
247
|
+
// answer inside a `display: none` subtree — `_syncGeometry` handles it.
|
|
281
248
|
this._measuredWidth = this.offsetWidth;
|
|
282
249
|
}
|
|
283
250
|
|
|
@@ -303,11 +270,10 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
303
270
|
return;
|
|
304
271
|
}
|
|
305
272
|
this._readAttribute(_name);
|
|
306
|
-
// `collapsed` is in the list because `get_sidebar_width` IGNORES the
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
// is the frame-late bug the NativeScript port paid for.
|
|
273
|
+
// `collapsed` is in the list because `get_sidebar_width` IGNORES the fraction
|
|
274
|
+
// while collapsed and clamps the VIEW width instead — a collapsed sidebar is a
|
|
275
|
+
// different number, not the same one drawn differently. Omitting it lands the
|
|
276
|
+
// width a frame late.
|
|
311
277
|
if (
|
|
312
278
|
_name === 'min-sidebar-width' ||
|
|
313
279
|
_name === 'max-sidebar-width' ||
|
|
@@ -320,19 +286,15 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
320
286
|
}
|
|
321
287
|
|
|
322
288
|
/**
|
|
323
|
-
* Push ONE attribute into the state, then reflect back what the state made
|
|
324
|
-
* of
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
* is a value the state ALSO owns: collapsing auto-hides an unpinned sidebar,
|
|
328
|
-
* and a blanket re-read would immediately clobber that decision with the
|
|
329
|
-
* stale attribute.
|
|
289
|
+
* Push ONE attribute into the state, then reflect back what the state made of it.
|
|
290
|
+
* Per-attribute rather than a re-read of all of them: `show-sidebar` is a value the
|
|
291
|
+
* state ALSO owns (collapsing auto-hides an unpinned sidebar), so a blanket re-read
|
|
292
|
+
* would clobber that decision with the stale attribute.
|
|
330
293
|
*/
|
|
331
294
|
private _readAttribute(name: string) {
|
|
332
295
|
const state = this._state;
|
|
333
|
-
// `pin-sidebar` defaults FALSE
|
|
334
|
-
//
|
|
335
|
-
// cannot be, and `sidebar-position` is an enum defaulting to 'start'.
|
|
296
|
+
// `pin-sidebar` defaults FALSE, so it is an ordinary presence attribute;
|
|
297
|
+
// `show-sidebar` below is the one that cannot be.
|
|
336
298
|
if (name === 'pin-sidebar') state.setPinSidebar(this.hasAttribute('pin-sidebar'));
|
|
337
299
|
if (name === 'sidebar-position') {
|
|
338
300
|
const position = this.getAttribute('sidebar-position');
|
|
@@ -341,8 +303,7 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
341
303
|
if (name === 'show-sidebar') {
|
|
342
304
|
state.setShowSidebar(readTriStateAttr(this, 'show-sidebar', true));
|
|
343
305
|
}
|
|
344
|
-
// Both default TRUE
|
|
345
|
-
// through the tri-state helper for the same reason `show-sidebar` does.
|
|
306
|
+
// Both default TRUE, hence tri-state.
|
|
346
307
|
if (name === 'enable-show-gesture') {
|
|
347
308
|
state.setEnableShowGesture(readTriStateAttr(this, 'enable-show-gesture', true));
|
|
348
309
|
}
|
|
@@ -355,11 +316,10 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
355
316
|
}
|
|
356
317
|
|
|
357
318
|
/**
|
|
358
|
-
* Mirror the state's `showSidebar` back onto the attribute, so the DOM keeps
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
* without the flag that is an infinite reflection loop rather than a render.
|
|
319
|
+
* Mirror the state's `showSidebar` back onto the attribute, so the DOM keeps telling
|
|
320
|
+
* the truth after an auto-hide. Guarded: writing the attribute re-enters
|
|
321
|
+
* `attributeChangedCallback`, and without the flag that is an infinite reflection
|
|
322
|
+
* loop rather than a render.
|
|
363
323
|
*/
|
|
364
324
|
private _reflectShowSidebar() {
|
|
365
325
|
if (this._reflecting) return;
|
|
@@ -373,7 +333,6 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
373
333
|
}
|
|
374
334
|
}
|
|
375
335
|
|
|
376
|
-
/** (Re)bind the `breakpoint` condition to `collapsed`. */
|
|
377
336
|
private _syncBreakpoint() {
|
|
378
337
|
this._disposeBreakpoint?.();
|
|
379
338
|
this._disposeBreakpoint = bindBreakpointSetter(this, this.getAttribute('breakpoint'), (active) => {
|
|
@@ -401,18 +360,13 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
401
360
|
}
|
|
402
361
|
|
|
403
362
|
/**
|
|
404
|
-
* The reading direction `start` / `end` are resolved against
|
|
405
|
-
*
|
|
406
|
-
* `get_start_or_end` (adw-overlay-split-view.c:227-234) returns
|
|
407
|
-
* `GTK_PACK_END` under RTL, so a `start` sidebar is drawn on the RIGHT.
|
|
408
|
-
* Neither renderer mirrored any of it; the browser has the direction for
|
|
409
|
-
* free and only had to read it.
|
|
363
|
+
* The reading direction `start` / `end` are resolved against: `get_start_or_end`
|
|
364
|
+
* returns `GTK_PACK_END` under RTL, so a `start` sidebar is drawn on the RIGHT.
|
|
410
365
|
*/
|
|
411
366
|
private get _direction(): AdwTextDirection {
|
|
412
367
|
return getComputedStyle(this).direction === 'rtl' ? 'rtl' : 'ltr';
|
|
413
368
|
}
|
|
414
369
|
|
|
415
|
-
/** The sizing properties as the core takes them. */
|
|
416
370
|
private get _widthSpec() {
|
|
417
371
|
return {
|
|
418
372
|
minSidebarWidth: this.minSidebarWidth,
|
|
@@ -421,7 +375,7 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
421
375
|
};
|
|
422
376
|
}
|
|
423
377
|
|
|
424
|
-
/** The sidebar's allocated width in px — `get_sidebar_width
|
|
378
|
+
/** The sidebar's allocated width in px — `get_sidebar_width`. */
|
|
425
379
|
private get _sidebarWidth(): number {
|
|
426
380
|
return resolveOverlaySidebarWidth({
|
|
427
381
|
...this._widthSpec,
|
|
@@ -437,31 +391,24 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
437
391
|
const atStart = isSidebarAtVisualStart(state.sidebarPosition, this._direction);
|
|
438
392
|
this.classList.toggle('sidebar-start', state.sidebarPosition === 'start');
|
|
439
393
|
this.classList.toggle('sidebar-end', state.sidebarPosition === 'end');
|
|
440
|
-
// The VISUAL side, which under RTL is the opposite of the logical one.
|
|
441
|
-
//
|
|
442
|
-
//
|
|
394
|
+
// The VISUAL side, which under RTL is the opposite of the logical one. The
|
|
395
|
+
// stylesheet needs it for the divider edge, which `border-inline-*` alone
|
|
396
|
+
// cannot get right: the divider follows where the pane is DRAWN.
|
|
443
397
|
this.classList.toggle('sidebar-at-visual-start', atStart);
|
|
444
398
|
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
// sidebar drop the sidebar background for the window one, and they are
|
|
453
|
-
// the documented hook an app themes its own panes through — so leaving
|
|
454
|
-
// them off does not merely look wrong, it makes correct app CSS a no-op.
|
|
455
|
-
// The docs site is the proof: its split-view examples carried a
|
|
456
|
-
// hand-written `class="sidebar"` that styled nothing, written by someone
|
|
457
|
-
// reaching for exactly this and finding nothing there.
|
|
399
|
+
// The pane style classes are part of this widget's documented CSS node tree,
|
|
400
|
+
// switched in `update_collapsed`: docked, the panes are `.sidebar-pane` and
|
|
401
|
+
// `.content-pane`; collapsed, the sidebar becomes an `.overlay-pane` above a
|
|
402
|
+
// content pane carrying no class at all. Not decoration — `.overlay-pane` is
|
|
403
|
+
// what swaps the sidebar background for the window one, and both are the
|
|
404
|
+
// documented hook an app themes its panes through, so omitting them makes
|
|
405
|
+
// correct app CSS a no-op.
|
|
458
406
|
this._sidebarEl?.classList.toggle('sidebar-pane', !state.collapsed);
|
|
459
407
|
this._sidebarEl?.classList.toggle('overlay-pane', state.collapsed);
|
|
460
408
|
this._contentEl?.classList.toggle('content-pane', !state.collapsed);
|
|
461
409
|
|
|
462
|
-
// Derived, not re-decided here: the shield only
|
|
463
|
-
//
|
|
464
|
-
// the one on screen.
|
|
410
|
+
// Derived, not re-decided here: the shield exists only while collapsed AND
|
|
411
|
+
// revealed, and a pane is keyboard-reachable only while it is on screen.
|
|
465
412
|
if (this._backdropEl) this._backdropEl.hidden = !state.shieldVisible;
|
|
466
413
|
this._sidebarEl?.setAttribute('aria-hidden', String(!state.sidebarFocusable));
|
|
467
414
|
this._contentEl?.setAttribute('aria-hidden', String(!state.contentFocusable));
|
|
@@ -470,35 +417,23 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
470
417
|
}
|
|
471
418
|
|
|
472
419
|
/**
|
|
473
|
-
* Place the sidebar for the current progress — `allocate_uncollapsed`
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
* two states, so a swipe had nothing to drive. `layoutOverlaySplitView`
|
|
479
|
-
* returns the pane rect for any progress including the overshoot, which is
|
|
480
|
-
* what makes a continuous gesture expressible at all.
|
|
420
|
+
* Place the sidebar for the current progress — `allocate_uncollapsed` /
|
|
421
|
+
* `allocate_collapsed`, through the core. `layoutOverlaySplitView` returns the pane
|
|
422
|
+
* rect for ANY progress, overshoot included, which is what makes a continuous
|
|
423
|
+
* gesture expressible; the offset also has to follow the edge the sidebar is on, or
|
|
424
|
+
* an `end` sidebar slides the wrong way.
|
|
481
425
|
*/
|
|
482
426
|
private _syncGeometry() {
|
|
483
427
|
const sidebar = this._sidebarEl;
|
|
484
428
|
if (!sidebar) return;
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
// in v0.32.0's `feat(adwaita)` commit and visible on the docs site,
|
|
494
|
-
// whose slideshow builds each slide hidden and moves it in — so the
|
|
495
|
-
// first measurement never arrived and the fallback was all there was.
|
|
496
|
-
//
|
|
497
|
-
// Clearing rather than skipping matters just as much: a STALE inline
|
|
498
|
-
// `left` from an earlier measurement outranks the resting rule (an
|
|
499
|
-
// absolutely-positioned box with `left` + `width` ignores `right`), so
|
|
500
|
-
// leaving the old value behind would defeat the stylesheet exactly when
|
|
501
|
-
// it is needed. `_resting.scss` carries the four end states.
|
|
429
|
+
// Nothing measured yet — hand the placement back to the stylesheet, and CLEAR
|
|
430
|
+
// rather than skip. The stylesheet carries no `transform: translateX(±100%)`
|
|
431
|
+
// for the hidden end state (the point of driving the offset per frame), so an
|
|
432
|
+
// absolutely-positioned pane with no `left` resolves to `left: 0` at full
|
|
433
|
+
// opacity — a hidden sidebar painted over the content at the wrong edge. A
|
|
434
|
+
// stale inline `left` is just as bad: with `left` + `width` set, the box
|
|
435
|
+
// ignores `right` and outranks the resting rule. The resting states live in
|
|
436
|
+
// `scss/_overlay_split_view.scss`.
|
|
502
437
|
if (this._measuredWidth <= 0) {
|
|
503
438
|
for (const prop of [
|
|
504
439
|
'left',
|
|
@@ -529,23 +464,22 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
529
464
|
|
|
530
465
|
sidebar.style.width = `${layout.sidebar.width}px`;
|
|
531
466
|
if (state.collapsed) {
|
|
532
|
-
//
|
|
533
|
-
//
|
|
467
|
+
// Absolutely placed at the rect the core returned, so an overshoot
|
|
468
|
+
// widens the pane instead of detaching it.
|
|
534
469
|
sidebar.style.marginLeft = '';
|
|
535
470
|
sidebar.style.marginRight = '';
|
|
536
471
|
sidebar.style.insetInlineStart = '';
|
|
537
472
|
sidebar.style.left = `${layout.sidebar.x}px`;
|
|
538
473
|
} else {
|
|
539
|
-
// Docked: the pane keeps its place in the flex row and the hidden
|
|
540
|
-
//
|
|
541
|
-
// start, the right one at visual end.
|
|
474
|
+
// Docked: the pane keeps its place in the flex row and the hidden part
|
|
475
|
+
// is taken off the edge it is ON.
|
|
542
476
|
sidebar.style.left = '';
|
|
543
477
|
const hidden = measured - Math.trunc(measured * Math.min(state.showProgress, 1));
|
|
544
478
|
sidebar.style.marginLeft = atStart ? `${-hidden}px` : '';
|
|
545
479
|
sidebar.style.marginRight = atStart ? '' : `${-hidden}px`;
|
|
546
480
|
}
|
|
547
481
|
sidebar.style.opacity = state.showProgress <= 0 ? '0' : '1';
|
|
548
|
-
// `sidebarPainted` is the snapshot gate
|
|
482
|
+
// `sidebarPainted` is the snapshot gate: below zero progress there
|
|
549
483
|
// is nothing on screen, and a pane that is not painted must not take
|
|
550
484
|
// pointer events either.
|
|
551
485
|
sidebar.style.pointerEvents = state.sidebarPainted ? '' : 'none';
|
|
@@ -561,21 +495,15 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
561
495
|
const bounds = resolveSidebarBounds(this._widthSpec, 0, { ceil: true });
|
|
562
496
|
this._sidebarEl.style.minWidth = `${bounds.min}px`;
|
|
563
497
|
this._sidebarEl.style.maxWidth = `${bounds.max}px`;
|
|
564
|
-
//
|
|
565
|
-
// is fractional
|
|
566
|
-
// this element carried one for its whole life.
|
|
498
|
+
// px, never a CSS percentage: the C `(int)` truncates toward zero and a
|
|
499
|
+
// percentage is fractional. Core's header forbids it.
|
|
567
500
|
this._syncGeometry();
|
|
568
501
|
}
|
|
569
502
|
|
|
570
|
-
// --- swipe (Adw.Swipeable) ---
|
|
571
|
-
|
|
572
503
|
/**
|
|
573
|
-
* The pan gesture the five `OVERLAY_SWIPE_*` tables describe.
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
-
* split views returned zero — so `showProgress` was only ever 0 or 1 and the
|
|
577
|
-
* tables shipped unexercised. Pointer events give the browser the whole
|
|
578
|
-
* gesture in three handlers; every DECISION in them is core's.
|
|
504
|
+
* The `Adw.Swipeable` pan gesture the five `OVERLAY_SWIPE_*` tables describe.
|
|
505
|
+
* Pointer events give the browser the whole gesture in three handlers; every
|
|
506
|
+
* DECISION in them is core's.
|
|
579
507
|
*/
|
|
580
508
|
private _bindSwipe() {
|
|
581
509
|
this.addEventListener('pointerdown', (event) => {
|
|
@@ -621,13 +549,11 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
621
549
|
return;
|
|
622
550
|
}
|
|
623
551
|
state.beginSwipe();
|
|
624
|
-
// Capture keeps the move/up events coming once the pointer
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
// whole gesture before the progress was ever written. The swipe
|
|
630
|
-
// still works without it, so the failure is swallowed on purpose.
|
|
552
|
+
// Capture keeps the move/up events coming once the pointer leaves the
|
|
553
|
+
// element — an optimisation, not a precondition. It THROWS for a pointer
|
|
554
|
+
// id with no active pointer (Firefox strictly, Chromium tolerates), which
|
|
555
|
+
// a synthetic or already-released event hits, so the failure is swallowed
|
|
556
|
+
// rather than aborting the gesture.
|
|
631
557
|
try {
|
|
632
558
|
this.setPointerCapture(event.pointerId);
|
|
633
559
|
} catch {
|
|
@@ -646,8 +572,7 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
646
572
|
// capture above was refused, so the release is already conditional.
|
|
647
573
|
if (this.hasPointerCapture(event.pointerId)) this.releasePointerCapture(event.pointerId);
|
|
648
574
|
// A cancelled gesture snaps back to where it came from
|
|
649
|
-
// (`get_cancel_progress
|
|
650
|
-
// NEAREST allowed snap point.
|
|
575
|
+
// (`get_cancel_progress`); a released one settles on the NEAREST snap point.
|
|
651
576
|
const points = resolveSwipeSnapPoints({
|
|
652
577
|
showProgress: state.showProgress,
|
|
653
578
|
enableShowGesture: state.enableShowGesture,
|
|
@@ -2,27 +2,18 @@
|
|
|
2
2
|
// masked, a trailing peek button reveals it, and a warning shows while Caps Lock
|
|
3
3
|
// is engaged.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// connectedCallback and attributeChangedCallback bodies were the same code — so
|
|
9
|
-
// every entry-row fix had to be made twice and one of the two always drifted.
|
|
10
|
-
// What is left here is only what the C subclass adds: the masked input, the peek
|
|
11
|
-
// toggle, and the caps-lock source.
|
|
5
|
+
// EXTENDS `<adw-entry-row>`, as `AdwPasswordEntryRow` extends `AdwEntryRow` in the C, so
|
|
6
|
+
// only what the subclass adds lives here: the masked input, the peek toggle and the
|
|
7
|
+
// caps-lock source.
|
|
12
8
|
//
|
|
13
|
-
// The reveal/caps-lock DERIVATION is HEADLESS and lives in
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// losing focus.
|
|
9
|
+
// The reveal/caps-lock DERIVATION is HEADLESS and lives in `@gjsify/adwaita-core`
|
|
10
|
+
// (ADR 0004) as {@link PasswordEntryRowState}, which composes the parent's
|
|
11
|
+
// `EntryRowState` the way the C drives its parent through
|
|
12
|
+
// `adw_entry_row_set_show_indicator`. Two rules fall out of it: peeking suppresses the
|
|
13
|
+
// caps-lock warning, and so does losing focus.
|
|
19
14
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// Events: `notify::revealed` — only on a REAL change. The old implementation
|
|
23
|
-
// fired it on every `setAttribute`, including a redundant one, because
|
|
24
|
-
// attributeChangedCallback runs for a same-value write; the core's setter
|
|
25
|
-
// guard (the house rule at adw-entry-row.c:984/:1198/:1247) is what fixes it.
|
|
15
|
+
// `notify::revealed` fires only on a REAL change. That needs the core's setter guard,
|
|
16
|
+
// because `attributeChangedCallback` also runs for a same-value write.
|
|
26
17
|
//
|
|
27
18
|
// Reference: refs/libadwaita/src/adw-password-entry-row.c
|
|
28
19
|
// Reference: refs/adwaita-web/adwaita-web/docs/widgets/passwordentryrow.md
|
|
@@ -66,17 +57,16 @@ export class AdwPasswordEntryRow extends AdwEntryRow {
|
|
|
66
57
|
}
|
|
67
58
|
|
|
68
59
|
/**
|
|
69
|
-
* Feed the platform's Caps Lock state in — GDK gets it from the keyboard
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* test) can drive it directly.
|
|
60
|
+
* Feed the platform's Caps Lock state in — GDK gets it from the keyboard device.
|
|
61
|
+
* Public so a host that knows better than `KeyboardEvent.getModifierState` (a virtual
|
|
62
|
+
* keyboard, a test) can drive it directly.
|
|
73
63
|
*/
|
|
74
64
|
setCapsLockOn(on: boolean): void {
|
|
75
65
|
this._password.setCapsLockOn(on);
|
|
76
66
|
}
|
|
77
67
|
|
|
78
68
|
protected override _onConnected(): void {
|
|
79
|
-
// `gtk_text_set_visibility (…, FALSE)` + GTK_INPUT_PURPOSE_PASSWORD
|
|
69
|
+
// C's `gtk_text_set_visibility (…, FALSE)` + GTK_INPUT_PURPOSE_PASSWORD.
|
|
80
70
|
this._input.type = 'password';
|
|
81
71
|
this._input.autocomplete = 'off';
|
|
82
72
|
|
|
@@ -86,7 +76,7 @@ export class AdwPasswordEntryRow extends AdwEntryRow {
|
|
|
86
76
|
this._toggleIcon = createAdwIcon(null);
|
|
87
77
|
this._toggle.append(this._toggleIcon);
|
|
88
78
|
this._toggle.addEventListener('click', () => this._password.togglePeek());
|
|
89
|
-
//
|
|
79
|
+
// Installed through add_suffix as C does, so it is the FIRST suffix and a
|
|
90
80
|
// consumer's own suffix lands after it instead of replacing it.
|
|
91
81
|
this.addSuffix(this._toggle);
|
|
92
82
|
|
|
@@ -96,8 +86,8 @@ export class AdwPasswordEntryRow extends AdwEntryRow {
|
|
|
96
86
|
this._input.addEventListener('keydown', readCapsLock);
|
|
97
87
|
this._input.addEventListener('keyup', readCapsLock);
|
|
98
88
|
|
|
99
|
-
// The indicator icon + tooltip are set once at init
|
|
100
|
-
//
|
|
89
|
+
// The indicator icon + tooltip are set once at init, as in C; their canonical names
|
|
90
|
+
// come from the core so both ports spell them alike.
|
|
101
91
|
const initial = this._password.state;
|
|
102
92
|
this.setIndicatorIconName(initial.indicatorIconName);
|
|
103
93
|
this.setIndicatorTooltip(initial.indicatorTooltip);
|
|
@@ -125,7 +115,7 @@ export class AdwPasswordEntryRow extends AdwEntryRow {
|
|
|
125
115
|
this._password.setRevealed(value !== null && value !== 'false');
|
|
126
116
|
}
|
|
127
117
|
|
|
128
|
-
/** `notify_visibility_cb
|
|
118
|
+
/** `notify_visibility_cb`, applied to the DOM. */
|
|
129
119
|
private _renderPassword(state: PasswordEntryRowRenderState): void {
|
|
130
120
|
this._input.type = state.revealed ? 'text' : 'password';
|
|
131
121
|
// The libadwaita name travels in `data-icon-name`; the mask class is the
|