Keep a date picker's and an app bar's settings on their own component

materialDatepicker set its first day, format, min, max and year range in
init() without declaring them, and materialAppBar did the same with its
resize observer and scroll handler. Alpine writes an undeclared property to
the outermost x-data scope, so two pickers inside one page scope (ReStride's
plan setup wraps its form in x-data) shared the last one's min and first day,
and a second app bar would have taken the first one's observer. Both now
declare them; a browser test puts two pickers in an outer scope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
This commit is contained in:
Andreas Reinhold / reini
2026-09-13 19:17:11 +02:00
co-authored by Claude Opus 5
parent 8640d815c8
commit 65788d0c61
3 changed files with 65 additions and 0 deletions
+51
View File
@@ -125,6 +125,46 @@ function dateFormatProbe(string $locale = 'en')
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
}
class ScopedDateProbe extends Component
{
public ?string $early = '2026-09-13';
public ?string $late = '2026-09-13';
public function render(): string
{
return <<<'BLADE'
<div class="grid max-w-md gap-6 p-4" x-data="{ step: 1 }">
<x-datepicker id="early-field" label="Early" wire:model.live="early" min="2026-09-10" week-start="0" />
<x-datepicker id="late-field" label="Late" wire:model.live="late" week-start="1" />
</div>
BLADE;
}
}
function scopedDateProbe()
{
Livewire::component('scoped-date-probe', ScopedDateProbe::class);
Route::middleware('web')->get('/scoped-date-probe', fn () => Blade::render(<<<'BLADE'
<!DOCTYPE html>
<html>
<head>
<x-theme-script />
@vite(config('livewire-material.showcase.vite'))
@livewireStyles
</head>
<body class="bg-surface">
<livewire:scoped-date-probe />
@livewireScripts
</body>
</html>
BLADE));
return visit('/scoped-date-probe')->waitForEvent('networkidle')
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
}
/** A picker's day cell, by its ISO date. */
function day(string $picker, string $date): string
{
@@ -451,3 +491,14 @@ it('lays out and shows a range in the given first day and format', function () {
->assertSeeIn('#span', '{"start":"2026-09-20","end":"2026-09-24"}')
->assertValue('#span-field', '20/09/2026 24/09/2026');
});
it('keeps each picker\'s own settings inside a page\'s outer x-data scope', function () {
// Settings assigned in init() without being declared would land on the outermost scope,
// where the last picker's null min and Monday start would overwrite the first's.
$page = scopedDateProbe()
->click('[aria-controls="early-field-picker"][data-datepicker-toggle]')
->assertScript(focusedDay('2026-09-13'));
$page->assertAttribute(day('early-field', '2026-09-09'), 'aria-disabled', 'true')
->assertScript('! (\'min\' in Alpine.$data(document.querySelector(\'[x-data="{ step: 1 }"]\')))');
});