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 }"]\')))'); +});