Size the timepicker in px, as its landscape queries already are

Plan step 36 review of d46b07c1. The landscape queries moved from rem
to px (560/364/346, equal at the default 16px) while every size they
make room for stayed in rem, so with a larger browser text size the
dial grew past a threshold that no longer moved with it. The sizes are
px now, as M3 gives them in dp, spacing the measurement tokens, the
selected period corner corner-md, and disabled the 38% and 12% state
tokens; the supporting text's two-line room stays in rem, being text.
The header's arithmetic is in px, and its note on targets says why the
period halves render no md-touch-target instead of claiming nothing
here reaches 48px.

The landscape browser test expected the display and the dial to share a
top edge, but the display is centred in the dial's 256px row, 61px
lower; it now checks the display sits inside that row with the dial
36px after it. Feature tests pin the classes and the three queries.

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:37:05 +02:00
co-authored by Claude Opus 5
parent 22c141d380
commit 459678c341
3 changed files with 93 additions and 57 deletions
+4 -3
View File
@@ -370,14 +370,15 @@ it('lies the dial on its side in a short landscape window', function () {
->click('#meeting')
->assertScript("document.querySelector('#meeting-dialog').open");
// Side by side rather than stacked: the display and the dial share a top edge, the dial to
// the display's end, and the picker's own layout (flex column) gives way to the grid areas.
// Side by side rather than stacked: the display (216px wide) sits centred in the dial's row,
// inside its height, with the dial 36px after it, and the picker's own layout (flex column)
// gives way to the grid areas.
$page->assertScript(<<<'JS'
(() => {
const display = document.querySelector('#meeting-dialog [data-md-timepicker-display]').getBoundingClientRect();
const dial = document.querySelector('#meeting-dialog [data-md-timepicker-dial]').getBoundingClientRect();
return Math.abs(display.top - dial.top) < 4 && dial.left >= display.right;
return display.top > dial.top && display.bottom < dial.bottom && Math.abs(dial.left - display.right - 36) < 1 && Math.round(display.width) === 216;
})()
JS)
->assertScript("getComputedStyle(document.querySelector('#meeting-dialog [data-md-timepicker-picker]')).display === 'contents'");
@@ -2,6 +2,7 @@
use Livewire\Component;
use Livewire\Livewire;
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
/**
* The JSON the component hands its Alpine data as its second argument.
@@ -87,6 +88,34 @@ it('offers the input variant, the period selector and the actions', function ()
->toContain('>OK</span>');
});
it('draws the boxes, the period and the dial from the interaction classes', function () {
$html = (string) $this->blade('<x-timepicker id="pickup" label="Pickup" />');
$css = ComponentStylesheet::read('timepicker');
// Two boxes and two period selectors of two radios each; the dial takes the ring alone.
expect(substr_count($html, 'class="md-state-layer md-focus-ring"'))->toBe(6)
->and($html)->toMatch('/class="md-focus-ring"\s+data-md-timepicker-dial/')
->and($css->declarations('[data-md-timepicker-dial]'))->not->toHaveKey('outline')
->and($css->declarations('[data-md-timepicker-box]'))->not->toHaveKeys(['outline', 'position', 'isolation'])
->and($css->declarations('[data-md-timepicker-period] > button:disabled'))->toMatchArray([
'color' => 'color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent)',
'background-color' => 'color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-container-opacity) * 100%), transparent)',
]);
});
it('lies the dial down by orientation and viewport height alone, at the px the upright dial needs', function () {
$css = ComponentStylesheet::read('timepicker');
expect($css->mediaQueries())->toBe([
'(orientation: landscape) and (height < 560px)',
'(orientation: landscape) and (height <= 364px)',
'(orientation: landscape) and (height <= 346px)',
])
->and($css->declarations('[data-md-timepicker-dial]'))->toMatchArray(['--dial' => '256px'])
->and($css->declarations("[data-md-timepicker-surface][data-md-mode='dial'] [data-md-timepicker-dial]", ['@media (orientation: landscape) and (height <= 364px)']))->toBe(['--dial' => '238px'])
->and($css->declarations("[data-md-timepicker-surface][data-md-mode='dial'] [data-md-timepicker-dial]", ['@media (orientation: landscape) and (height <= 346px)']))->toBe(['--dial' => '200px']);
});
it('passes the format, locale, limits and step to the picker, and drops what it cannot use', function () {
app()->setLocale('de_CH');