From 65788d0c611b479bfe77b353c3699c97c859caaf Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Sun, 13 Sep 2026 19:17:11 +0200 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2 --- resources/js/app-bar.js | 4 +++ resources/js/datepicker.js | 10 +++++++ tests/Browser/DatepickerTest.php | 51 ++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/resources/js/app-bar.js b/resources/js/app-bar.js index 16f25e21..c0822bee 100644 --- a/resources/js/app-bar.js +++ b/resources/js/app-bar.js @@ -11,6 +11,10 @@ document.addEventListener('alpine:init', () => { collapsed: false, height: null, frame: null, + // Set in init(). Declared here, or Alpine writes them to the outermost x-data scope, + // where a second app bar in the same page scope would take the first one's observer. + schedule: null, + resizes: null, init() { this.measure = this.measure.bind(this) diff --git a/resources/js/datepicker.js b/resources/js/datepicker.js index b298da32..232c7abb 100644 --- a/resources/js/datepicker.js +++ b/resources/js/datepicker.js @@ -178,6 +178,16 @@ document.addEventListener('alpine:init', () => { today: localToday(), compact: false, refocus: true, + // Set in init() from the config. Declared here, or Alpine writes them to the outermost + // x-data scope, where every picker inside the same page scope would share the last one's. + firstDay: 0, + format: null, + numbers: null, + formats: {}, + min: null, + max: null, + yearsFrom: null, + yearsTo: null, init() { const locale = config.locale || document.documentElement.lang || 'en' diff --git a/tests/Browser/DatepickerTest.php b/tests/Browser/DatepickerTest.php index 70b0666c..a73d9f20 100644 --- a/tests/Browser/DatepickerTest.php +++ b/tests/Browser/DatepickerTest.php @@ -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' +
+ + +
+ BLADE; + } +} + +function scopedDateProbe() +{ + Livewire::component('scoped-date-probe', ScopedDateProbe::class); + + Route::middleware('web')->get('/scoped-date-probe', fn () => Blade::render(<<<'BLADE' + + + + + @vite(config('livewire-material.showcase.vite')) + @livewireStyles + + + + @livewireScripts + + + 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 }"]\')))'); +});