@expressivecss/expressive 0.7.0-rc.0 → 0.8.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 (105) hide show
  1. package/CHANGELOG.md +1311 -1
  2. package/README.md +14 -11
  3. package/dist/css/expressive.css +4215 -1668
  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/js/expressive.cjs +1231 -1253
  8. package/dist/js/expressive.cjs.map +4 -4
  9. package/dist/js/expressive.js +1231 -1253
  10. package/dist/js/expressive.js.map +4 -4
  11. package/dist/js/expressive.min.js +12 -12
  12. package/dist/js/expressive.min.js.map +4 -4
  13. package/dist/js/expressive.mjs +1231 -1253
  14. package/dist/js/expressive.mjs.map +4 -4
  15. package/dist/types/behaviors/bottomSheets.d.ts +19 -11
  16. package/dist/types/behaviors/sheetDrag.d.ts +40 -0
  17. package/dist/types/behaviors/sideSheets.d.ts +2 -9
  18. package/dist/types/components/autocomplete.d.ts +15 -3
  19. package/dist/types/components/buttons.d.ts +5 -1
  20. package/dist/types/components/carousel.d.ts +87 -53
  21. package/dist/types/components/chips.d.ts +21 -1
  22. package/dist/types/components/expandingCard.d.ts +31 -0
  23. package/dist/types/components/index.d.ts +21 -20
  24. package/dist/types/components/lightbox.d.ts +1 -0
  25. package/dist/types/components/menu.d.ts +7 -1
  26. package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
  27. package/dist/types/components/registry.d.ts +8 -8
  28. package/dist/types/components/slider.d.ts +26 -85
  29. package/dist/types/components/snackbar.d.ts +20 -5
  30. package/dist/types/components/tabs.d.ts +12 -0
  31. package/dist/types/components/timepicker.d.ts +12 -9
  32. package/dist/types/core/utils.d.ts +64 -53
  33. package/dist/types/index.d.ts +8 -9
  34. package/package.json +15 -1
  35. package/src/sass/README.md +61 -11
  36. package/src/sass/abstracts/_breakpoints.scss +17 -19
  37. package/src/sass/abstracts/_mixins.scss +103 -21
  38. package/src/sass/abstracts/_variables.scss +50 -5
  39. package/src/sass/base/_global.scss +6 -24
  40. package/src/sass/base/_grid.scss +15 -12
  41. package/src/sass/base/_normalize.scss +34 -38
  42. package/src/sass/base/_typography.scss +4 -18
  43. package/src/sass/components/_badges.scss +2 -3
  44. package/src/sass/components/_banners.scss +298 -0
  45. package/src/sass/components/_bottom-app-bar.scss +180 -0
  46. package/src/sass/components/_bottom-sheet.scss +12 -4
  47. package/src/sass/components/_button-groups.scss +186 -0
  48. package/src/sass/components/_buttons.scss +245 -92
  49. package/src/sass/components/_cards.scss +494 -73
  50. package/src/sass/components/_carousel.scss +562 -68
  51. package/src/sass/components/_chips.scss +64 -5
  52. package/src/sass/components/_datepicker.scss +24 -24
  53. package/src/sass/components/_dialog.scss +17 -11
  54. package/src/sass/components/_drag-handle.scss +130 -0
  55. package/src/sass/components/_expanding-card.scss +260 -0
  56. package/src/sass/components/_fab-menu.scss +232 -0
  57. package/src/sass/components/_floating-sheet.scss +86 -0
  58. package/src/sass/components/_icon-buttons.scss +194 -0
  59. package/src/sass/components/_icons-material-design.scss +16 -4
  60. package/src/sass/components/_index.scss +22 -6
  61. package/src/sass/components/_list.scss +16 -13
  62. package/src/sass/components/_loading-indicator.scss +106 -0
  63. package/src/sass/components/_menu.scss +335 -61
  64. package/src/sass/components/_navbar.scss +71 -24
  65. package/src/sass/components/_navigation-bar.scss +13 -10
  66. package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
  67. package/src/sass/components/_navigation-rail.scss +22 -22
  68. package/src/sass/components/_page-footer.scss +19 -16
  69. package/src/sass/components/_pagination.scss +24 -6
  70. package/src/sass/components/_panes.scss +52 -11
  71. package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
  72. package/src/sass/components/_scrim.scss +37 -0
  73. package/src/sass/components/_search.scss +259 -0
  74. package/src/sass/components/_segmented-buttons.scss +188 -0
  75. package/src/sass/components/_side-sheet.scss +2 -2
  76. package/src/sass/components/_snackbar.scss +20 -15
  77. package/src/sass/components/_split-button.scss +196 -0
  78. package/src/sass/components/_tabs.scss +7 -8
  79. package/src/sass/components/_timepicker.scss +67 -58
  80. package/src/sass/components/_toolbar.scss +206 -80
  81. package/src/sass/components/forms/_checkboxes.scss +49 -12
  82. package/src/sass/components/forms/_fieldset.scss +4 -4
  83. package/src/sass/components/forms/_file-input.scss +8 -1
  84. package/src/sass/components/forms/_index.scss +1 -1
  85. package/src/sass/components/forms/_input-fields.scss +50 -44
  86. package/src/sass/components/forms/_radio-buttons.scss +42 -10
  87. package/src/sass/components/forms/_select.scss +0 -1
  88. package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
  89. package/src/sass/components/forms/_switches.scss +6 -7
  90. package/src/sass/tokens/_index.scss +4 -1
  91. package/src/sass/tokens/_reference.scss +70 -61
  92. package/src/sass/tokens/_state.scss +24 -0
  93. package/src/sass/tokens/_theme.scss +6 -3
  94. package/src/sass/tokens/_vibrant.scss +76 -0
  95. package/src/sass/utilities/_colors.scss +14 -1
  96. package/src/sass/utilities/_helpers.scss +5 -5
  97. package/src/sass/utilities/_visibility.scss +48 -26
  98. package/dist/types/behaviors/waves.d.ts +0 -15
  99. package/dist/types/components/parallax.d.ts +0 -16
  100. package/dist/types/components/range.d.ts +0 -43
  101. package/dist/types/core/bounding.d.ts +0 -6
  102. package/dist/types/core/edges.d.ts +0 -6
  103. package/src/sass/components/_parallax.scss +0 -104
  104. package/src/sass/components/_pulse.scss +0 -37
  105. package/src/sass/components/_slider.scss +0 -129
@@ -4,18 +4,24 @@
4
4
  //
5
5
  // Anatomy is the HTML. A <menu id> is the surface; each <li> is an item.
6
6
  // A leading <i> is the leading icon; a trailing <i> or <kbd> is trailing
7
- // content. An <hr> is a divider. A .gap <li> splits groups. A .label
7
+ // content. An <li class="divider" role="separator"> is a divider; a bare
8
+ // <hr> is the older form and still styled, though <menu> only permits <li>.
9
+ // A .gap <li> splits groups. A .label
8
10
  // <li> is a 32dp group heading. A nested <menu> is a flyout. The trigger
9
11
  // is .menu-trigger[data-target] — that is the JS contract.
10
12
  //
11
- // Tokens follow https://m3.material.io/components/menus/specs
12
- // container surface-container, 16dp corners, elevation 2, 112–280
13
- // padding 4dp
14
- // item 48dp, extra-small (4dp) corners, label-large / on-surface
13
+ // Tokens are md.comp.menus - the expressive family, not baseline
14
+ // md.comp.menu, which is a different component with its own values.
15
+ // https://m3.material.io/components/menus/specs
16
+ // container surface-container-low, 16dp corners, level 2, 112–280
17
+ // group 2dp padding, 2dp gap, 8dp corners
18
+ // item 44dp, 16dp insets, 4dp corners, label-large / on-surface
19
+ // end items 12dp outer corner, 4dp inner
20
+ // state layer hover 8%, focus / pressed 10%
15
21
  // selected medium (12dp) corners, tertiary-container / on-tertiary
16
22
  // icon 20dp, on-surface-variant
17
23
  // divider 1dp outline-variant, inset (not full-bleed)
18
- // gap 6dp, more expressive than a divider
24
+ // gap 2dp, more expressive than a divider
19
25
  // label 32dp, on-surface-variant
20
26
  // vibrant tertiary-container surface; selected is tertiary
21
27
  // submenu scale + fade; focused flyout rounds up, parent rounds down
@@ -29,24 +35,43 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
29
35
  }
30
36
 
31
37
  @mixin menu-surface {
32
- --md-comp-menu-container-color: var(--md-sys-color-surface-container);
38
+ --md-comp-menu-container-color: var(--md-sys-color-surface-container-low);
33
39
  --md-comp-menu-container-shape: 16px;
34
- --md-comp-menu-item-container-height: 48px;
40
+ --md-comp-menu-group-shape: 8px;
41
+ --md-comp-menu-container-inactive-shape: var(--md-comp-menu-group-shape);
42
+ --md-comp-menu-group-padding: 2px;
43
+ --md-comp-menu-gap: 2px;
44
+ --md-comp-menu-item-container-height: 44px;
35
45
  --md-comp-menu-item-container-shape: 4px;
46
+ --md-comp-menu-item-edge-shape: 12px;
36
47
  --md-comp-menu-item-selected-container-shape: 12px;
48
+ --md-comp-menu-item-leading-space: 16px;
49
+ --md-comp-menu-item-trailing-space: 16px;
50
+ --md-comp-menu-item-between-space: 12px;
37
51
  --md-comp-menu-item-label-text-color: var(--md-sys-color-on-surface);
38
- --md-comp-menu-item-leading-icon-color: var(--md-sys-color-on-surface-variant);
39
- --md-comp-menu-item-trailing-icon-color: var(--md-sys-color-on-surface-variant);
40
- --md-comp-menu-item-selected-container-color: var(--md-sys-color-tertiary-container);
41
- --md-comp-menu-item-selected-label-text-color: var(--md-sys-color-on-tertiary-container);
42
- --md-comp-menu-item-selected-leading-icon-color: var(--md-sys-color-on-tertiary-container);
52
+ --md-comp-menu-item-leading-icon-color: var(
53
+ --md-sys-color-on-surface-variant
54
+ );
55
+ --md-comp-menu-item-trailing-icon-color: var(
56
+ --md-sys-color-on-surface-variant
57
+ );
58
+ --md-comp-menu-item-selected-container-color: var(
59
+ --md-sys-color-tertiary-container
60
+ );
61
+ --md-comp-menu-item-selected-label-text-color: var(
62
+ --md-sys-color-on-tertiary-container
63
+ );
64
+ --md-comp-menu-item-selected-leading-icon-color: var(
65
+ --md-sys-color-on-tertiary-container
66
+ );
43
67
  --md-comp-menu-item-icon-size: 20px;
44
68
  --md-comp-menu-supporting-text-color: var(--md-sys-color-on-surface-variant);
45
69
 
46
70
  box-sizing: border-box;
47
71
  min-width: 112px;
72
+ max-width: 280px;
48
73
  margin: 0;
49
- padding: 4px;
74
+ padding: var(--md-comp-menu-group-padding);
50
75
  list-style: none;
51
76
  background-color: var(--md-comp-menu-container-color);
52
77
  border-radius: var(--md-comp-menu-container-shape);
@@ -54,15 +79,37 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
54
79
  @include z-depth("2");
55
80
  transition: border-radius 200ms ease;
56
81
 
57
- &.vibrant {
82
+ // The [vibrant] arms are not a second mapping - they are the statement that
83
+ // a menu on a vibrant surface IS a vibrant menu. Without them the foundation
84
+ // paints the container tertiary-container while the selected item keeps the
85
+ // same tertiary-container from menu-surface above, and the selected state
86
+ // disappears into its own menu. M3's menus-vibrant answers that by moving
87
+ // selection to solid tertiary, which is exactly what this block already did.
88
+ // FormSelect's option fill reads the same token, so it follows.
89
+ &.vibrant,
90
+ &[vibrant],
91
+ [vibrant] &,
92
+ :host([vibrant]) & {
58
93
  --md-comp-menu-container-color: var(--md-sys-color-tertiary-container);
59
- --md-comp-menu-item-label-text-color: var(--md-sys-color-on-tertiary-container);
60
- --md-comp-menu-item-leading-icon-color: var(--md-sys-color-on-tertiary-container);
61
- --md-comp-menu-item-trailing-icon-color: var(--md-sys-color-on-tertiary-container);
62
- --md-comp-menu-supporting-text-color: var(--md-sys-color-on-tertiary-container);
94
+ --md-comp-menu-item-label-text-color: var(
95
+ --md-sys-color-on-tertiary-container
96
+ );
97
+ --md-comp-menu-item-leading-icon-color: var(
98
+ --md-sys-color-on-tertiary-container
99
+ );
100
+ --md-comp-menu-item-trailing-icon-color: var(
101
+ --md-sys-color-on-tertiary-container
102
+ );
103
+ --md-comp-menu-supporting-text-color: var(
104
+ --md-sys-color-on-tertiary-container
105
+ );
63
106
  --md-comp-menu-item-selected-container-color: var(--md-sys-color-tertiary);
64
- --md-comp-menu-item-selected-label-text-color: var(--md-sys-color-on-tertiary);
65
- --md-comp-menu-item-selected-leading-icon-color: var(--md-sys-color-on-tertiary);
107
+ --md-comp-menu-item-selected-label-text-color: var(
108
+ --md-sys-color-on-tertiary
109
+ );
110
+ --md-comp-menu-item-selected-leading-icon-color: var(
111
+ --md-sys-color-on-tertiary
112
+ );
66
113
  }
67
114
 
68
115
  > li {
@@ -74,21 +121,31 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
74
121
  text-align: left;
75
122
  border-radius: var(--md-comp-menu-item-container-shape);
76
123
 
124
+ // The bare-span arm is the non-interactive item. It has to exclude icons,
125
+ // which are spans as well and are a legal direct child of the <li> (see
126
+ // the `> #{$icon}` arm below) - otherwise one gets stretched to a
127
+ // full-width 48dp row.
77
128
  > a,
78
129
  > button,
79
- > span {
130
+ > #{$icon-label} {
80
131
  display: flex;
81
132
  align-items: center;
82
- gap: 12px;
133
+ // A <button> takes the common button's centered flex layout; menu
134
+ // commands must line up with link items at the leading inset instead.
135
+ justify-content: flex-start;
136
+ gap: var(--md-comp-menu-item-between-space);
83
137
  box-sizing: border-box;
84
138
  width: 100%;
85
139
  min-height: var(--md-comp-menu-item-container-height);
86
140
  margin: 0;
87
- padding: 0 8px;
141
+ padding-block: 8px;
142
+ padding-inline: var(--md-comp-menu-item-leading-space)
143
+ var(--md-comp-menu-item-trailing-space);
88
144
  border: 0;
89
145
  background: none;
90
146
  color: var(--md-comp-menu-item-label-text-color);
91
147
  text-decoration: none;
148
+ text-align: start;
92
149
  cursor: pointer;
93
150
 
94
151
  &:hover,
@@ -96,12 +153,24 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
96
153
  background: none;
97
154
  }
98
155
  @include typescale("label-large");
99
- font-family: $font-stack;
100
156
  }
101
157
 
102
- > a > i,
103
- > button > i,
104
- > i {
158
+ > :is(a, button) > .menu-item-text {
159
+ display: flex;
160
+ min-width: 0;
161
+ flex: 1 1 auto;
162
+ flex-direction: column;
163
+ align-items: flex-start;
164
+ }
165
+
166
+ > :is(a, button) .supporting-text {
167
+ color: var(--md-comp-menu-supporting-text-color);
168
+ @include typescale("body-small");
169
+ }
170
+
171
+ > a > #{$icon},
172
+ > button > #{$icon},
173
+ > #{$icon} {
105
174
  flex: 0 0 var(--md-comp-menu-item-icon-size);
106
175
  font-size: var(--md-comp-menu-item-icon-size);
107
176
  width: var(--md-comp-menu-item-icon-size);
@@ -110,7 +179,16 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
110
179
  color: var(--md-comp-menu-item-leading-icon-color);
111
180
  }
112
181
 
113
- > :is(a, button) > i:last-child:not(:only-child) {
182
+ // A lone icon is ambiguous to CSS. In `<a>Label<span icon/></a>` and
183
+ // `<a><span icon/>Label</a>` the span is :first-child, :last-child AND
184
+ // :only-child in both cases - the label is a text node, and selectors
185
+ // cannot see text. So :not(:only-child) only ever caught rows carrying two
186
+ // icons, and every text-plus-trailing-icon row silently kept its icon
187
+ // butted against the label instead of flush right.
188
+ //
189
+ // Leading stays the default; a trailing icon names its side, the same way
190
+ // a field icon must (see rule 3 in CLAUDE.md).
191
+ > :is(a, button) > #{$icon}:is(.suffix, :last-child:not(:only-child)) {
114
192
  margin-inline-start: auto;
115
193
  color: var(--md-comp-menu-item-trailing-icon-color);
116
194
  }
@@ -124,15 +202,39 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
124
202
  font-family: inherit;
125
203
  @include typescale("label-large");
126
204
  }
205
+
206
+ > :is(a, button) > .badge {
207
+ margin-inline-start: auto;
208
+ }
127
209
  }
128
210
 
129
- > li:not(.disabled, .divider, .optgroup, .gap, .label) {
211
+ > li:not(.divider, .optgroup, .gap, .label) {
130
212
  min-height: var(--md-comp-menu-item-container-height);
131
213
  }
132
214
 
215
+ > li:not(.divider, .optgroup, .gap, .label):first-child {
216
+ border-radius: var(--md-comp-menu-item-edge-shape)
217
+ var(--md-comp-menu-item-edge-shape)
218
+ var(--md-comp-menu-item-container-shape)
219
+ var(--md-comp-menu-item-container-shape);
220
+ }
221
+
222
+ > li:not(.divider, .optgroup, .gap, .label):last-child {
223
+ border-radius: var(--md-comp-menu-item-container-shape)
224
+ var(--md-comp-menu-item-container-shape)
225
+ var(--md-comp-menu-item-edge-shape) var(--md-comp-menu-item-edge-shape);
226
+ }
227
+
133
228
  // Hover / press sit on extra-small corners and do not span the container.
134
- > li:not(.disabled, .divider, .optgroup, .gap, .label):hover,
135
- > li.active:not(.selected, [aria-selected="true"]) {
229
+ > li:not(
230
+ .disabled,
231
+ [aria-disabled="true"],
232
+ .divider,
233
+ .optgroup,
234
+ .gap,
235
+ .label
236
+ ):hover,
237
+ > li.active:not(.selected, [aria-selected="true"], [aria-checked="true"]) {
136
238
  background-color: color-mix(
137
239
  in oklab,
138
240
  var(--md-comp-menu-item-label-text-color) 8%,
@@ -140,7 +242,14 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
140
242
  );
141
243
  }
142
244
 
143
- > li:not(.disabled, .divider, .optgroup, .gap, .label):active {
245
+ > li:not(
246
+ .disabled,
247
+ [aria-disabled="true"],
248
+ .divider,
249
+ .optgroup,
250
+ .gap,
251
+ .label
252
+ ):active {
144
253
  background-color: color-mix(
145
254
  in oklab,
146
255
  var(--md-comp-menu-item-label-text-color) 10%,
@@ -148,24 +257,31 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
148
257
  );
149
258
  }
150
259
 
151
- > li.selected,
152
- > li[aria-selected="true"] {
260
+ // :not(.divider) only buys specificity: menu-item.selected.shape is 12dp
261
+ // unconditionally, so it has to outrank the first/last-child edge rule that
262
+ // would otherwise leave a selected end item with 4dp inner corners.
263
+ > li:is(.selected, [aria-selected="true"], [aria-checked="true"]):not(
264
+ .divider
265
+ ) {
153
266
  border-radius: var(--md-comp-menu-item-selected-container-shape);
154
267
  background-color: var(--md-comp-menu-item-selected-container-color);
155
268
  color: var(--md-comp-menu-item-selected-label-text-color);
156
269
 
157
- > :is(a, button, span) {
270
+ > :is(a, button, #{$icon-label}) {
158
271
  color: var(--md-comp-menu-item-selected-label-text-color);
159
272
  }
160
273
 
161
- > :is(a, button, span) > i,
162
- > i {
274
+ > :is(a, button, #{$icon-label}) > #{$icon},
275
+ > #{$icon} {
163
276
  color: var(--md-comp-menu-item-selected-leading-icon-color);
164
277
  }
278
+
279
+ > :is(a, button, #{$icon-label}) :is(.supporting-text, kbd, .shortcut) {
280
+ color: var(--md-comp-menu-item-selected-label-text-color);
281
+ }
165
282
  }
166
283
 
167
- > li.selected:hover,
168
- > li[aria-selected="true"]:hover {
284
+ > li:is(.selected, [aria-selected="true"], [aria-checked="true"]):hover {
169
285
  background-color: color-mix(
170
286
  in oklab,
171
287
  var(--md-comp-menu-item-selected-label-text-color) 8%,
@@ -173,12 +289,24 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
173
289
  );
174
290
  }
175
291
 
292
+ > li:is(.selected, [aria-selected="true"], [aria-checked="true"]):is(
293
+ :focus,
294
+ :focus-visible,
295
+ :active
296
+ ) {
297
+ background-color: color-mix(
298
+ in oklab,
299
+ var(--md-comp-menu-item-selected-label-text-color) 10%,
300
+ var(--md-comp-menu-item-selected-container-color)
301
+ );
302
+ }
303
+
176
304
  > li:focus-visible {
177
305
  outline: 3px solid var(--md-sys-color-secondary);
178
306
  outline-offset: -3px;
179
307
  }
180
308
 
181
- > li.disabled {
309
+ > li:is(.disabled, [aria-disabled="true"]) {
182
310
  pointer-events: none;
183
311
  opacity: 0.38;
184
312
  }
@@ -201,9 +329,9 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
201
329
 
202
330
  // Gap: more expressive than a divider. One or two per menu; not in a scroller.
203
331
  > li.gap {
204
- height: 6px;
205
- min-height: 6px;
206
- margin: 0 -4px;
332
+ height: var(--md-comp-menu-gap);
333
+ min-height: var(--md-comp-menu-gap);
334
+ margin: 0 calc(-1 * var(--md-comp-menu-group-padding));
207
335
  padding: 0;
208
336
  pointer-events: none;
209
337
  cursor: default;
@@ -221,14 +349,126 @@ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg
221
349
  cursor: default;
222
350
  color: var(--md-comp-menu-supporting-text-color);
223
351
  @include typescale("label-large");
224
- font-family: $font-stack;
352
+ }
353
+
354
+ // Grouped layout: each run of items between .gap separators becomes its
355
+ // own coplanar surface. Standard remains the default continuous surface.
356
+ &.grouped {
357
+ padding: 0;
358
+ background-color: transparent;
359
+ box-shadow: none;
360
+ filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.14))
361
+ drop-shadow(0 1px 4px rgba(0, 0, 0, 0.12));
362
+
363
+ > li:not(.divider, .optgroup, .gap, .label) {
364
+ padding-inline: var(--md-comp-menu-group-padding);
365
+ background-color: var(--md-comp-menu-container-color);
366
+ border-radius: 0;
367
+ }
368
+
369
+ > li:not(.divider, .optgroup, .gap, .label):first-child,
370
+ > li.gap + li:not(.divider, .optgroup, .gap, .label) {
371
+ padding-block-start: var(--md-comp-menu-group-padding);
372
+ border-start-start-radius: var(--md-comp-menu-group-shape);
373
+ border-start-end-radius: var(--md-comp-menu-group-shape);
374
+ }
375
+
376
+ > li:not(.divider, .optgroup, .gap, .label):has(+ li.gap),
377
+ > li:not(.divider, .optgroup, .gap, .label):last-child {
378
+ padding-block-end: var(--md-comp-menu-group-padding);
379
+ border-end-start-radius: var(--md-comp-menu-group-shape);
380
+ border-end-end-radius: var(--md-comp-menu-group-shape);
381
+ }
382
+
383
+ > li.gap {
384
+ margin: 0;
385
+ }
386
+
387
+ // State layers belong to the inset item while the group surface remains.
388
+ > li:not(
389
+ .disabled,
390
+ [aria-disabled="true"],
391
+ .divider,
392
+ .optgroup,
393
+ .gap,
394
+ .label
395
+ ):hover,
396
+ > li.active,
397
+ > li.selected,
398
+ > li[aria-selected="true"],
399
+ > li[aria-checked="true"] {
400
+ background-color: var(--md-comp-menu-container-color);
401
+ }
402
+
403
+ > li:not(
404
+ .disabled,
405
+ [aria-disabled="true"],
406
+ .divider,
407
+ .optgroup,
408
+ .gap,
409
+ .label
410
+ ):hover
411
+ > :is(a, button, #{$icon-label}),
412
+ > li.active:not(.selected, [aria-selected="true"], [aria-checked="true"])
413
+ > :is(a, button, #{$icon-label}) {
414
+ border-radius: var(--md-comp-menu-item-container-shape);
415
+ background-color: color-mix(
416
+ in oklab,
417
+ var(--md-comp-menu-item-label-text-color) 8%,
418
+ transparent
419
+ );
420
+ }
421
+
422
+ > li:not(
423
+ .disabled,
424
+ [aria-disabled="true"],
425
+ .divider,
426
+ .optgroup,
427
+ .gap,
428
+ .label
429
+ ):active
430
+ > :is(a, button, #{$icon-label}) {
431
+ background-color: color-mix(
432
+ in oklab,
433
+ var(--md-comp-menu-item-label-text-color) 10%,
434
+ transparent
435
+ );
436
+ }
437
+
438
+ > li:is(.selected, [aria-selected="true"], [aria-checked="true"])
439
+ > :is(a, button, #{$icon-label}) {
440
+ border-radius: var(--md-comp-menu-item-selected-container-shape);
441
+ background-color: var(--md-comp-menu-item-selected-container-color);
442
+ }
443
+
444
+ > li:is(.selected, [aria-selected="true"], [aria-checked="true"]):hover
445
+ > :is(a, button, #{$icon-label}) {
446
+ background-color: color-mix(
447
+ in oklab,
448
+ var(--md-comp-menu-item-selected-label-text-color) 8%,
449
+ var(--md-comp-menu-item-selected-container-color)
450
+ );
451
+ }
452
+
453
+ > li:is(.selected, [aria-selected="true"], [aria-checked="true"]):is(
454
+ :focus,
455
+ :focus-visible,
456
+ :active
457
+ )
458
+ > :is(a, button, #{$icon-label}) {
459
+ background-color: color-mix(
460
+ in oklab,
461
+ var(--md-comp-menu-item-selected-label-text-color) 10%,
462
+ var(--md-comp-menu-item-selected-container-color)
463
+ );
464
+ }
225
465
  }
226
466
 
227
467
  li:has(> menu) {
228
468
  position: relative;
229
469
  }
230
470
 
231
- li:has(> menu) > :is(a, button, span)::after {
471
+ li:has(> menu) > :is(a, button, #{$icon-label})::after {
232
472
  content: "";
233
473
  flex: 0 0 var(--md-comp-menu-item-icon-size);
234
474
  width: var(--md-comp-menu-item-icon-size);
@@ -259,7 +499,9 @@ menu[id] {
259
499
 
260
500
  // Unfocused parent rounds down while a flyout is the focus.
261
501
  &:has(> li:is(:hover, :focus-within, .open) > menu) {
262
- --md-comp-menu-container-shape: 8px;
502
+ --md-comp-menu-container-shape: var(
503
+ --md-comp-menu-container-inactive-shape
504
+ );
263
505
  }
264
506
  }
265
507
 
@@ -293,7 +535,7 @@ menu[id] li > menu {
293
535
  opacity: 1;
294
536
  pointer-events: auto;
295
537
  transform: none;
296
- --md-comp-menu-container-shape: 20px;
538
+ --md-comp-menu-container-shape: 16px;
297
539
  }
298
540
  }
299
541
 
@@ -302,7 +544,7 @@ menu[id] li.open > menu {
302
544
  opacity: 1;
303
545
  pointer-events: auto;
304
546
  transform: none;
305
- --md-comp-menu-container-shape: 20px;
547
+ --md-comp-menu-container-shape: 16px;
306
548
  }
307
549
 
308
550
  menu[id] li.submenu-start > menu {
@@ -315,22 +557,46 @@ menu[id] li.submenu-start:is(:hover, :focus-within, .open) > menu {
315
557
  transform: none;
316
558
  }
317
559
 
318
- body.keyboard-focused {
319
- menu[id] > li:focus {
320
- background-color: color-mix(
321
- in oklab,
322
- var(--md-comp-menu-item-label-text-color) 10%,
323
- transparent
324
- );
325
- }
560
+ // Keyboard focus only. This used to be nested under `body.keyboard-focused`, a
561
+ // class the bundle stamped from four capture-phase document listeners; the
562
+ // browser answers the same question. A programmatic focus - which is how a menu
563
+ // item gets focus here - inherits :focus-visible from the element that had it,
564
+ // so opening the menu by keyboard still lights the item and opening it by
565
+ // pointer still does not.
566
+ menu[id] > li:focus-visible {
567
+ background-color: color-mix(
568
+ in oklab,
569
+ var(--md-comp-menu-item-label-text-color) 10%,
570
+ transparent
571
+ );
572
+ }
326
573
 
327
- menu[id] > li:focus:not(.selected, [aria-selected="true"]) {
328
- outline: 3px solid var(--md-sys-color-secondary);
329
- outline-offset: -3px;
330
- }
574
+ menu[id]
575
+ > li:focus-visible:not(.selected, [aria-selected="true"], [aria-checked="true"]) {
576
+ outline: 3px solid var(--md-sys-color-secondary);
577
+ outline-offset: -3px;
578
+ }
579
+
580
+ menu[id].grouped > li:focus-visible {
581
+ background-color: var(--md-comp-menu-container-color);
582
+ }
583
+
584
+ menu[id].grouped
585
+ > li:focus-visible:not(.selected, [aria-selected="true"], [aria-checked="true"])
586
+ > :is(a, button, span:not(.material-symbols, .badge, .supporting-text)) {
587
+ border-radius: var(--md-comp-menu-item-container-shape);
588
+ background-color: color-mix(
589
+ in oklab,
590
+ var(--md-comp-menu-item-label-text-color) 10%,
591
+ transparent
592
+ );
331
593
  }
332
594
 
333
595
  @media (prefers-reduced-motion: reduce) {
596
+ menu[id] {
597
+ transition: none !important;
598
+ }
599
+
334
600
  menu[id] li > menu {
335
601
  transition: none;
336
602
  }
@@ -338,4 +604,12 @@ body.keyboard-focused {
338
604
 
339
605
  .menu-trigger {
340
606
  cursor: pointer;
607
+
608
+ // The trigger stays in its pressed state for as long as its menu is open.
609
+ &[aria-expanded="true"] {
610
+ background-image: linear-gradient(
611
+ color-mix(in oklab, currentcolor 10%, transparent),
612
+ color-mix(in oklab, currentcolor 10%, transparent)
613
+ );
614
+ }
341
615
  }