@expressivecss/expressive 0.7.0 → 0.9.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 (122) hide show
  1. package/CHANGELOG.md +1313 -1
  2. package/README.md +76 -30
  3. package/dist/css/expressive.css +4896 -1777
  4. package/dist/css/expressive.css.map +1 -1
  5. package/dist/css/expressive.min.css +1 -1
  6. package/dist/css/expressive.min.css.map +1 -1
  7. package/dist/fonts/LICENSE-material-symbols +93 -0
  8. package/dist/fonts/LICENSE-noto-sans +93 -0
  9. package/dist/fonts/LICENSE-roboto +93 -0
  10. package/dist/fonts/material-symbols-outlined.woff2 +0 -0
  11. package/dist/fonts/material-symbols-rounded.woff2 +0 -0
  12. package/dist/fonts/material-symbols-sharp.woff2 +0 -0
  13. package/dist/fonts/noto-sans-latin-400.woff2 +0 -0
  14. package/dist/fonts/noto-sans-latin-500.woff2 +0 -0
  15. package/dist/fonts/roboto-latin-400.woff2 +0 -0
  16. package/dist/fonts/roboto-latin-500.woff2 +0 -0
  17. package/dist/js/expressive.cjs +1673 -1338
  18. package/dist/js/expressive.cjs.map +4 -4
  19. package/dist/js/expressive.js +1673 -1338
  20. package/dist/js/expressive.js.map +4 -4
  21. package/dist/js/expressive.min.js +13 -13
  22. package/dist/js/expressive.min.js.map +4 -4
  23. package/dist/js/expressive.mjs +1673 -1338
  24. package/dist/js/expressive.mjs.map +4 -4
  25. package/dist/types/behaviors/bottomSheets.d.ts +19 -11
  26. package/dist/types/behaviors/sheetDrag.d.ts +40 -0
  27. package/dist/types/behaviors/sideSheets.d.ts +2 -9
  28. package/dist/types/components/appBar.d.ts +26 -0
  29. package/dist/types/components/autocomplete.d.ts +15 -3
  30. package/dist/types/components/buttonGroup.d.ts +39 -0
  31. package/dist/types/components/buttons.d.ts +5 -1
  32. package/dist/types/components/cards.d.ts +11 -13
  33. package/dist/types/components/carousel.d.ts +87 -53
  34. package/dist/types/components/chips.d.ts +21 -1
  35. package/dist/types/components/expandingCard.d.ts +31 -0
  36. package/dist/types/components/index.d.ts +23 -20
  37. package/dist/types/components/lightbox.d.ts +1 -0
  38. package/dist/types/components/menu.d.ts +11 -1
  39. package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
  40. package/dist/types/components/registry.d.ts +18 -8
  41. package/dist/types/components/slider.d.ts +26 -85
  42. package/dist/types/components/snackbar.d.ts +20 -5
  43. package/dist/types/components/tabs.d.ts +12 -0
  44. package/dist/types/components/timepicker.d.ts +12 -10
  45. package/dist/types/components/tooltip.d.ts +1 -0
  46. package/dist/types/core/utils.d.ts +64 -53
  47. package/dist/types/index.d.ts +8 -9
  48. package/package.json +39 -3
  49. package/src/sass/README.md +61 -11
  50. package/src/sass/abstracts/_breakpoints.scss +17 -19
  51. package/src/sass/abstracts/_mixins.scss +155 -21
  52. package/src/sass/abstracts/_variables.scss +69 -5
  53. package/src/sass/base/_fonts.scss +63 -0
  54. package/src/sass/base/_global.scss +6 -24
  55. package/src/sass/base/_grid.scss +15 -12
  56. package/src/sass/base/_normalize.scss +34 -38
  57. package/src/sass/base/_typography.scss +4 -18
  58. package/src/sass/components/_badges.scss +3 -5
  59. package/src/sass/components/_banners.scss +298 -0
  60. package/src/sass/components/_bottom-app-bar.scss +180 -0
  61. package/src/sass/components/_bottom-sheet.scss +12 -4
  62. package/src/sass/components/_button-groups.scss +358 -0
  63. package/src/sass/components/_buttons.scss +245 -92
  64. package/src/sass/components/_cards.scss +514 -78
  65. package/src/sass/components/_carousel.scss +562 -68
  66. package/src/sass/components/_chips.scss +64 -5
  67. package/src/sass/components/_datepicker.scss +24 -24
  68. package/src/sass/components/_dialog.scss +17 -11
  69. package/src/sass/components/_drag-handle.scss +130 -0
  70. package/src/sass/components/_expanding-card.scss +226 -0
  71. package/src/sass/components/_fab-menu.scss +232 -0
  72. package/src/sass/components/_floating-sheet.scss +86 -0
  73. package/src/sass/components/_icon-buttons.scss +172 -0
  74. package/src/sass/components/_icons-material-design.scss +22 -10
  75. package/src/sass/components/_index.scss +22 -6
  76. package/src/sass/components/_list.scss +16 -13
  77. package/src/sass/components/_loading-indicator.scss +106 -0
  78. package/src/sass/components/_menu.scss +335 -61
  79. package/src/sass/components/_navbar.scss +245 -45
  80. package/src/sass/components/_navigation-bar.scss +22 -11
  81. package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
  82. package/src/sass/components/_navigation-rail.scss +31 -23
  83. package/src/sass/components/_page-footer.scss +19 -16
  84. package/src/sass/components/_pagination.scss +24 -6
  85. package/src/sass/components/_panes.scss +52 -11
  86. package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
  87. package/src/sass/components/_scrim.scss +37 -0
  88. package/src/sass/components/_search.scss +259 -0
  89. package/src/sass/components/_segmented-buttons.scss +188 -0
  90. package/src/sass/components/_side-sheet.scss +2 -2
  91. package/src/sass/components/_snackbar.scss +20 -15
  92. package/src/sass/components/_split-button.scss +196 -0
  93. package/src/sass/components/_tabs.scss +7 -8
  94. package/src/sass/components/_timepicker.scss +67 -58
  95. package/src/sass/components/_toolbar.scss +206 -80
  96. package/src/sass/components/_transitions.scss +8 -1
  97. package/src/sass/components/forms/_checkboxes.scss +49 -12
  98. package/src/sass/components/forms/_fieldset.scss +4 -4
  99. package/src/sass/components/forms/_file-input.scss +8 -1
  100. package/src/sass/components/forms/_index.scss +1 -1
  101. package/src/sass/components/forms/_input-fields.scss +50 -44
  102. package/src/sass/components/forms/_radio-buttons.scss +42 -10
  103. package/src/sass/components/forms/_select.scss +38 -1
  104. package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
  105. package/src/sass/components/forms/_switches.scss +6 -7
  106. package/src/sass/expressive.scss +5 -0
  107. package/src/sass/tokens/_index.scss +4 -1
  108. package/src/sass/tokens/_reference.scss +70 -61
  109. package/src/sass/tokens/_state.scss +24 -0
  110. package/src/sass/tokens/_theme.scss +6 -3
  111. package/src/sass/tokens/_vibrant.scss +76 -0
  112. package/src/sass/utilities/_colors.scss +14 -1
  113. package/src/sass/utilities/_helpers.scss +5 -5
  114. package/src/sass/utilities/_visibility.scss +48 -26
  115. package/dist/types/behaviors/waves.d.ts +0 -15
  116. package/dist/types/components/parallax.d.ts +0 -16
  117. package/dist/types/components/range.d.ts +0 -43
  118. package/dist/types/core/bounding.d.ts +0 -6
  119. package/dist/types/core/edges.d.ts +0 -6
  120. package/src/sass/components/_parallax.scss +0 -104
  121. package/src/sass/components/_pulse.scss +0 -37
  122. package/src/sass/components/_slider.scss +0 -129
@@ -3,7 +3,7 @@
3
3
  // Navigation drawer.
4
4
  //
5
5
  // Overlay is a modal <dialog> (JS wraps a <ul class="sidenav"> in
6
- // dialog.sidenav-overlay, or the author writes <dialog class="sidenav">).
6
+ // dialog:is(.sidenav-overlay, .navigation-drawer-overlay), or the author writes <dialog class="sidenav">).
7
7
  // showModal() puts it on the top layer; ::backdrop is the scrim; Escape
8
8
  // and a scrim tap close it. Drag writes --md-comp-nav-drawer-shift;
9
9
  // this file does the slide.
@@ -12,7 +12,7 @@
12
12
  // accordion. The summary is a destination row with an expand_more
13
13
  // chevron; there is no Collapsible plugin.
14
14
  //
15
- // .sidenav-fixed is a breakpoint, not a JS open(): at `large` the host
15
+ // :is(.sidenav-fixed, .navigation-drawer-fixed) is a breakpoint, not a JS open(): at `large` the host
16
16
  // is display: contents so the list is a docked sidebar. Below that it
17
17
  // is the same overlay drawer as any other sidenav.
18
18
  //
@@ -26,13 +26,31 @@
26
26
  // --md-comp-nav-drawer-font-size 14px
27
27
  // --md-comp-nav-drawer-padding 16px
28
28
  // --md-comp-nav-drawer-line-height item height
29
-
30
- :root {
29
+ // --md-comp-nav-drawer-user-view-shape
30
+ // 16px, bottom corners of the header
31
+ // --md-comp-nav-drawer-user-view-scrim
32
+ // gradient laid over the header image
33
+ // --md-comp-nav-drawer-user-view-ink-color
34
+ // text colour over that scrim
35
+
36
+ :root, :host {
31
37
  --md-comp-nav-drawer-width: 300px;
32
38
  }
33
39
 
34
- dialog.sidenav-overlay,
35
- dialog.sidenav {
40
+ // A drawer row is whatever element the list item holds: a destination
41
+ // <a>, the <summary> of a nested section, the <button> a command row is
42
+ // written with (.navigation-drawer-close), and the plain element a
43
+ // .subheader label is. Keyed on `li > a`, the row layout disappeared the
44
+ // moment a row stopped being an anchor - the same trap $_icon documents.
45
+ // $_row-control is the subset that is operable: it takes the hover and
46
+ // focus treatment, a label does not. Both are wrapped in :is() - a bare
47
+ // comma list breaks out of its context (see $_bar in _navbar).
48
+ $_row: ":is(li > :is(a, button, .subheader), summary)";
49
+ $_row-control: ":is(li > :is(a, button):not(.button, .icon-button, .circle, .extra), summary)";
50
+
51
+
52
+ dialog:is(.sidenav-overlay, .navigation-drawer-overlay),
53
+ dialog:is(.sidenav, .navigation-drawer) {
36
54
  --md-comp-nav-drawer-item-height: 48px;
37
55
  --md-comp-nav-drawer-duration: 250ms;
38
56
  --md-comp-nav-drawer-easing: ease;
@@ -41,6 +59,14 @@ dialog.sidenav {
41
59
  --md-comp-nav-drawer-font-size: 14px;
42
60
  --md-comp-nav-drawer-padding: 16px;
43
61
  --md-comp-nav-drawer-line-height: var(--md-comp-nav-drawer-item-height);
62
+ --md-comp-nav-drawer-user-view-shape: 16px;
63
+ --md-comp-nav-drawer-user-view-ink-color: var(--md-ref-palette-neutral100);
64
+ --md-comp-nav-drawer-user-view-scrim: linear-gradient(
65
+ to bottom,
66
+ color-mix(in oklab, var(--md-sys-color-scrim) 15%, transparent) 0%,
67
+ color-mix(in oklab, var(--md-sys-color-scrim) 50%, transparent) 55%,
68
+ color-mix(in oklab, var(--md-sys-color-scrim) 80%, transparent) 100%
69
+ );
44
70
 
45
71
  box-sizing: border-box;
46
72
  width: var(--md-comp-nav-drawer-width);
@@ -85,7 +111,7 @@ dialog.sidenav {
85
111
  }
86
112
 
87
113
  &::backdrop {
88
- background-color: color-mix(in oklab, var(--md-sys-color-scrim) 32%, transparent);
114
+ background-color: var(--md-comp-scrim-color);
89
115
  opacity: var(--md-comp-nav-drawer-open, 1);
90
116
  backdrop-filter: none;
91
117
  transition:
@@ -96,22 +122,22 @@ dialog.sidenav {
96
122
  }
97
123
 
98
124
  @starting-style {
99
- :is(dialog.sidenav-overlay, dialog.sidenav):modal {
125
+ :is(dialog:is(.sidenav-overlay, .navigation-drawer-overlay), dialog:is(.sidenav, .navigation-drawer)):modal {
100
126
  transform: translateX(-100%);
101
127
  }
102
128
 
103
- :is(dialog.sidenav-overlay, dialog.sidenav).right-aligned:modal {
129
+ :is(dialog:is(.sidenav-overlay, .navigation-drawer-overlay), dialog:is(.sidenav, .navigation-drawer)).right-aligned:modal {
104
130
  transform: translateX(100%);
105
131
  }
106
132
 
107
- :is(dialog.sidenav-overlay, dialog.sidenav):modal::backdrop {
133
+ :is(dialog:is(.sidenav-overlay, .navigation-drawer-overlay), dialog:is(.sidenav, .navigation-drawer)):modal::backdrop {
108
134
  opacity: 0;
109
135
  }
110
136
  }
111
137
 
112
138
  @media (prefers-reduced-motion: reduce) {
113
- dialog.sidenav-overlay,
114
- dialog.sidenav {
139
+ dialog:is(.sidenav-overlay, .navigation-drawer-overlay),
140
+ dialog:is(.sidenav, .navigation-drawer) {
115
141
  transition: none;
116
142
 
117
143
  &::backdrop {
@@ -123,17 +149,25 @@ dialog.sidenav {
123
149
  // Docked sidebar: the host must not be a box, or it would steal the
124
150
  // list's position:fixed. Closed, so it is not :modal and does not
125
151
  // lock the page.
126
- @include bp-up("large") {
127
- dialog.sidenav-overlay:has(> .sidenav-fixed):not(:modal) {
152
+ @include bp-up("expanded") {
153
+ dialog:is(.sidenav-overlay, .navigation-drawer-overlay):has(> :is(.sidenav-fixed, .navigation-drawer-fixed)):not(:modal) {
128
154
  display: contents;
129
155
  }
130
156
  }
131
157
 
132
- .sidenav {
158
+ :is(.sidenav, .navigation-drawer) {
133
159
  --md-comp-nav-drawer-item-height: 48px;
134
160
  --md-comp-nav-drawer-font-size: 14px;
135
161
  --md-comp-nav-drawer-padding: 16px;
136
162
  --md-comp-nav-drawer-line-height: var(--md-comp-nav-drawer-item-height);
163
+ --md-comp-nav-drawer-user-view-shape: 16px;
164
+ --md-comp-nav-drawer-user-view-ink-color: var(--md-ref-palette-neutral100);
165
+ --md-comp-nav-drawer-user-view-scrim: linear-gradient(
166
+ to bottom,
167
+ color-mix(in oklab, var(--md-sys-color-scrim) 15%, transparent) 0%,
168
+ color-mix(in oklab, var(--md-sys-color-scrim) 50%, transparent) 55%,
169
+ color-mix(in oklab, var(--md-sys-color-scrim) 80%, transparent) 100%
170
+ );
137
171
 
138
172
  box-sizing: border-box;
139
173
  position: fixed;
@@ -162,12 +196,32 @@ dialog.sidenav {
162
196
  align-content: center;
163
197
  }
164
198
 
165
- li.active > a:not(.button, .circle, .extra) {
199
+ // The selected destination keeps its pill under the pointer: the row's own
200
+ // state layer mixes into `transparent`, so letting it reach a selected row
201
+ // would erase the fill rather than tint it. Higher specificity than the row
202
+ // rule, so order does not matter.
203
+ li.active > :is(a, button):not(.button, .icon-button, .circle, .extra) {
166
204
  background-color: var(--md-sys-color-secondary-container);
167
205
  color: var(--md-sys-color-on-secondary-container);
206
+
207
+ &:hover {
208
+ background-color: color-mix(
209
+ in oklab,
210
+ var(--md-sys-color-on-secondary-container) state-opacity("hover"),
211
+ var(--md-sys-color-secondary-container)
212
+ );
213
+ }
214
+
215
+ &:active {
216
+ background-color: color-mix(
217
+ in oklab,
218
+ var(--md-sys-color-on-secondary-container) state-opacity("pressed"),
219
+ var(--md-sys-color-secondary-container)
220
+ );
221
+ }
168
222
  }
169
223
 
170
- :is(li > a, summary) {
224
+ #{$_row} {
171
225
  display: flex;
172
226
  align-items: center;
173
227
  margin: 0 12px;
@@ -191,25 +245,32 @@ dialog.sidenav {
191
245
  }
192
246
  }
193
247
 
194
- li > a:not(.button, .circle, .extra),
195
- summary {
248
+ #{$_row-control} {
196
249
  color: var(--md-sys-color-on-surface-variant);
197
250
 
198
251
  &:hover {
199
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
252
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
253
+ }
254
+
255
+ &:active {
256
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("pressed"), transparent);
200
257
  }
201
258
 
202
259
  @include focus-visible;
203
260
  }
204
261
 
205
- li > a.button,
206
- li > a.circle,
207
- li > a.extra {
262
+ li > :is(a, button):is(.button, .circle, .extra) {
208
263
  margin: 10px 15px;
209
264
  }
210
265
 
211
- summary {
266
+ // Every <button> is a styled button ($_btn in _buttons is `button:not(...)`),
267
+ // so a command row has to be stripped back to a row the same way <summary>
268
+ // is. The chevron below stays summary-only.
269
+ :is(summary, li > button:not(.button, .icon-button, .circle, .extra)) {
212
270
  box-sizing: border-box;
271
+ // A <button> centres its own content, so a command row's label sat in the
272
+ // middle of the row while every anchor row started at the inset.
273
+ justify-content: flex-start;
213
274
  border: 0;
214
275
  background: transparent;
215
276
  font: inherit;
@@ -217,7 +278,9 @@ dialog.sidenav {
217
278
  list-style: none;
218
279
  cursor: pointer;
219
280
  appearance: none;
281
+ }
220
282
 
283
+ summary {
221
284
  &::-webkit-details-marker {
222
285
  display: none;
223
286
  }
@@ -282,16 +345,33 @@ dialog.sidenav {
282
345
  height: auto;
283
346
  padding: 0;
284
347
 
285
- &:hover {
348
+ &:is(:hover, :active) {
286
349
  background-color: transparent;
287
350
  }
288
351
  }
289
352
 
353
+ // The header image is clipped by .background, so the radius goes on the
354
+ // clipping box rather than the <img>: the image is a child of an
355
+ // overflow: hidden box, and rounding the child alone leaves the box's own
356
+ // square corners painting behind it.
290
357
  .background {
291
358
  overflow: hidden;
292
359
  position: absolute;
293
360
  inset: 0;
294
361
  z-index: -1;
362
+ border-end-start-radius: var(--md-comp-nav-drawer-user-view-shape);
363
+ border-end-end-radius: var(--md-comp-nav-drawer-user-view-shape);
364
+
365
+ // The name and email sit on whatever photograph the author supplied, so
366
+ // the scrim is what makes them legible - bottom-weighted, because that
367
+ // is where the text is. It rides inside .background so the rounded
368
+ // corners clip it too.
369
+ &::after {
370
+ content: "";
371
+ position: absolute;
372
+ inset: 0;
373
+ background: var(--md-comp-nav-drawer-user-view-scrim);
374
+ }
295
375
  }
296
376
 
297
377
  .circle,
@@ -311,6 +391,17 @@ dialog.sidenav {
311
391
  line-height: calc(var(--md-comp-nav-drawer-line-height) * 0.5);
312
392
  }
313
393
 
394
+ // Over an image the surface roles mean nothing and the link colour these
395
+ // spans inherit is unreadable on a mid-tone photograph. Scoped to
396
+ // :has(> .background): with no image the header is on the drawer surface
397
+ // and must keep tracking the theme, where fixed light ink would vanish.
398
+ &:has(> .background) {
399
+ .name,
400
+ .email {
401
+ color: var(--md-comp-nav-drawer-user-view-ink-color);
402
+ }
403
+ }
404
+
314
405
  .name {
315
406
  margin-top: 16px;
316
407
  font-weight: 500;
@@ -324,10 +415,10 @@ dialog.sidenav {
324
415
  }
325
416
 
326
417
  // Overlay only: the dialog is the panel, so the list fills it. When the
327
- // host is `display: contents` (a docked .sidenav-fixed) this must not
418
+ // host is `display: contents` (a docked :is(.sidenav-fixed, .navigation-drawer-fixed)) this must not
328
419
  // apply — static positioning would drop the list into the header flow
329
420
  // and it would read as part of the app bar.
330
- dialog.sidenav-overlay:modal > .sidenav {
421
+ dialog:is(.sidenav-overlay, .navigation-drawer-overlay):modal > :is(.sidenav, .navigation-drawer) {
331
422
  position: static;
332
423
  inset: auto;
333
424
  width: 100%;
@@ -338,7 +429,7 @@ dialog.sidenav-overlay:modal > .sidenav {
338
429
  z-index: auto;
339
430
  }
340
431
 
341
- .sidenav.sidenav-fixed {
432
+ :is(.sidenav, .navigation-drawer):is(.sidenav-fixed, .navigation-drawer-fixed) {
342
433
  transform: none;
343
434
 
344
435
  &.right-aligned {
@@ -346,11 +437,11 @@ dialog.sidenav-overlay:modal > .sidenav {
346
437
  }
347
438
  }
348
439
 
349
- @include bp-down("large") {
440
+ @include bp-down("expanded") {
350
441
  // Uninitialized / no-js: keep a fixed drawer off-canvas. Once JS wraps
351
442
  // it, the host's display:none hides the closed drawer and this must
352
443
  // not also hide the list inside an open dialog.
353
- .sidenav.sidenav-fixed {
444
+ :is(.sidenav, .navigation-drawer):is(.sidenav-fixed, .navigation-drawer-fixed) {
354
445
  transform: translateX(-100%);
355
446
 
356
447
  &.right-aligned {
@@ -358,7 +449,7 @@ dialog.sidenav-overlay:modal > .sidenav {
358
449
  }
359
450
  }
360
451
 
361
- .sidenav .user-view {
452
+ :is(.sidenav, .navigation-drawer) .user-view {
362
453
  padding: var(--md-comp-nav-drawer-padding) var(--md-comp-nav-drawer-padding) 0;
363
454
  }
364
455
  }
@@ -376,11 +467,10 @@ dialog.sidenav-overlay:modal > .sidenav {
376
467
  }
377
468
  }
378
469
 
379
- @include bp-up("large") {
470
+ @include bp-up("expanded") {
380
471
  .drag-target[data-fixed] {
381
472
  display: none;
382
473
  pointer-events: none;
383
474
  }
384
475
  }
385
476
 
386
-
@@ -24,7 +24,7 @@
24
24
  //
25
25
  // Nest descendants inside this block.
26
26
 
27
- :root {
27
+ :root, :host {
28
28
  --md-comp-nav-rail-collapsed-width: 96px;
29
29
  --md-comp-nav-rail-expanded-width: 280px;
30
30
  }
@@ -59,7 +59,7 @@
59
59
  padding var(--md-comp-nav-rail-duration) ease;
60
60
 
61
61
  // Menu toggle.
62
- > button:not(.button):first-child {
62
+ > button:not(.button, .icon-button):first-child {
63
63
  display: flex;
64
64
  flex: 0 0 auto;
65
65
  align-items: center;
@@ -76,14 +76,14 @@
76
76
  appearance: none;
77
77
  @include focus-visible;
78
78
 
79
- > i {
79
+ > #{$icon} {
80
80
  font-size: var(--md-comp-nav-rail-icon-size);
81
81
  width: var(--md-comp-nav-rail-icon-size);
82
82
  height: var(--md-comp-nav-rail-icon-size);
83
83
  }
84
84
 
85
85
  &:hover {
86
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
86
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
87
87
  }
88
88
  }
89
89
 
@@ -105,7 +105,7 @@
105
105
  background-color: var(--md-sys-color-primary-container);
106
106
  text-decoration: none;
107
107
 
108
- > :not(i) {
108
+ > :not(#{$icon}) {
109
109
  position: absolute;
110
110
  width: 1px;
111
111
  height: 1px;
@@ -115,8 +115,8 @@
115
115
  }
116
116
 
117
117
  // Destinations.
118
- > a:not(.button),
119
- > button:not(.button):not(:first-child) {
118
+ > a:not(.button, .icon-button),
119
+ > button:not(.button, .icon-button):not(:first-child) {
120
120
  position: relative;
121
121
  display: flex;
122
122
  flex: 0 0 auto;
@@ -138,7 +138,7 @@
138
138
  @include typescale("label-medium");
139
139
  @include focus-visible;
140
140
 
141
- > i {
141
+ > #{$icon} {
142
142
  display: flex;
143
143
  flex-shrink: 0;
144
144
  align-items: center;
@@ -152,15 +152,15 @@
152
152
  transition: background-color var(--md-comp-nav-rail-duration) ease;
153
153
  }
154
154
 
155
- &:hover:not(.active, [aria-current]) > i {
156
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
155
+ &:hover:not(.active, [aria-current]) > #{$icon} {
156
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
157
157
  }
158
158
 
159
159
  &.active,
160
160
  &[aria-current] {
161
161
  color: var(--md-sys-color-on-surface);
162
162
 
163
- > i {
163
+ > #{$icon} {
164
164
  background-color: var(--md-comp-nav-rail-active-indicator-color);
165
165
  color: var(--md-comp-nav-rail-active-color);
166
166
  --md-icon-fill: 1;
@@ -173,8 +173,12 @@
173
173
  position: absolute;
174
174
  z-index: 1;
175
175
  top: calc(6px + (var(--md-comp-nav-rail-indicator-height) - 24px) / 2);
176
- left: calc(50% + 12px);
176
+ inset-inline-start: calc(50% + 12px);
177
177
  transform: translate(-50%, -50%);
178
+
179
+ &:dir(rtl) {
180
+ transform: translate(50%, -50%);
181
+ }
178
182
  }
179
183
  }
180
184
 
@@ -197,7 +201,7 @@
197
201
  min-width: 220px;
198
202
  padding-inline: 16px;
199
203
 
200
- > button:not(.button):first-child {
204
+ > button:not(.button, .icon-button):first-child {
201
205
  justify-content: flex-start;
202
206
  width: auto;
203
207
  padding-inline: 16px;
@@ -210,7 +214,7 @@
210
214
  color: var(--md-sys-color-on-primary-container);
211
215
  background-color: var(--md-sys-color-primary-container);
212
216
 
213
- > :not(i) {
217
+ > :not(#{$icon}) {
214
218
  position: static;
215
219
  width: auto;
216
220
  height: auto;
@@ -220,8 +224,8 @@
220
224
  }
221
225
  }
222
226
 
223
- > a:not(.button),
224
- > button:not(.button):not(:first-child) {
227
+ > a:not(.button, .icon-button),
228
+ > button:not(.button, .icon-button):not(:first-child) {
225
229
  flex-direction: row;
226
230
  justify-content: flex-start;
227
231
  gap: 12px;
@@ -230,16 +234,16 @@
230
234
  border-radius: var(--md-comp-nav-rail-indicator-shape);
231
235
  text-align: start;
232
236
 
233
- > i {
237
+ > #{$icon} {
234
238
  width: var(--md-comp-nav-rail-icon-size);
235
239
  height: var(--md-comp-nav-rail-icon-size);
236
240
  border-radius: 0;
237
241
  }
238
242
 
239
243
  &:hover:not(.active, [aria-current]) {
240
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
244
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
241
245
 
242
- > i {
246
+ > #{$icon} {
243
247
  background-color: transparent;
244
248
  }
245
249
  }
@@ -249,7 +253,7 @@
249
253
  background-color: var(--md-comp-nav-rail-active-indicator-color);
250
254
  color: var(--md-comp-nav-rail-active-color);
251
255
 
252
- > i {
256
+ > #{$icon} {
253
257
  background-color: transparent;
254
258
  color: inherit;
255
259
  }
@@ -260,6 +264,10 @@
260
264
  left: auto;
261
265
  inset-inline-start: calc(16px + 24px);
262
266
  transform: translate(-50%, -50%);
267
+
268
+ &:dir(rtl) {
269
+ transform: translate(50%, -50%);
270
+ }
263
271
  }
264
272
  }
265
273
 
@@ -278,12 +286,12 @@
278
286
  content: "";
279
287
  position: fixed;
280
288
  inset: 0;
281
- background-color: color-mix(in oklab, var(--md-sys-color-scrim) 32%, transparent);
289
+ background-color: var(--md-comp-scrim-color);
282
290
  z-index: -1;
283
291
  }
284
292
  }
285
293
 
286
- @include bp-down("small") {
294
+ @include bp-down("medium") {
287
295
  &.expanded:not(.modal) {
288
296
  position: fixed;
289
297
  inset: 0 auto 0 0;
@@ -294,7 +302,7 @@
294
302
  content: "";
295
303
  position: fixed;
296
304
  inset: 0;
297
- background-color: color-mix(in oklab, var(--md-sys-color-scrim) 32%, transparent);
305
+ background-color: var(--md-comp-scrim-color);
298
306
  z-index: -1;
299
307
  }
300
308
  }
@@ -3,7 +3,8 @@
3
3
  // Site footer.
4
4
  //
5
5
  // Anatomy is the HTML. A <footer> is the landmark. A heading is a column
6
- // title. A <nav> (or ul) is a link list. The last child <small> is the
6
+ // title. A <nav> (or ul) is a link list; a column with no links is a
7
+ // <section>. The last child <small> is the
7
8
  // legal bar. A footer whose only child is nav.navigation-bar is the
8
9
  // navigation bar, not this component.
9
10
  //
@@ -32,10 +33,9 @@ footer:not(:has(> .navigation-bar:only-child)) {
32
33
  margin: 0 0 8px;
33
34
  color: var(--md-sys-color-on-surface-variant);
34
35
  @include typescale("body-medium");
35
- font-family: $font-stack;
36
36
  }
37
37
 
38
- a:not(.button, .circle, .extra) {
38
+ a:not(.button, .icon-button, .circle, .extra) {
39
39
  color: var(--md-sys-color-primary);
40
40
  text-decoration: none;
41
41
  @include focus-visible;
@@ -45,7 +45,6 @@ footer:not(:has(> .navigation-bar:only-child)) {
45
45
  margin: 0 0 8px;
46
46
  color: var(--md-sys-color-on-surface);
47
47
  @include typescale("title-small");
48
- font-family: $font-stack;
49
48
  }
50
49
 
51
50
  ul {
@@ -69,19 +68,19 @@ footer:not(:has(> nav:only-child)):not(:has(> .container)) {
69
68
  gap: var(--md-comp-footer-gap);
70
69
  grid-template-columns: 1fr;
71
70
 
72
- @include bp-up("small") {
71
+ @include bp-up("medium") {
73
72
  grid-template-columns: repeat(2, 1fr);
74
73
  }
75
74
 
76
- @include bp-up("large") {
75
+ @include bp-up("expanded") {
77
76
  grid-template-columns: repeat(4, 1fr);
78
77
  }
79
78
 
80
- @include bp-up("xlarge") {
79
+ @include bp-up("large") {
81
80
  grid-template-columns: repeat(5, 1fr);
82
81
  }
83
82
 
84
- @include bp-up("xxlarge") {
83
+ @include bp-up("extra-large") {
85
84
  grid-template-columns: repeat(6, 1fr);
86
85
  }
87
86
  }
@@ -91,7 +90,9 @@ footer:not(:has(> nav:only-child)) > .row {
91
90
  grid-column: 1 / -1;
92
91
  }
93
92
 
94
- footer:not(:has(> nav:only-child)) nav {
93
+ // A column with no links is a <section>, not a <nav> - see rule 4. Both are
94
+ // columns to look at.
95
+ footer:not(:has(> nav:only-child)) :is(nav, section) {
95
96
  display: flex;
96
97
  flex-direction: column;
97
98
  align-items: flex-start;
@@ -99,8 +100,8 @@ footer:not(:has(> nav:only-child)) nav {
99
100
  min-width: 0;
100
101
  }
101
102
 
102
- footer:not(:has(> nav:only-child)) nav > a:not(.button, .circle, .extra),
103
- footer:not(:has(> nav:only-child)) ul a:not(.button, .circle, .extra) {
103
+ footer:not(:has(> nav:only-child)) nav > a:not(.button, .icon-button, .circle, .extra),
104
+ footer:not(:has(> nav:only-child)) ul a:not(.button, .icon-button, .circle, .extra) {
104
105
  display: inline-flex;
105
106
  align-items: center;
106
107
  box-sizing: border-box;
@@ -109,14 +110,17 @@ footer:not(:has(> nav:only-child)) ul a:not(.button, .circle, .extra) {
109
110
  margin-inline-start: -12px;
110
111
  border-radius: 2rem;
111
112
  @include typescale("label-large");
112
- font-family: $font-stack;
113
113
 
114
114
  &:hover {
115
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
115
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
116
116
  }
117
117
  }
118
118
 
119
- footer:not(:has(> nav:only-child)) > small:last-child {
119
+ // The legal bar. `.container` is a supported columns wrapper - the rule above
120
+ // already accounts for it - so the bar has to be found through it too, or a
121
+ // footer written the documented way gets an unstyled copyright row.
122
+ footer:not(:has(> nav:only-child)) > small:last-child,
123
+ footer:not(:has(> nav:only-child)) > .container > small:last-child {
120
124
  display: flex;
121
125
  flex-wrap: wrap;
122
126
  align-items: center;
@@ -130,9 +134,8 @@ footer:not(:has(> nav:only-child)) > small:last-child {
130
134
  border-top: 1px solid var(--md-sys-color-outline-variant);
131
135
  color: var(--md-sys-color-on-surface-variant);
132
136
  @include typescale("body-small");
133
- font-family: $font-stack;
134
137
 
135
- a:not(.button) {
138
+ a:not(.button, .icon-button) {
136
139
  color: var(--md-sys-color-primary);
137
140
  }
138
141
  }
@@ -4,7 +4,9 @@
4
4
  // <ol> (or <ul>) of page links. The current page is aria-current="page".
5
5
  // A bare <ul class="pagination"> stays as an alias.
6
6
  //
7
- // .active / .disabled / .prev / .next / .pages still work.
7
+ // .active / .disabled / .pages still work. .prev / .next are accepted but
8
+ // inert - nothing matches them. They used to drive a 10%/80% split, which
9
+ // .nowrap replaced with scrolling.
8
10
  // .nowrap (or the old li.pages nest) scrolls a long run instead of wrapping.
9
11
  //
10
12
  // Tokens (defaults match the original compact pager):
@@ -86,14 +88,22 @@
86
88
  @include focus-visible;
87
89
 
88
90
  &:hover {
89
- background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
91
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("hover"), transparent);
90
92
  }
91
93
 
92
- &:has(> span) {
94
+ // The pressed layer is the only feedback a touch tap gets: hover never
95
+ // fires there and the tap highlight is suppressed above.
96
+ &:active {
97
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) state-opacity("pressed"), transparent);
98
+ }
99
+
100
+ // "Does this control carry a text label?" - the icon is a <span>, so a
101
+ // bare `span` here answers yes for every icon-only prev/next button.
102
+ &:has(> #{$icon-label}) {
93
103
  width: auto;
94
104
  }
95
105
 
96
- > i {
106
+ > #{$icon} {
97
107
  font-size: var(--md-comp-pagination-icon-size);
98
108
  width: var(--md-comp-pagination-icon-size);
99
109
  height: var(--md-comp-pagination-icon-size);
@@ -110,7 +120,15 @@
110
120
  &:hover {
111
121
  background-color: color-mix(
112
122
  in oklab,
113
- var(--md-comp-pagination-selected-on-color) 8%,
123
+ var(--md-comp-pagination-selected-on-color) state-opacity("hover"),
124
+ var(--md-comp-pagination-selected-color)
125
+ );
126
+ }
127
+
128
+ &:active {
129
+ background-color: color-mix(
130
+ in oklab,
131
+ var(--md-comp-pagination-selected-on-color) state-opacity("pressed"),
114
132
  var(--md-comp-pagination-selected-color)
115
133
  );
116
134
  }
@@ -124,7 +142,7 @@
124
142
  cursor: default;
125
143
  }
126
144
 
127
- li > span {
145
+ li > #{$icon-label} {
128
146
  box-sizing: border-box;
129
147
  display: inline-flex;
130
148
  align-items: center;