@gjsify/adwaita-web 0.36.0 → 0.37.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 (146) hide show
  1. package/dist/adwaita-web.css +433 -33
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
  4. package/lib/types/breakpoints.d.ts +10 -12
  5. package/lib/types/elements/adw-about-dialog.d.ts +19 -36
  6. package/lib/types/elements/adw-action-row.d.ts +9 -12
  7. package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
  8. package/lib/types/elements/adw-avatar.d.ts +1 -0
  9. package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
  10. package/lib/types/elements/adw-button-content.d.ts +3 -4
  11. package/lib/types/elements/adw-button-row.d.ts +1 -1
  12. package/lib/types/elements/adw-carousel.d.ts +28 -49
  13. package/lib/types/elements/adw-checks.d.ts +2 -8
  14. package/lib/types/elements/adw-combo-row.d.ts +6 -6
  15. package/lib/types/elements/adw-data-grid.d.ts +5 -10
  16. package/lib/types/elements/adw-drop-down.d.ts +3 -11
  17. package/lib/types/elements/adw-entry-row.d.ts +20 -23
  18. package/lib/types/elements/adw-icon.d.ts +2 -4
  19. package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
  20. package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
  21. package/lib/types/elements/adw-navigation-view.d.ts +2 -10
  22. package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
  23. package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
  24. package/lib/types/elements/adw-popover.d.ts +2 -7
  25. package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
  26. package/lib/types/elements/adw-preferences-group.d.ts +12 -14
  27. package/lib/types/elements/adw-progress-bar.d.ts +3 -4
  28. package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
  29. package/lib/types/elements/adw-sidebar.d.ts +5 -10
  30. package/lib/types/elements/adw-spinner.d.ts +3 -5
  31. package/lib/types/elements/adw-split-button.d.ts +6 -16
  32. package/lib/types/elements/adw-switch-row.d.ts +3 -3
  33. package/lib/types/elements/adw-tab-view.d.ts +16 -26
  34. package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
  35. package/lib/types/elements/adw-view-stack.d.ts +7 -17
  36. package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
  37. package/lib/types/elements/adw-view-switcher.d.ts +18 -38
  38. package/lib/types/elements/adw-wrap-box.d.ts +7 -17
  39. package/lib/types/elements/view-switcher-dom.d.ts +10 -13
  40. package/lib/types/index.d.ts +1 -0
  41. package/lib/types/source-view/hex.d.ts +7 -13
  42. package/lib/types/style-classes.spec.d.ts +11 -0
  43. package/lib/types/styles.generated.d.ts +1 -1
  44. package/package.json +6 -6
  45. package/scss/_about_dialog.scss +3 -2
  46. package/scss/_action_row.scss +33 -0
  47. package/scss/_avatar.scss +12 -0
  48. package/scss/_button.scss +44 -22
  49. package/scss/_card.scss +11 -8
  50. package/scss/_checks.scss +2 -4
  51. package/scss/_dialog.scss +3 -2
  52. package/scss/_headerbar.scss +23 -0
  53. package/scss/_popover.scss +8 -4
  54. package/scss/_preferences.scss +7 -7
  55. package/scss/_progress_bar.scss +9 -6
  56. package/scss/_reset.scss +1 -1
  57. package/scss/_shortcut_label.scss +80 -0
  58. package/scss/_sidebar.scss +57 -0
  59. package/scss/_status_page.scss +20 -0
  60. package/scss/_style_classes.scss +407 -0
  61. package/scss/_switch.scss +4 -3
  62. package/scss/_variables.scss +25 -0
  63. package/scss/adwaita-skin.scss +12 -0
  64. package/src/adw-about-dialog.spec.ts +34 -49
  65. package/src/adw-action-rows.spec.ts +25 -33
  66. package/src/adw-avatar.spec.ts +51 -20
  67. package/src/adw-banner.spec.ts +5 -8
  68. package/src/adw-button-content.spec.ts +9 -12
  69. package/src/adw-button-row.spec.ts +2 -4
  70. package/src/adw-carousel.spec.ts +40 -53
  71. package/src/adw-checks.spec.ts +36 -47
  72. package/src/adw-drop-down.spec.ts +25 -32
  73. package/src/adw-header-bar.spec.ts +5 -7
  74. package/src/adw-icon.spec.ts +23 -32
  75. package/src/adw-progress-bar.spec.ts +10 -12
  76. package/src/adw-row-state.spec.ts +9 -10
  77. package/src/adw-shortcut-label.spec.ts +112 -0
  78. package/src/adw-sidebar.spec.ts +17 -24
  79. package/src/adw-switch.spec.ts +11 -17
  80. package/src/adw-tab-view.spec.ts +7 -10
  81. package/src/adw-toast-overlay.spec.ts +7 -11
  82. package/src/adw-view-switcher-bar.spec.ts +10 -14
  83. package/src/adw-wrap-box.spec.ts +25 -36
  84. package/src/bottom-sheet.spec.ts +30 -36
  85. package/src/breakpoints.ts +15 -18
  86. package/src/chrome.spec.ts +13 -21
  87. package/src/elements/adw-about-dialog.ts +71 -114
  88. package/src/elements/adw-action-row.ts +22 -37
  89. package/src/elements/adw-alert-dialog.ts +25 -50
  90. package/src/elements/adw-avatar.ts +40 -15
  91. package/src/elements/adw-banner.ts +14 -18
  92. package/src/elements/adw-bottom-sheet.ts +31 -52
  93. package/src/elements/adw-button-content.ts +30 -37
  94. package/src/elements/adw-button-row.ts +14 -24
  95. package/src/elements/adw-carousel.ts +87 -148
  96. package/src/elements/adw-checks.ts +43 -56
  97. package/src/elements/adw-clamp.ts +8 -9
  98. package/src/elements/adw-combo-row.ts +17 -25
  99. package/src/elements/adw-data-grid.ts +26 -38
  100. package/src/elements/adw-dialog.ts +25 -56
  101. package/src/elements/adw-drop-down.ts +31 -51
  102. package/src/elements/adw-entry-row.ts +39 -46
  103. package/src/elements/adw-expander-row.ts +30 -53
  104. package/src/elements/adw-icon.ts +30 -53
  105. package/src/elements/adw-inline-view-switcher.ts +38 -67
  106. package/src/elements/adw-menu-button.ts +28 -58
  107. package/src/elements/adw-navigation-split-view.ts +35 -47
  108. package/src/elements/adw-navigation-view.ts +12 -28
  109. package/src/elements/adw-overlay-split-view.ts +100 -175
  110. package/src/elements/adw-password-entry-row.ts +18 -28
  111. package/src/elements/adw-popover.ts +28 -44
  112. package/src/elements/adw-preferences-dialog.ts +41 -66
  113. package/src/elements/adw-preferences-group.ts +33 -46
  114. package/src/elements/adw-progress-bar.ts +27 -47
  115. package/src/elements/adw-shortcut-label.ts +138 -0
  116. package/src/elements/adw-sidebar.ts +31 -39
  117. package/src/elements/adw-spin-row.ts +7 -14
  118. package/src/elements/adw-spinner.ts +31 -43
  119. package/src/elements/adw-split-button.ts +45 -75
  120. package/src/elements/adw-switch-row.ts +29 -46
  121. package/src/elements/adw-switch.ts +28 -47
  122. package/src/elements/adw-tab-view.ts +62 -103
  123. package/src/elements/adw-toast-overlay.ts +28 -35
  124. package/src/elements/adw-toggle-group.ts +9 -16
  125. package/src/elements/adw-view-stack.ts +32 -51
  126. package/src/elements/adw-view-switcher-bar.ts +26 -50
  127. package/src/elements/adw-view-switcher.ts +60 -110
  128. package/src/elements/adw-window-title.ts +4 -4
  129. package/src/elements/adw-wrap-box.ts +33 -59
  130. package/src/elements/view-switcher-dom.ts +15 -22
  131. package/src/entry-rows.spec.ts +18 -24
  132. package/src/index.ts +13 -15
  133. package/src/navigation-view.spec.ts +7 -10
  134. package/src/preferences-search.ts +11 -15
  135. package/src/preferences.spec.ts +13 -15
  136. package/src/source-view/adw-source-view.ts +6 -19
  137. package/src/source-view/hex.ts +7 -13
  138. package/src/split-button.spec.ts +32 -44
  139. package/src/split-views.spec.ts +83 -149
  140. package/src/style-classes.spec.ts +181 -0
  141. package/src/style-isolation.spec.ts +3 -6
  142. package/src/styles.generated.ts +1 -1
  143. package/src/tab-view.spec.ts +26 -35
  144. package/src/test.browser.mts +4 -0
  145. package/src/view-stack.spec.ts +23 -34
  146. 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
- * libadwaita animates with a spring `(1, 0.5, 500)`
34
- * (adw-overlay-split-view.c:1163-1165). A browser cannot be handed spring
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; the CURVE is an approximation and says so, the ENDPOINTS
46
- // are exact because `onDone` writes `to`.
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: fast first, settling — the visible signature of a spring
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
- * read: absence means the default, not false.
76
- *
77
- * HTML boolean attributes are presence-only, which can only ever express a FALSE
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 single source of truth for the property interplay, shared with the
96
- * NativeScript renderer (ADR 0004). The element owns the attributes and the
97
- * painting; every rule about what `collapsed` + `show-sidebar` + `pin-sidebar`
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 DEAD after construction. `pin-sidebar` was missing, which made the
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 come from the widget, not from an app's taste: 180 /
152
- // 280 / 0.25 (adw-overlay-split-view.c:1036-1075). They used to be 280 /
153
- // 400 / 0.30 here, which are the values the three.js and canvas2d showcases
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, it is not built
217
- // and then mutated. Applying them as sequential setters instead would
218
- // fire the collapse transition and auto-hide a sidebar that the markup
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 — the reveal is a CONTINUUM now, not two
230
- // end states, which is what the five OVERLAY_SWIPE_* tables describe.
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` (adw-overlay-split-view.c:705-716) — absent from
237
- // BOTH ports, which made `OverlaySplitViewState.escape()` dead code.
238
- // Bound on the element, so it only fires while focus is inside the view
239
- // and still propagates when the state declines to consume it.
240
- //
241
- // Bound ONCE, here, and never removed: a listener on the element itself
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
- * document — NOT once per lifetime.
254
- *
255
- * `connectedCallback` returns early on the second connect (the DOM is
256
- * already built), so anything torn down in `disconnectedCallback` used to be
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
- // use: the sidebar width is a FRACTION of it and has to be recomputed
265
- // when it changes, which a one-shot read at connect could not do.
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
- // placed. The observer's initial callback is a frame away, and on a
278
- // re-connect there may be no size change at all for it to report.
279
- // Zero is the honest answer while the view is in a `display: none`
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
- // fraction while collapsed and clamps the VIEW width instead
308
- // (adw-overlay-split-view.c:462-463) — a collapsed sidebar is a
309
- // different number, not the same one drawn differently. Leaving it out
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 it.
325
- *
326
- * Per-attribute rather than a re-read of all of them, because `show-sidebar`
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 (adw-overlay-split-view.c:990-992), so it
334
- // is an ordinary presence attribute; `show-sidebar` below is the one that
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 (adw-overlay-split-view.c:1178-1210), so they read
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
- * telling the truth after an auto-hide.
360
- *
361
- * Guarded: writing the attribute re-enters `attributeChangedCallback`, and
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` (:441-466). */
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
- // The stylesheet needs it for the divider edge, which `border-inline-*`
442
- // alone cannot get right: the divider follows where the pane is DRAWN.
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
- // THE PANE STYLE CLASSES, which libadwaita documents as part of this
446
- // widget's CSS node tree (adw-overlay-split-view.c:138-152) and switches
447
- // in `update_collapsed` (:718-741): docked, the panes are `.sidebar-pane`
448
- // and `.content-pane`; collapsed, the sidebar becomes an `.overlay-pane`
449
- // floating above a content pane that carries no class at all.
450
- //
451
- // They are not decoration. `.overlay-pane` is what makes a collapsed
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 exists while collapsed
463
- // AND revealed, and each pane is reachable by keyboard only when it is
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
- * (:572-610) and `allocate_collapsed` (:653-703), through the core.
475
- *
476
- * The slide used to be `marginRight: -offsetWidth` REGARDLESS of which edge
477
- * the sidebar is on, so an `end` sidebar slid the wrong way; and it was only
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
- // NOTHING MEASURED YET — HAND THE PLACEMENT BACK TO THE STYLESHEET.
486
- //
487
- // This used to `return` and leave the inline styles alone, which is the
488
- // one thing it must not do. The stylesheet no longer carries a
489
- // `transform: translateX(±100%)` for the hidden end state (that is the
490
- // point of driving the offset per frame), so an absolutely-positioned
491
- // pane with no `left` resolves to `left: 0` at full opacity: a HIDDEN
492
- // sidebar painted over the content at the wrong edge. Shipped that way
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
- // Floating above the content: absolutely placed at the rect the core
533
- // returned, so an overshoot widens the pane instead of detaching it.
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
- // part is taken off the edge it is ON — the left one at visual
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 (:757): below zero progress there
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
- // NOT a CSS percentage. `(int)` truncates toward zero, and a percentage
565
- // is fractional — core's own header forbids it in as many words, and
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
- * There was no gesture code in either renderer — grep for `pan` in both
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
- // leaves the element — an optimisation, not a precondition. It
626
- // THROWS for a pointer id the browser has no active pointer for,
627
- // which Firefox does strictly and Chromium tolerates: a synthetic
628
- // event, or one whose pointer was already released, aborted the
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`, :1253-1258); a released one settles on the
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
- // It now EXTENDS `<adw-entry-row>`, as `AdwPasswordEntryRow` extends
6
- // `AdwEntryRow` in the C (adw-password-entry-row.c:50). This file used to be a
7
- // near copy-paste of `adw-entry-row.ts` — a normalized diff showed the
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
- // `@gjsify/adwaita-core` (ADR 0004) as {@link PasswordEntryRowState}, which
15
- // composes the parent's `EntryRowState` exactly like the C drives its
16
- // parent through `adw_entry_row_set_show_indicator`. Two rules fall out of it
17
- // that neither port had: peeking suppresses the caps-lock warning, and so does
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
- // Attributes: everything `<adw-entry-row>` observes, plus `revealed`.
21
- // Properties: `revealed`, `capsLockOn` (+ everything inherited).
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
- * device (adw-password-entry-row.c:111-115). Public because a host that
71
- * knows better than `KeyboardEvent.getModifierState` (a virtual keyboard, a
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 (C:158-160).
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
- // C:152 — installed through add_suffix, so it is the FIRST suffix and a
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 (C:169-171); their
100
- // canonical names come from the core so both ports spell them alike.
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` (C:62-81), applied to the DOM. */
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