@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
@@ -0,0 +1,407 @@
1
+ // _style_classes.scss — libadwaita's documented, WIDGET-INDEPENDENT style classes.
2
+ //
3
+ // THE GAP THIS CLOSES
4
+ //
5
+ // `refs/libadwaita/doc/style-classes.md` is the public contract an app author
6
+ // reads: put `.card` on a box, `.boxed-list` on a list, `.toolbar` on a bar,
7
+ // `.linked` around a button group. Twenty-one of those classes had NO rule in
8
+ // this package (#1049) — and two of them looked ported because their names
9
+ // appear in comments (`.osd`, `.linked`) while the code spells its own
10
+ // `.adw-linked`. A consumer following the upstream documentation got nothing,
11
+ // silently, which is the worst failure mode for a styling contract.
12
+ //
13
+ // Widget-scoped classes are NOT here — they live with the widget whose markup
14
+ // they modify (`.property` in `_action_row.scss`, `.compact` in
15
+ // `_status_page.scss`, `.devel` in `_headerbar.scss`, `.osd`'s button and
16
+ // progress-bar variants in theirs), because that is where their selector needs
17
+ // to see the widget's inner nodes. `scripts/check-adwaita-style-classes.mjs`
18
+ // holds the ledger of which class lives where and fails when one has no rule at
19
+ // all.
20
+ //
21
+ // Reference: refs/libadwaita/doc/style-classes.md (the documented surface)
22
+ // Reference: refs/libadwaita/src/stylesheet/_common.scss:25-28,43-60,90-104
23
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_misc.scss:4-7,21-32,197-236
24
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_views.scss:1-18
25
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_lists.scss:520-600
26
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_toolbars.scss:91-102
27
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_linked.scss:1-45
28
+ // Reference: refs/libadwaita/src/stylesheet/widgets/_scrolling.scss:150-166
29
+ // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
30
+ // Modifications: Adapted for @gjsify/adwaita-web — GTK node names (`row`,
31
+ // `list`, `button`) become the custom-element tags and `.adw-*` structure
32
+ // classes this package renders, and GTK-only drawing (`image()`,
33
+ // `-gtk-icon-shadow`, `border-spacing` on a box) becomes its CSS equivalent.
34
+
35
+ /************
36
+ * Surfaces *
37
+ ************/
38
+
39
+ // _common.scss:25-28 — an opaque window-coloured background on any node.
40
+ .background {
41
+ background-color: var(--window-bg-color);
42
+ color: var(--window-fg-color);
43
+ }
44
+
45
+ // _views.scss:1-18 — the content-area surface. `:disabled` and `:selected` are
46
+ // GTK pseudo-classes with no DOM counterpart; `[disabled]`/`[aria-selected]`
47
+ // are how the same states are spelled here, and both are what our own widgets
48
+ // already set.
49
+ .view {
50
+ background-color: var(--view-bg-color);
51
+ color: var(--view-fg-color);
52
+
53
+ &:disabled,
54
+ &[disabled] {
55
+ color: color-mix(in srgb, var(--view-fg-color) var(--disabled-opacity), transparent);
56
+ background-color: color-mix(in srgb, var(--window-bg-color) 60%, var(--view-bg-color));
57
+ }
58
+
59
+ &[aria-selected='true'] {
60
+ background-color: color-mix(in srgb, var(--accent-bg-color) 25%, transparent);
61
+ border-radius: var(--button-radius);
62
+ }
63
+ }
64
+
65
+ // _misc.scss:4-7 — the default border. `--border-color` is derived from
66
+ // `currentColor`, so this follows the foreground of whatever it is drawn on.
67
+ .frame {
68
+ border: 1px solid var(--border-color);
69
+ }
70
+
71
+ /*********
72
+ * Cards *
73
+ *********/
74
+
75
+ // _misc.scss:197-236. The ONE card recipe: `adw-card` and the preferences
76
+ // group's list box each carried their own copy of these five declarations
77
+ // before this placeholder existed.
78
+ %card {
79
+ background-color: var(--card-bg-color);
80
+ color: var(--card-fg-color);
81
+ border-radius: var(--card-radius);
82
+ box-shadow:
83
+ 0 0 0 1px rgb(0 0 6 / 3%),
84
+ 0 1px 3px 1px rgb(0 0 6 / 7%),
85
+ 0 2px 6px 2px rgb(0 0 6 / 3%);
86
+
87
+ @media (prefers-contrast: more) {
88
+ box-shadow:
89
+ 0 0 0 1px var(--border-color),
90
+ 0 1px 3px 1px rgb(0 0 6 / 7%),
91
+ 0 2px 6px 2px rgb(0 0 6 / 3%);
92
+ }
93
+ }
94
+
95
+ .card {
96
+ @extend %card;
97
+
98
+ // `.card.activatable` gets the hover/active states of an activatable row
99
+ // (:225-235). GTK's `background-image: image(…)` is a solid-colour layer over
100
+ // the background; a one-stop gradient is its CSS equivalent, and it composes
101
+ // with `background-color` the same way — so a re-themed card keeps its colour
102
+ // and only gains the wash.
103
+ &.activatable {
104
+ transition:
105
+ background-color 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
106
+ box-shadow 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
107
+
108
+ &:hover {
109
+ background-image: linear-gradient(
110
+ color-mix(in srgb, currentColor 3%, transparent),
111
+ color-mix(in srgb, currentColor 3%, transparent)
112
+ );
113
+ }
114
+
115
+ &:active {
116
+ background-image: linear-gradient(
117
+ color-mix(in srgb, currentColor 8%, transparent),
118
+ color-mix(in srgb, currentColor 8%, transparent)
119
+ );
120
+ }
121
+ }
122
+
123
+ // A card inside (or itself) an OSD surface loses its own elevation and reads
124
+ // as a translucent inset instead (:212-220).
125
+ .osd &,
126
+ &.osd {
127
+ background-color: color-mix(in srgb, currentColor 10%, transparent);
128
+ color: rgb(255 255 255 / 90%);
129
+ box-shadow: none;
130
+
131
+ @media (prefers-contrast: more) {
132
+ box-shadow: 0 0 0 1px var(--border-color);
133
+ }
134
+ }
135
+ }
136
+
137
+ /***************
138
+ * Boxed lists *
139
+ ***************/
140
+
141
+ // _lists.scss:520-556. In GTK the row separators are drawn by the rows
142
+ // themselves and the list only supplies the card box plus the rounding of its
143
+ // two ends — which is exactly how this package's rows behave already
144
+ // (`adw-action-row` and friends draw their own `border-bottom` and drop it on
145
+ // `:last-child`), so the class is the box and the clip.
146
+ .boxed-list {
147
+ @extend %card;
148
+
149
+ display: flex;
150
+ flex-direction: column;
151
+ // The rounding of the first and last row is a CLIP here rather than per-row
152
+ // corner radii: a row is a custom element whose own background paints over
153
+ // the list's corners, and every row type would otherwise need the same four
154
+ // radii repeated.
155
+ overflow: hidden;
156
+ }
157
+
158
+ // :556-600 — same content, each row its own card. The list drops its box, and
159
+ // the rows carry it, spaced 12px apart.
160
+ .boxed-list-separate {
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: 12px;
164
+ background: none;
165
+ color: var(--card-fg-color);
166
+
167
+ > * {
168
+ @extend %card;
169
+
170
+ overflow: hidden;
171
+ // A row that draws a separator for its neighbours must not draw one when it
172
+ // IS the card (:585-586: `border: none`).
173
+ border-bottom: none;
174
+ }
175
+ }
176
+
177
+ /************
178
+ * Toolbars *
179
+ ************/
180
+
181
+ // _toolbars.scss:91-102 + doc "Toolbars". GTK's `border-spacing: 6px` on a box
182
+ // is the gap between children; the flex row is what makes that expressible.
183
+ //
184
+ // The automatic flat appearance follows upstream's rule list exactly: icon-only
185
+ // buttons, buttons with an `adw-button-content` (icon + label), menu buttons and
186
+ // split buttons go flat — unless they are `.raised`, `.suggested-action`,
187
+ // `.destructive-action`, `.opaque`, or sit inside a `.linked` group. Text-only
188
+ // buttons keep their frame, which is why this cannot be a blanket
189
+ // `.toolbar adw-button { … }`.
190
+ $_keeps-frame: ':not(.raised):not(.suggested-action):not(.destructive-action):not(.opaque)';
191
+
192
+ .toolbar {
193
+ display: flex;
194
+ align-items: center;
195
+ gap: 6px;
196
+ padding: 6px;
197
+
198
+ adw-button#{$_keeps-frame} .adw-button.icon-only,
199
+ adw-button#{$_keeps-frame}:has(adw-button-content) .adw-button,
200
+ adw-menu-button#{$_keeps-frame} .adw-button,
201
+ adw-split-button#{$_keeps-frame} .adw-button {
202
+ background-color: transparent;
203
+
204
+ &:hover {
205
+ background-color: var(--button-hover-color);
206
+ }
207
+
208
+ &:active {
209
+ background-color: var(--button-active-color);
210
+ }
211
+ }
212
+
213
+ // Inside a linked group the frames come BACK — upstream states this as its own
214
+ // rule (`_toolbars.scss:20-26`) rather than as an exclusion, because a linked
215
+ // group only reads as one control if its members are raised.
216
+ .linked,
217
+ .adw-linked {
218
+ adw-button#{$_keeps-frame} .adw-button,
219
+ adw-menu-button#{$_keeps-frame} .adw-button,
220
+ adw-split-button#{$_keeps-frame} .adw-button {
221
+ background-color: var(--button-bg-color);
222
+
223
+ &:hover {
224
+ background-color: var(--button-hover-color);
225
+ }
226
+
227
+ &:active {
228
+ background-color: var(--button-active-color);
229
+ }
230
+ }
231
+ }
232
+
233
+ // Stand-alone OSD toolbars — the floating video-player-controls shape
234
+ // (:99-102).
235
+ &.osd {
236
+ padding: 12px;
237
+ border-radius: calc(var(--button-radius) + 6px);
238
+ }
239
+ }
240
+
241
+ // _misc.scss:21-32 — a separator that reserves whitespace instead of drawing a
242
+ // line, for separating groups inside a toolbar.
243
+ .spacer {
244
+ background: none;
245
+ border: none;
246
+ flex-shrink: 0;
247
+
248
+ &,
249
+ &.horizontal {
250
+ min-width: 12px;
251
+ }
252
+
253
+ &.vertical {
254
+ min-height: 12px;
255
+ }
256
+ }
257
+
258
+ /*******
259
+ * OSD *
260
+ *******/
261
+
262
+ // _common.scss:43-60. The exclusions are upstream's: a progress bar, button,
263
+ // menu button, split button and inline view switcher each define what `.osd`
264
+ // means for them, and must not get the dark surface treatment on top.
265
+ //
266
+ // This is a token REBIND, not just a colour: everything accent-coloured inside
267
+ // an OSD surface turns white without knowing it is inside one.
268
+ .osd:not(adw-progress-bar):not(adw-button):not(adw-menu-button):not(adw-split-button):not(
269
+ adw-inline-view-switcher
270
+ ) {
271
+ --accent-bg-color: rgb(255 255 255 / 75%);
272
+ --accent-fg-color: rgb(0 0 0 / 75%);
273
+ --accent-color: oklab(from var(--accent-bg-color) max(l, 0.85) a b);
274
+ --destructive-color: oklab(from var(--destructive-bg-color) max(l, 0.85) a b);
275
+ --success-color: oklab(from var(--success-bg-color) max(l, 0.85) a b);
276
+ --warning-color: oklab(from var(--warning-bg-color) max(l, 0.85) a b);
277
+ --error-color: oklab(from var(--error-bg-color) max(l, 0.85) a b);
278
+
279
+ color: rgb(255 255 255 / 90%);
280
+ border: none;
281
+ background-color: rgb(0 0 0 / 70%);
282
+ background-clip: padding-box;
283
+ }
284
+
285
+ /*******************
286
+ * Linked controls *
287
+ *******************/
288
+
289
+ // _linked.scss:1-45 + doc "Linked Controls": the children of a linked box share
290
+ // their touching edges. Upstream squares the inner corners and overlaps the
291
+ // borders by -1px; this package's controls have no border, so the overlap is
292
+ // dropped and only the corner squaring remains.
293
+ //
294
+ // `.adw-linked` is this package's own earlier spelling, kept as an alias — and
295
+ // it USED to add a 2px gap between children, which is a segmented look, not a
296
+ // linked one. The gap is gone; a consumer who wanted it can say `gap`.
297
+ // The styled node is either the child itself (a bare `.adw-button` /
298
+ // `.adw-entry`) or one rendered inside the child host element (`<adw-button>`,
299
+ // `<adw-menu-button>`, `<adw-drop-down>`, …) — so both spellings are matched
300
+ // rather than enumerating host tags the way GTK has to.
301
+ $_linked-nodes: '.adw-button', '.adw-entry';
302
+
303
+ .linked,
304
+ .adw-linked {
305
+ display: inline-flex;
306
+
307
+ &:not(.vertical) > * {
308
+ @each $node in $_linked-nodes {
309
+ &:not(:first-child) #{$node},
310
+ &:not(:first-child)#{$node} {
311
+ border-start-start-radius: 0;
312
+ border-end-start-radius: 0;
313
+ }
314
+
315
+ &:not(:last-child) #{$node},
316
+ &:not(:last-child)#{$node} {
317
+ border-start-end-radius: 0;
318
+ border-end-end-radius: 0;
319
+ }
320
+ }
321
+ }
322
+
323
+ &.vertical {
324
+ flex-direction: column;
325
+
326
+ > * {
327
+ @each $node in $_linked-nodes {
328
+ &:not(:first-child) #{$node},
329
+ &:not(:first-child)#{$node} {
330
+ border-start-start-radius: 0;
331
+ border-start-end-radius: 0;
332
+ }
333
+
334
+ &:not(:last-child) #{$node},
335
+ &:not(:last-child)#{$node} {
336
+ border-end-start-radius: 0;
337
+ border-end-end-radius: 0;
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ /*************
345
+ * App icons *
346
+ *************/
347
+
348
+ // _common.scss:90-104. `-gtk-icon-shadow` has no CSS counterpart; `filter:
349
+ // drop-shadow()` is the equivalent that follows the icon's alpha rather than its
350
+ // box, which is what makes these legible on a light background. Each upstream
351
+ // shadow layer becomes one `drop-shadow()`.
352
+ .lowres-icon {
353
+ filter: drop-shadow(0 -1px rgb(0 0 6 / 5%)) drop-shadow(1px 0 rgb(0 0 6 / 10%))
354
+ drop-shadow(0 1px rgb(0 0 6 / 30%)) drop-shadow(-1px 0 rgb(0 0 6 / 10%));
355
+ }
356
+
357
+ .icon-dropshadow {
358
+ filter: drop-shadow(0 1px 12px rgb(0 0 6 / 5%)) drop-shadow(0 -1px rgb(0 0 6 / 5%))
359
+ drop-shadow(1px 0 rgb(0 0 6 / 10%)) drop-shadow(0 1px rgb(0 0 6 / 30%))
360
+ drop-shadow(-1px 0 rgb(0 0 6 / 10%));
361
+ }
362
+
363
+ /************************
364
+ * Undershoot indicators *
365
+ ************************/
366
+
367
+ // _scrolling.scss:150-166 — a shadow on the given edge of a scrolling box,
368
+ // telling the reader there is content past it. GTK draws them with an inset
369
+ // background; an inset `box-shadow` on the scroll container is the CSS
370
+ // equivalent and, unlike a `::before`, stays put while the content scrolls.
371
+ //
372
+ // `-start`/`-end` follow text direction, same as `margin-start`/`margin-end`,
373
+ // which `:dir()` expresses directly.
374
+ //
375
+ // `adw-toolbar-view` carries the same two class names for its OWN top/bottom
376
+ // indicators, drawn on its content node against the header shade
377
+ // (`_toolbar_view.scss:67-86`) — so it is excluded here rather than getting both
378
+ // a host shadow and a content gradient.
379
+ $undershoot-color: color-mix(in srgb, currentColor 6%, transparent);
380
+
381
+ .undershoot-top:not(adw-toolbar-view) {
382
+ box-shadow: inset 0 6px 6px -6px $undershoot-color;
383
+ }
384
+
385
+ .undershoot-bottom:not(adw-toolbar-view) {
386
+ box-shadow: inset 0 -6px 6px -6px $undershoot-color;
387
+ }
388
+
389
+ .undershoot-start {
390
+ &:dir(ltr) {
391
+ box-shadow: inset 6px 0 6px -6px $undershoot-color;
392
+ }
393
+
394
+ &:dir(rtl) {
395
+ box-shadow: inset -6px 0 6px -6px $undershoot-color;
396
+ }
397
+ }
398
+
399
+ .undershoot-end {
400
+ &:dir(ltr) {
401
+ box-shadow: inset -6px 0 6px -6px $undershoot-color;
402
+ }
403
+
404
+ &:dir(rtl) {
405
+ box-shadow: inset 6px 0 6px -6px $undershoot-color;
406
+ }
407
+ }
package/scss/_switch.scss CHANGED
@@ -79,10 +79,11 @@
79
79
  }
80
80
 
81
81
  // libadwaita dims the whole widget (`filter: Opacity(var(--disabled-opacity))`,
82
- // _switch.scss:20-22). This package has no `--disabled-opacity` token, so the
83
- // existing `--dim-opacity` stands in rather than a fourth invented colour.
82
+ // _switch.scss:20-22), and `--disabled-opacity` is a real token here now — it
83
+ // used to be stood in for by `--dim-opacity`, which is a different number and
84
+ // moves the OTHER way under high contrast.
84
85
  .adw-switch.disabled {
85
- opacity: var(--dim-opacity);
86
+ opacity: var(--disabled-opacity);
86
87
 
87
88
  .adw-switch-slider {
88
89
  cursor: not-allowed;
@@ -117,6 +117,21 @@
117
117
  --font-size-heading: 12pt;
118
118
  --dim-opacity: 0.55;
119
119
 
120
+ // Borders and disabled dimming, as libadwaita's own two-step (_colors.scss:263,
121
+ // 317-329): an OPACITY token the high-contrast query moves, and a colour
122
+ // DERIVED from it against `currentColor` — so a border follows the text colour
123
+ // of the surface it is drawn on instead of being a fixed grey.
124
+ //
125
+ // `--disabled-opacity` had no home here, and three partials said so in a
126
+ // comment and reached for `--dim-opacity` instead (`_switch.scss:81-83`,
127
+ // `_checks.scss:164-166`, `_progress_bar.scss:87-89`). Those are different
128
+ // numbers upstream — 50% disabled vs 55% dimmed — and under high contrast they
129
+ // move in OPPOSITE directions (disabled 40%, dimmed 90%), so the stand-in was
130
+ // most wrong exactly where it mattered most.
131
+ --border-opacity: 15%;
132
+ --border-color: color-mix(in srgb, currentColor var(--border-opacity), transparent);
133
+ --disabled-opacity: 0.5;
134
+
120
135
  // Document + monospace families, behind `.document` / `.monospace`
121
136
  // (_labels.scss:75-84). In GTK these four are written into `:root` at RUNTIME
122
137
  // by AdwStyleManager from the desktop's font settings
@@ -134,3 +149,13 @@
134
149
  --monospace-font-family: 'Adwaita Mono', ui-monospace, 'SF Mono', 'Menlo', monospace;
135
150
  --monospace-font-size: var(--font-size-base);
136
151
  }
152
+
153
+ // High contrast moves the three opacity tokens, and only those — every colour
154
+ // that reads them follows without its own query (_colors.scss:325-329).
155
+ @media (prefers-contrast: more) {
156
+ :root {
157
+ --border-opacity: 50%;
158
+ --dim-opacity: 0.9;
159
+ --disabled-opacity: 0.4;
160
+ }
161
+ }
@@ -25,6 +25,15 @@
25
25
  // it (`_progress_bar.scss`'s text node, `_data_grid.scss`'s cells) exactly as
26
26
  // libadwaita's own `_progress-bar.scss` does.
27
27
  @use 'labels';
28
+ // The REST of libadwaita's documented style-class surface — the widget-
29
+ // independent half: the surfaces (`.background`, `.view`, `.frame`), `.card` and
30
+ // the two boxed lists, `.toolbar`, `.spacer`, `.osd`, `.linked`, the app-icon
31
+ // shadows and the undershoot indicators. Emitted here for the same two reasons
32
+ // as `_labels.scss`: a widget's own rule then wins on ordinary specificity, and
33
+ // `_card.scss` / `_preferences.scss` @extend `%card` from it.
34
+ // `scripts/check-adwaita-style-classes.mjs` holds the ledger of the whole
35
+ // documented surface and fails on a class with no rule behind it.
36
+ @use 'style_classes';
28
37
  @use 'window';
29
38
  @use 'headerbar';
30
39
  @use 'window_title';
@@ -51,6 +60,9 @@
51
60
  @use 'data_grid';
52
61
  @use 'clamp';
53
62
  @use 'spinner';
63
+ // <adw-shortcut-label>'s keycaps. After `labels`, because a separator inside it
64
+ // is an ordinary `.dimmed`.
65
+ @use 'shortcut_label';
54
66
  @use 'avatar';
55
67
  @use 'banner';
56
68
  @use 'status_page';
@@ -1,13 +1,10 @@
1
1
  // DOM-level conformance tests for <adw-about-dialog>, driven by the vectors in
2
2
  // `@gjsify/adwaita-core/conformance` — the same table the core suite asserts.
3
3
  //
4
- // This element carried its own copy of `parse_person`, its own page-visibility
5
- // rules and its own set of invented fallbacks, and diverged from libadwaita on
6
- // thirteen counts. Two of them were not subtle: Support Questions and Report an
7
- // Issue were rendered on the DETAILS page, one navigation step deeper than GTK
8
- // puts them (adw-about-dialog.ui:146-186), and `website` was treated as Details
9
- // content, which is exactly the one input libadwaita leaves OUT of that
10
- // predicate (:1108). Nothing compared the two, so nothing failed.
4
+ // A local copy of `parse_person`, of the page-visibility rules or of the fallbacks
5
+ // diverges from libadwaita in thirteen ways, two of them structural: Support Questions
6
+ // and Report an Issue belong on the MAIN page, not one navigation step deeper on Details;
7
+ // and `website` is the one input the Details-visibility predicate leaves OUT.
11
8
  import { describe, expect, it } from '@gjsify/unit';
12
9
 
13
10
  import { ADW_ABOUT_DIALOG_LABELS, stripMnemonic } from '@gjsify/adwaita-core';
@@ -33,9 +30,9 @@ interface Mounted {
33
30
  }
34
31
 
35
32
  /**
36
- * Mount a dialog and set every attribute imperatively — several vectors hinge on
37
- * exact whitespace, which a value parsed out of markup is not a reliable
38
- * carrier for. `window.open` is stubbed because the link rows really do call it.
33
+ * Mount a dialog and set every attribute imperatively: several vectors hinge on exact
34
+ * whitespace, which markup is not a reliable carrier for. `window.open` is stubbed
35
+ * because the link rows really do call it.
39
36
  */
40
37
  function mount(attributes: Record<string, string> = {}): Mounted {
41
38
  const host = document.createElement('div');
@@ -85,10 +82,8 @@ function rowTitles(scope: Element | null): string[] {
85
82
  }
86
83
 
87
84
  /**
88
- * Activate a row and report the URI it asked for.
89
- *
90
- * The URI never reaches the DOM — it is captured in the row's activation
91
- * handler — so the only way to observe it is to drive the row and listen.
85
+ * Activate a row and report the URI it asked for. The URI never reaches the DOM — it is
86
+ * captured in the row's activation handler — so driving the row is the only way to see it.
92
87
  */
93
88
  function activateRow(dialog: HTMLElement, row: Element): string | null {
94
89
  let uri: string | null = null;
@@ -121,8 +116,8 @@ export const AdwAboutDialogTest = async () => {
121
116
  }
122
117
 
123
118
  await it('makes a row with an empty link a LINK row, not a plain one', () => {
124
- // `if (link)` is a pointer test in the C, so `"Ada <>"` still links,
125
- // to the URI `mailto:` — collapsing "" to null loses the row type.
119
+ // `if (link)` is a POINTER test in the C, so `"Ada <>"` still links, to the
120
+ // URI `mailto:` — collapsing "" to null loses the row type.
126
121
  const mounted = mount();
127
122
  mounted.dialog.developers = ['Ada <>'];
128
123
  const row = page(mounted.dialog, 'credits')!.querySelector('adw-action-row')!;
@@ -197,9 +192,9 @@ export const AdwAboutDialogTest = async () => {
197
192
  }
198
193
 
199
194
  await it('keeps a website-only dialog on the main page with no Details row', () => {
200
- // `show_details` is `has_comments || has_custom_links` (:1108) —
201
- // website is deliberately not in it, so a dialog whose only extra is
202
- // a website links straight from the main page.
195
+ // `show_details` is `has_comments || has_custom_links`; website is
196
+ // deliberately not in it, so a dialog whose only extra is a website links
197
+ // straight from the main page.
203
198
  const mounted = mount({ 'application-name': 'Builder', website: 'https://example.org' });
204
199
  expect(page(mounted.dialog, 'details')).toBe(null);
205
200
  expect(hasRow(page(mounted.dialog, 'main'), label('websiteRow'))).toBe(true);
@@ -208,8 +203,8 @@ export const AdwAboutDialogTest = async () => {
208
203
  });
209
204
 
210
205
  await describe('adw-about-dialog main page (update_support vectors)', async () => {
211
- // debug-info has no counterpart on this element (there is no
212
- // Troubleshooting page), so those rows are asserted by the core suite.
206
+ // debug-info has no counterpart here (no Troubleshooting page), so the core
207
+ // suite asserts those rows.
213
208
  for (const vector of ABOUT_DIALOG_SUPPORT_VECTORS.filter((row) => !row.debugInfo)) {
214
209
  const { supportUrl, issueUrl, visible, rule } = vector;
215
210
  await it(`support=${supportUrl} issue=${issueUrl} — ${rule}`, () => {
@@ -235,8 +230,7 @@ export const AdwAboutDialogTest = async () => {
235
230
  }
236
231
 
237
232
  await it('puts the support and issue rows on the MAIN page, not on Details', () => {
238
- // adw-about-dialog.ui:146-186 — `support_group` is a child of the
239
- // main page, and the class docs say so outright (:87-90).
233
+ // In `adw-about-dialog.ui`, `support_group` is a child of the MAIN page.
240
234
  const mounted = mount({
241
235
  'application-name': 'Builder',
242
236
  'support-url': 'https://example.org/help',
@@ -304,9 +298,8 @@ export const AdwAboutDialogTest = async () => {
304
298
  }
305
299
 
306
300
  await it('titles the dialog "About", not "About <app name>"', () => {
307
- // The main page's title is bound to AdwDialog:title, whose template
308
- // default is the bare word (.ui:6, :19); the application name lives
309
- // in the header revealer instead (.ui:29-31).
301
+ // The main page's title is bound to AdwDialog:title, whose template default
302
+ // is the bare word; the application name lives in the header revealer.
310
303
  const mounted = mount({ 'application-name': 'Builder' });
311
304
  expect(page(mounted.dialog, 'main')!.getAttribute('title')).toBe('About');
312
305
  mounted.dispose();
@@ -323,12 +316,11 @@ export const AdwAboutDialogTest = async () => {
323
316
  });
324
317
 
325
318
  await it('does NOT open when a handler cancels it', () => {
326
- // `activate_link_cb` returns the accumulated handler result
327
- // (:429-437, accumulator `g_signal_accumulator_true_handled`
328
- // :2098): a handler saying "handled" suppresses the default
329
- // navigation. `preventDefault()` is the web spelling of that, and it
330
- // was unavailable — the event was not cancelable and `window.open`
331
- // ran regardless.
319
+ // `activate_link_cb` returns the accumulated handler result (accumulator
320
+ // `g_signal_accumulator_true_handled`): a handler saying "handled"
321
+ // suppresses the default navigation, and `preventDefault()` is the web
322
+ // spelling of that — so the event must be cancelable, or `window.open` runs
323
+ // regardless.
332
324
  const mounted = mount({ 'application-name': 'Builder', website: 'https://example.org' });
333
325
  mounted.dialog.addEventListener('activate-link', (event) => event.preventDefault());
334
326
  const row = page(mounted.dialog, 'main')!.querySelector(`adw-action-row[title="${label('websiteRow')}"]`)!;
@@ -348,19 +340,14 @@ export const AdwAboutDialogTest = async () => {
348
340
  });
349
341
  });
350
342
 
351
- // AN OPENED DIALOG HAS TO OCCUPY SPACE.
343
+ // An opened dialog has to OCCUPY SPACE, and no other test here can see that: they
344
+ // all read the built DOM, and all passed while the sheet measured 360×0.
352
345
  //
353
- // Every other test in this file reads the built DOM, and all of them passed
354
- // while the dialog rendered INVISIBLE — its sheet measured 360×0. The suite
355
- // could not see that, because none of it looks at geometry.
356
- //
357
- // The sheet is `position: fixed` with a width and no height, which is what
358
- // `adw-about-dialog.ui:7` declares: `content-width` 360 and no
359
- // content-height, libadwaita taking the height from the content. The content
360
- // could not supply one — the navigation pages were absolutely positioned, so
361
- // they contributed nothing to the page stack, which contributed nothing to
362
- // the view, which left the sheet at zero; and the pages, being `inset: 0` of
363
- // a zero-height parent, were zero in turn.
346
+ // `adw-about-dialog.ui` declares `content-width` 360 and no content-height, so
347
+ // libadwaita takes the height from the content — which absolutely-positioned
348
+ // navigation pages cannot supply, since they contribute nothing to the page stack,
349
+ // which contributes nothing to the view, and the pages are then `inset: 0` of a
350
+ // zero-height parent.
364
351
  await describe('adw-about-dialog — an opened dialog is actually on screen', async () => {
365
352
  await it('gives its sheet a height derived from its content', () => {
366
353
  const mounted = mount({ 'application-name': 'Adwaita Storybook', version: '0.11.0' });
@@ -368,11 +355,9 @@ export const AdwAboutDialogTest = async () => {
368
355
 
369
356
  const sheet = mounted.dialog.querySelector('.adw-about-dialog-sheet') as HTMLElement;
370
357
  const rect = sheet.getBoundingClientRect();
371
- // HEIGHT only. The width was never the defect, and pinning it here
372
- // needs `calc(100vw - 48px)` restated in JS — where `100vw` and
373
- // `innerWidth` disagree by the scrollbar (360 against a measured
374
- // 342), so the assertion would report the harness rather than the
375
- // widget. A test should assert the rule the bug broke.
358
+ // HEIGHT only: pinning the width needs `calc(100vw - 48px)` restated in JS,
359
+ // where `100vw` and `innerWidth` disagree by the scrollbar, so the assertion
360
+ // would report the harness rather than the widget.
376
361
  expect(rect.height > 0).toBe(true);
377
362
  // And not merely the header: the sheet has to be taller than the one
378
363
  // child that had a size of its own all along, which is what made the