Draw the timepicker without Tailwind
Plan step 36 (inputs group, last of the "not started" pair): <x-timepicker> renders data-md-timepicker-* throughout (data-md-mode, data-md-cycle, data-md-view, data-md-inner, data-md-dragging, data-md-disabled, data-md-error and data-md-value replace their unprefixed hooks), and timepicker.css moves into material.components, importing field.css, icon.css and button.css. The dialog's Tailwind utility class (position, the scrim, the spatial-fast pop and its @starting-style) becomes plain declarations under data-md-timepicker-dialog, matching the datepicker's modal dialog. The landscape rule stays keyed on orientation and viewport height, in px (560/364/346), never a breakpoint, as M3 asks. The hour and minute boxes and the period buttons swap their Tailwind `state-layer focus-ring` utilities for `md-state-layer md-focus-ring`: same declarations, so nothing else in the stylesheet changes for them. The dial swaps in `md-focus-ring` for its own hand-rolled, byte-identical outline-on-:focus-visible rule, which timepicker.css no longer carries. The period selector was already the radio-in-a-list M3's labelling table asks for (role="radio" in a role="radiogroup", moved by the arrow keys), and Shift+M/Y already reached the date picker's dropdowns — both predate this rewrite. field.css's data-md-field-button note is tidied now that every component that selects it (input, password, datepicker, timepicker) is rewritten. Browser tests written but not run (the inputs group's Chromium run follows this stream): the period radios moving with the arrow keys, and the dial lying on its side below the landscape height threshold. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
72d9dbfc89
commit
d46b07c13c
@@ -34,6 +34,7 @@ dataset('input components', [
|
||||
'table',
|
||||
'sort-header',
|
||||
'datepicker',
|
||||
'timepicker',
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,13 +28,13 @@ it('draws a read-only field that opens the picker in a labelled dialog', functio
|
||||
->toContain('aria-describedby="starts-support"')
|
||||
->toMatch('/<p id="starts-support" data-md-field-support\s*>In your time zone<\/p>/')
|
||||
->toContain('x-on:keydown.enter.prevent="show()"')
|
||||
->toContain('data-timepicker-open')
|
||||
->toContain('data-md-timepicker-open')
|
||||
->toContain('aria-label="Choose time"')
|
||||
->toContain('<dialog')
|
||||
->toContain('id="starts-dialog"')
|
||||
->toContain('wire:ignore')
|
||||
->toContain('aria-labelledby="starts-title"')
|
||||
->toContain('<h2 id="starts-title" data-timepicker-title')
|
||||
->toContain('<h2 id="starts-title" data-md-timepicker-title')
|
||||
->toContain('x-modelable="value"')
|
||||
->toContain("materialTimepicker( '14:30' ,")
|
||||
->not->toContain('aria-invalid="true"')
|
||||
@@ -47,18 +47,18 @@ it('draws the dial as a slider, with twelve numbers per ring and M3\'s 24-hour i
|
||||
expect($html)
|
||||
->toContain('role="slider"')
|
||||
->toContain('x-bind:aria-valuetext="valueText"')
|
||||
->and(substr_count($html, 'data-timepicker-label="hour12"'))->toBe(12)
|
||||
->and(substr_count($html, 'data-timepicker-label="hour24"'))->toBe(24)
|
||||
->and(substr_count($html, 'data-timepicker-label="minute"'))->toBe(12)
|
||||
->and(substr_count($html, 'data-md-timepicker-label="hour12"'))->toBe(12)
|
||||
->and(substr_count($html, 'data-md-timepicker-label="hour24"'))->toBe(24)
|
||||
->and(substr_count($html, 'data-md-timepicker-label="minute"'))->toBe(12)
|
||||
->and($html)
|
||||
->toMatch('/data-timepicker-label="hour24" data-value="0"\s+style="--x: 0\.0000; --y: -1\.0000"\s*>00</')
|
||||
->toMatch('/data-timepicker-label="hour24" data-value="12"\s+data-inner\s+style="--x: 0\.0000; --y: -1\.0000"\s*>12</')
|
||||
->toMatch('/data-timepicker-label="hour24" data-value="15"\s+data-inner\s+style="--x: 1\.0000; --y: -0\.0000"\s*>15</')
|
||||
->toMatch('/data-timepicker-label="minute" data-value="0"\s+style="[^"]*"\s*>00</')
|
||||
->toMatch('/data-timepicker-label="hour12" data-value="12"\s+style="--x: 0\.0000; --y: -1\.0000"\s*>12</')
|
||||
->toContain('data-timepicker-handle')
|
||||
->toContain('data-timepicker-ink')
|
||||
->not->toContain('x-bind:data-disabled');
|
||||
->toMatch('/data-md-timepicker-label="hour24" data-md-value="0"\s+style="--x: 0\.0000; --y: -1\.0000"\s*>00</')
|
||||
->toMatch('/data-md-timepicker-label="hour24" data-md-value="12"\s+data-md-inner\s+style="--x: 0\.0000; --y: -1\.0000"\s*>12</')
|
||||
->toMatch('/data-md-timepicker-label="hour24" data-md-value="15"\s+data-md-inner\s+style="--x: 1\.0000; --y: -0\.0000"\s*>15</')
|
||||
->toMatch('/data-md-timepicker-label="minute" data-md-value="0"\s+style="[^"]*"\s*>00</')
|
||||
->toMatch('/data-md-timepicker-label="hour12" data-md-value="12"\s+style="--x: 0\.0000; --y: -1\.0000"\s*>12</')
|
||||
->toContain('data-md-timepicker-handle')
|
||||
->toContain('data-md-timepicker-ink')
|
||||
->not->toContain('x-bind:data-md-disabled');
|
||||
});
|
||||
|
||||
it('offers the input variant, the period selector and the actions', function () {
|
||||
@@ -70,19 +70,19 @@ it('offers the input variant, the period selector and the actions', function ()
|
||||
->toContain('inputmode="numeric"')
|
||||
->toContain('aria-describedby="pickup-hour-support"')
|
||||
->toContain('aria-describedby="pickup-minute-support"')
|
||||
->toContain('data-timepicker-period-option="am"')
|
||||
->toContain('data-timepicker-period-option="pm"')
|
||||
->toContain('data-md-timepicker-period-option="am"')
|
||||
->toContain('data-md-timepicker-period-option="pm"')
|
||||
->toContain('aria-label="Select AM or PM"')
|
||||
// M3's labelling table: AM and PM are radios in a list, not toggle buttons.
|
||||
->toContain('role="radiogroup"')
|
||||
->toContain('role="radio"')
|
||||
->toContain('x-on:keydown.arrow-right.prevent="movePeriod($event)"')
|
||||
// The 24-hour selector widens into the room the period selector leaves.
|
||||
->toContain('data-timepicker-display x-bind:data-cycle="is24 ? \'24\' : \'12\'"')
|
||||
->toContain('data-md-timepicker-display x-bind:data-md-cycle="is24 ? \'24\' : \'12\'"')
|
||||
->toContain('aria-label="Switch to text input mode"')
|
||||
->toContain('aria-label="Switch to clock mode"')
|
||||
->toContain('data-timepicker-cancel')
|
||||
->toContain('data-timepicker-confirm')
|
||||
->toContain('data-md-timepicker-cancel')
|
||||
->toContain('data-md-timepicker-confirm')
|
||||
->toContain('>Cancel</span>')
|
||||
->toContain('>OK</span>');
|
||||
});
|
||||
@@ -104,8 +104,8 @@ it('marks what lies outside the limits only when there are limits', function ()
|
||||
$html = (string) $this->blade('<x-timepicker label="Slot" step="15" />');
|
||||
|
||||
expect($html)
|
||||
->toContain('x-bind:data-disabled="hourAllowed(3 + (isPm ? 12 : 0)) ? null : \'\'"')
|
||||
->toContain('x-bind:data-disabled="minuteAllowed(45) ? null : \'\'"');
|
||||
->toContain('x-bind:data-md-disabled="hourAllowed(3 + (isPm ? 12 : 0)) ? null : \'\'"')
|
||||
->toContain('x-bind:data-md-disabled="minuteAllowed(45) ? null : \'\'"');
|
||||
});
|
||||
|
||||
it('writes the first frame as the locale writes the time', function () {
|
||||
@@ -167,11 +167,11 @@ it('puts the field props on the field, its attributes on the input, and posts a
|
||||
->toMatch('/^<div\s+class="w-60"/')
|
||||
->toContain('data-md-variant="filled"')
|
||||
->toContain('data-md-size="sm"')
|
||||
->toContain('data-timepicker-field')
|
||||
->toContain('data-md-timepicker-field')
|
||||
->toContain('required')
|
||||
->toContain('placeholder="hh:mm"')
|
||||
->toContain('<input type="hidden" name="alarm" value="06:30" x-bind:value="value ?? \'\'" />')
|
||||
->toContain('data-md-field-clear')
|
||||
->toMatch('/<button[^>]*data-timepicker-open[^>]*disabled/s')
|
||||
->toMatch('/<button[^>]*data-md-timepicker-open[^>]*disabled/s')
|
||||
->and(substr_count($html, 'class="field w-60"'))->toBe(0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user