@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
@@ -65,21 +65,26 @@ fine — it crosses no boundary.
65
65
 
66
66
  ## Breakpoints
67
67
 
68
- Four of them, in `abstracts/_breakpoints.scss`, reached through mixins:
68
+ The five Material 3 window size classes live in
69
+ `abstracts/_breakpoints.scss` and are reached through mixins:
69
70
 
70
71
  ```scss
71
- @include bp-up("small") { } // @media (width >= 601px)
72
- @include bp-down("large") { } // @media (width < 993px)
73
- @include bp-between("small", "large") { }
72
+ @include bp-up("medium") { } // width >= 600px
73
+ @include bp-down("expanded") { } // width < 840px
74
+ @include bp-between("expanded", "large") { } // 840px–1199px
74
75
  ```
75
76
 
76
- Names are `small` (601px), `large` (993px), `xlarge` (1201px), `xxlarge`
77
- (1601px) — the boundary a query sits on, not the size of the screen it
78
- targets. `xxlarge` is M3 extra-large (desktop / widescreen); the 12-column
79
- grid grows a `.xxl` prefix there, and `.container` lifts its 1280px cap to
80
- 1920px. Range syntax has an exclusive comparator, so the old `600.99px` /
81
- `992.99px` values used to stop `max-width` and `min-width` both matching on
82
- the boundary pixel are gone, and each boundary is written once.
77
+ | M3 class | Width | Grid prefix |
78
+ | --- | --- | --- |
79
+ | Compact | `< 600px` | `.s` (default) |
80
+ | Medium | `600–839px` | `.m` |
81
+ | Expanded | `840–1199px` | `.l` |
82
+ | Large | `1200–1599px` | `.xl` |
83
+ | Extra-large | `>= 1600px` | `.xxl` |
84
+
85
+ The grid prefixes remain unchanged for markup compatibility. At Extra-large,
86
+ `.container` lifts its 1280px cap to 1920px. Range syntax uses exclusive
87
+ comparators, so each boundary is written once without fractional max-widths.
83
88
 
84
89
  ## Theming
85
90
 
@@ -126,6 +131,51 @@ only has to set `color-scheme: dark` and all 30 tokens re-resolve — and settin
126
131
  `color-scheme` on any subtree re-themes just that subtree. `color-scheme` is
127
132
  load-bearing now; changing it is not cosmetic.
128
133
 
134
+ ## Shadow roots
135
+
136
+ The sheet is supported as a shadow root's **only** stylesheet — adopted into
137
+ `shadowRoot.adoptedStyleSheets` with no copy in the document. So a rule that
138
+ declares a token pairs its anchor with the `:host` twin that reaches the same
139
+ elements from inside a shadow tree:
140
+
141
+ ```scss
142
+ // no - :root matches the document element and nothing else, so this token
143
+ // is simply absent in a shadow-only load, and every declaration reading it
144
+ // is then invalid at computed-value time
145
+ :root { --md-comp-nav-drawer-width: 300px; }
146
+
147
+ // yes
148
+ :root, :host { --md-comp-nav-drawer-width: 300px; }
149
+ :root[theme='dark'], :host([theme='dark']) { color-scheme: dark; }
150
+ [vibrant], :host([vibrant]) { … }
151
+ ```
152
+
153
+ An anchor is anything that can only match a document root or a descendant of
154
+ one, which is `html` and `body` as well: a shadow root has neither inside it.
155
+ `base/_grid.scss` put `--gap-size` on `body` and `.row` reads it without a
156
+ fallback, so a `.row` in a shadow-only load had an invalid `gap` rather than a
157
+ default one.
158
+
159
+ `color-scheme` counts as a token here: `light-dark()` resolves against the
160
+ element's used `color-scheme`, so an unpaired `:root[theme='dark']` means a
161
+ shadow tree cannot be pinned to a theme the way a page can.
162
+
163
+ `:host` matches nothing in a document, so pairing an anchor cannot change a
164
+ normal load. It is not weightless, though: bare `:host` matches `:root` at
165
+ (0,1,0), but `:host(<compound>)` adds its argument's weight on top, so
166
+ `:host([vibrant])` is (0,2,0) against `[vibrant]`'s (0,1,0). The two arms match
167
+ disjoint elements and never compete, so nothing in the sheet turns on it — but
168
+ do not reach for the pairing as a way to keep specificity flat.
169
+
170
+ `tests/shadow-dom.test.js` fails any rule that skips the pairing; the commitment
171
+ behind it is `adr/0002-shadow-only-stylesheet-adoption.md`. That commitment
172
+ covers everything *inside* the shadow root, portals included: the six places that
173
+ create an element at runtime (Tooltip, Snackbar, Lightbox's caption, the drawer's
174
+ drag target, and the Datepicker's two menus) route through `Utils.portalRoot()`
175
+ and append into the originating shadow root, so no document-level copy is needed.
176
+ The cost is that they no longer escape an ancestor's `overflow` or stacking
177
+ context — the ADR states what that re-anchors.
178
+
129
179
  ## Translucent colors
130
180
 
131
181
  ```scss
@@ -1,38 +1,36 @@
1
1
  @use "sass:map";
2
2
  @use "sass:list";
3
3
 
4
- // Breakpoints, as media-query range syntax.
4
+ // Material 3 window size classes, expressed as media-query boundaries.
5
5
  //
6
6
  // The old form built a query by interpolating a whole string:
7
7
  //
8
8
  // $medium-and-down: "only screen and (max-width : 992.99px)";
9
9
  // @media #{$medium-and-down} { ... }
10
10
  //
11
- // which needed the `.99` because `max-width: 993px` and `min-width: 993px`
12
- // both match at exactly 993px. Range syntax has an exclusive comparator, so
13
- // the fractional values are gone and the boundary is stated once:
11
+ // which needed the `.99` because inclusive max/min queries both match on the
12
+ // boundary pixel. Range syntax has an exclusive comparator, so fractional
13
+ // values are unnecessary and every boundary is stated once:
14
14
  //
15
- // @include bp-down("large") { ... } -> @media (width < 993px)
15
+ // @include bp-down("expanded") { ... } -> @media (width < 840px)
16
16
  //
17
17
  // `only screen` went with them: it existed to hide queries from Netscape 4
18
18
  // and IE, and `screen` alone excludes print anyway - which these rules never
19
19
  // wanted to do, since a hidden element should stay hidden on paper.
20
20
 
21
- // Boundaries, not the size of the screen they target. Column prefixes
22
- // s / m / l / xl / xxl sit on these in order; there is no `medium` key
23
- // because the tablet class is `m` and the mixin name is the boundary.
24
- //
25
- // M3 window-size classes, for the ones that line up:
26
- // compact width < 600 below "small"
27
- // medium 600 ≤ width < 840 (we do not split 840 out of "small")
28
- // expanded 840 ≤ width < 1200 "large" is 993, close enough
29
- // large 1200 ≤ width < 1600 "xlarge"
30
- // extra-large width ≥ 1600 "xxlarge"
21
+ // Compact starts at zero and is the unwrapped/default style. The remaining
22
+ // entries are the exact lower bounds from the M3 adaptive-layout spec:
23
+ // compact 0 ≤ width < 600
24
+ // medium 600 ≤ width < 840
25
+ // expanded 840 ≤ width < 1200
26
+ // large 1200 ≤ width < 1600
27
+ // extra-large width ≥ 1600
31
28
  $breakpoints: (
32
- "small": 601px, // phone -> tablet
33
- "large": 993px, // tablet -> desktop
34
- "xlarge": 1201px, // desktop -> large desktop
35
- "xxlarge": 1601px, // large -> extra-large / widescreen
29
+ "compact": 0px,
30
+ "medium": 600px,
31
+ "expanded": 840px,
32
+ "large": 1200px,
33
+ "extra-large": 1600px,
36
34
  ) !default;
37
35
 
38
36
  @function breakpoint($name) {
@@ -1,14 +1,36 @@
1
+ @use "sass:list";
2
+ @use "sass:map";
1
3
  @use "./elevation" as *;
2
4
  @use "./variables" as *;
3
5
 
6
+ $_state-layers: ("hover", "focus", "pressed", "dragged");
4
7
 
5
- // Common-button geometry. M3 default is 40dp, label-large, 20dp corners
6
- // (a stadium), 24dp inset, 8dp icon gap. Variants only change color/elevation.
8
+ /// A state layer opacity, as the percentage `color-mix()` wants.
9
+ ///
10
+ /// The tokens hold numbers (0.08), because an overlay uses one as `opacity`
11
+ /// directly. A tint mixed with `color-mix()` needs a percentage, so it is
12
+ /// multiplied here rather than at forty-odd call sites.
13
+ ///
14
+ /// Validated for the same reason `z-depth()` validates: a typo would produce a
15
+ /// `var()` naming a token nothing declares, which invalidates the whole
16
+ /// declaration at computed-value time - silently, and in a way nothing but the
17
+ /// rendered page would show.
18
+ @function state-opacity($state) {
19
+ @if not list.index($_state-layers, $state) {
20
+ @error "Unknown state layer #{$state}. Expected one of #{$_state-layers}.";
21
+ }
22
+ @return calc(var(--md-sys-state-#{$state}-state-layer-opacity) * 100%);
23
+ }
24
+
25
+
26
+ // Common-button geometry. M3 Expressive's default size is small: 40dp,
27
+ // label-large, corner.full, 16dp inset, 8dp icon gap. The size axis moves
28
+ // those tokens; the style mixins only change color and elevation.
7
29
  @mixin btn(
8
30
  $height: var(--md-comp-filled-button-container-height, 40px),
9
- $border-radius: var(--md-comp-filled-button-container-shape, 20px),
10
- $padding-left: var(--md-comp-filled-button-leading-space, 24px),
11
- $padding-right: var(--md-comp-filled-button-trailing-space, 24px),
31
+ $border-radius: var(--md-comp-filled-button-container-shape, 9999px),
32
+ $padding-left: var(--md-comp-filled-button-leading-space, 16px),
33
+ $padding-right: var(--md-comp-filled-button-trailing-space, 16px),
12
34
  $font-size: var(--md-sys-typescale-label-large-font-size),
13
35
  ) {
14
36
  box-sizing: border-box;
@@ -36,8 +58,8 @@
36
58
  transition: background-color .2s ease-out, box-shadow .2s ease-out;
37
59
  }
38
60
 
39
- // State layers are 8% hover / 10% focus+press of the on-color, mixed in oklab
40
- // onto the container. Filled and tonal have no elevation.
61
+ // State layers mix the on-color into the container in oklab; the opacities come
62
+ // from md.sys.state via `state-opacity()`. Filled and tonal have no elevation.
41
63
 
42
64
  @mixin btn-filled {
43
65
  color: var(--md-comp-filled-button-label-text-color, var(--md-sys-color-on-primary));
@@ -46,7 +68,7 @@
46
68
  &:hover {
47
69
  background-color: color-mix(
48
70
  in oklab,
49
- var(--md-sys-color-on-primary) 8%,
71
+ var(--md-sys-color-on-primary) state-opacity("hover"),
50
72
  var(--md-sys-color-primary)
51
73
  );
52
74
  }
@@ -55,7 +77,7 @@
55
77
  &:focus {
56
78
  background-color: color-mix(
57
79
  in oklab,
58
- var(--md-sys-color-on-primary) 10%,
80
+ var(--md-sys-color-on-primary) state-opacity("focus"),
59
81
  var(--md-sys-color-primary)
60
82
  );
61
83
  }
@@ -70,7 +92,7 @@
70
92
  &:hover {
71
93
  background-color: color-mix(
72
94
  in oklab,
73
- var(--md-sys-color-on-secondary-container) 8%,
95
+ var(--md-sys-color-on-secondary-container) state-opacity("hover"),
74
96
  var(--md-sys-color-secondary-container)
75
97
  );
76
98
  }
@@ -79,7 +101,7 @@
79
101
  &:focus {
80
102
  background-color: color-mix(
81
103
  in oklab,
82
- var(--md-sys-color-on-secondary-container) 10%,
104
+ var(--md-sys-color-on-secondary-container) state-opacity("focus"),
83
105
  var(--md-sys-color-secondary-container)
84
106
  );
85
107
  }
@@ -94,13 +116,13 @@
94
116
 
95
117
  &:hover {
96
118
  @include z-depth("2");
97
- background-color: color-mix(in oklab, var(--md-sys-color-primary) 8%, var(--md-sys-color-surface-container-low));
119
+ background-color: color-mix(in oklab, var(--md-sys-color-primary) state-opacity("hover"), var(--md-sys-color-surface-container-low));
98
120
  }
99
121
 
100
122
  &:active,
101
123
  &:focus {
102
124
  @include z-depth("1");
103
- background-color: color-mix(in oklab, var(--md-sys-color-primary) 10%, var(--md-sys-color-surface-container-low));
125
+ background-color: color-mix(in oklab, var(--md-sys-color-primary) state-opacity("focus"), var(--md-sys-color-surface-container-low));
104
126
  }
105
127
 
106
128
  @include focus-visible();
@@ -109,15 +131,19 @@
109
131
  @mixin btn-outlined {
110
132
  background-color: transparent;
111
133
  color: var(--md-sys-color-primary);
112
- border: 1px solid var(--md-sys-color-outline);
134
+ // The width is not subtracted from the inset the way `.icon-button.outlined`
135
+ // subtracts it: there it keeps a round control as wide as it is tall, and a
136
+ // common button's width is its content's. `border-box` already keeps the
137
+ // height right.
138
+ border: var(--md-comp-filled-button-outline-width, 1px) solid var(--md-sys-color-outline);
113
139
 
114
140
  &:hover {
115
- background-color: color-mix(in oklab, var(--md-sys-color-primary) 8%, transparent);
141
+ background-color: color-mix(in oklab, var(--md-sys-color-primary) state-opacity("hover"), transparent);
116
142
  }
117
143
 
118
144
  &:active,
119
145
  &:focus {
120
- background-color: color-mix(in oklab, var(--md-sys-color-primary) 10%, transparent);
146
+ background-color: color-mix(in oklab, var(--md-sys-color-primary) state-opacity("focus"), transparent);
121
147
  }
122
148
 
123
149
  @include focus-visible();
@@ -130,12 +156,12 @@
130
156
  border-color: transparent;
131
157
 
132
158
  &:hover {
133
- background-color: color-mix(in oklab, var(--md-sys-color-primary) 8%, transparent);
159
+ background-color: color-mix(in oklab, var(--md-sys-color-primary) state-opacity("hover"), transparent);
134
160
  }
135
161
 
136
162
  &:active,
137
163
  &:focus {
138
- background-color: color-mix(in oklab, var(--md-sys-color-primary) 10%, transparent);
164
+ background-color: color-mix(in oklab, var(--md-sys-color-primary) state-opacity("focus"), transparent);
139
165
  }
140
166
 
141
167
  @include focus-visible();
@@ -151,21 +177,129 @@
151
177
  cursor: default;
152
178
  }
153
179
 
180
+ // Off the screen but not out of the document: still focusable, still read, and
181
+ // still a form control. `display: none` and `visibility: hidden` both take the
182
+ // element out of the tab order, which is the wrong trade for a control whose
183
+ // label is drawn elsewhere - a hidden radio that cannot be focused has no
184
+ // arrow-key model left.
185
+ //
186
+ // _chips.scss and _navigation-rail.scss each carry their own copy of this and
187
+ // predate the mixin; fold them in when either is next touched.
188
+ @mixin visually-hidden {
189
+ position: absolute;
190
+ width: 1px;
191
+ height: 1px;
192
+ margin: -1px;
193
+ padding: 0;
194
+ overflow: hidden;
195
+ clip-path: inset(50%);
196
+ white-space: nowrap;
197
+ }
198
+
154
199
  // Focus with Keyboard
200
+ // The ring itself, for the rare selector that cannot be written as
201
+ // `&:focus-visible` on the focused element - a filter chip draws its ring on
202
+ // the <label>, but the focus is on the checkbox before it.
203
+ @mixin focus-ring {
204
+ outline: 3px solid var(--md-sys-color-secondary);
205
+ outline-offset: 2px;
206
+ }
207
+
155
208
  @mixin focus-visible {
156
209
  &:focus-visible {
157
- outline: 3px solid var(--md-sys-color-secondary);
158
- outline-offset: 2px;
210
+ @include focus-ring();
159
211
  }
160
212
  }
161
213
 
162
214
  // M3 type role. Every property must name a token tokens/_reference.scss
163
215
  // actually defines — a missing var() drops the whole declaration.
164
216
  @mixin typescale($role) {
165
- font-family: var(--md-sys-typescale-#{$role}-font-family-name), #{$font-stack};
217
+ font-family: var(--md-sys-typescale-#{$role}-font-family-name);
166
218
  font-size: var(--md-sys-typescale-#{$role}-font-size);
167
219
  font-weight: var(--md-sys-typescale-#{$role}-font-weight);
168
220
  line-height: var(--md-sys-typescale-#{$role}-line-height);
169
221
  letter-spacing: var(--md-sys-typescale-#{$role}-letter-spacing);
170
222
  }
171
223
 
224
+ /// The state layer some controls draw as a ring rather than an overlay.
225
+ ///
226
+ /// Checkboxes and radio buttons have no room for an overlay pseudo-element, so
227
+ /// they grow a `box-shadow` from the control's edge out to the state layer
228
+ /// size. `color-mix()` wants a percentage where the token is a number, hence
229
+ /// the `* 100%`.
230
+ @mixin state-layer-ring($outer, $inner, $color, $state) {
231
+ box-shadow: 0 0 0 calc((#{$outer} - #{$inner}) / 2)
232
+ color-mix(
233
+ in oklab,
234
+ #{$color} state-opacity($state),
235
+ transparent
236
+ );
237
+ }
238
+
239
+ /// The button size tokens for one rung of `$button-sizes`.
240
+ ///
241
+ /// Tokens only. The type role is emitted beside the call, never from here:
242
+ /// `:root` takes this mixin for the default size, and a typescale on :root
243
+ /// would set the page's font.
244
+ @mixin button-size($name) {
245
+ $s: map.get($button-sizes, $name);
246
+
247
+ --md-comp-filled-button-container-height: #{map.get($s, height)};
248
+ --md-comp-filled-button-icon-size: #{map.get($s, icon)};
249
+ --md-comp-filled-button-leading-space: #{map.get($s, space)};
250
+ --md-comp-filled-button-trailing-space: #{map.get($s, space)};
251
+ --md-comp-filled-button-icon-label-space: #{map.get($s, gap)};
252
+ --md-comp-filled-button-outline-width: #{map.get($s, outline)};
253
+ }
254
+
255
+ /// The icon-button tokens for one rung of `$icon-button-sizes`.
256
+ @mixin icon-button-size($name) {
257
+ $s: map.get($icon-button-sizes, $name);
258
+
259
+ --md-comp-icon-button-container-height: #{map.get($s, height)};
260
+ --md-comp-icon-button-icon-size: #{map.get($s, icon)};
261
+ --md-comp-icon-button-leading-space: #{map.get($s, space)};
262
+ --md-comp-icon-button-trailing-space: #{map.get($s, space)};
263
+ --md-comp-icon-button-narrow-space: #{map.get($s, narrow)};
264
+ --md-comp-icon-button-wide-space: #{map.get($s, wide)};
265
+ --md-comp-icon-button-container-shape-square: #{map.get($s, square)};
266
+ --md-comp-icon-button-pressed-container-shape: #{map.get($s, pressed)};
267
+ --md-comp-icon-button-outline-width: #{map.get($s, outline)};
268
+ }
269
+
270
+ // Media-covering triggers share the card state layer and focus indicator.
271
+ @mixin card-media-trigger {
272
+ position: absolute;
273
+ inset: 0;
274
+ z-index: 1;
275
+ display: block;
276
+ width: 100%;
277
+ height: 100%;
278
+ min-width: 0;
279
+ min-height: 0;
280
+ margin: 0;
281
+ padding: 0;
282
+ background: none;
283
+ border: 0;
284
+ border-radius: inherit;
285
+ box-shadow: none;
286
+ color: transparent;
287
+ cursor: pointer;
288
+
289
+ &:hover {
290
+ background-color: color-mix(in oklab, var(--md-comp-card-state-layer-color) calc(var(--md-comp-card-hover-state-layer-opacity) * 100%), transparent);
291
+ box-shadow: none;
292
+ }
293
+
294
+ &:focus-visible {
295
+ background-color: color-mix(in oklab, var(--md-comp-card-state-layer-color) calc(var(--md-comp-card-focus-state-layer-opacity) * 100%), transparent);
296
+ box-shadow: none;
297
+ outline: var(--md-comp-card-focus-indicator-thickness) solid var(--md-comp-card-focus-indicator-color);
298
+ outline-offset: calc(-1 * var(--md-comp-card-focus-indicator-thickness));
299
+ }
300
+
301
+ &:active {
302
+ background-color: color-mix(in oklab, var(--md-comp-card-state-layer-color) calc(var(--md-comp-card-pressed-state-layer-opacity) * 100%), transparent);
303
+ box-shadow: none;
304
+ }
305
+ }
@@ -1,5 +1,20 @@
1
1
  @use "sass:math";
2
2
 
3
+ // "Is this child an icon, or is it the label?" - a question several components
4
+ // ask, and every one of them used to answer it by element: <i> was the icon,
5
+ // <span> was the label. That broke the day the canonical icon became a <span>,
6
+ // silently, in five selectors at once: icon+label buttons lost their leading
7
+ // inset, icon-only toolbar actions stopped being 48dp circles and picked up the
8
+ // text-action pill instead.
9
+ //
10
+ // So the answer lives in one place. `i` stays in the list for markup written
11
+ // before 0.8.0. Interpolate it inside :is() - never as a bare comma list, which
12
+ // breaks out of its context (see $_bar in components/_navbar).
13
+ $icon: ":is(i, .material-symbols, .material-symbols-outlined, .material-symbols-rounded, .material-symbols-sharp, .material-icons)" !default;
14
+
15
+ // The label beside an icon: any span that is not itself an icon.
16
+ $icon-label: "span:not(.material-symbols):not(.material-symbols-outlined):not(.material-symbols-rounded):not(.material-symbols-sharp):not(.material-icons)" !default;
17
+
3
18
  // Colors
4
19
  //
5
20
  // The 2014 Material palette ($colors + colorFunc()) is gone. It generated 532
@@ -38,7 +53,7 @@ $menu-item-height: 50px !default;
38
53
 
39
54
  // Media query ranges now live in abstracts/_breakpoints.scss, which states
40
55
  // each boundary once and emits range syntax - see the note there about why
41
- // the 600.99px / 992.99px / 1200.99px values are gone.
56
+ // the old fractional max-width values are gone.
42
57
 
43
58
  // Grid
44
59
  $gutter-width: 1.5rem !default;
@@ -46,12 +61,22 @@ $element-top-margin: math.div($gutter-width, 3) !default;
46
61
  $element-bottom-margin: math.div($gutter-width * 2, 3) !default;
47
62
 
48
63
  // Typography
49
- $font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !default;
64
+ // M3's typeface tokens choose Roboto by default. Noto Sans is the first
65
+ // fallback so unsupported scripts keep a compatible visual style.
66
+ $font-stack: var(--md-ref-typeface-plain), var(--md-ref-typeface-fallback) !default;
67
+
68
+ // Self-hosted woff2 directory, relative to the compiled CSS file. The npm
69
+ // build writes CSS to dist/css/ and fonts to dist/fonts/. Override when
70
+ // compiling Sass to a different location. Set $expressive-include-fonts to
71
+ // false to emit no @font-face rules (for example when the page already
72
+ // loads these families from elsewhere).
73
+ $expressive-font-path: "../fonts" !default;
74
+ $expressive-include-fonts: true !default;
50
75
  $text-color: var(--md-sys-color-on-background) !default;
51
76
 
52
- // Base of the html font-size ladder in base/_typography.scss. Flow text is
53
- // derived from it, so the two cannot drift.
54
- $root-font-size: 14px !default;
77
+ // Modern browsers default to 16px. M3's web conversion is SP_SIZE / 16 = rem;
78
+ // flow text uses the same basis without overriding the user's browser setting.
79
+ $root-font-size: 16px !default;
55
80
 
56
81
  // Flow text: font-size interpolates between these two across the viewport
57
82
  // range below. This used to be a @while loop emitting 21 media queries.
@@ -59,3 +84,42 @@ $flow-text-min: 1.2rem !default;
59
84
  $flow-text-max: 1.68rem !default; // the old loop's ceiling: 1.2rem * (1 + .02 * 20)
60
85
  $flow-text-from: 360px !default;
61
86
  $flow-text-to: 960px !default; // the old loop's last step: 360px + 20 * 30px
87
+
88
+ // The common-button size axis, md.comp.button.{xsmall…xlarge} (DSP 34.0.21).
89
+ //
90
+ // size height icon space gap label outline
91
+ // xsmall 32 20 12 8 label-large 1
92
+ // small 40 20 16 8 label-large 1 <- default
93
+ // medium 56 24 24 8 title-medium 1
94
+ // large 96 32 48 12 headline-small 2
95
+ // xlarge 136 40 64 16 headline-large 3
96
+ //
97
+ // `space` is both the leading and the trailing inset. Expressive dropped the
98
+ // old "24dp, but 16dp next to an icon" asymmetry, so a button is symmetric at
99
+ // every size, whatever is inside it. The round shape is corner.full at every
100
+ // size, so it is declared once by the component rather than five times here.
101
+ // `outline` is the one number the style axis reads back: M3 thickens an
102
+ // outlined button's border with its size.
103
+ //
104
+ // It lives in abstracts because two components read it - `_buttons.scss` for
105
+ // the size classes, `_button-groups.scss` to size the items of a group from
106
+ // the group itself. A partial may not @use another partial, so a table two of
107
+ // them share has to be here.
108
+ $button-sizes: (
109
+ "xsmall": (height: 32px, icon: 20px, space: 12px, gap: 8px, outline: 1px, label: "label-large"),
110
+ "small": (height: 40px, icon: 20px, space: 16px, gap: 8px, outline: 1px, label: "label-large"),
111
+ "medium": (height: 56px, icon: 24px, space: 24px, gap: 8px, outline: 1px, label: "title-medium"),
112
+ "large": (height: 96px, icon: 32px, space: 48px, gap: 12px, outline: 2px, label: "headline-small"),
113
+ "xlarge": (height: 136px, icon: 40px, space: 64px, gap: 16px, outline: 3px, label: "headline-large"),
114
+ );
115
+
116
+ // Icon-button geometry is shared with button groups. A group-size class must
117
+ // reach both common buttons and icon buttons without asking authors to repeat
118
+ // the size on every child.
119
+ $icon-button-sizes: (
120
+ "xsmall": (height: 32px, icon: 20px, space: 6px, narrow: 4px, wide: 10px, square: 12px, pressed: 8px, outline: 1px),
121
+ "small": (height: 40px, icon: 24px, space: 8px, narrow: 4px, wide: 14px, square: 12px, pressed: 8px, outline: 1px),
122
+ "medium": (height: 56px, icon: 24px, space: 16px, narrow: 12px, wide: 24px, square: 16px, pressed: 12px, outline: 1px),
123
+ "large": (height: 96px, icon: 32px, space: 32px, narrow: 16px, wide: 48px, square: 28px, pressed: 16px, outline: 2px),
124
+ "xlarge": (height: 136px, icon: 40px, space: 48px, narrow: 32px, wide: 72px, square: 28px, pressed: 16px, outline: 3px),
125
+ );
@@ -0,0 +1,63 @@
1
+ @use "../abstracts" as *;
2
+
3
+ // Self-hosted faces for the families the tokens and icon classes name.
4
+ // Unlayered on purpose: @font-face inside a cascade layer is unreliable.
5
+ // $expressive-font-path is relative to the compiled CSS (dist/css -> dist/fonts).
6
+
7
+ @if $expressive-include-fonts {
8
+ @font-face {
9
+ font-family: "Material Symbols Outlined";
10
+ font-style: normal;
11
+ font-weight: 100 700;
12
+ font-display: block;
13
+ src: url("#{$expressive-font-path}/material-symbols-outlined.woff2") format("woff2");
14
+ }
15
+
16
+ @font-face {
17
+ font-family: "Material Symbols Rounded";
18
+ font-style: normal;
19
+ font-weight: 100 700;
20
+ font-display: block;
21
+ src: url("#{$expressive-font-path}/material-symbols-rounded.woff2") format("woff2");
22
+ }
23
+
24
+ @font-face {
25
+ font-family: "Material Symbols Sharp";
26
+ font-style: normal;
27
+ font-weight: 100 700;
28
+ font-display: block;
29
+ src: url("#{$expressive-font-path}/material-symbols-sharp.woff2") format("woff2");
30
+ }
31
+
32
+ @font-face {
33
+ font-family: "Roboto";
34
+ font-style: normal;
35
+ font-weight: 400;
36
+ font-display: swap;
37
+ src: url("#{$expressive-font-path}/roboto-latin-400.woff2") format("woff2");
38
+ }
39
+
40
+ @font-face {
41
+ font-family: "Roboto";
42
+ font-style: normal;
43
+ font-weight: 500;
44
+ font-display: swap;
45
+ src: url("#{$expressive-font-path}/roboto-latin-500.woff2") format("woff2");
46
+ }
47
+
48
+ @font-face {
49
+ font-family: "Noto Sans";
50
+ font-style: normal;
51
+ font-weight: 400;
52
+ font-display: swap;
53
+ src: url("#{$expressive-font-path}/noto-sans-latin-400.woff2") format("woff2");
54
+ }
55
+
56
+ @font-face {
57
+ font-family: "Noto Sans";
58
+ font-style: normal;
59
+ font-weight: 500;
60
+ font-display: swap;
61
+ src: url("#{$expressive-font-path}/noto-sans-latin-500.woff2") format("woff2");
62
+ }
63
+ }
@@ -35,30 +35,13 @@ a {
35
35
  // Blockquote
36
36
  blockquote { margin: 20px 0; padding-left: 1.5rem; border-left: 5px solid var(--md-sys-color-primary); }
37
37
 
38
- // Icon Styles
38
+ // <i> as an element - italics. The icon modifiers (.left, .right, .tiny ..)
39
+ // used to live here, keyed on `i`, which silently tied them to one element:
40
+ // writing an icon as the canonical <span class="material-symbols"> dropped
41
+ // them. They now hang off the icon class list in components/_icons-material-design,
42
+ // where the size modifiers already were, so they follow the class instead.
39
43
  i {
40
44
  line-height: inherit;
41
- &.left {
42
- float: left;
43
- margin-left: -8px;
44
- // margin-right: 15px;
45
- }
46
- &.right {
47
- float: right;
48
- // margin-left: 15px;
49
- }
50
- &.tiny {
51
- font-size: 1rem;
52
- }
53
- &.small {
54
- font-size: 2rem;
55
- }
56
- &.medium {
57
- font-size: 4rem;
58
- }
59
- &.large {
60
- font-size: 6rem;
61
- }
62
45
  }
63
46
 
64
47
  // Tables
@@ -113,7 +96,7 @@ td, th {
113
96
  }
114
97
 
115
98
  // Responsive Table
116
- @include bp-down("large") {
99
+ @include bp-down("expanded") {
117
100
 
118
101
  table.responsive-table {
119
102
  width: 100%;
@@ -178,4 +161,3 @@ td, th {
178
161
  }
179
162
  }
180
163
  }
181
-