Draw the datepicker's controls from the shared classes, in px

Plan step 36 review of 72d9dbfc. The year, the three menu buttons and
the docked lists' options hand-rolled md-state-layer and md-focus-ring
on themselves, in their own ink, on the claim that each was a bigger box
than an indicator inside it; only the day is. They now render the
classes, with the option's inset ring as the one refinement. The day
keeps its own layer on the 40px indicator, now at M3's state-opacity
tokens, gone from a disabled or blank day (a focused disabled day still
showed it), and in its own stacking context so it lies over a chosen
day's primary fill instead of under it.

The month announcement is md-visually-hidden again, not a hook with a
copy of its clip. The stylesheet kept every length in rem although the
commit said px: sizes are now px, spacing the measurement tokens, and
disabled ink the 38% state token, as search and slider were drawn. The
view header's "below sm" is 600px, medium.

Six browser assertions still read the old unprefixed hooks
(dataset.presentation, data-selected, data-between, data-start,
data-end); one of them would have passed vacuously.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 19:34:51 +02:00
co-authored by Claude Opus 5
parent e3fcbf39ed
commit 22c141d380
4 changed files with 157 additions and 155 deletions
+109 -146
View File
@@ -33,16 +33,17 @@
* [data-md-datepicker-entry] the text field(s) of the modal input
* [data-md-datepicker-actions] Cancel and OK
*
* Cancel, OK and the icon buttons in the app bar and the nav are <x-button>, which draws its own
* state layer and focus ring from the foundation classes. The day, the year, the menu button and
* the list option keep their own hand-rolled state layer and focus ring, as `data-md-field-button`
* does (field.css): each interactive box is bigger than the round indicator drawn inside it (a
* day's cell is 48px, its indicator 40px; a menu button and an option are full rows), which the
* foundation's classes, fixed to the element they are on, cannot draw — and `:focus-visible` only
* ever matches the element actually focused, never a smaller child inside it.
* The year, the menu buttons and the docked lists' options render the foundation's `md-state-layer`
* and `md-focus-ring` (foundation/interaction.css): each draws its layer in its own ink and its
* ring round itself, so the classes need one refinement only — an option's ring sits inside its
* full-width row. Cancel, OK and the icon buttons are <x-button>, which renders the classes too.
* The day alone keeps a state layer and ring of its own: the element focused and pressed is the
* 48px cell, while both are drawn on the 40px round indicator inside it, which a class drawn on the
* element it sits on cannot do, and `:focus-visible` never matches that child. Its layer takes
* M3's state opacities (tokens/state.css) and is gone from a disabled or blank day.
*
* Every state layer here is the content colour at 8% on hover (only where a pointer can hover) and
* 10% on focus and press; keyboard focus adds the package's 3px secondary ring.
* Lengths are px, and spacing is the measurement tokens: M3 gives every size here in dp. A
* disabled day or option is on-surface at M3's 38% content opacity.
*/
@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;
@@ -56,7 +57,7 @@
/* The padding is on the lines, not the box, so a box with nothing to say takes no room. */
[data-md-datepicker-support] {
--datepicker-pad: 1rem;
--datepicker-pad: var(--md-sys-measurement-space200);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-body-sm);
@@ -64,15 +65,15 @@
}
[data-md-datepicker-support] > * {
padding: 0.25rem var(--datepicker-pad) 0;
padding: var(--md-sys-measurement-space50) var(--datepicker-pad) 0;
}
[data-md-datepicker-support][data-md-size='sm'] {
--datepicker-pad: 0.75rem;
--datepicker-pad: 12px;
}
[data-md-datepicker-support][data-md-size='xs'] {
--datepicker-pad: 0.625rem;
--datepicker-pad: var(--md-sys-measurement-space125);
}
[data-md-datepicker-error] {
@@ -83,9 +84,9 @@
[data-md-datepicker-picker] {
inset: auto;
width: 22.5rem;
max-width: calc(100vw - 1rem);
max-height: calc(100dvh - 1rem);
width: 360px;
max-width: calc(100vw - var(--md-sys-measurement-space200));
max-height: calc(100dvh - var(--md-sys-measurement-space200));
margin: 0;
padding: 0;
overflow: visible;
@@ -97,7 +98,7 @@
[data-md-datepicker-picker]:popover-open {
top: anchor(bottom);
left: anchor(left);
margin-block: 0.25rem;
margin-block: var(--md-sys-measurement-space50);
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
opacity: 1;
transition-property: opacity;
@@ -184,22 +185,22 @@
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
height: 4rem;
padding-inline: 0.25rem 0.75rem;
gap: var(--md-sys-measurement-space100);
height: 64px;
padding-inline: var(--md-sys-measurement-space50) 12px;
}
[data-md-datepicker-app-bar-actions] {
display: flex;
align-items: center;
gap: 0.25rem;
gap: var(--md-sys-measurement-space50);
}
[data-md-datepicker-full-headline] {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: 0 1.5rem 1rem;
gap: var(--md-sys-measurement-space50);
padding: 0 var(--md-sys-measurement-space300) var(--md-sys-measurement-space200);
}
/* The calendar is the only part that scrolls, with the weekday header held at its top. */
@@ -218,11 +219,11 @@
}
[data-md-datepicker-picker][data-md-presentation='full'] [data-md-datepicker-day] {
scroll-margin-block: 3rem;
scroll-margin-block: 48px;
}
[data-md-datepicker-month-label] {
padding: 1.25rem 0 0.5rem;
padding: 20px 0 var(--md-sys-measurement-space100);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-title-sm);
letter-spacing: var(--md-sys-typescale-title-sm-tracking);
@@ -236,17 +237,17 @@
flex-direction: column;
justify-content: space-between;
flex: none;
min-height: 7.5rem;
min-height: 120px;
border-bottom: 1px solid var(--md-sys-color-outline-variant);
color: var(--md-sys-color-on-surface-variant);
}
[data-md-datepicker-header][data-md-range] {
min-height: 8rem;
min-height: 128px;
}
[data-md-datepicker-title] {
padding: 1rem 0.75rem 0 1.5rem;
padding: var(--md-sys-measurement-space200) 12px 0 var(--md-sys-measurement-space300);
font: var(--md-sys-typescale-label-lg);
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
}
@@ -255,8 +256,8 @@
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
padding: 0 0.75rem 0.75rem 1.5rem;
gap: var(--md-sys-measurement-space100);
padding: 0 12px 12px var(--md-sys-measurement-space300);
}
[data-md-datepicker-headline] {
@@ -280,14 +281,14 @@
flex: none;
align-items: center;
justify-content: space-between;
height: 3.5rem;
padding-inline: 0.75rem;
height: 56px;
padding-inline: 12px;
color: var(--md-sys-color-on-surface-variant);
}
[data-md-datepicker-nav][data-md-docked] {
height: 4rem;
padding-inline: 0.25rem;
height: 64px;
padding-inline: var(--md-sys-measurement-space50);
}
[data-md-datepicker-stepper],
@@ -302,25 +303,22 @@
}
[data-md-datepicker-menu-button] {
position: relative;
display: inline-flex;
align-items: center;
gap: 0.5rem;
height: 2.5rem;
padding-inline: 0.75rem 0.5rem;
gap: var(--md-sys-measurement-space100);
height: 40px;
padding-inline: 12px var(--md-sys-measurement-space100);
border-radius: var(--md-sys-shape-corner-full);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-label-lg);
letter-spacing: var(--md-sys-typescale-label-lg-tracking);
white-space: nowrap;
cursor: pointer;
outline: none;
isolation: isolate;
}
[data-md-datepicker-nav][data-md-docked] [data-md-datepicker-menu-button] {
padding-inline: 0.5rem 0.25rem;
gap: 0.25rem;
padding-inline: var(--md-sys-measurement-space100) var(--md-sys-measurement-space50);
gap: var(--md-sys-measurement-space50);
}
[data-md-datepicker-menu-arrow] {
@@ -331,66 +329,17 @@
rotate: 180deg;
}
/* ---- State layers shared by days, years, menu buttons and list rows ------------------------ */
[data-md-datepicker-menu-button]::before,
[data-md-datepicker-day] > span::before,
[data-md-datepicker-year]::before,
[data-md-datepicker-option]::before {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
background-color: var(--datepicker-layer, currentColor);
opacity: 0;
pointer-events: none;
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
}
@media (hover: hover) {
[data-md-datepicker-menu-button]:hover::before,
[data-md-datepicker-day]:not([aria-disabled='true'], [data-md-blank]):hover > span::before,
[data-md-datepicker-year]:hover::before,
[data-md-datepicker-option]:not([aria-disabled='true']):hover::before {
opacity: 0.08;
}
}
[data-md-datepicker-menu-button]:focus-visible::before,
[data-md-datepicker-menu-button]:active::before,
[data-md-datepicker-day]:focus-visible > span::before,
[data-md-datepicker-day]:not([aria-disabled='true'], [data-md-blank]):active > span::before,
[data-md-datepicker-year]:focus-visible::before,
[data-md-datepicker-year]:active::before,
[data-md-datepicker-option]:focus-visible::before,
[data-md-datepicker-option]:not([aria-disabled='true']):active::before {
opacity: 0.1;
}
[data-md-datepicker-menu-button]:focus-visible,
[data-md-datepicker-day]:focus-visible > span,
[data-md-datepicker-year]:focus-visible {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
[data-md-datepicker-option]:focus-visible {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: -3px;
}
/* ---- The days ----------------------------------------------------------------------------- */
[data-md-datepicker-grid] {
width: calc(100% - 1.5rem);
margin-inline: 0.75rem;
width: calc(100% - 24px);
margin-inline: 12px;
border-collapse: collapse;
table-layout: fixed;
}
[data-md-datepicker-grid] th {
height: 3rem;
height: 48px;
padding: 0;
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-body-lg);
@@ -402,7 +351,7 @@
--datepicker-layer: var(--md-sys-color-on-surface-variant);
position: relative;
height: 3rem;
height: 48px;
padding: 0;
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-body-lg);
@@ -414,17 +363,56 @@
-webkit-tap-highlight-color: transparent;
}
/* The 40px indicator, its own stacking context so the state layer lies over a chosen day's
primary fill rather than under it. */
[data-md-datepicker-day] > span {
position: relative;
display: grid;
place-items: center;
width: 2.5rem;
height: 2.5rem;
width: 40px;
height: 40px;
margin: auto;
border-radius: var(--md-sys-shape-corner-full);
isolation: isolate;
transition: background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
}
/* The day's state layer and focus ring, on the indicator (see the header). */
[data-md-datepicker-day] > span::before {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
background-color: var(--datepicker-layer);
opacity: 0;
pointer-events: none;
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
}
@media (hover: hover) {
[data-md-datepicker-day]:hover > span::before {
opacity: var(--md-sys-state-hover-state-layer-opacity);
}
}
[data-md-datepicker-day]:focus-visible > span::before {
opacity: var(--md-sys-state-focus-state-layer-opacity);
}
[data-md-datepicker-day]:active > span::before {
opacity: var(--md-sys-state-pressed-state-layer-opacity);
}
[data-md-datepicker-day]:is([aria-disabled='true'], [data-md-blank]) > span::before {
display: none;
}
[data-md-datepicker-day]:focus-visible > span {
outline: 3px solid var(--md-sys-color-secondary);
outline-offset: 2px;
}
[data-md-datepicker-day][data-md-blank] {
cursor: default;
}
@@ -450,7 +438,7 @@
[data-md-datepicker-day][data-md-end]::before {
content: '';
position: absolute;
inset-block: 0.25rem;
inset-block: var(--md-sys-measurement-space50);
inset-inline: 0;
z-index: -1;
background-color: var(--md-sys-color-secondary-container);
@@ -482,12 +470,12 @@
}
[data-md-datepicker-day][aria-disabled='true'] {
color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
cursor: default;
}
[data-md-datepicker-day][aria-disabled='true'][data-md-today] > span {
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
}
/* ---- The modal year grid ----------------------------------------------------------------- */
@@ -497,40 +485,31 @@
grid-template-columns: repeat(3, 1fr);
align-content: start;
justify-items: center;
row-gap: 1rem;
height: 20.9375rem;
padding: 0.5rem 0.75rem;
row-gap: var(--md-sys-measurement-space200);
height: 335px;
padding: var(--md-sys-measurement-space100) 12px;
overflow-y: auto;
overscroll-behavior: contain;
border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
[data-md-datepicker-year] {
--datepicker-layer: var(--md-sys-color-on-surface-variant);
position: relative;
width: 4.5rem;
height: 2.25rem;
width: 72px;
height: 36px;
border-radius: var(--md-sys-shape-corner-full);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-body-lg);
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
cursor: pointer;
outline: none;
isolation: isolate;
scroll-margin-block: 0.5rem;
scroll-margin-block: var(--md-sys-measurement-space100);
}
[data-md-datepicker-year][data-md-current] {
--datepicker-layer: var(--md-sys-color-primary);
color: var(--md-sys-color-primary);
box-shadow: inset 0 0 0 1px var(--md-sys-color-primary);
}
[data-md-datepicker-year][aria-selected='true'] {
--datepicker-layer: var(--md-sys-color-on-primary);
background-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
box-shadow: none;
@@ -539,30 +518,30 @@
/* ---- The docked month and year lists ------------------------------------------------------ */
[data-md-datepicker-menu] {
height: 21.5rem;
padding-block: 0.5rem;
height: 344px;
padding-block: var(--md-sys-measurement-space100);
overflow-y: auto;
overscroll-behavior: contain;
border-top: 1px solid var(--md-sys-color-outline-variant);
}
[data-md-datepicker-option] {
--datepicker-layer: var(--md-sys-color-on-surface);
position: relative;
display: flex;
align-items: center;
gap: 1rem;
gap: var(--md-sys-measurement-space200);
width: 100%;
height: 3rem;
padding-inline: 1rem;
height: 48px;
padding-inline: var(--md-sys-measurement-space200);
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-body-lg);
letter-spacing: var(--md-sys-typescale-body-lg-tracking);
text-align: start;
cursor: pointer;
outline: none;
isolation: isolate;
}
/* The foundation's ring, drawn inside the full-width row rather than round it. */
[data-md-datepicker-option]:focus-visible {
outline-offset: -3px;
}
[data-md-datepicker-option][aria-selected='true'] {
@@ -570,7 +549,7 @@
}
[data-md-datepicker-option][aria-disabled='true'] {
color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
cursor: default;
}
@@ -585,29 +564,13 @@
/* ---- The modal input ------------------------------------------------------------------------ */
[data-md-datepicker-entry] {
padding: 0.625rem 1.5rem 1rem;
padding: var(--md-sys-measurement-space125) var(--md-sys-measurement-space300) var(--md-sys-measurement-space200);
}
[data-md-datepicker-entry-fields][data-md-range] {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
/* ---- The month announcement, spoken but not drawn -------------------------------------------- */
/* Same clip as the text classes' `md-visually-hidden`, kept as a hook because a package view
writes no class but the interaction classes (plan step 36). */
[data-md-datepicker-month-live] {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border-width: 0;
gap: var(--md-sys-measurement-space100);
}
/* ---- Cancel and OK ---------------------------------------------------------------------------- */
@@ -618,7 +581,7 @@
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
padding: 0.25rem 0.375rem 0.5rem 0.75rem;
gap: var(--md-sys-measurement-space100);
padding: var(--md-sys-measurement-space50) var(--md-sys-measurement-space75) var(--md-sys-measurement-space100) 12px;
}
}