Files
livewire-material/resources/css/components/datepicker.css
T
surtic86andClaude Opus 5 ad230e0f48
tests / feature (8.4) (push) Successful in 1m46s
tests / feature (8.5) (push) Successful in 1m53s
tests / browser (chrome, chromium) (push) Successful in 7m46s
tests / browser (firefox, firefox) (push) Successful in 11m56s
tests / browser (safari, webkit) (push) Failing after 13m58s
Keep the full-screen range picker's months where they were as it grows
When the full-screen range picker adds months above the ones on screen,
extendMonths() puts the scroll back by however much the list grew. An
engine with scroll anchoring moved it back as well, so a reader paging up
landed half a year on, and a press meant for one day chose another. The
calendar now opts out of scroll anchoring (`overflow-anchor: none`) and
the picker's own correction is the only one, in every engine alike. A
test holds what sits under a fixed point of the list while it grows.

The browser plugin retries an action the way it retries an assertion:
when the page takes longer than a second to handle a press, it presses
again. On a loaded runner the full-screen picker, which re-renders every
day of every month it holds, took that long — so its toggle was pressed
a second time under the picker it had just opened, and never landed, and
a day pressed twice became the range's end as well as its start.
`pressOnce()` sends a press that must not be repeated exactly once, with
Playwright's own wait for the element; the picker's toggle, its paging
keys, its days and its Save go through it, and so do the menu triggers
that toggle.

Browser 300 passed on Chrome, Firefox and WebKit on macOS, and the date
picker and menu tests 61 passed on Linux Firefox and WebKitGTK held to
two busy cores. Feature 1159 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 13:45:51 +02:00

579 lines
21 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* M3's date pickers — docked, modal and modal input (resources/views/components/datepicker.blade.php).
*
* One `<dialog>` wears all three: docked it is a popover hung under the field by CSS anchor
* positioning (large corner), modal it is opened with `showModal()` over a scrim (extra-large
* corner, with M3's header: a title, the chosen date as the headline, and the switch between
* calendar and typing), and full — a `range` picker on a compact window — is that modal dialog
* grown to the whole screen, M3's full-screen range picker. The first two are 360px of
* surface-container-high at elevation 3; the third keeps the colour and drops the corner.
*
* Values from DatePickerModalTokens and DateInputModalTokens (androidx Compose Material 3, commit
* 27cf9a7d5788aa0f5f2d8b6699ce279560daf326) and the layout of DatePicker.kt, DateRangePicker.kt and
* DateInput.kt; the docked picker's from material-web's md-comp-date-picker-docked tokens (v0_192).
* Apache-2.0.
*
* [data-md-datepicker] the root: the field, its support line, the dialog
* [data-md-datepicker-support] the hint, a server error, or what is wrong with the typed date
* [data-md-datepicker-picker] the <dialog>; data-md-presentation="docked" | "modal" | "full"
* [data-md-datepicker-surface]
* [data-md-datepicker-header] modal only: title, headline, switch
* [data-md-datepicker-full] full only: [data-md-datepicker-app-bar] (close, switch, Save)
* and [data-md-datepicker-full-headline] (title, headline)
* [data-md-datepicker-calendar] the scroller, full screen
* [data-md-datepicker-nav] month and year: one menu button and arrows (modal),
* or a month and a year stepper (docked, data-md-docked)
* [data-md-datepicker-grid] the weekdays and six weeks of [data-md-datepicker-day]
* cells: data-md-today, data-md-selected, data-md-start,
* data-md-end, data-md-between, data-md-outside,
* data-md-blank, aria-disabled — full screen, every month
* of the window under a [data-md-datepicker-month-label] row
* [data-md-datepicker-years] the modal year grid
* [data-md-datepicker-menu] a docked month or year list of [data-md-datepicker-option]s
* [data-md-datepicker-entry] the text field(s) of the modal input
* [data-md-datepicker-actions] Cancel and OK
*
* 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.
*
* 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;
@import './field.css';
@import './icon.css';
@import './button.css';
@import './modal.css';
@layer material.components {
/* ---- The support line under the field ------------------------------------------------------ */
/* 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: var(--md-sys-measurement-space200);
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-body-sm);
letter-spacing: var(--md-sys-typescale-body-sm-tracking);
}
[data-md-datepicker-support] > * {
padding: var(--md-sys-measurement-space50) var(--datepicker-pad) 0;
}
[data-md-datepicker-support][data-md-size='sm'] {
--datepicker-pad: 12px;
}
[data-md-datepicker-support][data-md-size='xs'] {
--datepicker-pad: var(--md-sys-measurement-space125);
}
[data-md-datepicker-error] {
color: var(--md-sys-color-error);
}
/* ---- The dialog ---------------------------------------------------------------------------- */
[data-md-datepicker-picker] {
inset: auto;
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;
border: 0;
background: transparent;
color: var(--md-sys-color-on-surface);
}
[data-md-datepicker-picker]:popover-open {
top: anchor(bottom);
left: anchor(left);
margin-block: var(--md-sys-measurement-space50);
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
opacity: 1;
transition-property: opacity;
transition-duration: var(--md-sys-motion-effects-fast-duration);
transition-timing-function: var(--md-sys-motion-effects-fast);
}
@starting-style {
[data-md-datepicker-picker]:popover-open {
opacity: 0;
}
}
/* The entry pop and the scrim are modal.css's shared dialog chrome, imported above. */
[data-md-datepicker-picker]:modal {
inset: 0;
margin: auto;
}
[data-md-datepicker-surface] {
display: flex;
flex-direction: column;
max-height: inherit;
overflow-x: hidden;
overflow-y: auto;
border-radius: var(--md-sys-shape-corner-lg);
background-color: var(--md-sys-color-surface-container-high);
box-shadow: var(--md-sys-elevation-3);
outline: none;
}
[data-md-datepicker-picker][data-md-presentation='modal'] [data-md-datepicker-surface] {
border-radius: var(--md-sys-shape-corner-xl);
}
/* ---- The full-screen range picker (compact) ------------------------------------------------- */
/* M3 § Date Pickers: a range picker at a compact breakpoint is a full-screen dialog, "covering
* the whole screen for readability and touch-target size", with a close (×) icon button and a
* Save confirmation instead of Cancel and OK. Its anatomy is the modal picker's parts in a
* different order: an app bar, the supporting text and the range as the headline, a divider, the
* day-of-week labels, and a vertically scrolling list of months under them, each with its label.
* Only the months scroll; everything above them stays where it is. */
[data-md-datepicker-picker][data-md-presentation='full'] {
inset: 0;
width: 100vw;
max-width: none;
height: 100dvh;
max-height: none;
margin: 0;
}
[data-md-datepicker-picker][data-md-presentation='full'] [data-md-datepicker-surface] {
height: 100%;
max-height: none;
padding-block: var(--material-safe-top, env(safe-area-inset-top)) var(--material-safe-bottom, env(safe-area-inset-bottom));
overflow: hidden;
border-radius: 0;
box-shadow: none;
}
[data-md-datepicker-full] {
flex: none;
border-bottom: 1px solid var(--md-sys-color-outline-variant);
color: var(--md-sys-color-on-surface-variant);
}
[data-md-datepicker-app-bar] {
display: flex;
align-items: center;
justify-content: space-between;
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: var(--md-sys-measurement-space50);
}
[data-md-datepicker-full-headline] {
display: flex;
flex-direction: column;
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. It
opts out of the browser's scroll anchoring: when months are added above, extendMonths()
(resources/js/datepicker.js) puts the scroll back itself, in every engine alike, and an
engine that anchors as well moved it back a second time — six months on from where the
reader was. */
[data-md-datepicker-picker][data-md-presentation='full'] [data-md-datepicker-calendar] {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
overflow-anchor: none;
overscroll-behavior: contain;
}
[data-md-datepicker-picker][data-md-presentation='full'] [data-md-datepicker-grid] thead th {
position: sticky;
top: 0;
z-index: 1;
background-color: var(--md-sys-color-surface-container-high);
}
[data-md-datepicker-picker][data-md-presentation='full'] [data-md-datepicker-day] {
scroll-margin-block: 48px;
}
[data-md-datepicker-month-label] {
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);
text-align: start;
}
/* ---- The modal header ---------------------------------------------------------------------- */
[data-md-datepicker-header] {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: none;
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: 128px;
}
[data-md-datepicker-title] {
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);
}
[data-md-datepicker-headline-row] {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--md-sys-measurement-space100);
padding: 0 12px 12px var(--md-sys-measurement-space300);
}
[data-md-datepicker-headline] {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font: var(--md-sys-typescale-headline-lg);
letter-spacing: var(--md-sys-typescale-headline-lg-tracking);
}
[data-md-datepicker-header][data-md-range] [data-md-datepicker-headline] {
font: var(--md-sys-typescale-title-lg);
letter-spacing: var(--md-sys-typescale-title-lg-tracking);
}
/* ---- Month and year navigation -------------------------------------------------------------- */
[data-md-datepicker-nav] {
display: flex;
flex: none;
align-items: center;
justify-content: space-between;
height: 56px;
padding-inline: 12px;
color: var(--md-sys-color-on-surface-variant);
}
[data-md-datepicker-nav][data-md-docked] {
height: 64px;
padding-inline: var(--md-sys-measurement-space50);
}
[data-md-datepicker-stepper],
[data-md-datepicker-arrows] {
display: flex;
align-items: center;
}
/* While a year or month list is open the arrows keep their place but are gone, as androidx hides them. */
[data-md-datepicker-arrows][data-md-concealed] {
visibility: hidden;
}
[data-md-datepicker-menu-button] {
display: inline-flex;
align-items: center;
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;
}
[data-md-datepicker-nav][data-md-docked] [data-md-datepicker-menu-button] {
padding-inline: var(--md-sys-measurement-space100) var(--md-sys-measurement-space50);
gap: var(--md-sys-measurement-space50);
}
[data-md-datepicker-menu-arrow] {
transition: rotate var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
}
[data-md-datepicker-menu-button][aria-expanded='true'] [data-md-datepicker-menu-arrow] {
rotate: 180deg;
}
/* ---- The days ----------------------------------------------------------------------------- */
[data-md-datepicker-grid] {
width: calc(100% - 24px);
margin-inline: 12px;
border-collapse: collapse;
table-layout: fixed;
}
[data-md-datepicker-grid] th {
height: 48px;
padding: 0;
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: center;
}
[data-md-datepicker-day] {
--datepicker-layer: var(--md-sys-color-on-surface-variant);
position: relative;
height: 48px;
padding: 0;
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: center;
cursor: pointer;
outline: none;
isolation: isolate;
-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: 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;
}
/* A docked picker shows the neighbouring months' days, quieter. */
[data-md-datepicker-day][data-md-outside] {
color: var(--md-sys-color-on-surface-variant);
}
[data-md-datepicker-day][data-md-today] {
--datepicker-layer: var(--md-sys-color-primary);
color: var(--md-sys-color-primary);
}
[data-md-datepicker-day][data-md-today] > span {
box-shadow: inset 0 0 0 1px var(--md-sys-color-primary);
}
/* A range's band: secondary-container behind the days between its ends, from the middle of each end. */
[data-md-datepicker-day][data-md-between]::before,
[data-md-datepicker-day][data-md-start]::before,
[data-md-datepicker-day][data-md-end]::before {
content: '';
position: absolute;
inset-block: var(--md-sys-measurement-space50);
inset-inline: 0;
z-index: -1;
background-color: var(--md-sys-color-secondary-container);
}
[data-md-datepicker-day][data-md-start]::before {
inset-inline-start: 50%;
}
[data-md-datepicker-day][data-md-end]::before {
inset-inline-end: 50%;
}
[data-md-datepicker-day][data-md-between] {
--datepicker-layer: var(--md-sys-color-on-secondary-container);
color: var(--md-sys-color-on-secondary-container);
}
[data-md-datepicker-day][data-md-selected] {
--datepicker-layer: var(--md-sys-color-on-primary);
color: var(--md-sys-color-on-primary);
}
[data-md-datepicker-day][data-md-selected] > span {
background-color: var(--md-sys-color-primary);
box-shadow: none;
}
[data-md-datepicker-day][aria-disabled='true'] {
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) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent);
}
/* ---- The modal year grid ----------------------------------------------------------------- */
[data-md-datepicker-years] {
display: grid;
grid-template-columns: repeat(3, 1fr);
align-content: start;
justify-items: center;
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] {
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;
scroll-margin-block: var(--md-sys-measurement-space100);
}
[data-md-datepicker-year][data-md-current] {
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'] {
background-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
box-shadow: none;
}
/* ---- The docked month and year lists ------------------------------------------------------ */
[data-md-datepicker-menu] {
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] {
display: flex;
align-items: center;
gap: var(--md-sys-measurement-space200);
width: 100%;
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;
}
/* 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'] {
background-color: var(--md-sys-color-surface-container-highest);
}
[data-md-datepicker-option][aria-disabled='true'] {
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-check] {
color: var(--md-sys-color-on-surface);
}
[data-md-datepicker-option]:not([aria-selected='true']) [data-md-datepicker-check] {
visibility: hidden;
}
/* ---- The modal input ------------------------------------------------------------------------ */
[data-md-datepicker-entry] {
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: var(--md-sys-measurement-space100);
}
/* ---- Cancel and OK ---------------------------------------------------------------------------- */
[data-md-datepicker-actions] {
display: flex;
flex: none;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: var(--md-sys-measurement-space100);
padding: var(--md-sys-measurement-space50) var(--md-sys-measurement-space75) var(--md-sys-measurement-space100) 12px;
}
}