@smwb/ui-styles 2.1.0 → 2.3.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.
@@ -211,6 +211,52 @@
211
211
  font-weight: 500;
212
212
  letter-spacing: normal;
213
213
  text-transform: none;
214
+
215
+ // On a coarse pointer the row is never shorter than the sm step (44px there).
216
+ @media (pointer: coarse) {
217
+ min-height: max(var(--smui-tabDenseHeight, @tabDenseHeight), var(--smui-control-h-sm, @smui-control-h-sm-coarse));
218
+ }
219
+ }
220
+
221
+ // Content-width tabs (Tabs `fit`): a tab is as wide as what it holds — marker, label, close button —
222
+ // and the row no longer shares itself out between its tabs, so what is left of the row stays empty.
223
+ // The width stays between two tokens: a short label does not shrink the tab to a sliver, a long one
224
+ // is cut with an ellipsis at the maximum (the full text is the tab's `title`). The limits belong to
225
+ // the row's flex child: the item when the tab has a close button or actions, the tab otherwise. In
226
+ // an item the tab gives way to its controls, which keep their size, and fills the minimum.
227
+ .smwb-tabs--fit {
228
+ // How far a cut label's box reaches past its line box, so that overflow: hidden, which text-overflow
229
+ // needs, does not clip the descenders and accents that line-height: 1 leaves outside it.
230
+ @tabFitLabelOverhang: 0.25em;
231
+
232
+ .smwb-tab,
233
+ .smwb-tab-item {
234
+ flex: 0 0 auto;
235
+ min-width: var(--smui-tabFitMinWidth, @tabFitMinWidth);
236
+ max-width: var(--smui-tabFitMaxWidth, @tabFitMaxWidth);
237
+ }
238
+
239
+ .smwb-tab-item > .smwb-tab {
240
+ flex: 1 1 auto;
241
+ min-width: 0;
242
+ max-width: none;
243
+ }
244
+
245
+ .smwb-tab__content {
246
+ min-width: 0;
247
+ }
248
+
249
+ .smwb-tab__icon {
250
+ flex: 0 0 auto;
251
+ }
252
+
253
+ .smwb-tab__text-label {
254
+ min-width: 0;
255
+ padding-block: @tabFitLabelOverhang;
256
+ margin-block: -@tabFitLabelOverhang;
257
+ overflow: hidden;
258
+ text-overflow: ellipsis;
259
+ }
214
260
  }
215
261
 
216
262
  // The close button shows when it is reachable: the item is hovered (item not disabled),
@@ -69,6 +69,11 @@
69
69
  opacity: 0.38;
70
70
  cursor: default;
71
71
  }
72
+
73
+ // On a coarse pointer the button is at least as wide as the sm step (44px there).
74
+ @media (pointer: coarse) {
75
+ width: max(@tabsScrollButtonWidth, var(--smui-control-h-sm, @smui-control-h-sm-coarse));
76
+ }
72
77
  }
73
78
 
74
79
  // The overflow-menu trigger (Tabs `overflowMenu`) renders beside the scroller in the
@@ -95,6 +100,11 @@
95
100
  opacity: 0.38;
96
101
  cursor: default;
97
102
  }
103
+
104
+ // On a coarse pointer the button is at least as wide as the sm step (44px there).
105
+ @media (pointer: coarse) {
106
+ width: max(@tabsOverflowTriggerWidth, var(--smui-control-h-sm, @smui-control-h-sm-coarse));
107
+ }
98
108
  }
99
109
 
100
110
  &__indicator {
@@ -41,6 +41,30 @@
41
41
  min-height: var(--smui-control-h-lg, @smui-control-h-lg);
42
42
  justify-content: center;
43
43
  }
44
+ // The compact look, for a status row or a toolbar: the label shares the line with
45
+ // the switch (placement "top", the default, counts as "left"; "right" keeps its
46
+ // order) and the row is the small control height, capped at the compact maximum, in
47
+ // place of the size rule's. The :has() rules above weigh the same, so this one
48
+ // follows them. The switch gives up height to fit the row; its width and thumb
49
+ // stay, and the thumb centres itself on the track. Nothing here clips: the focus
50
+ // ring and the thumb halo draw outside the row.
51
+ @compact-height: min(var(--smui-control-h-sm, @smui-control-h-sm), @toggleCompactMaxHeight);
52
+ &__wrapper_compact:has(.smwb-toggle) {
53
+ flex-direction: row;
54
+ align-items: center;
55
+ justify-content: flex-start;
56
+ min-height: @compact-height;
57
+ height: @compact-height;
58
+ .smwb-toggle {
59
+ height: min(@toggleTrackHeight, 100%);
60
+ }
61
+ .smwb-toggle__label {
62
+ white-space: nowrap;
63
+ }
64
+ }
65
+ &__wrapper_compact.smwb-toggle__wrapper_label-right:has(.smwb-toggle) {
66
+ flex-direction: row-reverse;
67
+ }
44
68
  &.smwb-disabled {
45
69
  opacity: 0.35;
46
70
  pointer-events: none;
@@ -1,31 +1,54 @@
1
1
  .smwb-tree {
2
- &__wrapper {
3
- .smwb-tree__node {
4
- &:first-child {
5
- border-left: 0;
6
- &::before {
7
- opacity: 0;
8
- }
9
- }
2
+ // The row height, the node's left padding and the corner's horizontal arm are custom properties of the tree,
3
+ // so the guide corner (drawn by each node's ::before) follows the row: it is half the row height deep, which
4
+ // puts its arm at the middle of a row of any height. The dense look swaps in the small control height and a
5
+ // tighter indent; a caller can set any of the three on the tree itself (the `style` prop).
6
+ --smwb-tree-row-h: var(--smui-control-h-md, @smui-control-h-md);
7
+ --smwb-tree-indent: @treeViewIndent;
8
+ --smwb-tree-arm: @treeViewGuideArm;
9
+
10
+ &_dense {
11
+ --smwb-tree-row-h: var(--smui-control-h-sm, @smui-control-h-sm);
12
+ --smwb-tree-indent: @treeViewIndentDense;
13
+ --smwb-tree-arm: @treeViewGuideArmDense;
14
+ }
15
+
16
+ // A root node sits alone in its wrapper, so it is the wrapper's first child: that is the one to leave without
17
+ // a connector. Matched through the child combinator, the rule does not reach the first child of a nested level.
18
+ &__wrapper > .smwb-tree__node:first-child {
19
+ border-left: 0;
20
+ &::before {
21
+ opacity: 0;
10
22
  }
11
23
  }
24
+
25
+ // Without guides a nested node swaps its 1px line for the same width of padding, so the levels keep their step.
26
+ &_no-guides &__node &__node {
27
+ border-left-width: 0;
28
+ padding-left: calc(var(--smwb-tree-indent) + @treeViewGuideWidth);
29
+
30
+ &::before {
31
+ display: none;
32
+ }
33
+ }
34
+
12
35
  &__node {
13
36
  &:last-child {
14
37
  border-left-color: transparent;
15
38
  }
16
- padding-left: @treeViewIndent;
17
- margin-left: 9px;
39
+ padding-left: var(--smwb-tree-indent);
40
+ margin-left: @treeViewNodeMargin;
18
41
  position: relative;
19
- border-left: 1px solid var(--smui-primaryColor);
42
+ border-left: @treeViewGuideWidth solid var(--smui-primaryColor);
20
43
 
21
44
  &::before {
22
45
  position: absolute;
23
46
  top: 0;
24
- left: -1px;
25
- height: 20px;
26
- width: 25px;
27
- border-bottom: 1px solid var(--smui-primaryColor);
28
- border-left: 1px solid var(--smui-primaryColor);
47
+ left: -@treeViewGuideWidth;
48
+ height: calc(var(--smwb-tree-row-h) / 2);
49
+ width: var(--smwb-tree-arm);
50
+ border-bottom: @treeViewGuideWidth solid var(--smui-primaryColor);
51
+ border-left: @treeViewGuideWidth solid var(--smui-primaryColor);
29
52
  content: "";
30
53
  font-weight: 600;
31
54
  font-size: 14px;
@@ -54,7 +77,7 @@
54
77
  gap: @treeViewItemGap;
55
78
  // min-height, not height: a fixed row height let a wrapped two-line label
56
79
  // overflow the node and overlap the next one.
57
- min-height: 40px;
80
+ min-height: var(--smwb-tree-row-h);
58
81
  }
59
82
 
60
83
  &__toggle {
@@ -97,6 +97,14 @@
97
97
  @avatarGroupMaxStacked: 12; // children laid each over the next; any past these overlap the other way
98
98
 
99
99
  @breadcrumbsGap: @smui-space-sm;
100
+ // Single-line trail: the leading crumbs give way first (this many times as fast as the last one), down to
101
+ // a floor, so the last crumb is cut only once they are.
102
+ @breadcrumbsLeadingShrink: 100;
103
+ @breadcrumbsLeadingMinWidth: 3em;
104
+ // The last crumb never gets narrower than this, so a very narrow box still shows the start of the page title.
105
+ @breadcrumbsLastMinWidth: 4em;
106
+ // How far a clipped crumb still paints outside its box: a 2px focus ring at a 2px offset.
107
+ @breadcrumbsClipMargin: @smui-space-xs;
100
108
 
101
109
  @dividerSpacing: @smui-space-sm;
102
110
  @dividerTextGap: @smui-space-md;
@@ -120,6 +128,8 @@
120
128
  @appBarPaddingX: @smui-space-lg;
121
129
  @appBarActionPadding: @smui-space-md;
122
130
  @appBarActionsGap: 10px; // off-grid
131
+ // How far a clipped title still paints outside its box: a 2px focus ring at a 2px offset.
132
+ @appBarClipMargin: @smui-space-xs;
123
133
  // Width reserved for the OS window buttons in `AppBar variant="window"`: the macOS traffic lights of Electron's
124
134
  // `hiddenInset` end around 70-78px, so with @appBarPaddingX the content starts at 80px; Windows/Linux draw three
125
135
  // 46px caption buttons. Not theme tokens — an app overrides both sides with --smui-appBarWindowControlsWidth.
@@ -177,9 +187,18 @@
177
187
  @progressDotMargin: @smui-space-xs;
178
188
  @meterHeight: 6px;
179
189
  @meterMinBarWidth: 3.5rem; // the bar keeps at least this much beside its text
190
+ @meterCompactHeight: 4px; // off-grid
191
+ @meterCompactBarWidth: 64px; // the compact meter's bar is this wide, not flexible
180
192
  @statusDotSizeSm: 8px; // @smui-space-2
181
193
  @statusDotSizeMd: 12px; // @smui-space-3
182
194
  @statusDotRing: 2px; // the unknown status is a ring this thick
195
+ // A square turned 45deg spans side * sqrt(2), so the diamond's side is the dot's size
196
+ // divided by this: the two shapes then share one bounding box, and a margin makes up
197
+ // the layout box, which a transform leaves at the unturned side.
198
+ @statusDotDiamondRatio: sqrt(2);
199
+ @statusDotDiamondPrecision: 4; // decimals kept in the diamond's side and margin
200
+ @statusDotDiamondRotation: 45deg; // corner-up, the diamond of the onreel thread marker
201
+ @statusDotDiamondRadius: @smui-radius-none; // sharp corners; the dot's are a circle
183
202
 
184
203
  // A QR code is always dark modules on a white square — never theme tokens, so no
185
204
  // theme (or forced-colours mode, see the component's print/forced-color-adjust) can
@@ -261,6 +280,8 @@
261
280
  @tabLabelPaddingLeft: @smui-space-sm;
262
281
  @tabDensePaddingX: @smui-space-md;
263
282
  @tabDenseHeight: 36px; // off-grid; the row height of Tabs dense, the height of the compact onreel strips
283
+ @tabFitMinWidth: (@smui-space-10 * 2); // Tabs `fit`: a short label does not shrink the tab below this
284
+ @tabFitMaxWidth: (@smui-space-16 * 4); // Tabs `fit`: a longer label is cut with an ellipsis at this width
264
285
  @tabCloseSize: @smui-space-6; // the close button of a closable tab
265
286
  @tabControlsGap: @smui-space-xs; // between a tab's close button, its actions and the row's next tab
266
287
  @tabsFadeWidth: 2.5rem; // scrollable row: the fading edge beyond which tabs are hidden
@@ -273,6 +294,8 @@
273
294
  @tabGroupsSeparatorHitArea: 8px; // off-grid; the invisible grip around that track
274
295
  @tabGroupsDropTintOpacity: 0.16; // how much primary the zone a dragged tab would land in shows
275
296
  @tabGroupsActiveLineWidth: 2px; // off-grid; the line over the header of the group in use
297
+ @tabGroupsStripActionsGap: @smui-space-xs; // between the controls the app puts at the end of a group's tab row
298
+ @tabGroupsStripActionsPaddingX: @smui-space-xs; // that content's distance from the tabs and from the group's actions button
276
299
 
277
300
  @tableCellPaddingX: @smui-space-lg;
278
301
  @tableToolbarMarginBottom: 10px; // off-grid
@@ -295,11 +318,20 @@
295
318
  @textFieldAdornmentFilledMarginTop: @smui-space-md;
296
319
 
297
320
  @toggleGap: @smui-space-sm;
321
+ // The compact Toggle's row is the small control height, but never taller than this.
322
+ @toggleCompactMaxHeight: 28px;
298
323
 
299
324
  @tooltipPaddingX: @smui-space-sm;
300
325
 
301
326
  @treeViewIndent: 30px; // off-grid
302
327
  @treeViewItemGap: 10px; // off-grid
328
+ @treeViewGuideWidth: 1px;
329
+ @treeViewNodeMargin: 9px; // off-grid
330
+ @treeViewGuideArm: 25px; // off-grid
331
+ // The dense look. A node's indent is its padding; with @treeViewNodeMargin (9px) and @treeViewGuideWidth (1px) the step from a row to its
332
+ // children's is 16px (6 + 9 + 1), the arm ends at the child's row, and the row height is @smui-control-h-sm.
333
+ @treeViewIndentDense: 6px; // off-grid
334
+ @treeViewGuideArmDense: 6px; // off-grid
303
335
 
304
336
  /*-------------------
305
337
  Text Field
@@ -367,6 +399,9 @@
367
399
  Button
368
400
  --------------------*/
369
401
  @buttonBorderRadius: 24px;
402
+ // The most an icon-only button rounds its corners: the smaller of this and @buttonBorderRadius.
403
+ // A pill radius would make the square a circle; set @smui-radius-pill here to get one.
404
+ @buttonIconOnlyMaxBorderRadius: @smui-radius-md;
370
405
  @smallButtonFontSize: 13px;
371
406
  @mediumButtonFontSize: 14px;
372
407
  @largeButtonFontSize: 15px;
@@ -529,6 +564,7 @@
529
564
  // are the theme's runtime tokens, so the dark theme follows on its own.
530
565
  @dagViewNodeBg: ~"var(--smui-surfaceBackground)";
531
566
  @dagViewNodeBorder: ~"var(--smui-outlineColor)";
567
+ @dagViewNodeBorderStyle: solid;
532
568
  @dagViewEdgeColor: ~"var(--smui-outlineColor)";
533
569
  @dagViewEdgeActiveColor: ~"var(--smui-primaryColor)";
534
570
  @dagViewSelectedRing: ~"var(--smui-primaryColor)";
@@ -540,6 +576,13 @@
540
576
  @dagViewStatusWarning: ~"var(--smui-warningColor)";
541
577
  @dagViewStatusError: ~"var(--smui-errorColor)";
542
578
  @dagViewStatusSkipped: ~"var(--smui-disabledTextColor)";
579
+ // The line style of a status's node frame; `false`: the theme-wide @dagViewNodeBorderStyle.
580
+ @dagViewStatusPendingBorderStyle: false;
581
+ @dagViewStatusRunningBorderStyle: false;
582
+ @dagViewStatusSuccessBorderStyle: false;
583
+ @dagViewStatusWarningBorderStyle: false;
584
+ @dagViewStatusErrorBorderStyle: false;
585
+ @dagViewStatusSkippedBorderStyle: false;
543
586
  // How far the nodes and edges away from the selection fade, with dimUnrelated.
544
587
  @dagViewDimOpacity: 0.35;
545
588
  // The graph's height when the app sets none (its `height` prop or `style`).
@@ -43,6 +43,23 @@
43
43
  // Button, so they inherit the tokens
44
44
  //
45
45
  // Override one step from theme.config, e.g. @smui-control-h-md: 44px;
46
+ //
47
+ // Touch density: on a coarse pointer (finger, stylus) the three steps are
48
+ // replaced by @smui-control-h-*-coarse, so every control above is at least
49
+ // @smui-touch-target (44px) tall. variables.less remaps the runtime tokens in
50
+ // an `@media (pointer: coarse)` block, which is why most components need no
51
+ // rule of their own; only what sits off the scale has one (small clickable
52
+ // or link Chip, Tabs dense and its scroll buttons, TabGroups dense actions button,
53
+ // Menu dense items, List dense rows). A mouse keeps the sizes above.
54
+ // Still exempt on touch: the --filled and --inline fields, the compact Toggle
55
+ // (capped at @toggleCompactMaxHeight) and a small Chip that is neither a button
56
+ // nor a link.
57
+ // The -coarse steps never drop below the matching fine step, so a theme that
58
+ // raises a step is not shrunk on touch. Override one from theme.config
59
+ // (@smui-control-h-md-coarse: 48px;) or at runtime (--smui-control-h-md-coarse).
60
+ // A runtime override of --smui-control-h-* on :root, loaded after the library,
61
+ // also applies on a coarse pointer (the token layer always loses to it); set
62
+ // the -coarse token too, or scope the override to @media (pointer: fine).
46
63
  //=====================================================================
47
64
 
48
65
  // Control height (form fields, buttons, chips)
@@ -50,6 +67,14 @@
50
67
  @smui-control-h-md: 40px;
51
68
  @smui-control-h-lg: 48px;
52
69
 
70
+ // Smallest touch target (Material and WCAG 2.5.5 call for 44-48px)
71
+ @smui-touch-target: 44px;
72
+
73
+ // Control height on a coarse pointer (finger, stylus): the fine step, raised to the touch target
74
+ @smui-control-h-sm-coarse: max(@smui-touch-target, @smui-control-h-sm);
75
+ @smui-control-h-md-coarse: max(@smui-touch-target, @smui-control-h-md);
76
+ @smui-control-h-lg-coarse: max(@smui-touch-target, @smui-control-h-lg);
77
+
53
78
  // Horizontal padding inside controls
54
79
  @smui-control-px-sm: @smui-space-md; // 12
55
80
  @smui-control-px-md: @smui-space-lg; // 16
@@ -14,6 +14,13 @@
14
14
  .smui-tokens-light();
15
15
  }
16
16
 
17
+ // A coarse pointer swaps the control heights for the -coarse steps (foundation/sizing.less).
18
+ @media (pointer: coarse) {
19
+ :where(:root) {
20
+ .smui-tokens-coarse();
21
+ }
22
+ }
23
+
17
24
  :where(:root[data-theme="dark"]) {
18
25
  .smui-tokens-dark();
19
26
  }
@@ -93,6 +93,8 @@
93
93
  --smui-tabIconColor: @tabIconColor;
94
94
  --smui-tabColorDefault: @tabColorDefault;
95
95
  --smui-tabDenseHeight: @tabDenseHeight;
96
+ --smui-tabFitMinWidth: @tabFitMinWidth;
97
+ --smui-tabFitMaxWidth: @tabFitMaxWidth;
96
98
 
97
99
  --smui-sliderColor: @sliderColor;
98
100
  --smui-sliderDisabledColor: @sliderDisabledColor;
@@ -151,6 +153,10 @@
151
153
  --smui-control-h-sm: @smui-control-h-sm;
152
154
  --smui-control-h-md: @smui-control-h-md;
153
155
  --smui-control-h-lg: @smui-control-h-lg;
156
+ // Their values on a coarse pointer (see .smui-tokens-coarse below)
157
+ --smui-control-h-sm-coarse: @smui-control-h-sm-coarse;
158
+ --smui-control-h-md-coarse: @smui-control-h-md-coarse;
159
+ --smui-control-h-lg-coarse: @smui-control-h-lg-coarse;
154
160
 
155
161
  /* Border-radius scale */
156
162
  --smui-radius-xs: @smui-radius-xs;
@@ -277,10 +283,24 @@
277
283
  --smui-messageBackground: @messageBgDark;
278
284
  }
279
285
 
286
+ // Touch density: on a coarse pointer the control heights are the -coarse steps. Not part of
287
+ // the light/dark mixins for the same reason the heights are light-only: mode-independent.
288
+ .smui-tokens-coarse() {
289
+ --smui-control-h-sm: var(--smui-control-h-sm-coarse, @smui-control-h-sm-coarse);
290
+ --smui-control-h-md: var(--smui-control-h-md-coarse, @smui-control-h-md-coarse);
291
+ --smui-control-h-lg: var(--smui-control-h-lg-coarse, @smui-control-h-lg-coarse);
292
+ }
293
+
280
294
  :root {
281
295
  .smui-tokens-light();
282
296
  }
283
297
 
298
+ @media (pointer: coarse) {
299
+ :root {
300
+ .smui-tokens-coarse();
301
+ }
302
+ }
303
+
284
304
  :root[data-theme="dark"] {
285
305
  .smui-tokens-dark();
286
306
  }
package/normalize.css CHANGED
@@ -25,12 +25,11 @@ main {
25
25
  }
26
26
 
27
27
  /**
28
- * Correct the font size and margin on `h1` elements within `section` and
29
- * `article` contexts in Chrome, Firefox, and Safari.
28
+ * Correct the margin on `h1` elements. The font size is set with the other
29
+ * heading sizes at the end of this file, behind `:where()`.
30
30
  */
31
31
 
32
32
  h1 {
33
- font-size: 2em;
34
33
  margin: 0.67em 0;
35
34
  }
36
35
 
@@ -74,7 +73,7 @@ a {
74
73
  * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
75
74
  */
76
75
 
77
- abbr[title] {
76
+ :where(abbr[title]) {
78
77
  border-bottom: none; /* 1 */
79
78
  text-decoration: underline; /* 2 */
80
79
  text-decoration: underline dotted; /* 2 */
@@ -186,10 +185,7 @@ select {
186
185
  * Correct the inability to style clickable types in iOS and Safari.
187
186
  */
188
187
 
189
- button,
190
- [type="button"],
191
- [type="reset"],
192
- [type="submit"] {
188
+ :where(button, [type="button"], [type="reset"], [type="submit"]) {
193
189
  -webkit-appearance: button;
194
190
  }
195
191
 
@@ -197,25 +193,11 @@ button,
197
193
  * Remove the inner border and padding in Firefox.
198
194
  */
199
195
 
200
- button::-moz-focus-inner,
201
- [type="button"]::-moz-focus-inner,
202
- [type="reset"]::-moz-focus-inner,
203
- [type="submit"]::-moz-focus-inner {
196
+ :where(button, [type="button"], [type="reset"], [type="submit"])::-moz-focus-inner {
204
197
  border-style: none;
205
198
  padding: 0;
206
199
  }
207
200
 
208
- /**
209
- * Restore the focus styles unset by the previous rule.
210
- */
211
-
212
- button:-moz-focusring,
213
- [type="button"]:-moz-focusring,
214
- [type="reset"]:-moz-focusring,
215
- [type="submit"]:-moz-focusring {
216
- outline: 1px dotted ButtonText;
217
- }
218
-
219
201
  /**
220
202
  * Correct the padding in Firefox.
221
203
  */
@@ -261,8 +243,7 @@ textarea {
261
243
  * 2. Remove the padding in IE 10.
262
244
  */
263
245
 
264
- [type="checkbox"],
265
- [type="radio"] {
246
+ :where([type="checkbox"], [type="radio"]) {
266
247
  box-sizing: border-box; /* 1 */
267
248
  padding: 0; /* 2 */
268
249
  }
@@ -271,8 +252,8 @@ textarea {
271
252
  * Correct the cursor style of increment and decrement buttons in Chrome.
272
253
  */
273
254
 
274
- [type="number"]::-webkit-inner-spin-button,
275
- [type="number"]::-webkit-outer-spin-button {
255
+ :where([type="number"])::-webkit-inner-spin-button,
256
+ :where([type="number"])::-webkit-outer-spin-button {
276
257
  height: auto;
277
258
  }
278
259
 
@@ -281,7 +262,7 @@ textarea {
281
262
  * 2. Correct the outline style in Safari.
282
263
  */
283
264
 
284
- [type="search"] {
265
+ :where([type="search"]) {
285
266
  -webkit-appearance: textfield; /* 1 */
286
267
  outline-offset: -2px; /* 2 */
287
268
  }
@@ -290,7 +271,7 @@ textarea {
290
271
  * Remove the inner padding in Chrome and Safari on macOS.
291
272
  */
292
273
 
293
- [type="search"]::-webkit-search-decoration {
274
+ :where([type="search"])::-webkit-search-decoration {
294
275
  -webkit-appearance: none;
295
276
  }
296
277
 
@@ -353,13 +334,20 @@ template {
353
334
  -webkit-box-sizing: border-box;
354
335
  box-sizing: border-box;
355
336
  }
356
- :focus,
357
- :active {
337
+ :where(:focus, :active) {
358
338
  outline: none;
359
339
  }
360
- a:focus,
361
- a:active {
362
- outline: none;
340
+ /**
341
+ * Restore the focus styles unset by the rule above. Declared after it: both have zero
342
+ * specificity, so the later one wins, and a component's own :focus-visible ring beats both.
343
+ */
344
+ :where(
345
+ button:-moz-focusring,
346
+ [type="button"]:-moz-focusring,
347
+ [type="reset"]:-moz-focusring,
348
+ [type="submit"]:-moz-focusring
349
+ ) {
350
+ outline: 1px dotted ButtonText;
363
351
  }
364
352
  nav,
365
353
  footer,
@@ -392,11 +380,7 @@ button::-moz-focus-inner {
392
380
  padding: 0;
393
381
  border: 0;
394
382
  }
395
- a,
396
- a:visited {
397
- text-decoration: none;
398
- }
399
- a:hover {
383
+ a {
400
384
  text-decoration: none;
401
385
  }
402
386
  ul li {
@@ -406,72 +390,78 @@ img {
406
390
  vertical-align: top;
407
391
  }
408
392
 
409
- /* heading elements */
410
- h1 {
393
+ /*
394
+ * heading elements
395
+ *
396
+ * All behind :where(), so these have zero specificity and any component class (the
397
+ * font size and weight of Typography, say) wins, in or out of article, aside, nav and
398
+ * section. Among themselves they cascade in source order, as before.
399
+ *
400
+ * The section rules keep `:-webkit-any()`, which Firefox does not know: :where() takes a
401
+ * forgiving list, so there the branch matches nothing and a bare h1 stays 2em, as it
402
+ * always did. Do not turn it into :is(), that would resize Firefox's headings.
403
+ */
404
+ :where(h1, h2, h3, h4, h5, h6) {
411
405
  display: block;
412
- font-size: 2em;
413
406
  font-weight: bold;
414
407
  }
415
408
 
416
- :-webkit-any(article, aside, nav, section) h1 {
409
+ :where(h1) {
410
+ font-size: 2em;
411
+ }
412
+
413
+ :where(:-webkit-any(article, aside, nav, section) h1) {
417
414
  font-size: 1.5em;
418
415
  }
419
416
 
420
- :-webkit-any(article, aside, nav, section) :-webkit-any(article, aside, nav, section) h1 {
417
+ :where(:-webkit-any(article, aside, nav, section) :-webkit-any(article, aside, nav, section) h1) {
421
418
  font-size: 1.17em;
422
419
  }
423
420
 
424
- :-webkit-any(article, aside, nav, section)
425
- :-webkit-any(article, aside, nav, section)
421
+ :where(
426
422
  :-webkit-any(article, aside, nav, section)
427
- h1 {
423
+ :-webkit-any(article, aside, nav, section)
424
+ :-webkit-any(article, aside, nav, section)
425
+ h1
426
+ ) {
428
427
  font-size: 1em;
429
428
  }
430
429
 
431
- :-webkit-any(article, aside, nav, section)
432
- :-webkit-any(article, aside, nav, section)
433
- :-webkit-any(article, aside, nav, section)
430
+ :where(
434
431
  :-webkit-any(article, aside, nav, section)
435
- h1 {
432
+ :-webkit-any(article, aside, nav, section)
433
+ :-webkit-any(article, aside, nav, section)
434
+ :-webkit-any(article, aside, nav, section)
435
+ h1
436
+ ) {
436
437
  font-size: 0.83em;
437
438
  }
438
439
 
439
- :-webkit-any(article, aside, nav, section)
440
+ :where(
440
441
  :-webkit-any(article, aside, nav, section)
441
- :-webkit-any(article, aside, nav, section)
442
- :-webkit-any(article, aside, nav, section)
443
- :-webkit-any(article, aside, nav, section)
444
- h1 {
442
+ :-webkit-any(article, aside, nav, section)
443
+ :-webkit-any(article, aside, nav, section)
444
+ :-webkit-any(article, aside, nav, section)
445
+ :-webkit-any(article, aside, nav, section)
446
+ h1
447
+ ) {
445
448
  font-size: 0.67em;
446
449
  }
447
450
 
448
- h2 {
449
- display: block;
451
+ :where(h2) {
450
452
  font-size: 1.5em;
451
- font-weight: bold;
452
453
  }
453
454
 
454
- h3 {
455
- display: block;
455
+ :where(h3) {
456
456
  font-size: 1.17em;
457
- font-weight: bold;
458
- }
459
-
460
- h4 {
461
- display: block;
462
- font-weight: bold;
463
457
  }
464
458
 
465
- h5 {
466
- display: block;
459
+ :where(h5) {
467
460
  font-size: 0.83em;
468
- font-weight: bold;
469
461
  }
470
462
 
471
- h6 {
472
- display: block;
463
+ :where(h6) {
473
464
  font-size: 0.67em;
474
- font-weight: bold;
475
465
  }
476
466
 
477
467
  body {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smwb/ui-styles",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Framework-agnostic LESS design tokens and component styles (smwb-*) shared by the @smwb UI libraries. Prebuilt aggregate CSS lives in @smwb/ui-css.",
5
5
  "license": "MIT",
6
6
  "sideEffects": [
@@ -19,7 +19,7 @@
19
19
  "./package.json": "./package.json"
20
20
  },
21
21
  "scripts": {
22
- "lint": "node scripts/check-aggregate.mjs && node scripts/check-tokens.mjs && node scripts/check-control-heights.mjs && node scripts/check-typography-case.mjs && node scripts/check-chart-series.mjs"
22
+ "lint": "node scripts/check-aggregate.mjs && node scripts/check-tokens.mjs && node scripts/check-control-heights.mjs && node scripts/check-typography-case.mjs && node scripts/check-chart-series.mjs && node scripts/check-normalize-specificity.mjs"
23
23
  },
24
24
  "homepage": "https://sourcecraft.dev/elfenmail/ui",
25
25
  "repository": {