From 829d7c0fabc2e82d4e20e7700331762c08c0d431 Mon Sep 17 00:00:00 2001 From: Alex Justesen Date: Mon, 24 Nov 2025 16:19:00 -0500 Subject: [PATCH 1/9] Refactor data integration event handling (#2436) Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> --- app/Listeners/DataIntegrationSubscriber.php | 42 +++++++++++++++++ app/Listeners/SpeedtestEventSubscriber.php | 52 --------------------- 2 files changed, 42 insertions(+), 52 deletions(-) create mode 100644 app/Listeners/DataIntegrationSubscriber.php delete mode 100644 app/Listeners/SpeedtestEventSubscriber.php diff --git a/app/Listeners/DataIntegrationSubscriber.php b/app/Listeners/DataIntegrationSubscriber.php new file mode 100644 index 000000000..4c1bbc668 --- /dev/null +++ b/app/Listeners/DataIntegrationSubscriber.php @@ -0,0 +1,42 @@ +settings->influxdb_v2_enabled) { + WriteResult::dispatch($event->result); + } + } + + /** + * Register the listeners for the subscriber. + * + * @return array + */ + public function subscribe(Dispatcher $events): array + { + return [ + SpeedtestCompleted::class => 'handleSpeedtestCompleted', + SpeedtestFailed::class => 'handleSpeedtestFailed', + ]; + } +} diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php deleted file mode 100644 index 7e81bc30e..000000000 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ /dev/null @@ -1,52 +0,0 @@ -influxdb_v2_enabled) { - WriteResult::dispatch($event->result); - } - } - - /** - * Handle speedtest completed events. - */ - public function handleSpeedtestCompleted(SpeedtestCompleted $event): void - { - $settings = app(DataIntegrationSettings::class); - - if ($settings->influxdb_v2_enabled) { - WriteResult::dispatch($event->result); - } - } - - /** - * Register the listeners for the subscriber. - */ - public function subscribe(Dispatcher $events): void - { - $events->listen( - SpeedtestFailed::class, - [SpeedtestEventSubscriber::class, 'handleSpeedtestFailed'] - ); - - $events->listen( - SpeedtestCompleted::class, - [SpeedtestEventSubscriber::class, 'handleSpeedtestCompleted'] - ); - } -} From ecb551d7e57b4adc6bef6e7d5c230a0f696cebb8 Mon Sep 17 00:00:00 2001 From: Alex Justesen Date: Tue, 25 Nov 2025 06:54:21 -0600 Subject: [PATCH 2/9] Fix data integrations listener (#2437) Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> --- ...hp => ProcessSpeedtestDataIntegrations.php} | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) rename app/Listeners/{DataIntegrationSubscriber.php => ProcessSpeedtestDataIntegrations.php} (52%) diff --git a/app/Listeners/DataIntegrationSubscriber.php b/app/Listeners/ProcessSpeedtestDataIntegrations.php similarity index 52% rename from app/Listeners/DataIntegrationSubscriber.php rename to app/Listeners/ProcessSpeedtestDataIntegrations.php index 4c1bbc668..effb9bdbd 100644 --- a/app/Listeners/DataIntegrationSubscriber.php +++ b/app/Listeners/ProcessSpeedtestDataIntegrations.php @@ -6,9 +6,8 @@ use App\Events\SpeedtestFailed; use App\Jobs\Influxdb\v2\WriteResult; use App\Settings\DataIntegrationSettings; -use Illuminate\Events\Dispatcher; -class DataIntegrationSubscriber +class ProcessSpeedtestDataIntegrations { /** * Create the event listener. @@ -20,23 +19,10 @@ public function __construct( /** * Handle the event. */ - public function handle(object $event): void + public function handle(SpeedtestCompleted|SpeedtestFailed $event): void { if ($this->settings->influxdb_v2_enabled) { WriteResult::dispatch($event->result); } } - - /** - * Register the listeners for the subscriber. - * - * @return array - */ - public function subscribe(Dispatcher $events): array - { - return [ - SpeedtestCompleted::class => 'handleSpeedtestCompleted', - SpeedtestFailed::class => 'handleSpeedtestFailed', - ]; - } } From 4d28618663a3cc6cdc53cd995a1b066e7cf38748 Mon Sep 17 00:00:00 2001 From: Alex Justesen Date: Tue, 25 Nov 2025 14:41:58 -0600 Subject: [PATCH 3/9] Refactored database, mail and webhook notifications (#2439) Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> --- .../SendMailTestNotification.php | 2 +- app/Filament/Pages/Settings/Notification.php | 240 ++++++++++-------- .../SendSpeedtestCompletedNotification.php | 34 --- .../SendSpeedtestThresholdNotification.php | 101 -------- .../SendSpeedtestCompletedNotification.php | 39 --- .../SendSpeedtestThresholdNotification.php | 117 --------- app/Listeners/ProcessCompletedSpeedtest.php | 133 +++++++++- app/Listeners/ProcessFailedSpeedtest.php | 25 +- app/Listeners/ProcessUnhealthySpeedtest.php | 169 ++++++++++++ .../SendSpeedtestCompletedNotification.php | 54 ---- .../SendSpeedtestThresholdNotification.php | 126 --------- ...tedMail.php => CompletedSpeedtestMail.php} | 4 +- app/Mail/SpeedtestThresholdMail.php | 57 ----- app/Mail/{Test.php => TestMail.php} | 4 +- app/Mail/UnhealthySpeedtestMail.php | 83 ++++++ app/Providers/Filament/AdminPanelProvider.php | 2 + composer.json | 1 + composer.lock | 77 +++++- lang/en/results.php | 2 + lang/en/settings/notifications.php | 3 - package-lock.json | 4 +- package.json | 4 +- .../{panel.css => filament/admin/theme.css} | 10 + .../emails/speedtest-threshold.blade.php | 24 -- .../speedtest/completed.blade.php} | 0 .../views/mail/speedtest/unhealthy.blade.php | 17 ++ .../views/{emails => mail}/test.blade.php | 0 vite.config.js | 2 +- 28 files changed, 649 insertions(+), 685 deletions(-) delete mode 100644 app/Listeners/Database/SendSpeedtestCompletedNotification.php delete mode 100644 app/Listeners/Database/SendSpeedtestThresholdNotification.php delete mode 100644 app/Listeners/Mail/SendSpeedtestCompletedNotification.php delete mode 100644 app/Listeners/Mail/SendSpeedtestThresholdNotification.php create mode 100644 app/Listeners/ProcessUnhealthySpeedtest.php delete mode 100644 app/Listeners/Webhook/SendSpeedtestCompletedNotification.php delete mode 100644 app/Listeners/Webhook/SendSpeedtestThresholdNotification.php rename app/Mail/{SpeedtestCompletedMail.php => CompletedSpeedtestMail.php} (93%) delete mode 100644 app/Mail/SpeedtestThresholdMail.php rename app/Mail/{Test.php => TestMail.php} (87%) create mode 100644 app/Mail/UnhealthySpeedtestMail.php rename resources/css/{panel.css => filament/admin/theme.css} (50%) delete mode 100644 resources/views/emails/speedtest-threshold.blade.php rename resources/views/{emails/speedtest-completed.blade.php => mail/speedtest/completed.blade.php} (100%) create mode 100644 resources/views/mail/speedtest/unhealthy.blade.php rename resources/views/{emails => mail}/test.blade.php (100%) diff --git a/app/Actions/Notifications/SendMailTestNotification.php b/app/Actions/Notifications/SendMailTestNotification.php index 6bcad272d..f8220e20f 100644 --- a/app/Actions/Notifications/SendMailTestNotification.php +++ b/app/Actions/Notifications/SendMailTestNotification.php @@ -2,7 +2,7 @@ namespace App\Actions\Notifications; -use App\Mail\Test as TestMail; +use App\Mail\TestMail; use Filament\Notifications\Notification; use Illuminate\Support\Facades\Mail; use Lorisleiva\Actions\Concerns\AsAction; diff --git a/app/Filament/Pages/Settings/Notification.php b/app/Filament/Pages/Settings/Notification.php index 66bb8575c..3fd0d82c5 100755 --- a/app/Filament/Pages/Settings/Notification.php +++ b/app/Filament/Pages/Settings/Notification.php @@ -13,7 +13,9 @@ use App\Actions\Notifications\SendTelegramTestNotification; use App\Actions\Notifications\SendWebhookTestNotification; use App\Settings\NotificationSettings; +use CodeWithDennis\SimpleAlert\Components\SimpleAlert; use Filament\Actions\Action; +use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\Repeater; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; @@ -22,13 +24,16 @@ use Filament\Schemas\Components\Fieldset; use Filament\Schemas\Components\Grid; use Filament\Schemas\Components\Section; +use Filament\Schemas\Components\Tabs; +use Filament\Schemas\Components\Tabs\Tab; use Filament\Schemas\Components\Utilities\Get; use Filament\Schemas\Schema; +use Filament\Support\Icons\Heroicon; use Illuminate\Support\Facades\Auth; class Notification extends SettingsPage { - protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bell'; + protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bell-alert'; protected static string|\UnitEnum|null $navigationGroup = 'Settings'; @@ -60,128 +65,145 @@ public function form(Schema $schema): Schema { return $schema ->components([ - Grid::make([ - 'default' => 1, - 'md' => 3, - ]) - ->columnSpan('full') + Tabs::make() ->schema([ - Grid::make([ - 'default' => 1, - ]) + Tab::make(__('settings/notifications.database')) + ->icon(Heroicon::OutlinedCircleStack) ->schema([ - Section::make(__('settings/notifications.database')) - ->description(__('settings/notifications.database_description')) + Toggle::make('database_enabled') + ->label(__('general.enable')) + ->live(), + + Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Get $get) => $get('database_enabled') !== true) ->schema([ - Toggle::make('database_enabled') - ->label(__('settings/notifications.enable_database_notifications')) - ->reactive() - ->columnSpanFull(), - Grid::make([ - 'default' => 1, - ]) - ->hidden(fn (Get $get) => $get('database_enabled') !== true) + Fieldset::make(__('settings.triggers')) + ->columns(1) ->schema([ - Fieldset::make(__('settings.triggers')) - ->schema([ - Toggle::make('database_on_speedtest_run') - ->label(__('settings/notifications.database_on_speedtest_run')) - ->columnSpanFull(), - Toggle::make('database_on_threshold_failure') - ->label(__('settings/notifications.database_on_threshold_failure')) - ->columnSpanFull(), - ]), - Actions::make([ - Action::make('test database') - ->label(__('settings/notifications.test_database_channel')) - ->action(fn () => SendDatabaseTestNotification::run(user: Auth::user())), - ]), + Checkbox::make('database_on_speedtest_run') + ->label(__('settings/notifications.database_on_speedtest_run')), + + Checkbox::make('database_on_threshold_failure') + ->label(__('settings/notifications.database_on_threshold_failure')), ]), - ]) - ->compact() - ->columnSpan('full'), - Section::make(__('settings/notifications.mail')) + Actions::make([ + Action::make('test database') + ->label(__('settings/notifications.test_database_channel')) + ->action(fn () => SendDatabaseTestNotification::run(user: Auth::user())), + ]), + ]), + + // ... + ]), + + Tab::make(__('settings/notifications.mail')) + ->icon(Heroicon::OutlinedEnvelope) + ->schema([ + Toggle::make('mail_enabled') + ->label(__('general.enable')) + ->live(), + + Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Get $get) => $get('mail_enabled') !== true) ->schema([ - Toggle::make('mail_enabled') - ->label(__('settings/notifications.enable_mail_notifications')) - ->reactive() - ->columnSpanFull(), - Grid::make([ - 'default' => 1, - ]) - ->hidden(fn (Get $get) => $get('mail_enabled') !== true) + Fieldset::make(__('settings.triggers')) + ->columns(1) ->schema([ - Fieldset::make(__('settings.triggers')) - ->schema([ - Toggle::make('mail_on_speedtest_run') - ->label(__('settings/notifications.mail_on_speedtest_run')) - ->columnSpanFull(), - Toggle::make('mail_on_threshold_failure') - ->label(__('settings/notifications.mail_on_threshold_failure')) - ->columnSpanFull(), - ]), - Repeater::make('mail_recipients') - ->label(__('settings/notifications.recipients')) - ->schema([ - TextInput::make('email_address') - ->placeholder('your@email.com') - ->email() - ->required(), - ]) - ->columnSpanFull(), - Actions::make([ - Action::make('test mail') - ->label(__('settings/notifications.test_mail_channel')) - ->action(fn (Get $get) => SendMailTestNotification::run(recipients: $get('mail_recipients'))) - ->hidden(fn (Get $get) => ! count($get('mail_recipients'))), - ]), + Checkbox::make('mail_on_speedtest_run') + ->label(__('settings/notifications.mail_on_speedtest_run')), + + Checkbox::make('mail_on_threshold_failure') + ->label(__('settings/notifications.mail_on_threshold_failure')), ]), - ]) - ->compact() - ->columnSpan('full'), - Section::make(__('settings/notifications.webhook')) + Repeater::make('mail_recipients') + ->label(__('settings/notifications.recipients')) + ->schema([ + TextInput::make('email_address') + ->placeholder('your@email.com') + ->email() + ->required(), + ]), + + Actions::make([ + Action::make('test mail') + ->label(__('settings/notifications.test_mail_channel')) + ->action(fn (Get $get) => SendMailTestNotification::run(recipients: $get('mail_recipients'))) + ->hidden(fn (Get $get) => ! count($get('mail_recipients'))), + ]), + ]), + + // ... + ]), + + Tab::make(__('settings/notifications.webhook')) + ->icon(Heroicon::OutlinedGlobeAlt) + ->schema([ + Toggle::make('webhook_enabled') + ->label(__('general.enable')) + ->live(), + + Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Get $get) => $get('webhook_enabled') !== true) ->schema([ - Toggle::make('webhook_enabled') - ->label(__('settings/notifications.enable_webhook_notifications')) - ->reactive() - ->columnSpanFull(), - Grid::make([ - 'default' => 1, - ]) - ->hidden(fn (Get $get) => $get('webhook_enabled') !== true) + Fieldset::make(__('settings.triggers')) + ->columns(1) ->schema([ - Fieldset::make(__('settings.triggers')) - ->schema([ - Toggle::make('webhook_on_speedtest_run') - ->label(__('settings/notifications.webhook_on_speedtest_run')) - ->columnSpan(2), - Toggle::make('webhook_on_threshold_failure') - ->label(__('settings/notifications.webhook_on_threshold_failure')) - ->columnSpan(2), - ]), - Repeater::make('webhook_urls') - ->label(__('settings/notifications.recipients')) - ->schema([ - TextInput::make('url') - ->placeholder('https://webhook.site/longstringofcharacters') - ->maxLength(2000) - ->required() - ->url(), - ]) - ->columnSpanFull(), - Actions::make([ - Action::make('test webhook') - ->label(__('settings/notifications.test_webhook_channel')) - ->action(fn (Get $get) => SendWebhookTestNotification::run(webhooks: $get('webhook_urls'))) - ->hidden(fn (Get $get) => ! count($get('webhook_urls'))), - ]), + Checkbox::make('webhook_on_speedtest_run') + ->label(__('settings/notifications.webhook_on_speedtest_run')), + + Checkbox::make('webhook_on_threshold_failure') + ->label(__('settings/notifications.webhook_on_threshold_failure')), ]), - ]) - ->compact() - ->columnSpan('full'), + Repeater::make('webhook_urls') + ->label(__('settings/notifications.recipients')) + ->schema([ + TextInput::make('url') + ->placeholder('https://webhook.site/longstringofcharacters') + ->maxLength(2000) + ->required() + ->url(), + ]), + + Actions::make([ + Action::make('test webhook') + ->label(__('settings/notifications.test_webhook_channel')) + ->action(fn (Get $get) => SendWebhookTestNotification::run(webhooks: $get('webhook_urls'))) + ->hidden(fn (Get $get) => ! count($get('webhook_urls'))), + ]), + ]), + + // ... + ]), + ]) + ->columnSpanFull(), + + // ! DEPRECATED CHANNELS + SimpleAlert::make('deprecation_warning') + ->title('Deprecated Notification Channels') + ->description('The following notification channels are deprecated and will be removed in a future release!') + ->border() + ->warning() + ->columnSpanFull(), + + Grid::make([ + 'default' => 1, + 'md' => 3, + ]) + ->columnSpan('full') + ->schema([ + Grid::make([ + 'default' => 1, + ]) + ->schema([ Section::make('Pushover') ->description('⚠️ Pushover is deprecated and will be removed in a future release.') ->schema([ diff --git a/app/Listeners/Database/SendSpeedtestCompletedNotification.php b/app/Listeners/Database/SendSpeedtestCompletedNotification.php deleted file mode 100644 index acaaf9d42..000000000 --- a/app/Listeners/Database/SendSpeedtestCompletedNotification.php +++ /dev/null @@ -1,34 +0,0 @@ -database_enabled) { - return; - } - - if (! $notificationSettings->database_on_speedtest_run) { - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title(__('results.speedtest_completed')) - ->success() - ->sendToDatabase($user); - } - } -} diff --git a/app/Listeners/Database/SendSpeedtestThresholdNotification.php b/app/Listeners/Database/SendSpeedtestThresholdNotification.php deleted file mode 100644 index a998faff5..000000000 --- a/app/Listeners/Database/SendSpeedtestThresholdNotification.php +++ /dev/null @@ -1,101 +0,0 @@ -database_enabled) { - return; - } - - if (! $notificationSettings->database_on_threshold_failure) { - return; - } - - $thresholdSettings = new ThresholdSettings; - - if (! $thresholdSettings->absolute_enabled) { - return; - } - - if ($thresholdSettings->absolute_download > 0) { - $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings); - } - - if ($thresholdSettings->absolute_upload > 0) { - $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings); - } - - if ($thresholdSettings->absolute_ping > 0) { - $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings); - } - } - - /** - * Send database notification if absolute download threshold is breached. - */ - protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void - { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title(__('results.download_threshold_breached')) - ->body('Speedtest #'.$event->result->id.' breached the download threshold of '.$thresholdSettings->absolute_download.' Mbps at '.Number::toBitRate($event->result->download_bits).'.') - ->warning() - ->sendToDatabase($user); - } - } - - /** - * Send database notification if absolute upload threshold is breached. - */ - protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void - { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title(__('results.upload_threshold_breached')) - ->body('Speedtest #'.$event->result->id.' breached the upload threshold of '.$thresholdSettings->absolute_upload.' Mbps at '.Number::toBitRate($event->result->upload_bits).'.') - ->warning() - ->sendToDatabase($user); - } - } - - /** - * Send database notification if absolute upload threshold is breached. - */ - protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void - { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title(__('results.ping_threshold_breached')) - ->body('Speedtest #'.$event->result->id.' breached the ping threshold of '.$thresholdSettings->absolute_ping.'ms at '.$event->result->ping.'ms.') - ->warning() - ->sendToDatabase($user); - } - } -} diff --git a/app/Listeners/Mail/SendSpeedtestCompletedNotification.php b/app/Listeners/Mail/SendSpeedtestCompletedNotification.php deleted file mode 100644 index 2e731cd99..000000000 --- a/app/Listeners/Mail/SendSpeedtestCompletedNotification.php +++ /dev/null @@ -1,39 +0,0 @@ -mail_enabled) { - return; - } - - if (! $notificationSettings->mail_on_speedtest_run) { - return; - } - - if (! count($notificationSettings->mail_recipients)) { - Log::warning('Mail recipients not found, check mail notification channel settings.'); - - return; - } - - foreach ($notificationSettings->mail_recipients as $recipient) { - Mail::to($recipient) - ->send(new SpeedtestCompletedMail($event->result)); - } - } -} diff --git a/app/Listeners/Mail/SendSpeedtestThresholdNotification.php b/app/Listeners/Mail/SendSpeedtestThresholdNotification.php deleted file mode 100644 index 774851df5..000000000 --- a/app/Listeners/Mail/SendSpeedtestThresholdNotification.php +++ /dev/null @@ -1,117 +0,0 @@ -mail_enabled) { - return; - } - - if (! $notificationSettings->mail_on_threshold_failure) { - return; - } - - if (! count($notificationSettings->mail_recipients) > 0) { - Log::warning('Mail recipients not found, check mail notification channel settings.'); - - return; - } - - $thresholdSettings = new ThresholdSettings; - - if (! $thresholdSettings->absolute_enabled) { - return; - } - - $failed = []; - - if ($thresholdSettings->absolute_download > 0) { - array_push($failed, $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings)); - } - - if ($thresholdSettings->absolute_upload > 0) { - array_push($failed, $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings)); - } - - if ($thresholdSettings->absolute_ping > 0) { - array_push($failed, $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings)); - } - - $failed = array_filter($failed); - - if (! count($failed)) { - Log::warning('Failed mail thresholds not found, won\'t send notification.'); - - return; - } - - foreach ($notificationSettings->mail_recipients as $recipient) { - Mail::to($recipient) - ->send(new SpeedtestThresholdMail($event->result, $failed)); - } - } - - /** - * Build mail notification if absolute download threshold is breached. - */ - protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { - return false; - } - - return [ - 'name' => 'Download', - 'threshold' => $thresholdSettings->absolute_download.' Mbps', - 'value' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), - ]; - } - - /** - * Build mail notification if absolute upload threshold is breached. - */ - protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { - return false; - } - - return [ - 'name' => 'Upload', - 'threshold' => $thresholdSettings->absolute_upload.' Mbps', - 'value' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), - ]; - } - - /** - * Build mail notification if absolute ping threshold is breached. - */ - protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array - { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { - return false; - } - - return [ - 'name' => 'Ping', - 'threshold' => $thresholdSettings->absolute_ping.' ms', - 'value' => round($event->result->ping, 2).' ms', - ]; - } -} diff --git a/app/Listeners/ProcessCompletedSpeedtest.php b/app/Listeners/ProcessCompletedSpeedtest.php index 01bf317e6..94c2caa89 100644 --- a/app/Listeners/ProcessCompletedSpeedtest.php +++ b/app/Listeners/ProcessCompletedSpeedtest.php @@ -3,13 +3,23 @@ namespace App\Listeners; use App\Events\SpeedtestCompleted; +use App\Mail\CompletedSpeedtestMail; use App\Models\Result; use App\Models\User; +use App\Settings\NotificationSettings; use Filament\Actions\Action; use Filament\Notifications\Notification; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Mail; +use Spatie\WebhookServer\WebhookCall; class ProcessCompletedSpeedtest { + public function __construct( + public NotificationSettings $notificationSettings, + ) {} + /** * Handle the event. */ @@ -17,8 +27,53 @@ public function handle(SpeedtestCompleted $event): void { $result = $event->result; - if ($result->dispatched_by && ! $result->scheduled) { - $this->notifyDispatchingUser($result); + $result->loadMissing(['dispatchedBy']); + + // $this->notifyAppriseChannels($result); + $this->notifyDatabaseChannels($result); + $this->notifyDispatchingUser($result); + $this->notifyMailChannels($result); + $this->notifyWebhookChannels($result); + } + + /** + * Notify Apprise channels. + */ + private function notifyAppriseChannels(Result $result): void + { + // Don't send Apprise notification if dispatched by a user or test is unhealthy. + if (filled($result->dispatched_by) || ! $result->healthy) { + return; + } + + // + } + + /** + * Notify database channels. + */ + private function notifyDatabaseChannels(Result $result): void + { + // Don't send database notification if dispatched by a user or test is unhealthy. + if (filled($result->dispatched_by) || ! $result->healthy) { + return; + } + + // Check if database notifications are enabled. + if (! $this->notificationSettings->database_enabled || ! $this->notificationSettings->database_on_speedtest_run) { + return; + } + + foreach (User::all() as $user) { + Notification::make() + ->title(__('results.speedtest_completed')) + ->actions([ + Action::make('view') + ->label(__('general.view')) + ->url(route('filament.admin.resources.results.index')), + ]) + ->success() + ->sendToDatabase($user); } } @@ -27,9 +82,11 @@ public function handle(SpeedtestCompleted $event): void */ private function notifyDispatchingUser(Result $result): void { - $user = User::find($result->dispatched_by); + if (empty($result->dispatched_by) || ! $result->healthy) { + return; + } - $user->notify( + $result->dispatchedBy->notify( Notification::make() ->title(__('results.speedtest_completed')) ->actions([ @@ -41,4 +98,72 @@ private function notifyDispatchingUser(Result $result): void ->toDatabase(), ); } + + /** + * Notify mail channels. + */ + private function notifyMailChannels(Result $result): void + { + if (empty($result->dispatched_by) || ! $result->healthy) { + return; + } + + if (! $this->notificationSettings->mail_enabled || ! $this->notificationSettings->mail_on_speedtest_run) { + return; + } + + if (! count($this->notificationSettings->mail_recipients)) { + Log::warning('Mail recipients not found, check mail notification channel settings.'); + + return; + } + + foreach ($this->notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new CompletedSpeedtestMail($result)); + } + } + + /** + * Notify webhook channels. + */ + private function notifyWebhookChannels(Result $result): void + { + // Don't send webhook if dispatched by a user or test is unhealthy. + if (filled($result->dispatched_by) || ! $result->healthy) { + return; + } + + // Check if webhook notifications are enabled. + if (! $this->notificationSettings->webhook_enabled || ! $this->notificationSettings->webhook_on_speedtest_run) { + return; + } + + // Check if webhook urls are configured. + if (! count($this->notificationSettings->webhook_urls)) { + Log::warning('Webhook urls not found, check webhook notification channel settings.'); + + return; + } + + foreach ($this->notificationSettings->webhook_urls as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload([ + 'result_id' => $result->id, + 'site_name' => config('app.name'), + 'server_name' => Arr::get($result->data, 'server.name'), + 'server_id' => Arr::get($result->data, 'server.id'), + 'isp' => Arr::get($result->data, 'isp'), + 'ping' => $result->ping, + 'download' => $result->downloadBits, + 'upload' => $result->uploadBits, + 'packet_loss' => Arr::get($result->data, 'packetLoss'), + 'speedtest_url' => Arr::get($result->data, 'result.url'), + 'url' => url('/admin/results'), + ]) + ->doNotSign() + ->dispatch(); + } + } } diff --git a/app/Listeners/ProcessFailedSpeedtest.php b/app/Listeners/ProcessFailedSpeedtest.php index d11e5e7a0..8ba154387 100644 --- a/app/Listeners/ProcessFailedSpeedtest.php +++ b/app/Listeners/ProcessFailedSpeedtest.php @@ -4,7 +4,6 @@ use App\Events\SpeedtestFailed; use App\Models\Result; -use App\Models\User; use Filament\Actions\Action; use Filament\Notifications\Notification; @@ -17,9 +16,23 @@ public function handle(SpeedtestFailed $event): void { $result = $event->result; - if ($result->dispatched_by && ! $result->scheduled) { - $this->notifyDispatchingUser($result); + $result->loadMissing(['dispatchedBy']); + + // $this->notifyAppriseChannels($result); + $this->notifyDispatchingUser($result); + } + + /** + * Notify Apprise channels. + */ + private function notifyAppriseChannels(Result $result): void + { + // Don't send Apprise notification if dispatched by a user or test is unhealthy. + if (filled($result->dispatched_by) || ! $result->healthy) { + return; } + + // } /** @@ -27,9 +40,11 @@ public function handle(SpeedtestFailed $event): void */ private function notifyDispatchingUser(Result $result): void { - $user = User::find($result->dispatched_by); + if (empty($result->dispatched_by)) { + return; + } - $user->notify( + $result->dispatchedBy->notify( Notification::make() ->title(__('results.speedtest_failed')) ->actions([ diff --git a/app/Listeners/ProcessUnhealthySpeedtest.php b/app/Listeners/ProcessUnhealthySpeedtest.php new file mode 100644 index 000000000..5a6d0e057 --- /dev/null +++ b/app/Listeners/ProcessUnhealthySpeedtest.php @@ -0,0 +1,169 @@ +result; + + $result->loadMissing(['dispatchedBy']); + + // $this->notifyAppriseChannels($result); + $this->notifyDatabaseChannels($result); + $this->notifyDispatchingUser($result); + $this->notifyMailChannels($result); + $this->notifyWebhookChannels($result); + } + + /** + * Notify Apprise channels. + */ + private function notifyAppriseChannels(Result $result): void + { + // Don't send Apprise notification if dispatched by a user. + if (filled($result->dispatched_by)) { + return; + } + + // + } + + /** + * Notify database channels. + */ + private function notifyDatabaseChannels(Result $result): void + { + // Don't send database notification if dispatched by a user. + if (filled($result->dispatched_by)) { + return; + } + + // Check if database notifications are enabled. + if (! $this->notificationSettings->database_enabled || ! $this->notificationSettings->database_on_threshold_failure) { + return; + } + + foreach (User::all() as $user) { + Notification::make() + ->title(__('results.speedtest_benchmark_failed')) + ->actions([ + Action::make('view') + ->label(__('general.view')) + ->url(route('filament.admin.resources.results.index')), + ]) + ->success() + ->sendToDatabase($user); + } + } + + /** + * Notify the user who dispatched the speedtest. + */ + private function notifyDispatchingUser(Result $result): void + { + if (empty($result->dispatched_by)) { + return; + } + + $result->dispatchedBy->notify( + Notification::make() + ->title(__('results.speedtest_benchmark_failed')) + ->actions([ + Action::make('view') + ->label(__('general.view')) + ->url(route('filament.admin.resources.results.index')), + ]) + ->warning() + ->toDatabase(), + ); + } + + /** + * Notify mail channels. + */ + private function notifyMailChannels(Result $result): void + { + // Don't send webhook if dispatched by a user. + if (filled($result->dispatched_by)) { + return; + } + + // Check if mail notifications are enabled. + if (! $this->notificationSettings->mail_enabled || ! $this->notificationSettings->mail_on_threshold_failure) { + return; + } + + // Check if mail recipients are configured. + if (! count($this->notificationSettings->mail_recipients)) { + Log::warning('Mail recipients not found, check mail notification channel settings.'); + + return; + } + + foreach ($this->notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new UnhealthySpeedtestMail($result)); + } + } + + /** + * Notify webhook channels. + */ + private function notifyWebhookChannels(Result $result): void + { + // Don't send webhook if dispatched by a user. + if (filled($result->dispatched_by)) { + return; + } + + // Check if webhook notifications are enabled. + if (! $this->notificationSettings->webhook_enabled || ! $this->notificationSettings->webhook_on_threshold_failure) { + return; + } + + // Check if webhook urls are configured. + if (! count($this->notificationSettings->webhook_urls)) { + Log::warning('Webhook urls not found, check webhook notification channel settings.'); + + return; + } + + foreach ($this->notificationSettings->webhook_urls as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload([ + 'result_id' => $result->id, + 'site_name' => config('app.name'), + 'isp' => $result->isp, + 'benchmarks' => $result->benchmarks, + 'speedtest_url' => $result->result_url, + 'url' => url('/admin/results'), + ]) + ->doNotSign() + ->dispatch(); + } + } +} diff --git a/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php b/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php deleted file mode 100644 index bee0668d6..000000000 --- a/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php +++ /dev/null @@ -1,54 +0,0 @@ -webhook_enabled) { - return; - } - - if (! $notificationSettings->webhook_on_speedtest_run) { - return; - } - - if (! count($notificationSettings->webhook_urls)) { - Log::warning('Webhook urls not found, check webhook notification channel settings.'); - - return; - } - - foreach ($notificationSettings->webhook_urls as $url) { - WebhookCall::create() - ->url($url['url']) - ->payload([ - 'result_id' => $event->result->id, - 'site_name' => config('app.name'), - 'server_name' => Arr::get($event->result->data, 'server.name'), - 'server_id' => Arr::get($event->result->data, 'server.id'), - 'isp' => Arr::get($event->result->data, 'isp'), - 'ping' => $event->result->ping, - 'download' => $event->result->downloadBits, - 'upload' => $event->result->uploadBits, - 'packet_loss' => Arr::get($event->result->data, 'packetLoss'), - 'speedtest_url' => Arr::get($event->result->data, 'result.url'), - 'url' => url('/admin/results'), - ]) - ->doNotSign() - ->dispatch(); - } - } -} diff --git a/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php b/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php deleted file mode 100644 index bb64866b4..000000000 --- a/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php +++ /dev/null @@ -1,126 +0,0 @@ -webhook_enabled) { - return; - } - - if (! $notificationSettings->webhook_on_threshold_failure) { - return; - } - - if (! count($notificationSettings->webhook_urls)) { - Log::warning('Webhook urls not found, check webhook notification channel settings.'); - - return; - } - - $thresholdSettings = new ThresholdSettings; - - if (! $thresholdSettings->absolute_enabled) { - return; - } - - $failed = []; - - if ($thresholdSettings->absolute_download > 0) { - array_push($failed, $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings)); - } - - if ($thresholdSettings->absolute_upload > 0) { - array_push($failed, $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings)); - } - - if ($thresholdSettings->absolute_ping > 0) { - array_push($failed, $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings)); - } - - $failed = array_filter($failed); - - if (! count($failed)) { - Log::warning('Failed webhook thresholds not found, won\'t send notification.'); - - return; - } - - foreach ($notificationSettings->webhook_urls as $url) { - WebhookCall::create() - ->url($url['url']) - ->payload([ - 'result_id' => $event->result->id, - 'site_name' => config('app.name'), - 'isp' => $event->result->isp, - 'metrics' => $failed, - 'speedtest_url' => $event->result->result_url, - 'url' => url('/admin/results'), - ]) - ->doNotSign() - ->dispatch(); - } - } - - /** - * Build webhook notification if absolute download threshold is breached. - */ - protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { - return false; - } - - return [ - 'name' => 'Download', - 'threshold' => $thresholdSettings->absolute_download.' Mbps', - 'value' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), - ]; - } - - /** - * Build webhook notification if absolute upload threshold is breached. - */ - protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { - return false; - } - - return [ - 'name' => 'Upload', - 'threshold' => $thresholdSettings->absolute_upload.' Mbps', - 'value' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), - ]; - } - - /** - * Build webhook notification if absolute ping threshold is breached. - */ - protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array - { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { - return false; - } - - return [ - 'name' => 'Ping', - 'threshold' => $thresholdSettings->absolute_ping.' ms', - 'value' => round($event->result->ping, 2).' ms', - ]; - } -} diff --git a/app/Mail/SpeedtestCompletedMail.php b/app/Mail/CompletedSpeedtestMail.php similarity index 93% rename from app/Mail/SpeedtestCompletedMail.php rename to app/Mail/CompletedSpeedtestMail.php index 6f7295771..109d95360 100644 --- a/app/Mail/SpeedtestCompletedMail.php +++ b/app/Mail/CompletedSpeedtestMail.php @@ -12,7 +12,7 @@ use Illuminate\Queue\SerializesModels; use Illuminate\Support\Str; -class SpeedtestCompletedMail extends Mailable implements ShouldQueue +class CompletedSpeedtestMail extends Mailable implements ShouldQueue { use Queueable, SerializesModels; @@ -41,7 +41,7 @@ public function envelope(): Envelope public function content(): Content { return new Content( - markdown: 'emails.speedtest-completed', + markdown: 'mail.speedtest.completed', with: [ 'id' => $this->result->id, 'service' => Str::title($this->result->service->getLabel()), diff --git a/app/Mail/SpeedtestThresholdMail.php b/app/Mail/SpeedtestThresholdMail.php deleted file mode 100644 index 94ad14af9..000000000 --- a/app/Mail/SpeedtestThresholdMail.php +++ /dev/null @@ -1,57 +0,0 @@ -result->id, - ); - } - - /** - * Get the message content definition. - */ - public function content(): Content - { - return new Content( - markdown: 'emails.speedtest-threshold', - with: [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, - 'speedtest_url' => $this->result->result_url, - 'url' => url('/admin/results'), - 'metrics' => $this->metrics, - ], - ); - } -} diff --git a/app/Mail/Test.php b/app/Mail/TestMail.php similarity index 87% rename from app/Mail/Test.php rename to app/Mail/TestMail.php index 5fe6fd88b..c6e43a269 100644 --- a/app/Mail/Test.php +++ b/app/Mail/TestMail.php @@ -9,7 +9,7 @@ use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; -class Test extends Mailable implements ShouldQueue +class TestMail extends Mailable implements ShouldQueue { use Queueable, SerializesModels; @@ -29,7 +29,7 @@ public function envelope(): Envelope public function content(): Content { return new Content( - markdown: 'emails.test', + markdown: 'mail.test', ); } } diff --git a/app/Mail/UnhealthySpeedtestMail.php b/app/Mail/UnhealthySpeedtestMail.php new file mode 100644 index 000000000..e22f4ec28 --- /dev/null +++ b/app/Mail/UnhealthySpeedtestMail.php @@ -0,0 +1,83 @@ +result->id, + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + $benchmarks = []; + + foreach ($this->result->benchmarks as $metric => $benchmark) { + $benchmarks[] = $this->formatBenchmark($metric, $benchmark); + } + + return new Content( + markdown: 'mail.speedtest.unhealthy', + with: [ + 'id' => $this->result->id, + 'service' => str($this->result->service->getLabel())->title(), + 'isp' => $this->result->isp, + 'url' => url('/admin/results'), + 'benchmarks' => $benchmarks, + ], + ); + } + + /** + * Format a benchmark for display in the email. + */ + private function formatBenchmark(string $metric, array $benchmark): array + { + $metricName = str($metric)->title(); + $type = str($benchmark['type'])->title(); + $thresholdValue = $benchmark['value'].' '.str($benchmark['unit'])->title(); + + // Get the actual result value + $resultValue = match ($metric) { + 'download' => Number::toBitRate($this->result->download_bits, 2), + 'upload' => Number::toBitRate($this->result->upload_bits, 2), + 'ping' => round(Number::castToType($this->result->ping, 'float'), 2).' ms', + default => 'N/A', + }; + + return [ + 'metric' => $metricName, + 'type' => $type, + 'threshold_value' => $thresholdValue, + 'result_value' => $resultValue, + 'passed' => $benchmark['passed'], + ]; + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index aa8e024ff..f72019481 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -32,6 +32,7 @@ public function panel(Panel $panel): Panel ->colors([ 'primary' => Color::Amber, ]) + ->viteTheme('resources/css/filament/admin/theme.css') ->favicon(asset('img/speedtest-tracker-icon.png')) ->sidebarCollapsibleOnDesktop() ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') @@ -40,6 +41,7 @@ public function panel(Panel $panel): Panel ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') ->widgets([]) ->databaseNotifications() + ->databaseNotificationsPolling('5s') ->maxContentWidth(config('speedtest.content_width')) ->middleware([ EncryptCookies::class, diff --git a/composer.json b/composer.json index 2b38b1ec7..5652a0c4d 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "require": { "php": "^8.2", "chrisullyott/php-filesize": "^4.2.1", + "codewithdennis/filament-simple-alert": "^4.0.2", "dragonmantank/cron-expression": "^3.6.0", "filament/filament": "4.1.0", "filament/spatie-laravel-settings-plugin": "^4.1", diff --git a/composer.lock b/composer.lock index b5d427f75..58af2d1e9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "39020dcee9d9965e781ef550aca663ac", + "content-hash": "3aff9923fe99afc6088082ec8c3be834", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -625,6 +625,79 @@ ], "time": "2023-12-20T15:40:13+00:00" }, + { + "name": "codewithdennis/filament-simple-alert", + "version": "v4.0.2", + "source": { + "type": "git", + "url": "https://github.com/CodeWithDennis/filament-simple-alert.git", + "reference": "d30b0cad908f3ade1bed153d486fd564ac312ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CodeWithDennis/filament-simple-alert/zipball/d30b0cad908f3ade1bed153d486fd564ac312ffd", + "reference": "d30b0cad908f3ade1bed153d486fd564ac312ffd", + "shasum": "" + }, + "require": { + "filament/filament": "^4.0", + "php": "^8.1", + "spatie/laravel-package-tools": "^1.15.0" + }, + "require-dev": { + "laravel/pint": "^1.16", + "nunomaduro/collision": "^7.9", + "orchestra/testbench": "^8.0", + "pestphp/pest": "^2.1", + "pestphp/pest-plugin-arch": "^2.0", + "pestphp/pest-plugin-laravel": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "SimpleAlert": "CodeWithDennis\\SimpleAlert\\Facades\\SimpleAlert" + }, + "providers": [ + "CodeWithDennis\\SimpleAlert\\SimpleAlertServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "CodeWithDennis\\SimpleAlert\\": "src/", + "CodeWithDennis\\SimpleAlert\\Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CodeWithDennis", + "role": "Developer" + } + ], + "description": "A plugin for adding straightforward alerts to your filament pages", + "homepage": "https://github.com/codewithdennis/filament-simple-alert", + "keywords": [ + "CodeWithDennis", + "filament-simple-alert", + "laravel" + ], + "support": { + "issues": "https://github.com/codewithdennis/filament-simple-alert/issues", + "source": "https://github.com/codewithdennis/filament-simple-alert" + }, + "funding": [ + { + "url": "https://github.com/CodeWithDennis", + "type": "github" + } + ], + "time": "2025-06-21T18:43:06+00:00" + }, { "name": "danharrin/date-format-converter", "version": "v0.3.1", @@ -13714,5 +13787,5 @@ "platform-overrides": { "php": "8.4" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/lang/en/results.php b/lang/en/results.php index f59df96d8..6d8a2016d 100644 --- a/lang/en/results.php +++ b/lang/en/results.php @@ -61,6 +61,8 @@ 'view_on_speedtest_net' => 'View on Speedtest.net', // Notifications + 'speedtest_benchmark_passed' => 'Speedtest benchmark passed', + 'speedtest_benchmark_failed' => 'Speedtest benchmark failed', 'speedtest_started' => 'Speedtest started', 'speedtest_completed' => 'Speedtest completed', 'speedtest_failed' => 'Speedtest failed', diff --git a/lang/en/settings/notifications.php b/lang/en/settings/notifications.php index 77f779529..6dcf09a88 100644 --- a/lang/en/settings/notifications.php +++ b/lang/en/settings/notifications.php @@ -7,14 +7,12 @@ // Database notifications 'database' => 'Database', 'database_description' => 'Notifications sent to this channel will show up under the 🔔 icon in the header.', - 'enable_database_notifications' => 'Enable database notifications', 'database_on_speedtest_run' => 'Notify on every speedtest run', 'database_on_threshold_failure' => 'Notify on threshold failures', 'test_database_channel' => 'Test database channel', // Mail notifications 'mail' => 'Mail', - 'enable_mail_notifications' => 'Enable mail notifications', 'recipients' => 'Recipients', 'mail_on_speedtest_run' => 'Notify on every speedtest run', 'mail_on_threshold_failure' => 'Notify on threshold failures', @@ -23,7 +21,6 @@ // Webhook 'webhook' => 'Webhook', 'webhooks' => 'Webhooks', - 'enable_webhook_notifications' => 'Enable webhook notifications', 'webhook_on_speedtest_run' => 'Notify on every speedtest run', 'webhook_on_threshold_failure' => 'Notify on threshold failures', 'test_webhook_channel' => 'Test webhook channel', diff --git a/package-lock.json b/package-lock.json index eb0c8bb10..e4153991b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,10 +6,10 @@ "": { "devDependencies": { "@tailwindcss/typography": "^0.5.10", - "@tailwindcss/vite": "^4.1.16", + "@tailwindcss/vite": "^4.1.17", "autoprefixer": "^10.4.15", "laravel-vite-plugin": "^1.0.0", - "tailwindcss": "^4.1.16", + "tailwindcss": "^4.1.17", "vite": "^6.4.1" } }, diff --git a/package.json b/package.json index cd7b12885..87e7212c3 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,10 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.10", - "@tailwindcss/vite": "^4.1.16", + "@tailwindcss/vite": "^4.1.17", "autoprefixer": "^10.4.15", "laravel-vite-plugin": "^1.0.0", - "tailwindcss": "^4.1.16", + "tailwindcss": "^4.1.17", "vite": "^6.4.1" } } diff --git a/resources/css/panel.css b/resources/css/filament/admin/theme.css similarity index 50% rename from resources/css/panel.css rename to resources/css/filament/admin/theme.css index 4503ad7e8..b39705336 100644 --- a/resources/css/panel.css +++ b/resources/css/filament/admin/theme.css @@ -1,3 +1,13 @@ +@import '../../../../vendor/filament/filament/resources/css/theme.css'; + +@source '../../../../app/Filament/**/*'; +@source '../../../../resources/views/filament/**/*'; + +/* Filament Plugins */ +@source '../../../../vendor/codewithdennis/filament-simple-alert/resources/**/*.blade.php'; +@source inline('animate-{spin,pulse,bounce}'); + +/* Additional styles */ .fi-topbar #dashboardAction .fi-btn-label, .fi-topbar #speedtestAction .fi-btn-label { display: none; diff --git a/resources/views/emails/speedtest-threshold.blade.php b/resources/views/emails/speedtest-threshold.blade.php deleted file mode 100644 index 55bdaff31..000000000 --- a/resources/views/emails/speedtest-threshold.blade.php +++ /dev/null @@ -1,24 +0,0 @@ - -# Speedtest Threshold Breached - #{{ $id }} - -A new speedtest was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached. - - -| **Metric** | **Threshold** | **Value** | -|:-----------|:--------------|----------:| -@foreach ($metrics as $item) -| {{ $item['name'] }} | {{ $item['threshold'] }} | {{ $item['value'] }} | -@endforeach - - - -View Results - - - -View Results on Ookla - - -Thanks,
-{{ config('app.name') }} -
diff --git a/resources/views/emails/speedtest-completed.blade.php b/resources/views/mail/speedtest/completed.blade.php similarity index 100% rename from resources/views/emails/speedtest-completed.blade.php rename to resources/views/mail/speedtest/completed.blade.php diff --git a/resources/views/mail/speedtest/unhealthy.blade.php b/resources/views/mail/speedtest/unhealthy.blade.php new file mode 100644 index 000000000..f5934fd1e --- /dev/null +++ b/resources/views/mail/speedtest/unhealthy.blade.php @@ -0,0 +1,17 @@ + +# Speedtest Threshold Breached - #{{ $id }} + +A new speedtest was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached. + + +| **Metric** | **Type** | **Threshold Value** | **Result Value** | **Status** | +|:-----------|:---------|:--------------------|:-----------------|:---------:| +@foreach ($benchmarks as $benchmark) +| {{ $benchmark['metric'] }} | {{ $benchmark['type'] }} | {{ $benchmark['threshold_value'] }} | {{ $benchmark['result_value'] }} | {{ $benchmark['passed'] ? '✅' : '❌' }} | +@endforeach + + + +{{ __('general.view') }} + + diff --git a/resources/views/emails/test.blade.php b/resources/views/mail/test.blade.php similarity index 100% rename from resources/views/emails/test.blade.php rename to resources/views/mail/test.blade.php diff --git a/vite.config.js b/vite.config.js index fd6e9f1b6..d4b0e527b 100644 --- a/vite.config.js +++ b/vite.config.js @@ -7,7 +7,7 @@ import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [ laravel({ - input: ['resources/css/app.css'], + input: ['resources/css/app.css', 'resources/css/filament/admin/theme.css'], refresh: [`resources/views/**/*`], }), tailwindcss(), From b84e04d622bf7247918a2ecc8d2534362df9c51a Mon Sep 17 00:00:00 2001 From: Alex Justesen Date: Wed, 26 Nov 2025 06:29:17 -0600 Subject: [PATCH 4/9] Normalize default chart range to lower case (#2443) Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> --- config/speedtest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/speedtest.php b/config/speedtest.php index 263729a5f..230afc091 100644 --- a/config/speedtest.php +++ b/config/speedtest.php @@ -16,7 +16,7 @@ 'public_dashboard' => env('PUBLIC_DASHBOARD', false), - 'default_chart_range' => env('DEFAULT_CHART_RANGE', '24h'), + 'default_chart_range' => strtolower(env('DEFAULT_CHART_RANGE', '24h')), /** * Speedtest settings. From d1d7b9eb016d618a9c68dd10a45653ffe6830ddd Mon Sep 17 00:00:00 2001 From: Alex Justesen Date: Wed, 26 Nov 2025 07:14:56 -0600 Subject: [PATCH 5/9] Remove unused CSS asset registration in FilamentServiceProvider (#2444) Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> --- app/Providers/FilamentServiceProvider.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/Providers/FilamentServiceProvider.php b/app/Providers/FilamentServiceProvider.php index 96274b131..1f6b2bd44 100644 --- a/app/Providers/FilamentServiceProvider.php +++ b/app/Providers/FilamentServiceProvider.php @@ -2,8 +2,6 @@ namespace App\Providers; -use Filament\Support\Assets\Css; -use Filament\Support\Facades\FilamentAsset; use Filament\Support\Facades\FilamentView; use Filament\View\PanelsRenderHook; use Illuminate\Support\Facades\Blade; @@ -24,10 +22,6 @@ public function register(): void */ public function boot(): void { - FilamentAsset::register([ - Css::make('panel', __DIR__.'/../../resources/css/panel.css'), - ]); - FilamentView::registerRenderHook( PanelsRenderHook::GLOBAL_SEARCH_BEFORE, fn (): string => Blade::render("@livewire('topbar.run-speedtest-action')"), From 86ea672f06635e480efe5abc30b656415eee1afe Mon Sep 17 00:00:00 2001 From: Joseph Carmosino Date: Wed, 26 Nov 2025 13:22:17 -0500 Subject: [PATCH 6/9] Fix pagination limit by using correct page[size] parameter #2441 (#2442) --- .../Controllers/Api/V1/ResultsController.php | 4 ++-- .../Annotations/V1/ResultsAnnotations.php | 2 +- app/OpenApi/Schemas/ResultsCollectionSchema.php | 2 +- config/json-api-paginate.php | 17 +++++++++++++++++ openapi.json | 2 +- 5 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 config/json-api-paginate.php diff --git a/app/Http/Controllers/Api/V1/ResultsController.php b/app/Http/Controllers/Api/V1/ResultsController.php index 9c802fe84..5f462ae3c 100644 --- a/app/Http/Controllers/Api/V1/ResultsController.php +++ b/app/Http/Controllers/Api/V1/ResultsController.php @@ -28,7 +28,7 @@ public function list(Request $request) ); } $validator = Validator::make($request->all(), [ - 'per_page' => 'integer|min:1|max:500', + 'page.size' => 'integer|min:1|max:'.config('json-api-paginate.max_results'), ]); if ($validator->fails()) { @@ -65,7 +65,7 @@ public function list(Request $request) 'created_at', 'updated_at', ]) - ->jsonPaginate($request->input('per_page', 25)); + ->jsonPaginate(); return ResultResource::collection($results); } diff --git a/app/OpenApi/Annotations/V1/ResultsAnnotations.php b/app/OpenApi/Annotations/V1/ResultsAnnotations.php index 9bfe6a691..76b794c90 100644 --- a/app/OpenApi/Annotations/V1/ResultsAnnotations.php +++ b/app/OpenApi/Annotations/V1/ResultsAnnotations.php @@ -19,7 +19,7 @@ class ResultsAnnotations parameters: [ new OA\Parameter(ref: '#/components/parameters/AcceptHeader'), new OA\Parameter( - name: 'per_page', + name: 'per.page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', minimum: 1, maximum: 500, default: 25), diff --git a/app/OpenApi/Schemas/ResultsCollectionSchema.php b/app/OpenApi/Schemas/ResultsCollectionSchema.php index 2a4ac55a7..2f8d82c5f 100644 --- a/app/OpenApi/Schemas/ResultsCollectionSchema.php +++ b/app/OpenApi/Schemas/ResultsCollectionSchema.php @@ -47,7 +47,7 @@ ) ), new OA\Property(property: 'path', type: 'string'), - new OA\Property(property: 'per_page', type: 'integer'), + new OA\Property(property: 'per.page', type: 'integer'), new OA\Property(property: 'to', type: 'integer'), new OA\Property(property: 'total', type: 'integer'), ], diff --git a/config/json-api-paginate.php b/config/json-api-paginate.php new file mode 100644 index 000000000..6ff3c2afa --- /dev/null +++ b/config/json-api-paginate.php @@ -0,0 +1,17 @@ + env('API_MAX_RESULTS', 500), + + /* + * The default number of results that will be returned + * when using the JSON API paginator. + */ + 'default_size' => 25, + +]; diff --git a/openapi.json b/openapi.json index ffe15d82d..1717e7ee6 100644 --- a/openapi.json +++ b/openapi.json @@ -21,7 +21,7 @@ "$ref": "#/components/parameters/AcceptHeader" }, { - "name": "per_page", + "name": "per.page", "in": "query", "description": "Number of results per page", "required": false, From 29a067269084fb29a58a317a86b88581376905e4 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Thu, 27 Nov 2025 13:56:46 +0100 Subject: [PATCH 7/9] chore: update ci workflows (#2445) --- .github/workflows/update-openapi.yml | 40 --------------------- .github/workflows/validate-openapi.yml | 49 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/update-openapi.yml create mode 100644 .github/workflows/validate-openapi.yml diff --git a/.github/workflows/update-openapi.yml b/.github/workflows/update-openapi.yml deleted file mode 100644 index 5aab78ef0..000000000 --- a/.github/workflows/update-openapi.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Update OpenAPI JSON - -on: - push: - paths: - - 'app/Http/Controllers/Api/**/*.php' - - 'app/OpenApi/**/*.php' - -jobs: - update-openapi: - runs-on: ubuntu-24.04 - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - - name: Install dependencies - run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress - - - name: Generate OpenAPI JSON - run: ./vendor/bin/openapi ./app/Http/Controllers/Api ./app/OpenApi -o openapi_temp.json -f json - - - name: Commit OpenAPI JSON if changed - run: | - if ! diff -q openapi.json openapi_temp.json; then - mv openapi_temp.json openapi.json - git config user.name "GitHub Action" - git config user.email "actions@github.com" - git add openapi.json - git commit -m "Update OpenAPI JSON" - git push - fi diff --git a/.github/workflows/validate-openapi.yml b/.github/workflows/validate-openapi.yml new file mode 100644 index 000000000..751ca222d --- /dev/null +++ b/.github/workflows/validate-openapi.yml @@ -0,0 +1,49 @@ +name: Validate OpenAPI JSON + +on: + pull_request: + paths: + - 'app/Http/Controllers/Api/**/*.php' + - 'app/OpenApi/**/*.php' + +jobs: + update-openapi: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout PR branch + uses: actions/checkout@v5 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + + - name: Install dependencies + run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress + + # Generate OpenAPI file into a temp location + - name: Generate OpenAPI JSON + run: ./vendor/bin/openapi ./app/Http/Controllers/Api ./app/OpenApi -o openapi_temp.json -f json + + # Compare the generated file to the committed file + - name: Validate OpenAPI JSON is up to date + run: | + if ! diff -q openapi.json openapi_temp.json; then + echo "❌ OpenAPI documentation is out of sync!" + echo "" + echo "API code was modified but openapi.json was NOT updated." + echo "" + echo "Please regenerate the OpenAPI JSON and commit it:" + echo " ./vendor/bin/openapi ./app/Http/Controllers/Api ./app/OpenApi -o openapi.json -f json" + echo "" + exit 1 + fi + + - name: ✓ OpenAPI is up to date + if: success() + run: echo "✅ OpenAPI documentation matches the committed version!" \ No newline at end of file From a5c77ecc7dffc9ae7a045090db64183dedafc085 Mon Sep 17 00:00:00 2001 From: Alex Justesen Date: Thu, 27 Nov 2025 07:06:10 -0600 Subject: [PATCH 8/9] New Crowdin updates (#2435) --- lang/de_DE/settings/notifications.php | 3 --- lang/es_ES/settings/notifications.php | 3 --- lang/fr_FR/settings/notifications.php | 3 --- lang/nl_NL/general.php | 1 + lang/nl_NL/results.php | 3 +++ lang/nl_NL/settings/notifications.php | 3 --- 6 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lang/de_DE/settings/notifications.php b/lang/de_DE/settings/notifications.php index b1ee21035..446b1c3c2 100644 --- a/lang/de_DE/settings/notifications.php +++ b/lang/de_DE/settings/notifications.php @@ -7,14 +7,12 @@ // Database notifications 'database' => 'Datenbank', 'database_description' => 'Benachrichtigungen, die an diesen Kanal gesendet werden, werden unter 🔔 Symbol in der Kopfzeile angezeigt.', - 'enable_database_notifications' => 'Datenbank-Benachrichtigungen aktivieren', 'database_on_speedtest_run' => 'Bei jedem Schnelltest benachrichtigen', 'database_on_threshold_failure' => 'Benachrichtigen bei Schwellenausfällen', 'test_database_channel' => 'Datenbankkanal testen', // Mail notifications 'mail' => 'Mail', - 'enable_mail_notifications' => 'E-Mail-Benachrichtigungen aktivieren', 'recipients' => 'Empfänger', 'mail_on_speedtest_run' => 'Bei jedem Schnelltest benachrichtigen', 'mail_on_threshold_failure' => 'Benachrichtigen bei Schwellenausfällen', @@ -23,7 +21,6 @@ // Webhook 'webhook' => 'Webhook', 'webhooks' => 'Webhooks', - 'enable_webhook_notifications' => 'Webhook Benachrichtigungen aktivieren', 'webhook_on_speedtest_run' => 'Bei jedem Schnelltest benachrichtigen', 'webhook_on_threshold_failure' => 'Benachrichtigen bei Schwellenausfällen', 'test_webhook_channel' => 'Webhook-Kanal testen', diff --git a/lang/es_ES/settings/notifications.php b/lang/es_ES/settings/notifications.php index 03edbec00..ddbb10dca 100644 --- a/lang/es_ES/settings/notifications.php +++ b/lang/es_ES/settings/notifications.php @@ -7,14 +7,12 @@ // Database notifications 'database' => 'Base de datos', 'database_description' => 'Las notificaciones enviadas a este canal se mostrarán bajo el icono :belell: en el encabezado.', - 'enable_database_notifications' => 'Habilitar notificaciones de base de datos', 'database_on_speedtest_run' => 'Notificar en cada prueba de velocidad', 'database_on_threshold_failure' => 'Notificar en los umbrales de fallos', 'test_database_channel' => 'Probar canal de base de datos', // Mail notifications 'mail' => 'Correo', - 'enable_mail_notifications' => 'Habilitar notificaciones de correo', 'recipients' => 'Destinatarios', 'mail_on_speedtest_run' => 'Notificar en cada prueba de velocidad', 'mail_on_threshold_failure' => 'Notificar en los umbrales de fallos', @@ -23,7 +21,6 @@ // Webhook 'webhook' => 'Webhook', 'webhooks' => 'Webhooks', - 'enable_webhook_notifications' => 'Habilitar notificaciones de webhook', 'webhook_on_speedtest_run' => 'Notificar en cada prueba de velocidad', 'webhook_on_threshold_failure' => 'Notificar en los umbrales de fallos', 'test_webhook_channel' => 'Probar canal webhook', diff --git a/lang/fr_FR/settings/notifications.php b/lang/fr_FR/settings/notifications.php index 01e42f327..21b3875cd 100644 --- a/lang/fr_FR/settings/notifications.php +++ b/lang/fr_FR/settings/notifications.php @@ -7,14 +7,12 @@ // Database notifications 'database' => 'Base de données', 'database_description' => 'Les notifications envoyées à ce salon apparaîtront sous l\'icône 🔔 dans l\'entête.', - 'enable_database_notifications' => 'Activer les notifications de base de données', 'database_on_speedtest_run' => 'Notifier à chaque test de vitesse', 'database_on_threshold_failure' => 'Notifier en cas de dépassement de seuil', 'test_database_channel' => 'Tester le canal de base de données', // Mail notifications 'mail' => 'Courrier', - 'enable_mail_notifications' => 'Activer les notifications par courriel', 'recipients' => 'Destinataires', 'mail_on_speedtest_run' => 'Notifier à chaque test de vitesse', 'mail_on_threshold_failure' => 'Notifier en cas de dépassement de seuil', @@ -23,7 +21,6 @@ // Webhook 'webhook' => 'Webhook', 'webhooks' => 'Webhooks', - 'enable_webhook_notifications' => 'Activer les notifications webhook', 'webhook_on_speedtest_run' => 'Notifier à chaque test de vitesse', 'webhook_on_threshold_failure' => 'Notifier en cas de dépassement de seuil', 'test_webhook_channel' => 'Tester le canal webhook', diff --git a/lang/nl_NL/general.php b/lang/nl_NL/general.php index 96b2a486a..75deb4f3a 100644 --- a/lang/nl_NL/general.php +++ b/lang/nl_NL/general.php @@ -16,6 +16,7 @@ 'no' => 'Neen', 'options' => 'Instellingen', 'details' => 'Beschrijving', + 'view' => 'Bekijk', // Common labels 'name' => 'Naam', diff --git a/lang/nl_NL/results.php b/lang/nl_NL/results.php index e1bb7b7b6..69befafa6 100644 --- a/lang/nl_NL/results.php +++ b/lang/nl_NL/results.php @@ -61,8 +61,11 @@ 'view_on_speedtest_net' => 'Bekijk op Speedtest.net', // Notifications + 'speedtest_benchmark_passed' => 'Snelheidstest benchmark geslaagd', + 'speedtest_benchmark_failed' => 'Snelheidstest benchmark mislukt', 'speedtest_started' => 'Snelheidstest gestart', 'speedtest_completed' => 'Snelheidstest voltooid', + 'speedtest_failed' => 'Speedtest mislukt', 'download_threshold_breached' => 'Drempelwaarde voor downloaden doorbroken!', 'upload_threshold_breached' => 'Upload drempelwaarde gelekt!', 'ping_threshold_breached' => 'Ping drempelwaarde doorgedrukt!', diff --git a/lang/nl_NL/settings/notifications.php b/lang/nl_NL/settings/notifications.php index 4afae4543..d36ad831d 100644 --- a/lang/nl_NL/settings/notifications.php +++ b/lang/nl_NL/settings/notifications.php @@ -7,14 +7,12 @@ // Database notifications 'database' => 'Database', 'database_description' => 'Meldingen die naar dit kanaal worden verzonden worden weergegeven onder de 🔔 icoon in de header.', - 'enable_database_notifications' => 'Database meldingen inschakelen', 'database_on_speedtest_run' => 'Notificatie bij elke snelheidstest uitgevoerd', 'database_on_threshold_failure' => 'Melding bij limiet overschrijding', 'test_database_channel' => 'Test database notificaties', // Mail notifications 'mail' => 'E-mailen', - 'enable_mail_notifications' => 'Schakel e-mailnotificaties in', 'recipients' => 'Ontvangers', 'mail_on_speedtest_run' => 'Notificatie bij elke snelheidstest uitgevoerd', 'mail_on_threshold_failure' => 'Melding bij limiet overschrijding', @@ -23,7 +21,6 @@ // Webhook 'webhook' => 'Webhook', 'webhooks' => 'Webhooks', - 'enable_webhook_notifications' => 'Webhook meldingen inschakelen', 'webhook_on_speedtest_run' => 'Notificatie bij elke snelheidstest uitgevoerd', 'webhook_on_threshold_failure' => 'Melding bij limiet overschrijding', 'test_webhook_channel' => 'Test webhook kanaal', From 4ced28108b324fcbb8df7c773d7794a3263d9a72 Mon Sep 17 00:00:00 2001 From: Alex Justesen Date: Thu, 27 Nov 2025 07:16:50 -0600 Subject: [PATCH 9/9] Release v1.10.0 (#2446) Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> --- .github/workflows/validate-openapi.yml | 2 +- composer.lock | 352 +++++++++++---------- config/speedtest.php | 4 +- package-lock.json | 200 ++++++------ public/vendor/livewire/livewire.esm.js | 270 ++++++++++++---- public/vendor/livewire/livewire.esm.js.map | 6 +- public/vendor/livewire/livewire.js | 268 ++++++++++++---- public/vendor/livewire/livewire.min.js | 15 +- public/vendor/livewire/livewire.min.js.map | 6 +- public/vendor/livewire/manifest.json | 2 +- 10 files changed, 697 insertions(+), 428 deletions(-) diff --git a/.github/workflows/validate-openapi.yml b/.github/workflows/validate-openapi.yml index 751ca222d..5465145ea 100644 --- a/.github/workflows/validate-openapi.yml +++ b/.github/workflows/validate-openapi.yml @@ -46,4 +46,4 @@ jobs: - name: ✓ OpenAPI is up to date if: success() - run: echo "✅ OpenAPI documentation matches the committed version!" \ No newline at end of file + run: echo "✅ OpenAPI documentation matches the committed version!" diff --git a/composer.lock b/composer.lock index 58af2d1e9..2cdd63f47 100644 --- a/composer.lock +++ b/composer.lock @@ -224,16 +224,16 @@ }, { "name": "brick/math", - "version": "0.14.0", + "version": "0.14.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", "shasum": "" }, "require": { @@ -272,7 +272,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.0" + "source": "https://github.com/brick/math/tree/0.14.1" }, "funding": [ { @@ -280,7 +280,7 @@ "type": "github" } ], - "time": "2025-08-29T12:40:03+00:00" + "time": "2025-11-24T14:40:29+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -353,16 +353,16 @@ }, { "name": "chillerlan/php-qrcode", - "version": "5.0.4", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/chillerlan/php-qrcode.git", - "reference": "390393e97a6e42ccae0e0d6205b8d4200f7ddc43" + "reference": "7b66282572fc14075c0507d74d9837dab25b38d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/390393e97a6e42ccae0e0d6205b8d4200f7ddc43", - "reference": "390393e97a6e42ccae0e0d6205b8d4200f7ddc43", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/7b66282572fc14075c0507d74d9837dab25b38d6", + "reference": "7b66282572fc14075c0507d74d9837dab25b38d6", "shasum": "" }, "require": { @@ -373,7 +373,7 @@ "require-dev": { "chillerlan/php-authenticator": "^4.3.1 || ^5.2.1", "ext-fileinfo": "*", - "phan/phan": "^5.5.1", + "phan/phan": "^5.5.2", "phpcompatibility/php-compatibility": "10.x-dev", "phpmd/phpmd": "^2.15", "phpunit/phpunit": "^9.6", @@ -442,7 +442,7 @@ "type": "Ko-Fi" } ], - "time": "2025-09-19T17:30:27+00:00" + "time": "2025-11-23T23:51:44+00:00" }, { "name": "chillerlan/php-settings-container", @@ -2476,16 +2476,16 @@ }, { "name": "laravel/framework", - "version": "v12.38.1", + "version": "v12.40.2", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "7f3012af6059f5f64a12930701cd8caed6cf7c17" + "reference": "1ccd99220b474500e672b373f32bd709ec38de50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/7f3012af6059f5f64a12930701cd8caed6cf7c17", - "reference": "7f3012af6059f5f64a12930701cd8caed6cf7c17", + "url": "https://api.github.com/repos/laravel/framework/zipball/1ccd99220b474500e672b373f32bd709ec38de50", + "reference": "1ccd99220b474500e672b373f32bd709ec38de50", "shasum": "" }, "require": { @@ -2597,7 +2597,7 @@ "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", "opis/json-schema": "^2.4.1", - "orchestra/testbench-core": "^10.7.0", + "orchestra/testbench-core": "^10.8.0", "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", @@ -2691,20 +2691,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-13T02:12:47+00:00" + "time": "2025-11-26T19:24:25+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.7", + "version": "v0.3.8", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc" + "reference": "096748cdfb81988f60090bbb839ce3205ace0d35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/a1891d362714bc40c8d23b0b1d7090f022ea27cc", - "reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc", + "url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35", + "reference": "096748cdfb81988f60090bbb839ce3205ace0d35", "shasum": "" }, "require": { @@ -2720,7 +2720,7 @@ "require-dev": { "illuminate/collections": "^10.0|^11.0|^12.0", "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3|^3.4", + "pestphp/pest": "^2.3|^3.4|^4.0", "phpstan/phpstan": "^1.12.28", "phpstan/phpstan-mockery": "^1.1.3" }, @@ -2748,22 +2748,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.7" + "source": "https://github.com/laravel/prompts/tree/v0.3.8" }, - "time": "2025-09-19T13:47:56+00:00" + "time": "2025-11-21T20:52:52+00:00" }, { "name": "laravel/sanctum", - "version": "v4.2.0", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677" + "reference": "f5fb373be39a246c74a060f2cf2ae2c2145b3664" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677", - "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/f5fb373be39a246c74a060f2cf2ae2c2145b3664", + "reference": "f5fb373be39a246c74a060f2cf2ae2c2145b3664", "shasum": "" }, "require": { @@ -2777,9 +2777,8 @@ }, "require-dev": { "mockery/mockery": "^1.6", - "orchestra/testbench": "^9.0|^10.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^11.3" + "orchestra/testbench": "^9.15|^10.8", + "phpstan/phpstan": "^1.10" }, "type": "library", "extra": { @@ -2814,20 +2813,20 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2025-07-09T19:45:24+00:00" + "time": "2025-11-21T13:59:03+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.6", + "version": "v2.0.7", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "038ce42edee619599a1debb7e81d7b3759492819" + "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819", - "reference": "038ce42edee619599a1debb7e81d7b3759492819", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd", + "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd", "shasum": "" }, "require": { @@ -2836,7 +2835,7 @@ "require-dev": { "illuminate/support": "^10.0|^11.0|^12.0", "nesbot/carbon": "^2.67|^3.0", - "pestphp/pest": "^2.36|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", "phpstan/phpstan": "^2.0", "symfony/var-dumper": "^6.2.0|^7.0.0" }, @@ -2875,20 +2874,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-10-09T13:42:30+00:00" + "time": "2025-11-21T20:52:36+00:00" }, { "name": "league/commonmark", - "version": "2.7.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", - "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", "shasum": "" }, "require": { @@ -2925,7 +2924,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.8-dev" + "dev-main": "2.9-dev" } }, "autoload": { @@ -2982,7 +2981,7 @@ "type": "tidelift" } ], - "time": "2025-07-20T12:47:49+00:00" + "time": "2025-11-26T21:48:24+00:00" }, { "name": "league/config", @@ -3347,33 +3346,38 @@ }, { "name": "league/uri", - "version": "7.5.1", + "version": "7.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "81fb5145d2644324614cc532b28efd0215bda430" + "reference": "f625804987a0a9112d954f9209d91fec52182344" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", - "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", + "reference": "f625804987a0a9112d954f9209d91fec52182344", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.5", - "php": "^8.1" + "league/uri-interfaces": "^7.6", + "php": "^8.1", + "psr/http-factory": "^1" }, "conflict": { "league/uri-schemes": "^1.0" }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", "league/uri-components": "Needed to easily manipulate URI objects components", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3401,6 +3405,7 @@ "description": "URI manipulation library", "homepage": "https://uri.thephpleague.com", "keywords": [ + "URN", "data-uri", "file-uri", "ftp", @@ -3413,9 +3418,11 @@ "psr-7", "query-string", "querystring", + "rfc2141", "rfc3986", "rfc3987", "rfc6570", + "rfc8141", "uri", "uri-template", "url", @@ -3425,7 +3432,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.5.1" + "source": "https://github.com/thephpleague/uri/tree/7.6.0" }, "funding": [ { @@ -3433,34 +3440,37 @@ "type": "github" } ], - "time": "2024-12-08T08:40:02+00:00" + "time": "2025-11-18T12:17:23+00:00" }, { "name": "league/uri-components", - "version": "7.5.1", + "version": "7.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-components.git", - "reference": "4aabf0e2f2f9421ffcacab35be33e4fb5e63c44f" + "reference": "ffa1215dbee72ee4b7bc08d983d25293812456c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/4aabf0e2f2f9421ffcacab35be33e4fb5e63c44f", - "reference": "4aabf0e2f2f9421ffcacab35be33e4fb5e63c44f", + "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/ffa1215dbee72ee4b7bc08d983d25293812456c2", + "reference": "ffa1215dbee72ee4b7bc08d983d25293812456c2", "shasum": "" }, "require": { - "league/uri": "^7.5", + "league/uri": "^7.6", "php": "^8.1" }, "suggest": { + "bakame/aide-uri": "A polyfill for PHP8.1 until PHP8.4 to add support to PHP Native URI parser", "ext-bcmath": "to improve IPV4 host parsing", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "ext-mbstring": "to use the sorting algorithm of URLSearchParams", "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3507,7 +3517,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-components/tree/7.5.1" + "source": "https://github.com/thephpleague/uri-components/tree/7.6.0" }, "funding": [ { @@ -3515,26 +3525,25 @@ "type": "github" } ], - "time": "2024-12-08T08:40:02+00:00" + "time": "2025-11-18T12:17:23+00:00" }, { "name": "league/uri-interfaces", - "version": "7.5.0", + "version": "7.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", "shasum": "" }, "require": { "ext-filter": "*", "php": "^8.1", - "psr/http-factory": "^1", "psr/http-message": "^1.1 || ^2.0" }, "suggest": { @@ -3542,6 +3551,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3566,7 +3576,7 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common interfaces and classes for URI representation and interaction", + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", "homepage": "https://uri.thephpleague.com", "keywords": [ "data-uri", @@ -3591,7 +3601,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.6.0" }, "funding": [ { @@ -3599,20 +3609,20 @@ "type": "github" } ], - "time": "2024-12-08T08:18:47+00:00" + "time": "2025-11-18T12:17:23+00:00" }, { "name": "livewire/livewire", - "version": "v3.6.4", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "ef04be759da41b14d2d129e670533180a44987dc" + "reference": "f5f9efe6d5a7059116bd695a89d95ceedf33f3cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/ef04be759da41b14d2d129e670533180a44987dc", - "reference": "ef04be759da41b14d2d129e670533180a44987dc", + "url": "https://api.github.com/repos/livewire/livewire/zipball/f5f9efe6d5a7059116bd695a89d95ceedf33f3cb", + "reference": "f5f9efe6d5a7059116bd695a89d95ceedf33f3cb", "shasum": "" }, "require": { @@ -3667,7 +3677,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.6.4" + "source": "https://github.com/livewire/livewire/tree/v3.7.0" }, "funding": [ { @@ -3675,7 +3685,7 @@ "type": "github" } ], - "time": "2025-07-17T05:12:15+00:00" + "time": "2025-11-12T17:58:16+00:00" }, { "name": "lorisleiva/laravel-actions", @@ -4380,16 +4390,16 @@ }, { "name": "nette/utils", - "version": "v4.0.8", + "version": "v4.0.9", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" + "reference": "505a30ad386daa5211f08a318e47015b501cad30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", - "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "url": "https://api.github.com/repos/nette/utils/zipball/505a30ad386daa5211f08a318e47015b501cad30", + "reference": "505a30ad386daa5211f08a318e47015b501cad30", "shasum": "" }, "require": { @@ -4463,9 +4473,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.8" + "source": "https://github.com/nette/utils/tree/v4.0.9" }, - "time": "2025-08-06T21:43:34+00:00" + "time": "2025-10-31T00:45:47+00:00" }, { "name": "nikic/php-parser", @@ -4527,31 +4537,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.3.2", + "version": "v2.3.3", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "eb61920a53057a7debd718a5b89c2178032b52c0" + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/eb61920a53057a7debd718a5b89c2178032b52c0", - "reference": "eb61920a53057a7debd718a5b89c2178032b52c0", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.3.4" + "symfony/console": "^7.3.6" }, "require-dev": { "illuminate/console": "^11.46.1", "laravel/pint": "^1.25.1", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.4", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.3.4", + "symfony/var-dumper": "^7.3.5", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -4594,7 +4604,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.2" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" }, "funding": [ { @@ -4610,7 +4620,7 @@ "type": "github" } ], - "time": "2025-10-18T11:10:27+00:00" + "time": "2025-11-20T02:34:59+00:00" }, { "name": "openspout/openspout", @@ -5155,16 +5165,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.10.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", - "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { @@ -5207,9 +5217,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "time": "2024-11-09T15:12:26+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { "name": "phpoption/phpoption", @@ -9813,16 +9823,16 @@ }, { "name": "zircote/swagger-php", - "version": "5.7.0", + "version": "5.7.3", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "39fcd46e79c2f3cfbf56cf5a92a86108c8eed401" + "reference": "4d0d3086d7c876626167d198cec285e98d3629dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/39fcd46e79c2f3cfbf56cf5a92a86108c8eed401", - "reference": "39fcd46e79c2f3cfbf56cf5a92a86108c8eed401", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/4d0d3086d7c876626167d198cec285e98d3629dc", + "reference": "4d0d3086d7c876626167d198cec285e98d3629dc", "shasum": "" }, "require": { @@ -9895,9 +9905,9 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/5.7.0" + "source": "https://github.com/zircote/swagger-php/tree/5.7.3" }, - "time": "2025-11-11T03:41:35+00:00" + "time": "2025-11-17T20:56:13+00:00" } ], "packages-dev": [ @@ -10302,25 +10312,25 @@ }, { "name": "laravel/boost", - "version": "v1.8.0", + "version": "v1.8.3", "source": { "type": "git", "url": "https://github.com/laravel/boost.git", - "reference": "3475be16be7552b11c57ce18a0c5e204d696da50" + "reference": "26572e858e67334952779c0110ca4c378a44d28d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/boost/zipball/3475be16be7552b11c57ce18a0c5e204d696da50", - "reference": "3475be16be7552b11c57ce18a0c5e204d696da50", + "url": "https://api.github.com/repos/laravel/boost/zipball/26572e858e67334952779c0110ca4c378a44d28d", + "reference": "26572e858e67334952779c0110ca4c378a44d28d", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^7.10", + "guzzlehttp/guzzle": "^7.9", "illuminate/console": "^10.49.0|^11.45.3|^12.28.1", "illuminate/contracts": "^10.49.0|^11.45.3|^12.28.1", "illuminate/routing": "^10.49.0|^11.45.3|^12.28.1", "illuminate/support": "^10.49.0|^11.45.3|^12.28.1", - "laravel/mcp": "^0.3.2", + "laravel/mcp": "^0.3.4", "laravel/prompts": "0.1.25|^0.3.6", "laravel/roster": "^0.2.9", "php": "^8.1" @@ -10364,20 +10374,20 @@ "issues": "https://github.com/laravel/boost/issues", "source": "https://github.com/laravel/boost" }, - "time": "2025-11-11T14:15:11+00:00" + "time": "2025-11-26T14:12:52+00:00" }, { "name": "laravel/mcp", - "version": "v0.3.3", + "version": "v0.3.4", "source": { "type": "git", "url": "https://github.com/laravel/mcp.git", - "reference": "feb475f819809e7db0a46e9f2cbcee6d77af2a14" + "reference": "0b86fb613a0df971cec89271c674a677c2cb4f77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/mcp/zipball/feb475f819809e7db0a46e9f2cbcee6d77af2a14", - "reference": "feb475f819809e7db0a46e9f2cbcee6d77af2a14", + "url": "https://api.github.com/repos/laravel/mcp/zipball/0b86fb613a0df971cec89271c674a677c2cb4f77", + "reference": "0b86fb613a0df971cec89271c674a677c2cb4f77", "shasum": "" }, "require": { @@ -10437,20 +10447,20 @@ "issues": "https://github.com/laravel/mcp/issues", "source": "https://github.com/laravel/mcp" }, - "time": "2025-11-11T22:50:25+00:00" + "time": "2025-11-18T14:41:05+00:00" }, { "name": "laravel/pail", - "version": "v1.2.3", + "version": "v1.2.4", "source": { "type": "git", "url": "https://github.com/laravel/pail.git", - "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a" + "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pail/zipball/8cc3d575c1f0e57eeb923f366a37528c50d2385a", - "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a", + "url": "https://api.github.com/repos/laravel/pail/zipball/49f92285ff5d6fc09816e976a004f8dec6a0ea30", + "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30", "shasum": "" }, "require": { @@ -10467,9 +10477,9 @@ "require-dev": { "laravel/framework": "^10.24|^11.0|^12.0", "laravel/pint": "^1.13", - "orchestra/testbench-core": "^8.13|^9.0|^10.0", - "pestphp/pest": "^2.20|^3.0", - "pestphp/pest-plugin-type-coverage": "^2.3|^3.0", + "orchestra/testbench-core": "^8.13|^9.17|^10.8", + "pestphp/pest": "^2.20|^3.0|^4.0", + "pestphp/pest-plugin-type-coverage": "^2.3|^3.0|^4.0", "phpstan/phpstan": "^1.12.27", "symfony/var-dumper": "^6.3|^7.0" }, @@ -10516,20 +10526,20 @@ "issues": "https://github.com/laravel/pail/issues", "source": "https://github.com/laravel/pail" }, - "time": "2025-06-05T13:55:57+00:00" + "time": "2025-11-20T16:29:35+00:00" }, { "name": "laravel/pint", - "version": "v1.25.1", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9" + "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/5016e263f95d97670d71b9a987bd8996ade6d8d9", - "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9", + "url": "https://api.github.com/repos/laravel/pint/zipball/69dcca060ecb15e4b564af63d1f642c81a241d6f", + "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f", "shasum": "" }, "require": { @@ -10540,13 +10550,13 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.87.2", - "illuminate/view": "^11.46.0", - "larastan/larastan": "^3.7.1", - "laravel-zero/framework": "^11.45.0", + "friendsofphp/php-cs-fixer": "^3.90.0", + "illuminate/view": "^12.40.1", + "larastan/larastan": "^3.8.0", + "laravel-zero/framework": "^12.0.4", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3.1", - "pestphp/pest": "^2.36.0" + "nunomaduro/termwind": "^2.3.3", + "pestphp/pest": "^3.8.4" }, "bin": [ "builds/pint" @@ -10572,6 +10582,7 @@ "description": "An opinionated code formatter for PHP.", "homepage": "https://laravel.com", "keywords": [ + "dev", "format", "formatter", "lint", @@ -10582,7 +10593,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-09-19T02:57:12+00:00" + "time": "2025-11-25T21:15:52+00:00" }, { "name": "laravel/roster", @@ -10647,16 +10658,16 @@ }, { "name": "laravel/sail", - "version": "v1.48.0", + "version": "v1.48.1", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "1bf3b8870b72a258a3b6b5119435835ece522e8a" + "reference": "ef122b223f5fca5e5d88bda5127c846710886329" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/1bf3b8870b72a258a3b6b5119435835ece522e8a", - "reference": "1bf3b8870b72a258a3b6b5119435835ece522e8a", + "url": "https://api.github.com/repos/laravel/sail/zipball/ef122b223f5fca5e5d88bda5127c846710886329", + "reference": "ef122b223f5fca5e5d88bda5127c846710886329", "shasum": "" }, "require": { @@ -10706,20 +10717,20 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2025-11-09T14:46:21+00:00" + "time": "2025-11-17T22:05:34+00:00" }, { "name": "laravel/telescope", - "version": "v5.15.0", + "version": "v5.15.1", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "cbdd61b025dddeccaffefc3b54d327c4e0a410b6" + "reference": "45e38e057343a94c570c5daad3273e9e29819738" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/cbdd61b025dddeccaffefc3b54d327c4e0a410b6", - "reference": "cbdd61b025dddeccaffefc3b54d327c4e0a410b6", + "url": "https://api.github.com/repos/laravel/telescope/zipball/45e38e057343a94c570c5daad3273e9e29819738", + "reference": "45e38e057343a94c570c5daad3273e9e29819738", "shasum": "" }, "require": { @@ -10732,10 +10743,9 @@ "require-dev": { "ext-gd": "*", "guzzlehttp/guzzle": "^6.0|^7.0", - "laravel/octane": "^1.4|^2.0|dev-develop", - "orchestra/testbench": "^6.40|^7.37|^8.17|^9.0|^10.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.0|^10.5|^11.5" + "laravel/octane": "^1.4|^2.0", + "orchestra/testbench": "^6.47.1|^7.55|^8.36|^9.15|^10.8", + "phpstan/phpstan": "^1.10" }, "type": "library", "extra": { @@ -10773,22 +10783,22 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v5.15.0" + "source": "https://github.com/laravel/telescope/tree/v5.15.1" }, - "time": "2025-10-23T15:19:35+00:00" + "time": "2025-11-25T14:45:17+00:00" }, { "name": "laravel/tinker", - "version": "v2.10.1", + "version": "v2.10.2", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3" + "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/22177cc71807d38f2810c6204d8f7183d88a57d3", - "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3", + "url": "https://api.github.com/repos/laravel/tinker/zipball/3bcb5f62d6f837e0f093a601e26badafb127bd4c", + "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c", "shasum": "" }, "require": { @@ -10839,9 +10849,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.10.1" + "source": "https://github.com/laravel/tinker/tree/v2.10.2" }, - "time": "2025-01-27T14:24:01+00:00" + "time": "2025-11-20T16:29:12+00:00" }, { "name": "mockery/mockery", @@ -10988,16 +10998,16 @@ }, { "name": "nunomaduro/collision", - "version": "v8.8.2", + "version": "v8.8.3", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb" + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", - "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4", "shasum": "" }, "require": { @@ -11019,7 +11029,7 @@ "laravel/sanctum": "^4.1.1", "laravel/tinker": "^2.10.1", "orchestra/testbench-core": "^9.12.0 || ^10.4", - "pestphp/pest": "^3.8.2", + "pestphp/pest": "^3.8.2 || ^4.0.0", "sebastian/environment": "^7.2.1 || ^8.0" }, "type": "library", @@ -11083,7 +11093,7 @@ "type": "patreon" } ], - "time": "2025-06-25T02:12:12+00:00" + "time": "2025-11-20T02:55:25+00:00" }, { "name": "pestphp/pest", @@ -11603,16 +11613,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.3", + "version": "5.6.4", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" + "reference": "90a04bcbf03784066f16038e87e23a0a83cee3c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", - "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/90a04bcbf03784066f16038e87e23a0a83cee3c2", + "reference": "90a04bcbf03784066f16038e87e23a0a83cee3c2", "shasum": "" }, "require": { @@ -11661,9 +11671,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.4" }, - "time": "2025-08-01T19:43:32+00:00" + "time": "2025-11-17T21:13:10+00:00" }, { "name": "phpunit/php-code-coverage", @@ -13668,16 +13678,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/d74205c497bfbca49f34d4bc4c19c17e22db4ebb", - "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -13706,7 +13716,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.0" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -13714,7 +13724,7 @@ "type": "github" } ], - "time": "2025-11-13T13:44:09+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "webmozart/assert", diff --git a/config/speedtest.php b/config/speedtest.php index 230afc091..0ef7468d0 100644 --- a/config/speedtest.php +++ b/config/speedtest.php @@ -6,9 +6,9 @@ /** * General settings. */ - 'build_date' => Carbon::parse('2025-11-24'), + 'build_date' => Carbon::parse('2025-11-27'), - 'build_version' => 'v1.9.0', + 'build_version' => 'v1.10.0', 'content_width' => env('CONTENT_WIDTH', '7xl'), diff --git a/package-lock.json b/package-lock.json index e4153991b..994ca40f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -506,9 +506,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz", - "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", "cpu": [ "arm" ], @@ -520,9 +520,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz", - "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", "cpu": [ "arm64" ], @@ -534,9 +534,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz", - "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", "cpu": [ "arm64" ], @@ -548,9 +548,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz", - "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", "cpu": [ "x64" ], @@ -562,9 +562,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz", - "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", "cpu": [ "arm64" ], @@ -576,9 +576,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz", - "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", "cpu": [ "x64" ], @@ -590,9 +590,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz", - "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", "cpu": [ "arm" ], @@ -604,9 +604,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz", - "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", "cpu": [ "arm" ], @@ -618,9 +618,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz", - "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", "cpu": [ "arm64" ], @@ -632,9 +632,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz", - "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", "cpu": [ "arm64" ], @@ -646,9 +646,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz", - "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", "cpu": [ "loong64" ], @@ -660,9 +660,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz", - "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", "cpu": [ "ppc64" ], @@ -674,9 +674,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz", - "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", "cpu": [ "riscv64" ], @@ -688,9 +688,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz", - "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", "cpu": [ "riscv64" ], @@ -702,9 +702,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz", - "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", "cpu": [ "s390x" ], @@ -716,9 +716,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz", - "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", "cpu": [ "x64" ], @@ -730,9 +730,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz", - "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz", - "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", "cpu": [ "arm64" ], @@ -758,9 +758,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz", - "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", "cpu": [ "arm64" ], @@ -772,9 +772,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz", - "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", "cpu": [ "ia32" ], @@ -786,9 +786,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz", - "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", "cpu": [ "x64" ], @@ -800,9 +800,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz", - "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", "cpu": [ "x64" ], @@ -1144,9 +1144,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.8.28", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz", - "integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==", + "version": "2.8.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", + "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1189,9 +1189,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001754", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", - "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "dev": true, "funding": [ { @@ -1233,9 +1233,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.250", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz", - "integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==", + "version": "1.5.262", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", + "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", "dev": true, "license": "ISC" }, @@ -1769,9 +1769,9 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "4.53.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", - "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", "dependencies": { @@ -1785,28 +1785,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.2", - "@rollup/rollup-android-arm64": "4.53.2", - "@rollup/rollup-darwin-arm64": "4.53.2", - "@rollup/rollup-darwin-x64": "4.53.2", - "@rollup/rollup-freebsd-arm64": "4.53.2", - "@rollup/rollup-freebsd-x64": "4.53.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", - "@rollup/rollup-linux-arm-musleabihf": "4.53.2", - "@rollup/rollup-linux-arm64-gnu": "4.53.2", - "@rollup/rollup-linux-arm64-musl": "4.53.2", - "@rollup/rollup-linux-loong64-gnu": "4.53.2", - "@rollup/rollup-linux-ppc64-gnu": "4.53.2", - "@rollup/rollup-linux-riscv64-gnu": "4.53.2", - "@rollup/rollup-linux-riscv64-musl": "4.53.2", - "@rollup/rollup-linux-s390x-gnu": "4.53.2", - "@rollup/rollup-linux-x64-gnu": "4.53.2", - "@rollup/rollup-linux-x64-musl": "4.53.2", - "@rollup/rollup-openharmony-arm64": "4.53.2", - "@rollup/rollup-win32-arm64-msvc": "4.53.2", - "@rollup/rollup-win32-ia32-msvc": "4.53.2", - "@rollup/rollup-win32-x64-gnu": "4.53.2", - "@rollup/rollup-win32-x64-msvc": "4.53.2", + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" } }, diff --git a/public/vendor/livewire/livewire.esm.js b/public/vendor/livewire/livewire.esm.js index e45b0459a..69e2f5711 100644 --- a/public/vendor/livewire/livewire.esm.js +++ b/public/vendor/livewire/livewire.esm.js @@ -5125,9 +5125,9 @@ var require_module_cjs5 = __commonJS({ } }); -// node_modules/@alpinejs/resize/dist/module.cjs.js +// ../alpine/packages/resize/dist/module.cjs.js var require_module_cjs6 = __commonJS({ - "node_modules/@alpinejs/resize/dist/module.cjs.js"(exports, module) { + "../alpine/packages/resize/dist/module.cjs.js"(exports, module) { var __defProp2 = Object.defineProperty; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; var __getOwnPropNames2 = Object.getOwnPropertyNames; @@ -7498,7 +7498,8 @@ function handleFileUpload(el, property, component, cleanup) { return; start2(); if (e.target.multiple) { - manager.uploadMultiple(property, e.target.files, finish, error2, progress, cancel); + let append = ["ui-file-upload"].includes(e.target.tagName.toLowerCase()); + manager.uploadMultiple(property, e.target.files, finish, error2, progress, cancel, append); } else { manager.upload(property, e.target.files[0], finish, error2, progress, cancel); } @@ -7550,17 +7551,19 @@ var UploadManager = class { finishCallback, errorCallback, progressCallback, - cancelledCallback + cancelledCallback, + append: false }); } - uploadMultiple(name, files, finishCallback, errorCallback, progressCallback, cancelledCallback) { + uploadMultiple(name, files, finishCallback, errorCallback, progressCallback, cancelledCallback, append = false) { this.setUpload(name, { files: Array.from(files), multiple: true, finishCallback, errorCallback, progressCallback, - cancelledCallback + cancelledCallback, + append }); } removeUpload(name, tmpFilename, finishCallback) { @@ -7613,7 +7616,7 @@ var UploadManager = class { request.addEventListener("load", () => { if ((request.status + "")[0] === "2") { let paths = retrievePaths(request.response && JSON.parse(request.response)); - this.component.$wire.call("_finishUpload", name, paths, this.uploadBag.first(name).multiple); + this.component.$wire.call("_finishUpload", name, paths, this.uploadBag.first(name).multiple, this.uploadBag.first(name).append); return; } let errors = null; @@ -7710,9 +7713,9 @@ function uploadMultiple(component, name, files, finishCallback = () => { }, errorCallback = () => { }, progressCallback = () => { }, cancelledCallback = () => { -}) { +}, append = false) { let uploadManager = getUploadManager(component); - uploadManager.uploadMultiple(name, files, finishCallback, errorCallback, progressCallback, cancelledCallback); + uploadManager.uploadMultiple(name, files, finishCallback, errorCallback, progressCallback, cancelledCallback, append); } function removeUpload(component, name, tmpFilename, finishCallback = () => { }, errorCallback = () => { @@ -7828,14 +7831,13 @@ function showHtmlModal(html) { if (typeof modal != "undefined" && modal != null) { modal.innerHTML = ""; } else { - modal = document.createElement("div"); + modal = document.createElement("dialog"); modal.id = "livewire-error"; - modal.style.position = "fixed"; - modal.style.width = "100vw"; - modal.style.height = "100vh"; - modal.style.padding = "50px"; - modal.style.backgroundColor = "rgba(0, 0, 0, .6)"; - modal.style.zIndex = 2e5; + modal.style.margin = "50px"; + modal.style.width = "calc(100% - 100px)"; + modal.style.height = "calc(100% - 100px)"; + modal.style.borderRadius = "5px"; + modal.style.padding = "0px"; } let iframe = document.createElement("iframe"); iframe.style.backgroundColor = "#17161A"; @@ -7849,14 +7851,15 @@ function showHtmlModal(html) { iframe.contentWindow.document.write(page.outerHTML); iframe.contentWindow.document.close(); modal.addEventListener("click", () => hideHtmlModal(modal)); - modal.setAttribute("tabindex", 0); - modal.addEventListener("keydown", (e) => { - if (e.key === "Escape") - hideHtmlModal(modal); - }); + modal.addEventListener("close", () => cleanupModal(modal)); + modal.showModal(); modal.focus(); + modal.blur(); } function hideHtmlModal(modal) { + modal.close(); +} +function cleanupModal(modal) { modal.outerHTML = ""; document.body.style.overflow = "visible"; } @@ -7935,11 +7938,18 @@ var Commit = class { prepare() { trigger("commit.prepare", { component: this.component }); } + getEncodedSnapshotWithLatestChildrenMergedIn() { + let { snapshotEncoded, children, snapshot } = this.component; + let childIds = children.map((child) => child.id); + let filteredChildren = Object.fromEntries(Object.entries(snapshot.memo.children).filter(([key, value]) => childIds.includes(value[1]))); + return snapshotEncoded.replace(/"children":\{[^}]*\}/, `"children":${JSON.stringify(filteredChildren)}`); + } toRequestPayload() { let propertiesDiff = diff(this.component.canonical, this.component.ephemeral); let updates = this.component.mergeQueuedUpdates(propertiesDiff); + let snapshotEncoded = this.getEncodedSnapshotWithLatestChildrenMergedIn(); let payload = { - snapshot: this.component.snapshotEncoded, + snapshot: snapshotEncoded, updates, calls: this.calls.map((i) => ({ path: i.path, @@ -8028,7 +8038,6 @@ var CommitBus = class { createAndSendNewPool() { trigger("commit.pooling", { commits: this.commits }); let pools = this.corraleCommitsIntoPools(); - this.commits.clear(); trigger("commit.pooled", { pools }); pools.forEach((pool) => { if (pool.empty()) @@ -8036,13 +8045,17 @@ var CommitBus = class { this.pools.add(pool); pool.send().then(() => { this.pools.delete(pool); - this.sendAnyQueuedCommits(); + queueMicrotask(() => { + this.sendAnyQueuedCommits(); + }); }); }); } corraleCommitsIntoPools() { let pools = /* @__PURE__ */ new Set(); for (let [idx, commit] of this.commits.entries()) { + if (this.findPoolWithComponent(commit.component)) + continue; let hasFoundPool = false; pools.forEach((pool) => { if (pool.shouldHoldCommit(commit)) { @@ -8055,6 +8068,7 @@ var CommitBus = class { newPool.add(commit); pools.add(newPool); } + this.commits.delete(commit); } return pools; } @@ -8430,7 +8444,7 @@ var Component = class { get children() { let meta = this.snapshot.memo; let childIds = Object.values(meta.children).map((i) => i[1]); - return childIds.map((id) => findComponent(id)); + return childIds.filter((id) => hasComponent(id)).map((id) => findComponent(id)); } get parent() { return closestComponent(this.el.parentElement); @@ -8488,6 +8502,9 @@ function destroyComponent(id) { component.cleanup(); delete components[id]; } +function hasComponent(id) { + return !!components[id]; +} function findComponent(id) { let component = components[id]; if (!component) @@ -8555,6 +8572,9 @@ function on2(eventName, callback) { }; } function dispatchEvent(target, name, params, bubbles = true) { + if (typeof params === "string") { + params = [params]; + } let e = new CustomEvent(name, { bubbles, detail: params }); e.__livewire = { name, params, receivedBy: [] }; target.dispatchEvent(e); @@ -8632,28 +8652,99 @@ var Directive = class { this.expression = this.el.getAttribute(this.rawName); } get method() { - const { method } = this.parseOutMethodAndParams(this.expression); - return method; + const methods = this.parseOutMethodsAndParams(this.expression); + return methods[0].method; + } + get methods() { + return this.parseOutMethodsAndParams(this.expression); } get params() { - const { params } = this.parseOutMethodAndParams(this.expression); - return params; - } - parseOutMethodAndParams(rawMethod) { - let method = rawMethod; - let params = []; - const methodAndParamString = method.match(/(.*?)\((.*)\)/s); - if (methodAndParamString) { - method = methodAndParamString[1]; - let func = new Function("$event", `return (function () { - for (var l=arguments.length, p=new Array(l), k=0; k 0) { + let argumentsToArray = function() { + for (var l = arguments.length, p = new Array(l), k = 0; k < l; k++) { + p[k] = arguments[k]; + } + return [].concat(p); + }; + try { + params = Alpine.evaluate(document, "argumentsToArray(" + paramString + ")", { + scope: { argumentsToArray } + }); + } catch (error2) { + console.warn("Failed to parse parameters:", paramString, error2); + params = []; + } + } + methods.push({ method, params }); + } + return methods; + } + splitAndParseMethods(methodExpression) { + let methods = []; + let current = ""; + let parenCount = 0; + let inString = false; + let stringChar = null; + let trimmedExpression = methodExpression.trim(); + for (let i = 0; i < trimmedExpression.length; i++) { + let char = trimmedExpression[i]; + if (!inString) { + if (char === '"' || char === "'") { + inString = true; + stringChar = char; + current += char; + } else if (char === "(") { + parenCount++; + current += char; + } else if (char === ")") { + parenCount--; + current += char; + } else if (char === "," && parenCount === 0) { + methods.push(this.parseMethodCall(current.trim())); + current = ""; + } else { + current += char; + } + } else { + if (char === stringChar && trimmedExpression[i - 1] !== "\\") { + inString = false; + stringChar = null; + } + current += char; + } + } + if (current.trim().length > 0) { + methods.push(this.parseMethodCall(current.trim())); + } + return methods; + } + parseMethodCall(methodString) { + let methodMatch = methodString.match(/^([^(]+)\(/); + if (!methodMatch) { + return { + method: methodString.trim(), + paramString: "" + }; } - return { method, params }; + let method = methodMatch[1].trim(); + let paramStart = methodMatch[0].length - 1; + let lastParenIndex = methodString.lastIndexOf(")"); + if (lastParenIndex === -1) { + throw new Error(`Missing closing parenthesis for method "${method}"`); + } + let paramString = methodString.slice(paramStart + 1, lastParenIndex).trim(); + return { + method, + paramString + }; } }; @@ -8852,12 +8943,12 @@ function performFetch(uri, callback) { // js/plugins/navigate/prefetch.js var prefetches = {}; +var cacheDuration = 3e4; function prefetchHtml(destination, callback) { let uri = getUriStringFromUrlObject(destination); if (prefetches[uri]) return; - prefetches[uri] = { finished: false, html: null, whenFinished: () => { - } }; + prefetches[uri] = { finished: false, html: null, whenFinished: () => setTimeout(() => delete prefetches[uri], cacheDuration) }; performFetch(uri, (html, routedUri) => { callback(html, routedUri); }); @@ -9137,6 +9228,7 @@ function isPopoverSupported() { var oldBodyScriptTagHashes = []; var attributesExemptFromScriptTagHashing = [ "data-csrf", + "nonce", "aria-hidden" ]; function swapCurrentPageWithNewHtml(html, andThen) { @@ -9288,7 +9380,8 @@ var showProgressBar = true; var restoreScroll = true; var autofocus = false; function navigate_default(Alpine23) { - Alpine23.navigate = (url) => { + Alpine23.navigate = (url, options = {}) => { + let { preserveScroll = false } = options; let destination = createUrlObjectFromString(url); let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", { url: destination, @@ -9297,7 +9390,7 @@ function navigate_default(Alpine23) { }); if (prevented) return; - navigateTo(destination); + navigateTo(destination, { preserveScroll }); }; Alpine23.navigate.disableProgressBar = () => { showProgressBar = false; @@ -9305,6 +9398,7 @@ function navigate_default(Alpine23) { Alpine23.addInitSelector(() => `[${Alpine23.prefixed("navigate")}]`); Alpine23.directive("navigate", (el, { modifiers }) => { let shouldPrefetchOnHover = modifiers.includes("hover"); + let preserveScroll = modifiers.includes("preserve-scroll"); shouldPrefetchOnHover && whenThisLinkIsHoveredFor(el, 60, () => { let destination = extractDestinationFromLink(el); if (!destination) @@ -9328,16 +9422,15 @@ function navigate_default(Alpine23) { }); if (prevented) return; - navigateTo(destination); + navigateTo(destination, { preserveScroll }); }); }); }); - function navigateTo(destination, shouldPushToHistoryState = true) { + function navigateTo(destination, { preserveScroll = false, shouldPushToHistoryState = true }) { showProgressBar && showAndStartProgressBar(); fetchHtmlOrUsePrefetchedHtml(destination, (html, finalDestination) => { fireEventForOtherLibrariesToHookInto("alpine:navigating"); restoreScroll && storeScrollInformationInHtmlBeforeNavigatingAway(); - showProgressBar && finishAndHideProgressBar(); cleanupAlpineElementsOnThePageThatArentInsideAPersistedElement(); updateCurrentPageHtmlInHistoryStateForLaterBackButtonClicks(); preventAlpineFromPickingUpDomChanges(Alpine23, (andAfterAllThis) => { @@ -9356,7 +9449,7 @@ function navigate_default(Alpine23) { unPackPersistedTeleports(persistedEl); unPackPersistedPopovers(persistedEl); }); - restoreScrollPositionOrScrollToTop(); + !preserveScroll && restoreScrollPositionOrScrollToTop(); afterNewScriptsAreDoneLoading(() => { andAfterAllThis(() => { setTimeout(() => { @@ -9364,6 +9457,7 @@ function navigate_default(Alpine23) { }); nowInitializeAlpineOnTheNewPage(Alpine23); fireEventForOtherLibrariesToHookInto("alpine:navigated"); + showProgressBar && finishAndHideProgressBar(); }); }); }); @@ -9380,8 +9474,7 @@ function navigate_default(Alpine23) { }); if (prevented) return; - let shouldPushToHistoryState = false; - navigateTo(destination, shouldPushToHistoryState); + navigateTo(destination, { shouldPushToHistoryState: false }); }); }, (html, url, currentPageUrl, currentPageKey) => { let destination = createUrlObjectFromString(url); @@ -9715,7 +9808,7 @@ function start() { import_alpinejs5.default.interceptInit(import_alpinejs5.default.skipDuringClone((el) => { if (!Array.from(el.attributes).some((attribute) => matchesForLivewireDirective(attribute.name))) return; - if (el.hasAttribute("wire:id")) { + if (el.hasAttribute("wire:id") && !el.__livewire && !hasComponent(el.getAttribute("wire:id"))) { let component2 = initComponent(el); import_alpinejs5.default.onAttributeRemoved(el, "wire:id", () => { destroyComponent(component2.id); @@ -9736,6 +9829,15 @@ function start() { } }); }); } + }, (el) => { + if (!Array.from(el.attributes).some((attribute) => matchesForLivewireDirective(attribute.name))) + return; + let directives = Array.from(el.getAttributeNames()).filter((name) => matchesForLivewireDirective(name)).map((name) => extractDirective(el, name)); + directives.forEach((directive2) => { + trigger("directive.global.init", { el, directive: directive2, cleanup: (callback) => { + import_alpinejs5.default.onAttributeRemoved(el, directive2.raw, callback); + } }); + }); })); import_alpinejs5.default.start(); setTimeout(() => window.Livewire.initialRenderIsFinished = true); @@ -9908,6 +10010,8 @@ on("effect", ({ component, effects }) => { var import_alpinejs8 = __toESM(require_module_cjs()); function morph2(component, el, html) { let wrapperTag = el.parentElement ? el.parentElement.tagName.toLowerCase() : "div"; + let customElement = customElements.get(wrapperTag); + wrapperTag = customElement ? customElement.name : wrapperTag; let wrapper = document.createElement(wrapperTag); wrapper.innerHTML = html; let parentComponent; @@ -9917,8 +10021,25 @@ function morph2(component, el, html) { } parentComponent && (wrapper.__livewire = parentComponent); let to = wrapper.firstElementChild; + to.setAttribute("wire:snapshot", component.snapshotEncoded); + let effects = { ...component.effects }; + delete effects.html; + to.setAttribute("wire:effects", JSON.stringify(effects)); to.__livewire = component; trigger("morph", { el, toEl: to, component }); + let existingComponentsMap = {}; + el.querySelectorAll("[wire\\:id]").forEach((component2) => { + existingComponentsMap[component2.getAttribute("wire:id")] = component2; + }); + to.querySelectorAll("[wire\\:id]").forEach((child) => { + if (child.hasAttribute("wire:snapshot")) + return; + let wireId = child.getAttribute("wire:id"); + let existingComponent = existingComponentsMap[wireId]; + if (existingComponent) { + child.replaceWith(existingComponent.cloneNode(true)); + } + }); import_alpinejs8.default.morph(el, to, { updating: (el2, toEl, childrenOnly, skip, skipChildren) => { if (isntElement(el2)) @@ -9995,7 +10116,13 @@ on("effect", ({ component, effects }) => { // js/features/supportDispatches.js on("effect", ({ component, effects }) => { - dispatchEvents(component, effects.dispatches || []); + queueMicrotask(() => { + queueMicrotask(() => { + queueMicrotask(() => { + dispatchEvents(component, effects.dispatches || []); + }); + }); + }); }); function dispatchEvents(component, dispatches) { dispatches.forEach(({ name, params = {}, self: self2 = false, to }) => { @@ -10451,11 +10578,20 @@ on("directive.init", ({ el, directive: directive2, cleanup, component }) => { var import_alpinejs13 = __toESM(require_module_cjs()); import_alpinejs13.default.addInitSelector(() => `[wire\\:navigate]`); import_alpinejs13.default.addInitSelector(() => `[wire\\:navigate\\.hover]`); +import_alpinejs13.default.addInitSelector(() => `[wire\\:navigate\\.preserve-scroll]`); +import_alpinejs13.default.addInitSelector(() => `[wire\\:navigate\\.preserve-scroll\\.hover]`); +import_alpinejs13.default.addInitSelector(() => `[wire\\:navigate\\.hover\\.preserve-scroll]`); import_alpinejs13.default.interceptInit(import_alpinejs13.default.skipDuringClone((el) => { if (el.hasAttribute("wire:navigate")) { import_alpinejs13.default.bind(el, { ["x-navigate"]: true }); } else if (el.hasAttribute("wire:navigate.hover")) { import_alpinejs13.default.bind(el, { ["x-navigate.hover"]: true }); + } else if (el.hasAttribute("wire:navigate.preserve-scroll")) { + import_alpinejs13.default.bind(el, { ["x-navigate.preserve-scroll"]: true }); + } else if (el.hasAttribute("wire:navigate.preserve-scroll.hover")) { + import_alpinejs13.default.bind(el, { ["x-navigate.preserve-scroll.hover"]: true }); + } else if (el.hasAttribute("wire:navigate.hover.preserve-scroll")) { + import_alpinejs13.default.bind(el, { ["x-navigate.hover.preserve-scroll"]: true }); } })); document.addEventListener("alpine:navigating", () => { @@ -10708,18 +10844,14 @@ function getTargets(el) { let inverted = false; if (directives.has("target")) { let directive2 = directives.get("target"); - let raw = directive2.expression; if (directive2.modifiers.includes("except")) inverted = true; - if (raw.includes("(") && raw.includes(")")) { - targets.push({ target: directive2.method, params: quickHash(JSON.stringify(directive2.params)) }); - } else if (raw.includes(",")) { - raw.split(",").map((i) => i.trim()).forEach((target) => { - targets.push({ target }); + directive2.methods.forEach(({ method, params }) => { + targets.push({ + target: method, + params: params && params.length > 0 ? quickHash(JSON.stringify(params)) : void 0 }); - } else { - targets.push({ target: raw }); - } + }); } else { let nonActionOrModelLivewireDirectives = ["init", "dirty", "offline", "target", "loading", "poll", "ignore", "key", "id"]; directives.all().filter((i) => !nonActionOrModelLivewireDirectives.includes(i.value)).map((i) => i.expression.split("(")[0]).forEach((target) => targets.push({ target })); @@ -10739,7 +10871,7 @@ directive("stream", ({ el, directive: directive2, cleanup }) => { if (modifiers.includes("replace") || replace2) { el.innerHTML = content; } else { - el.innerHTML = el.innerHTML + content; + el.insertAdjacentHTML("beforeend", content); } }); cleanup(off); @@ -10888,7 +11020,7 @@ directive("model", ({ el, directive: directive2, component, cleanup }) => { let onBlur = modifiers.includes("blur"); let isDebounced = modifiers.includes("debounce"); let update = expression.startsWith("$parent") ? () => component.$wire.$parent.$commit() : () => component.$wire.$commit(); - let debouncedUpdate = isTextInput(el) && !isDebounced && isLive ? debounce(update, 150) : update; + let debouncedUpdate = isRealtimeInput(el) && !isDebounced && isLive ? debounce(update, 150) : update; import_alpinejs16.default.bind(el, { ["@change"]() { isLazy && update(); @@ -10918,8 +11050,8 @@ function getModifierTail(modifiers) { return ""; return "." + modifiers.join("."); } -function isTextInput(el) { - return ["INPUT", "TEXTAREA"].includes(el.tagName.toUpperCase()) && !["checkbox", "radio"].includes(el.type); +function isRealtimeInput(el) { + return ["INPUT", "TEXTAREA"].includes(el.tagName.toUpperCase()) && !["checkbox", "radio"].includes(el.type) || el.tagName.toUpperCase() === "UI-SLIDER"; } function componentIsMissingProperty(component, property) { if (property.startsWith("$parent")) { diff --git a/public/vendor/livewire/livewire.esm.js.map b/public/vendor/livewire/livewire.esm.js.map index 939cd00df..32932b8da 100644 --- a/public/vendor/livewire/livewire.esm.js.map +++ b/public/vendor/livewire/livewire.esm.js.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../../alpine/packages/alpinejs/dist/module.cjs.js", "../../alpine/packages/collapse/dist/module.cjs.js", "../../alpine/packages/focus/dist/module.cjs.js", "../../alpine/packages/persist/dist/module.cjs.js", "../../alpine/packages/intersect/dist/module.cjs.js", "../node_modules/@alpinejs/resize/dist/module.cjs.js", "../../alpine/packages/anchor/dist/module.cjs.js", "../node_modules/nprogress/nprogress.js", "../../alpine/packages/morph/dist/module.cjs.js", "../../alpine/packages/mask/dist/module.cjs.js", "../js/utils.js", "../js/features/supportFileUploads.js", "../js/features/supportEntangle.js", "../js/hooks.js", "../js/request/modal.js", "../js/request/pool.js", "../js/request/commit.js", "../js/request/bus.js", "../js/request/index.js", "../js/$wire.js", "../js/component.js", "../js/store.js", "../js/events.js", "../js/directives.js", "../js/lifecycle.js", "../js/plugins/navigate/history.js", "../js/plugins/navigate/links.js", "../js/plugins/navigate/fetch.js", "../js/plugins/navigate/prefetch.js", "../js/plugins/navigate/teleport.js", "../js/plugins/navigate/scroll.js", "../js/plugins/navigate/persist.js", "../js/plugins/navigate/bar.js", "../js/plugins/navigate/popover.js", "../js/plugins/navigate/page.js", "../js/plugins/navigate/index.js", "../js/plugins/history/index.js", "../js/index.js", "../js/features/supportListeners.js", "../js/features/supportScriptsAndAssets.js", "../js/features/supportJsEvaluation.js", "../js/morph.js", "../js/features/supportMorphDom.js", "../js/features/supportDispatches.js", "../js/features/supportDisablingFormsDuringRequest.js", "../js/features/supportPropsAndModelables.js", "../js/features/supportFileDownloads.js", "../js/features/supportLazyLoading.js", "../js/features/supportQueryString.js", "../js/features/supportLaravelEcho.js", "../js/features/supportIsolating.js", "../js/features/supportNavigate.js", "../js/features/supportRedirects.js", "../js/directives/wire-transition.js", "../js/debounce.js", "../js/directives/wire-wildcard.js", "../js/directives/wire-navigate.js", "../js/directives/wire-confirm.js", "../js/directives/wire-current.js", "../js/directives/shared.js", "../js/directives/wire-offline.js", "../js/directives/wire-loading.js", "../js/directives/wire-stream.js", "../js/directives/wire-replace.js", "../js/directives/wire-ignore.js", "../js/directives/wire-cloak.js", "../js/directives/wire-dirty.js", "../js/directives/wire-model.js", "../js/directives/wire-init.js", "../js/directives/wire-poll.js", "../js/directives/wire-show.js", "../js/directives/wire-text.js"], - "sourcesContent": ["var __create = Object.create;\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __getProtoOf = Object.getPrototypeOf;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __commonJS = (cb, mod) => function __require() {\n return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;\n};\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(\n // If the importer is in node compatibility mode or this is not an ESM\n // file that has been converted to a CommonJS file using a Babel-\n // compatible transform (i.e. \"__esModule\" has not been set), then set\n // \"default\" to the CommonJS \"module.exports\" for node compatibility.\n isNodeMode || !mod || !mod.__esModule ? __defProp(target, \"default\", { value: mod, enumerable: true }) : target,\n mod\n));\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// node_modules/@vue/shared/dist/shared.cjs.js\nvar require_shared_cjs = __commonJS({\n \"node_modules/@vue/shared/dist/shared.cjs.js\"(exports) {\n \"use strict\";\n Object.defineProperty(exports, \"__esModule\", { value: true });\n function makeMap(str, expectsLowerCase) {\n const map = /* @__PURE__ */ Object.create(null);\n const list = str.split(\",\");\n for (let i = 0; i < list.length; i++) {\n map[list[i]] = true;\n }\n return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n }\n var PatchFlagNames = {\n [\n 1\n /* TEXT */\n ]: `TEXT`,\n [\n 2\n /* CLASS */\n ]: `CLASS`,\n [\n 4\n /* STYLE */\n ]: `STYLE`,\n [\n 8\n /* PROPS */\n ]: `PROPS`,\n [\n 16\n /* FULL_PROPS */\n ]: `FULL_PROPS`,\n [\n 32\n /* HYDRATE_EVENTS */\n ]: `HYDRATE_EVENTS`,\n [\n 64\n /* STABLE_FRAGMENT */\n ]: `STABLE_FRAGMENT`,\n [\n 128\n /* KEYED_FRAGMENT */\n ]: `KEYED_FRAGMENT`,\n [\n 256\n /* UNKEYED_FRAGMENT */\n ]: `UNKEYED_FRAGMENT`,\n [\n 512\n /* NEED_PATCH */\n ]: `NEED_PATCH`,\n [\n 1024\n /* DYNAMIC_SLOTS */\n ]: `DYNAMIC_SLOTS`,\n [\n 2048\n /* DEV_ROOT_FRAGMENT */\n ]: `DEV_ROOT_FRAGMENT`,\n [\n -1\n /* HOISTED */\n ]: `HOISTED`,\n [\n -2\n /* BAIL */\n ]: `BAIL`\n };\n var slotFlagsText = {\n [\n 1\n /* STABLE */\n ]: \"STABLE\",\n [\n 2\n /* DYNAMIC */\n ]: \"DYNAMIC\",\n [\n 3\n /* FORWARDED */\n ]: \"FORWARDED\"\n };\n var GLOBALS_WHITE_LISTED = \"Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt\";\n var isGloballyWhitelisted = /* @__PURE__ */ makeMap(GLOBALS_WHITE_LISTED);\n var range = 2;\n function generateCodeFrame(source, start2 = 0, end = source.length) {\n let lines = source.split(/(\\r?\\n)/);\n const newlineSequences = lines.filter((_, idx) => idx % 2 === 1);\n lines = lines.filter((_, idx) => idx % 2 === 0);\n let count = 0;\n const res = [];\n for (let i = 0; i < lines.length; i++) {\n count += lines[i].length + (newlineSequences[i] && newlineSequences[i].length || 0);\n if (count >= start2) {\n for (let j = i - range; j <= i + range || end > count; j++) {\n if (j < 0 || j >= lines.length)\n continue;\n const line = j + 1;\n res.push(`${line}${\" \".repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}`);\n const lineLength = lines[j].length;\n const newLineSeqLength = newlineSequences[j] && newlineSequences[j].length || 0;\n if (j === i) {\n const pad = start2 - (count - (lineLength + newLineSeqLength));\n const length = Math.max(1, end > count ? lineLength - pad : end - start2);\n res.push(` | ` + \" \".repeat(pad) + \"^\".repeat(length));\n } else if (j > i) {\n if (end > count) {\n const length = Math.max(Math.min(end - count, lineLength), 1);\n res.push(` | ` + \"^\".repeat(length));\n }\n count += lineLength + newLineSeqLength;\n }\n }\n break;\n }\n }\n return res.join(\"\\n\");\n }\n var specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`;\n var isSpecialBooleanAttr = /* @__PURE__ */ makeMap(specialBooleanAttrs);\n var isBooleanAttr2 = /* @__PURE__ */ makeMap(specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected`);\n var unsafeAttrCharRE = /[>/=\"'\\u0009\\u000a\\u000c\\u0020]/;\n var attrValidationCache = {};\n function isSSRSafeAttrName(name) {\n if (attrValidationCache.hasOwnProperty(name)) {\n return attrValidationCache[name];\n }\n const isUnsafe = unsafeAttrCharRE.test(name);\n if (isUnsafe) {\n console.error(`unsafe attribute name: ${name}`);\n }\n return attrValidationCache[name] = !isUnsafe;\n }\n var propsToAttrMap = {\n acceptCharset: \"accept-charset\",\n className: \"class\",\n htmlFor: \"for\",\n httpEquiv: \"http-equiv\"\n };\n var isNoUnitNumericStyleProp = /* @__PURE__ */ makeMap(`animation-iteration-count,border-image-outset,border-image-slice,border-image-width,box-flex,box-flex-group,box-ordinal-group,column-count,columns,flex,flex-grow,flex-positive,flex-shrink,flex-negative,flex-order,grid-row,grid-row-end,grid-row-span,grid-row-start,grid-column,grid-column-end,grid-column-span,grid-column-start,font-weight,line-clamp,line-height,opacity,order,orphans,tab-size,widows,z-index,zoom,fill-opacity,flood-opacity,stop-opacity,stroke-dasharray,stroke-dashoffset,stroke-miterlimit,stroke-opacity,stroke-width`);\n var isKnownAttr = /* @__PURE__ */ makeMap(`accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap`);\n function normalizeStyle(value) {\n if (isArray(value)) {\n const res = {};\n for (let i = 0; i < value.length; i++) {\n const item = value[i];\n const normalized = normalizeStyle(isString(item) ? parseStringStyle(item) : item);\n if (normalized) {\n for (const key in normalized) {\n res[key] = normalized[key];\n }\n }\n }\n return res;\n } else if (isObject(value)) {\n return value;\n }\n }\n var listDelimiterRE = /;(?![^(]*\\))/g;\n var propertyDelimiterRE = /:(.+)/;\n function parseStringStyle(cssText) {\n const ret = {};\n cssText.split(listDelimiterRE).forEach((item) => {\n if (item) {\n const tmp = item.split(propertyDelimiterRE);\n tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n }\n });\n return ret;\n }\n function stringifyStyle(styles) {\n let ret = \"\";\n if (!styles) {\n return ret;\n }\n for (const key in styles) {\n const value = styles[key];\n const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key);\n if (isString(value) || typeof value === \"number\" && isNoUnitNumericStyleProp(normalizedKey)) {\n ret += `${normalizedKey}:${value};`;\n }\n }\n return ret;\n }\n function normalizeClass(value) {\n let res = \"\";\n if (isString(value)) {\n res = value;\n } else if (isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const normalized = normalizeClass(value[i]);\n if (normalized) {\n res += normalized + \" \";\n }\n }\n } else if (isObject(value)) {\n for (const name in value) {\n if (value[name]) {\n res += name + \" \";\n }\n }\n }\n return res.trim();\n }\n var HTML_TAGS = \"html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot\";\n var SVG_TAGS = \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\";\n var VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\n var isHTMLTag = /* @__PURE__ */ makeMap(HTML_TAGS);\n var isSVGTag = /* @__PURE__ */ makeMap(SVG_TAGS);\n var isVoidTag = /* @__PURE__ */ makeMap(VOID_TAGS);\n var escapeRE = /[\"'&<>]/;\n function escapeHtml(string) {\n const str = \"\" + string;\n const match = escapeRE.exec(str);\n if (!match) {\n return str;\n }\n let html = \"\";\n let escaped;\n let index;\n let lastIndex = 0;\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34:\n escaped = \""\";\n break;\n case 38:\n escaped = \"&\";\n break;\n case 39:\n escaped = \"'\";\n break;\n case 60:\n escaped = \"<\";\n break;\n case 62:\n escaped = \">\";\n break;\n default:\n continue;\n }\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n lastIndex = index + 1;\n html += escaped;\n }\n return lastIndex !== index ? html + str.substring(lastIndex, index) : html;\n }\n var commentStripRE = /^-?>||--!>| looseEqual(item, val));\n }\n var toDisplayString = (val) => {\n return val == null ? \"\" : isObject(val) ? JSON.stringify(val, replacer, 2) : String(val);\n };\n var replacer = (_key, val) => {\n if (isMap(val)) {\n return {\n [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val2]) => {\n entries[`${key} =>`] = val2;\n return entries;\n }, {})\n };\n } else if (isSet(val)) {\n return {\n [`Set(${val.size})`]: [...val.values()]\n };\n } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {\n return String(val);\n }\n return val;\n };\n var babelParserDefaultPlugins = [\n \"bigInt\",\n \"optionalChaining\",\n \"nullishCoalescingOperator\"\n ];\n var EMPTY_OBJ = Object.freeze({});\n var EMPTY_ARR = Object.freeze([]);\n var NOOP = () => {\n };\n var NO = () => false;\n var onRE = /^on[^a-z]/;\n var isOn = (key) => onRE.test(key);\n var isModelListener = (key) => key.startsWith(\"onUpdate:\");\n var extend = Object.assign;\n var remove = (arr, el) => {\n const i = arr.indexOf(el);\n if (i > -1) {\n arr.splice(i, 1);\n }\n };\n var hasOwnProperty = Object.prototype.hasOwnProperty;\n var hasOwn = (val, key) => hasOwnProperty.call(val, key);\n var isArray = Array.isArray;\n var isMap = (val) => toTypeString(val) === \"[object Map]\";\n var isSet = (val) => toTypeString(val) === \"[object Set]\";\n var isDate = (val) => val instanceof Date;\n var isFunction = (val) => typeof val === \"function\";\n var isString = (val) => typeof val === \"string\";\n var isSymbol = (val) => typeof val === \"symbol\";\n var isObject = (val) => val !== null && typeof val === \"object\";\n var isPromise = (val) => {\n return isObject(val) && isFunction(val.then) && isFunction(val.catch);\n };\n var objectToString = Object.prototype.toString;\n var toTypeString = (value) => objectToString.call(value);\n var toRawType = (value) => {\n return toTypeString(value).slice(8, -1);\n };\n var isPlainObject = (val) => toTypeString(val) === \"[object Object]\";\n var isIntegerKey = (key) => isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n var isReservedProp = /* @__PURE__ */ makeMap(\n // the leading comma is intentional so empty string \"\" is also included\n \",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted\"\n );\n var cacheStringFunction = (fn) => {\n const cache = /* @__PURE__ */ Object.create(null);\n return (str) => {\n const hit = cache[str];\n return hit || (cache[str] = fn(str));\n };\n };\n var camelizeRE = /-(\\w)/g;\n var camelize = cacheStringFunction((str) => {\n return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : \"\");\n });\n var hyphenateRE = /\\B([A-Z])/g;\n var hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, \"-$1\").toLowerCase());\n var capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));\n var toHandlerKey = cacheStringFunction((str) => str ? `on${capitalize(str)}` : ``);\n var hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);\n var invokeArrayFns = (fns, arg) => {\n for (let i = 0; i < fns.length; i++) {\n fns[i](arg);\n }\n };\n var def = (obj, key, value) => {\n Object.defineProperty(obj, key, {\n configurable: true,\n enumerable: false,\n value\n });\n };\n var toNumber = (val) => {\n const n = parseFloat(val);\n return isNaN(n) ? val : n;\n };\n var _globalThis;\n var getGlobalThis = () => {\n return _globalThis || (_globalThis = typeof globalThis !== \"undefined\" ? globalThis : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : typeof global !== \"undefined\" ? global : {});\n };\n exports.EMPTY_ARR = EMPTY_ARR;\n exports.EMPTY_OBJ = EMPTY_OBJ;\n exports.NO = NO;\n exports.NOOP = NOOP;\n exports.PatchFlagNames = PatchFlagNames;\n exports.babelParserDefaultPlugins = babelParserDefaultPlugins;\n exports.camelize = camelize;\n exports.capitalize = capitalize;\n exports.def = def;\n exports.escapeHtml = escapeHtml;\n exports.escapeHtmlComment = escapeHtmlComment;\n exports.extend = extend;\n exports.generateCodeFrame = generateCodeFrame;\n exports.getGlobalThis = getGlobalThis;\n exports.hasChanged = hasChanged;\n exports.hasOwn = hasOwn;\n exports.hyphenate = hyphenate;\n exports.invokeArrayFns = invokeArrayFns;\n exports.isArray = isArray;\n exports.isBooleanAttr = isBooleanAttr2;\n exports.isDate = isDate;\n exports.isFunction = isFunction;\n exports.isGloballyWhitelisted = isGloballyWhitelisted;\n exports.isHTMLTag = isHTMLTag;\n exports.isIntegerKey = isIntegerKey;\n exports.isKnownAttr = isKnownAttr;\n exports.isMap = isMap;\n exports.isModelListener = isModelListener;\n exports.isNoUnitNumericStyleProp = isNoUnitNumericStyleProp;\n exports.isObject = isObject;\n exports.isOn = isOn;\n exports.isPlainObject = isPlainObject;\n exports.isPromise = isPromise;\n exports.isReservedProp = isReservedProp;\n exports.isSSRSafeAttrName = isSSRSafeAttrName;\n exports.isSVGTag = isSVGTag;\n exports.isSet = isSet;\n exports.isSpecialBooleanAttr = isSpecialBooleanAttr;\n exports.isString = isString;\n exports.isSymbol = isSymbol;\n exports.isVoidTag = isVoidTag;\n exports.looseEqual = looseEqual;\n exports.looseIndexOf = looseIndexOf;\n exports.makeMap = makeMap;\n exports.normalizeClass = normalizeClass;\n exports.normalizeStyle = normalizeStyle;\n exports.objectToString = objectToString;\n exports.parseStringStyle = parseStringStyle;\n exports.propsToAttrMap = propsToAttrMap;\n exports.remove = remove;\n exports.slotFlagsText = slotFlagsText;\n exports.stringifyStyle = stringifyStyle;\n exports.toDisplayString = toDisplayString;\n exports.toHandlerKey = toHandlerKey;\n exports.toNumber = toNumber;\n exports.toRawType = toRawType;\n exports.toTypeString = toTypeString;\n }\n});\n\n// node_modules/@vue/shared/index.js\nvar require_shared = __commonJS({\n \"node_modules/@vue/shared/index.js\"(exports, module2) {\n \"use strict\";\n if (false) {\n module2.exports = null;\n } else {\n module2.exports = require_shared_cjs();\n }\n }\n});\n\n// node_modules/@vue/reactivity/dist/reactivity.cjs.js\nvar require_reactivity_cjs = __commonJS({\n \"node_modules/@vue/reactivity/dist/reactivity.cjs.js\"(exports) {\n \"use strict\";\n Object.defineProperty(exports, \"__esModule\", { value: true });\n var shared = require_shared();\n var targetMap = /* @__PURE__ */ new WeakMap();\n var effectStack = [];\n var activeEffect;\n var ITERATE_KEY = Symbol(\"iterate\");\n var MAP_KEY_ITERATE_KEY = Symbol(\"Map key iterate\");\n function isEffect(fn) {\n return fn && fn._isEffect === true;\n }\n function effect3(fn, options = shared.EMPTY_OBJ) {\n if (isEffect(fn)) {\n fn = fn.raw;\n }\n const effect4 = createReactiveEffect(fn, options);\n if (!options.lazy) {\n effect4();\n }\n return effect4;\n }\n function stop2(effect4) {\n if (effect4.active) {\n cleanup(effect4);\n if (effect4.options.onStop) {\n effect4.options.onStop();\n }\n effect4.active = false;\n }\n }\n var uid = 0;\n function createReactiveEffect(fn, options) {\n const effect4 = function reactiveEffect() {\n if (!effect4.active) {\n return fn();\n }\n if (!effectStack.includes(effect4)) {\n cleanup(effect4);\n try {\n enableTracking();\n effectStack.push(effect4);\n activeEffect = effect4;\n return fn();\n } finally {\n effectStack.pop();\n resetTracking();\n activeEffect = effectStack[effectStack.length - 1];\n }\n }\n };\n effect4.id = uid++;\n effect4.allowRecurse = !!options.allowRecurse;\n effect4._isEffect = true;\n effect4.active = true;\n effect4.raw = fn;\n effect4.deps = [];\n effect4.options = options;\n return effect4;\n }\n function cleanup(effect4) {\n const { deps } = effect4;\n if (deps.length) {\n for (let i = 0; i < deps.length; i++) {\n deps[i].delete(effect4);\n }\n deps.length = 0;\n }\n }\n var shouldTrack = true;\n var trackStack = [];\n function pauseTracking() {\n trackStack.push(shouldTrack);\n shouldTrack = false;\n }\n function enableTracking() {\n trackStack.push(shouldTrack);\n shouldTrack = true;\n }\n function resetTracking() {\n const last = trackStack.pop();\n shouldTrack = last === void 0 ? true : last;\n }\n function track(target, type, key) {\n if (!shouldTrack || activeEffect === void 0) {\n return;\n }\n let depsMap = targetMap.get(target);\n if (!depsMap) {\n targetMap.set(target, depsMap = /* @__PURE__ */ new Map());\n }\n let dep = depsMap.get(key);\n if (!dep) {\n depsMap.set(key, dep = /* @__PURE__ */ new Set());\n }\n if (!dep.has(activeEffect)) {\n dep.add(activeEffect);\n activeEffect.deps.push(dep);\n if (activeEffect.options.onTrack) {\n activeEffect.options.onTrack({\n effect: activeEffect,\n target,\n type,\n key\n });\n }\n }\n }\n function trigger(target, type, key, newValue, oldValue, oldTarget) {\n const depsMap = targetMap.get(target);\n if (!depsMap) {\n return;\n }\n const effects = /* @__PURE__ */ new Set();\n const add2 = (effectsToAdd) => {\n if (effectsToAdd) {\n effectsToAdd.forEach((effect4) => {\n if (effect4 !== activeEffect || effect4.allowRecurse) {\n effects.add(effect4);\n }\n });\n }\n };\n if (type === \"clear\") {\n depsMap.forEach(add2);\n } else if (key === \"length\" && shared.isArray(target)) {\n depsMap.forEach((dep, key2) => {\n if (key2 === \"length\" || key2 >= newValue) {\n add2(dep);\n }\n });\n } else {\n if (key !== void 0) {\n add2(depsMap.get(key));\n }\n switch (type) {\n case \"add\":\n if (!shared.isArray(target)) {\n add2(depsMap.get(ITERATE_KEY));\n if (shared.isMap(target)) {\n add2(depsMap.get(MAP_KEY_ITERATE_KEY));\n }\n } else if (shared.isIntegerKey(key)) {\n add2(depsMap.get(\"length\"));\n }\n break;\n case \"delete\":\n if (!shared.isArray(target)) {\n add2(depsMap.get(ITERATE_KEY));\n if (shared.isMap(target)) {\n add2(depsMap.get(MAP_KEY_ITERATE_KEY));\n }\n }\n break;\n case \"set\":\n if (shared.isMap(target)) {\n add2(depsMap.get(ITERATE_KEY));\n }\n break;\n }\n }\n const run = (effect4) => {\n if (effect4.options.onTrigger) {\n effect4.options.onTrigger({\n effect: effect4,\n target,\n key,\n type,\n newValue,\n oldValue,\n oldTarget\n });\n }\n if (effect4.options.scheduler) {\n effect4.options.scheduler(effect4);\n } else {\n effect4();\n }\n };\n effects.forEach(run);\n }\n var isNonTrackableKeys = /* @__PURE__ */ shared.makeMap(`__proto__,__v_isRef,__isVue`);\n var builtInSymbols = new Set(Object.getOwnPropertyNames(Symbol).map((key) => Symbol[key]).filter(shared.isSymbol));\n var get2 = /* @__PURE__ */ createGetter();\n var shallowGet = /* @__PURE__ */ createGetter(false, true);\n var readonlyGet = /* @__PURE__ */ createGetter(true);\n var shallowReadonlyGet = /* @__PURE__ */ createGetter(true, true);\n var arrayInstrumentations = /* @__PURE__ */ createArrayInstrumentations();\n function createArrayInstrumentations() {\n const instrumentations = {};\n [\"includes\", \"indexOf\", \"lastIndexOf\"].forEach((key) => {\n instrumentations[key] = function(...args) {\n const arr = toRaw2(this);\n for (let i = 0, l = this.length; i < l; i++) {\n track(arr, \"get\", i + \"\");\n }\n const res = arr[key](...args);\n if (res === -1 || res === false) {\n return arr[key](...args.map(toRaw2));\n } else {\n return res;\n }\n };\n });\n [\"push\", \"pop\", \"shift\", \"unshift\", \"splice\"].forEach((key) => {\n instrumentations[key] = function(...args) {\n pauseTracking();\n const res = toRaw2(this)[key].apply(this, args);\n resetTracking();\n return res;\n };\n });\n return instrumentations;\n }\n function createGetter(isReadonly2 = false, shallow = false) {\n return function get3(target, key, receiver) {\n if (key === \"__v_isReactive\") {\n return !isReadonly2;\n } else if (key === \"__v_isReadonly\") {\n return isReadonly2;\n } else if (key === \"__v_raw\" && receiver === (isReadonly2 ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap).get(target)) {\n return target;\n }\n const targetIsArray = shared.isArray(target);\n if (!isReadonly2 && targetIsArray && shared.hasOwn(arrayInstrumentations, key)) {\n return Reflect.get(arrayInstrumentations, key, receiver);\n }\n const res = Reflect.get(target, key, receiver);\n if (shared.isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {\n return res;\n }\n if (!isReadonly2) {\n track(target, \"get\", key);\n }\n if (shallow) {\n return res;\n }\n if (isRef(res)) {\n const shouldUnwrap = !targetIsArray || !shared.isIntegerKey(key);\n return shouldUnwrap ? res.value : res;\n }\n if (shared.isObject(res)) {\n return isReadonly2 ? readonly(res) : reactive3(res);\n }\n return res;\n };\n }\n var set2 = /* @__PURE__ */ createSetter();\n var shallowSet = /* @__PURE__ */ createSetter(true);\n function createSetter(shallow = false) {\n return function set3(target, key, value, receiver) {\n let oldValue = target[key];\n if (!shallow) {\n value = toRaw2(value);\n oldValue = toRaw2(oldValue);\n if (!shared.isArray(target) && isRef(oldValue) && !isRef(value)) {\n oldValue.value = value;\n return true;\n }\n }\n const hadKey = shared.isArray(target) && shared.isIntegerKey(key) ? Number(key) < target.length : shared.hasOwn(target, key);\n const result = Reflect.set(target, key, value, receiver);\n if (target === toRaw2(receiver)) {\n if (!hadKey) {\n trigger(target, \"add\", key, value);\n } else if (shared.hasChanged(value, oldValue)) {\n trigger(target, \"set\", key, value, oldValue);\n }\n }\n return result;\n };\n }\n function deleteProperty(target, key) {\n const hadKey = shared.hasOwn(target, key);\n const oldValue = target[key];\n const result = Reflect.deleteProperty(target, key);\n if (result && hadKey) {\n trigger(target, \"delete\", key, void 0, oldValue);\n }\n return result;\n }\n function has(target, key) {\n const result = Reflect.has(target, key);\n if (!shared.isSymbol(key) || !builtInSymbols.has(key)) {\n track(target, \"has\", key);\n }\n return result;\n }\n function ownKeys(target) {\n track(target, \"iterate\", shared.isArray(target) ? \"length\" : ITERATE_KEY);\n return Reflect.ownKeys(target);\n }\n var mutableHandlers = {\n get: get2,\n set: set2,\n deleteProperty,\n has,\n ownKeys\n };\n var readonlyHandlers = {\n get: readonlyGet,\n set(target, key) {\n {\n console.warn(`Set operation on key \"${String(key)}\" failed: target is readonly.`, target);\n }\n return true;\n },\n deleteProperty(target, key) {\n {\n console.warn(`Delete operation on key \"${String(key)}\" failed: target is readonly.`, target);\n }\n return true;\n }\n };\n var shallowReactiveHandlers = /* @__PURE__ */ shared.extend({}, mutableHandlers, {\n get: shallowGet,\n set: shallowSet\n });\n var shallowReadonlyHandlers = /* @__PURE__ */ shared.extend({}, readonlyHandlers, {\n get: shallowReadonlyGet\n });\n var toReactive = (value) => shared.isObject(value) ? reactive3(value) : value;\n var toReadonly = (value) => shared.isObject(value) ? readonly(value) : value;\n var toShallow = (value) => value;\n var getProto = (v) => Reflect.getPrototypeOf(v);\n function get$1(target, key, isReadonly2 = false, isShallow = false) {\n target = target[\n \"__v_raw\"\n /* RAW */\n ];\n const rawTarget = toRaw2(target);\n const rawKey = toRaw2(key);\n if (key !== rawKey) {\n !isReadonly2 && track(rawTarget, \"get\", key);\n }\n !isReadonly2 && track(rawTarget, \"get\", rawKey);\n const { has: has2 } = getProto(rawTarget);\n const wrap = isShallow ? toShallow : isReadonly2 ? toReadonly : toReactive;\n if (has2.call(rawTarget, key)) {\n return wrap(target.get(key));\n } else if (has2.call(rawTarget, rawKey)) {\n return wrap(target.get(rawKey));\n } else if (target !== rawTarget) {\n target.get(key);\n }\n }\n function has$1(key, isReadonly2 = false) {\n const target = this[\n \"__v_raw\"\n /* RAW */\n ];\n const rawTarget = toRaw2(target);\n const rawKey = toRaw2(key);\n if (key !== rawKey) {\n !isReadonly2 && track(rawTarget, \"has\", key);\n }\n !isReadonly2 && track(rawTarget, \"has\", rawKey);\n return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey);\n }\n function size(target, isReadonly2 = false) {\n target = target[\n \"__v_raw\"\n /* RAW */\n ];\n !isReadonly2 && track(toRaw2(target), \"iterate\", ITERATE_KEY);\n return Reflect.get(target, \"size\", target);\n }\n function add(value) {\n value = toRaw2(value);\n const target = toRaw2(this);\n const proto = getProto(target);\n const hadKey = proto.has.call(target, value);\n if (!hadKey) {\n target.add(value);\n trigger(target, \"add\", value, value);\n }\n return this;\n }\n function set$1(key, value) {\n value = toRaw2(value);\n const target = toRaw2(this);\n const { has: has2, get: get3 } = getProto(target);\n let hadKey = has2.call(target, key);\n if (!hadKey) {\n key = toRaw2(key);\n hadKey = has2.call(target, key);\n } else {\n checkIdentityKeys(target, has2, key);\n }\n const oldValue = get3.call(target, key);\n target.set(key, value);\n if (!hadKey) {\n trigger(target, \"add\", key, value);\n } else if (shared.hasChanged(value, oldValue)) {\n trigger(target, \"set\", key, value, oldValue);\n }\n return this;\n }\n function deleteEntry(key) {\n const target = toRaw2(this);\n const { has: has2, get: get3 } = getProto(target);\n let hadKey = has2.call(target, key);\n if (!hadKey) {\n key = toRaw2(key);\n hadKey = has2.call(target, key);\n } else {\n checkIdentityKeys(target, has2, key);\n }\n const oldValue = get3 ? get3.call(target, key) : void 0;\n const result = target.delete(key);\n if (hadKey) {\n trigger(target, \"delete\", key, void 0, oldValue);\n }\n return result;\n }\n function clear() {\n const target = toRaw2(this);\n const hadItems = target.size !== 0;\n const oldTarget = shared.isMap(target) ? new Map(target) : new Set(target);\n const result = target.clear();\n if (hadItems) {\n trigger(target, \"clear\", void 0, void 0, oldTarget);\n }\n return result;\n }\n function createForEach(isReadonly2, isShallow) {\n return function forEach(callback, thisArg) {\n const observed = this;\n const target = observed[\n \"__v_raw\"\n /* RAW */\n ];\n const rawTarget = toRaw2(target);\n const wrap = isShallow ? toShallow : isReadonly2 ? toReadonly : toReactive;\n !isReadonly2 && track(rawTarget, \"iterate\", ITERATE_KEY);\n return target.forEach((value, key) => {\n return callback.call(thisArg, wrap(value), wrap(key), observed);\n });\n };\n }\n function createIterableMethod(method, isReadonly2, isShallow) {\n return function(...args) {\n const target = this[\n \"__v_raw\"\n /* RAW */\n ];\n const rawTarget = toRaw2(target);\n const targetIsMap = shared.isMap(rawTarget);\n const isPair = method === \"entries\" || method === Symbol.iterator && targetIsMap;\n const isKeyOnly = method === \"keys\" && targetIsMap;\n const innerIterator = target[method](...args);\n const wrap = isShallow ? toShallow : isReadonly2 ? toReadonly : toReactive;\n !isReadonly2 && track(rawTarget, \"iterate\", isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY);\n return {\n // iterator protocol\n next() {\n const { value, done } = innerIterator.next();\n return done ? { value, done } : {\n value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),\n done\n };\n },\n // iterable protocol\n [Symbol.iterator]() {\n return this;\n }\n };\n };\n }\n function createReadonlyMethod(type) {\n return function(...args) {\n {\n const key = args[0] ? `on key \"${args[0]}\" ` : ``;\n console.warn(`${shared.capitalize(type)} operation ${key}failed: target is readonly.`, toRaw2(this));\n }\n return type === \"delete\" ? false : this;\n };\n }\n function createInstrumentations() {\n const mutableInstrumentations2 = {\n get(key) {\n return get$1(this, key);\n },\n get size() {\n return size(this);\n },\n has: has$1,\n add,\n set: set$1,\n delete: deleteEntry,\n clear,\n forEach: createForEach(false, false)\n };\n const shallowInstrumentations2 = {\n get(key) {\n return get$1(this, key, false, true);\n },\n get size() {\n return size(this);\n },\n has: has$1,\n add,\n set: set$1,\n delete: deleteEntry,\n clear,\n forEach: createForEach(false, true)\n };\n const readonlyInstrumentations2 = {\n get(key) {\n return get$1(this, key, true);\n },\n get size() {\n return size(this, true);\n },\n has(key) {\n return has$1.call(this, key, true);\n },\n add: createReadonlyMethod(\n \"add\"\n /* ADD */\n ),\n set: createReadonlyMethod(\n \"set\"\n /* SET */\n ),\n delete: createReadonlyMethod(\n \"delete\"\n /* DELETE */\n ),\n clear: createReadonlyMethod(\n \"clear\"\n /* CLEAR */\n ),\n forEach: createForEach(true, false)\n };\n const shallowReadonlyInstrumentations2 = {\n get(key) {\n return get$1(this, key, true, true);\n },\n get size() {\n return size(this, true);\n },\n has(key) {\n return has$1.call(this, key, true);\n },\n add: createReadonlyMethod(\n \"add\"\n /* ADD */\n ),\n set: createReadonlyMethod(\n \"set\"\n /* SET */\n ),\n delete: createReadonlyMethod(\n \"delete\"\n /* DELETE */\n ),\n clear: createReadonlyMethod(\n \"clear\"\n /* CLEAR */\n ),\n forEach: createForEach(true, true)\n };\n const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n iteratorMethods.forEach((method) => {\n mutableInstrumentations2[method] = createIterableMethod(method, false, false);\n readonlyInstrumentations2[method] = createIterableMethod(method, true, false);\n shallowInstrumentations2[method] = createIterableMethod(method, false, true);\n shallowReadonlyInstrumentations2[method] = createIterableMethod(method, true, true);\n });\n return [\n mutableInstrumentations2,\n readonlyInstrumentations2,\n shallowInstrumentations2,\n shallowReadonlyInstrumentations2\n ];\n }\n var [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations, shallowReadonlyInstrumentations] = /* @__PURE__ */ createInstrumentations();\n function createInstrumentationGetter(isReadonly2, shallow) {\n const instrumentations = shallow ? isReadonly2 ? shallowReadonlyInstrumentations : shallowInstrumentations : isReadonly2 ? readonlyInstrumentations : mutableInstrumentations;\n return (target, key, receiver) => {\n if (key === \"__v_isReactive\") {\n return !isReadonly2;\n } else if (key === \"__v_isReadonly\") {\n return isReadonly2;\n } else if (key === \"__v_raw\") {\n return target;\n }\n return Reflect.get(shared.hasOwn(instrumentations, key) && key in target ? instrumentations : target, key, receiver);\n };\n }\n var mutableCollectionHandlers = {\n get: /* @__PURE__ */ createInstrumentationGetter(false, false)\n };\n var shallowCollectionHandlers = {\n get: /* @__PURE__ */ createInstrumentationGetter(false, true)\n };\n var readonlyCollectionHandlers = {\n get: /* @__PURE__ */ createInstrumentationGetter(true, false)\n };\n var shallowReadonlyCollectionHandlers = {\n get: /* @__PURE__ */ createInstrumentationGetter(true, true)\n };\n function checkIdentityKeys(target, has2, key) {\n const rawKey = toRaw2(key);\n if (rawKey !== key && has2.call(target, rawKey)) {\n const type = shared.toRawType(target);\n console.warn(`Reactive ${type} contains both the raw and reactive versions of the same object${type === `Map` ? ` as keys` : ``}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`);\n }\n }\n var reactiveMap = /* @__PURE__ */ new WeakMap();\n var shallowReactiveMap = /* @__PURE__ */ new WeakMap();\n var readonlyMap = /* @__PURE__ */ new WeakMap();\n var shallowReadonlyMap = /* @__PURE__ */ new WeakMap();\n function targetTypeMap(rawType) {\n switch (rawType) {\n case \"Object\":\n case \"Array\":\n return 1;\n case \"Map\":\n case \"Set\":\n case \"WeakMap\":\n case \"WeakSet\":\n return 2;\n default:\n return 0;\n }\n }\n function getTargetType(value) {\n return value[\n \"__v_skip\"\n /* SKIP */\n ] || !Object.isExtensible(value) ? 0 : targetTypeMap(shared.toRawType(value));\n }\n function reactive3(target) {\n if (target && target[\n \"__v_isReadonly\"\n /* IS_READONLY */\n ]) {\n return target;\n }\n return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);\n }\n function shallowReactive(target) {\n return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap);\n }\n function readonly(target) {\n return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap);\n }\n function shallowReadonly(target) {\n return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap);\n }\n function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {\n if (!shared.isObject(target)) {\n {\n console.warn(`value cannot be made reactive: ${String(target)}`);\n }\n return target;\n }\n if (target[\n \"__v_raw\"\n /* RAW */\n ] && !(isReadonly2 && target[\n \"__v_isReactive\"\n /* IS_REACTIVE */\n ])) {\n return target;\n }\n const existingProxy = proxyMap.get(target);\n if (existingProxy) {\n return existingProxy;\n }\n const targetType = getTargetType(target);\n if (targetType === 0) {\n return target;\n }\n const proxy = new Proxy(target, targetType === 2 ? collectionHandlers : baseHandlers);\n proxyMap.set(target, proxy);\n return proxy;\n }\n function isReactive2(value) {\n if (isReadonly(value)) {\n return isReactive2(value[\n \"__v_raw\"\n /* RAW */\n ]);\n }\n return !!(value && value[\n \"__v_isReactive\"\n /* IS_REACTIVE */\n ]);\n }\n function isReadonly(value) {\n return !!(value && value[\n \"__v_isReadonly\"\n /* IS_READONLY */\n ]);\n }\n function isProxy(value) {\n return isReactive2(value) || isReadonly(value);\n }\n function toRaw2(observed) {\n return observed && toRaw2(observed[\n \"__v_raw\"\n /* RAW */\n ]) || observed;\n }\n function markRaw(value) {\n shared.def(value, \"__v_skip\", true);\n return value;\n }\n var convert = (val) => shared.isObject(val) ? reactive3(val) : val;\n function isRef(r) {\n return Boolean(r && r.__v_isRef === true);\n }\n function ref(value) {\n return createRef(value);\n }\n function shallowRef(value) {\n return createRef(value, true);\n }\n var RefImpl = class {\n constructor(value, _shallow = false) {\n this._shallow = _shallow;\n this.__v_isRef = true;\n this._rawValue = _shallow ? value : toRaw2(value);\n this._value = _shallow ? value : convert(value);\n }\n get value() {\n track(toRaw2(this), \"get\", \"value\");\n return this._value;\n }\n set value(newVal) {\n newVal = this._shallow ? newVal : toRaw2(newVal);\n if (shared.hasChanged(newVal, this._rawValue)) {\n this._rawValue = newVal;\n this._value = this._shallow ? newVal : convert(newVal);\n trigger(toRaw2(this), \"set\", \"value\", newVal);\n }\n }\n };\n function createRef(rawValue, shallow = false) {\n if (isRef(rawValue)) {\n return rawValue;\n }\n return new RefImpl(rawValue, shallow);\n }\n function triggerRef(ref2) {\n trigger(toRaw2(ref2), \"set\", \"value\", ref2.value);\n }\n function unref(ref2) {\n return isRef(ref2) ? ref2.value : ref2;\n }\n var shallowUnwrapHandlers = {\n get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n set: (target, key, value, receiver) => {\n const oldValue = target[key];\n if (isRef(oldValue) && !isRef(value)) {\n oldValue.value = value;\n return true;\n } else {\n return Reflect.set(target, key, value, receiver);\n }\n }\n };\n function proxyRefs(objectWithRefs) {\n return isReactive2(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n }\n var CustomRefImpl = class {\n constructor(factory) {\n this.__v_isRef = true;\n const { get: get3, set: set3 } = factory(() => track(this, \"get\", \"value\"), () => trigger(this, \"set\", \"value\"));\n this._get = get3;\n this._set = set3;\n }\n get value() {\n return this._get();\n }\n set value(newVal) {\n this._set(newVal);\n }\n };\n function customRef(factory) {\n return new CustomRefImpl(factory);\n }\n function toRefs(object) {\n if (!isProxy(object)) {\n console.warn(`toRefs() expects a reactive object but received a plain one.`);\n }\n const ret = shared.isArray(object) ? new Array(object.length) : {};\n for (const key in object) {\n ret[key] = toRef(object, key);\n }\n return ret;\n }\n var ObjectRefImpl = class {\n constructor(_object, _key) {\n this._object = _object;\n this._key = _key;\n this.__v_isRef = true;\n }\n get value() {\n return this._object[this._key];\n }\n set value(newVal) {\n this._object[this._key] = newVal;\n }\n };\n function toRef(object, key) {\n return isRef(object[key]) ? object[key] : new ObjectRefImpl(object, key);\n }\n var ComputedRefImpl = class {\n constructor(getter, _setter, isReadonly2) {\n this._setter = _setter;\n this._dirty = true;\n this.__v_isRef = true;\n this.effect = effect3(getter, {\n lazy: true,\n scheduler: () => {\n if (!this._dirty) {\n this._dirty = true;\n trigger(toRaw2(this), \"set\", \"value\");\n }\n }\n });\n this[\n \"__v_isReadonly\"\n /* IS_READONLY */\n ] = isReadonly2;\n }\n get value() {\n const self2 = toRaw2(this);\n if (self2._dirty) {\n self2._value = this.effect();\n self2._dirty = false;\n }\n track(self2, \"get\", \"value\");\n return self2._value;\n }\n set value(newValue) {\n this._setter(newValue);\n }\n };\n function computed(getterOrOptions) {\n let getter;\n let setter;\n if (shared.isFunction(getterOrOptions)) {\n getter = getterOrOptions;\n setter = () => {\n console.warn(\"Write operation failed: computed value is readonly\");\n };\n } else {\n getter = getterOrOptions.get;\n setter = getterOrOptions.set;\n }\n return new ComputedRefImpl(getter, setter, shared.isFunction(getterOrOptions) || !getterOrOptions.set);\n }\n exports.ITERATE_KEY = ITERATE_KEY;\n exports.computed = computed;\n exports.customRef = customRef;\n exports.effect = effect3;\n exports.enableTracking = enableTracking;\n exports.isProxy = isProxy;\n exports.isReactive = isReactive2;\n exports.isReadonly = isReadonly;\n exports.isRef = isRef;\n exports.markRaw = markRaw;\n exports.pauseTracking = pauseTracking;\n exports.proxyRefs = proxyRefs;\n exports.reactive = reactive3;\n exports.readonly = readonly;\n exports.ref = ref;\n exports.resetTracking = resetTracking;\n exports.shallowReactive = shallowReactive;\n exports.shallowReadonly = shallowReadonly;\n exports.shallowRef = shallowRef;\n exports.stop = stop2;\n exports.toRaw = toRaw2;\n exports.toRef = toRef;\n exports.toRefs = toRefs;\n exports.track = track;\n exports.trigger = trigger;\n exports.triggerRef = triggerRef;\n exports.unref = unref;\n }\n});\n\n// node_modules/@vue/reactivity/index.js\nvar require_reactivity = __commonJS({\n \"node_modules/@vue/reactivity/index.js\"(exports, module2) {\n \"use strict\";\n if (false) {\n module2.exports = null;\n } else {\n module2.exports = require_reactivity_cjs();\n }\n }\n});\n\n// packages/alpinejs/builds/module.js\nvar module_exports = {};\n__export(module_exports, {\n Alpine: () => src_default,\n default: () => module_default\n});\nmodule.exports = __toCommonJS(module_exports);\n\n// packages/alpinejs/src/scheduler.js\nvar flushPending = false;\nvar flushing = false;\nvar queue = [];\nvar lastFlushedIndex = -1;\nfunction scheduler(callback) {\n queueJob(callback);\n}\nfunction queueJob(job) {\n if (!queue.includes(job))\n queue.push(job);\n queueFlush();\n}\nfunction dequeueJob(job) {\n let index = queue.indexOf(job);\n if (index !== -1 && index > lastFlushedIndex)\n queue.splice(index, 1);\n}\nfunction queueFlush() {\n if (!flushing && !flushPending) {\n flushPending = true;\n queueMicrotask(flushJobs);\n }\n}\nfunction flushJobs() {\n flushPending = false;\n flushing = true;\n for (let i = 0; i < queue.length; i++) {\n queue[i]();\n lastFlushedIndex = i;\n }\n queue.length = 0;\n lastFlushedIndex = -1;\n flushing = false;\n}\n\n// packages/alpinejs/src/reactivity.js\nvar reactive;\nvar effect;\nvar release;\nvar raw;\nvar shouldSchedule = true;\nfunction disableEffectScheduling(callback) {\n shouldSchedule = false;\n callback();\n shouldSchedule = true;\n}\nfunction setReactivityEngine(engine) {\n reactive = engine.reactive;\n release = engine.release;\n effect = (callback) => engine.effect(callback, { scheduler: (task) => {\n if (shouldSchedule) {\n scheduler(task);\n } else {\n task();\n }\n } });\n raw = engine.raw;\n}\nfunction overrideEffect(override) {\n effect = override;\n}\nfunction elementBoundEffect(el) {\n let cleanup = () => {\n };\n let wrappedEffect = (callback) => {\n let effectReference = effect(callback);\n if (!el._x_effects) {\n el._x_effects = /* @__PURE__ */ new Set();\n el._x_runEffects = () => {\n el._x_effects.forEach((i) => i());\n };\n }\n el._x_effects.add(effectReference);\n cleanup = () => {\n if (effectReference === void 0)\n return;\n el._x_effects.delete(effectReference);\n release(effectReference);\n };\n return effectReference;\n };\n return [wrappedEffect, () => {\n cleanup();\n }];\n}\nfunction watch(getter, callback) {\n let firstTime = true;\n let oldValue;\n let effectReference = effect(() => {\n let value = getter();\n JSON.stringify(value);\n if (!firstTime) {\n queueMicrotask(() => {\n callback(value, oldValue);\n oldValue = value;\n });\n } else {\n oldValue = value;\n }\n firstTime = false;\n });\n return () => release(effectReference);\n}\n\n// packages/alpinejs/src/mutation.js\nvar onAttributeAddeds = [];\nvar onElRemoveds = [];\nvar onElAddeds = [];\nfunction onElAdded(callback) {\n onElAddeds.push(callback);\n}\nfunction onElRemoved(el, callback) {\n if (typeof callback === \"function\") {\n if (!el._x_cleanups)\n el._x_cleanups = [];\n el._x_cleanups.push(callback);\n } else {\n callback = el;\n onElRemoveds.push(callback);\n }\n}\nfunction onAttributesAdded(callback) {\n onAttributeAddeds.push(callback);\n}\nfunction onAttributeRemoved(el, name, callback) {\n if (!el._x_attributeCleanups)\n el._x_attributeCleanups = {};\n if (!el._x_attributeCleanups[name])\n el._x_attributeCleanups[name] = [];\n el._x_attributeCleanups[name].push(callback);\n}\nfunction cleanupAttributes(el, names) {\n if (!el._x_attributeCleanups)\n return;\n Object.entries(el._x_attributeCleanups).forEach(([name, value]) => {\n if (names === void 0 || names.includes(name)) {\n value.forEach((i) => i());\n delete el._x_attributeCleanups[name];\n }\n });\n}\nfunction cleanupElement(el) {\n var _a, _b;\n (_a = el._x_effects) == null ? void 0 : _a.forEach(dequeueJob);\n while ((_b = el._x_cleanups) == null ? void 0 : _b.length)\n el._x_cleanups.pop()();\n}\nvar observer = new MutationObserver(onMutate);\nvar currentlyObserving = false;\nfunction startObservingMutations() {\n observer.observe(document, { subtree: true, childList: true, attributes: true, attributeOldValue: true });\n currentlyObserving = true;\n}\nfunction stopObservingMutations() {\n flushObserver();\n observer.disconnect();\n currentlyObserving = false;\n}\nvar queuedMutations = [];\nfunction flushObserver() {\n let records = observer.takeRecords();\n queuedMutations.push(() => records.length > 0 && onMutate(records));\n let queueLengthWhenTriggered = queuedMutations.length;\n queueMicrotask(() => {\n if (queuedMutations.length === queueLengthWhenTriggered) {\n while (queuedMutations.length > 0)\n queuedMutations.shift()();\n }\n });\n}\nfunction mutateDom(callback) {\n if (!currentlyObserving)\n return callback();\n stopObservingMutations();\n let result = callback();\n startObservingMutations();\n return result;\n}\nvar isCollecting = false;\nvar deferredMutations = [];\nfunction deferMutations() {\n isCollecting = true;\n}\nfunction flushAndStopDeferringMutations() {\n isCollecting = false;\n onMutate(deferredMutations);\n deferredMutations = [];\n}\nfunction onMutate(mutations) {\n if (isCollecting) {\n deferredMutations = deferredMutations.concat(mutations);\n return;\n }\n let addedNodes = [];\n let removedNodes = /* @__PURE__ */ new Set();\n let addedAttributes = /* @__PURE__ */ new Map();\n let removedAttributes = /* @__PURE__ */ new Map();\n for (let i = 0; i < mutations.length; i++) {\n if (mutations[i].target._x_ignoreMutationObserver)\n continue;\n if (mutations[i].type === \"childList\") {\n mutations[i].removedNodes.forEach((node) => {\n if (node.nodeType !== 1)\n return;\n if (!node._x_marker)\n return;\n removedNodes.add(node);\n });\n mutations[i].addedNodes.forEach((node) => {\n if (node.nodeType !== 1)\n return;\n if (removedNodes.has(node)) {\n removedNodes.delete(node);\n return;\n }\n if (node._x_marker)\n return;\n addedNodes.push(node);\n });\n }\n if (mutations[i].type === \"attributes\") {\n let el = mutations[i].target;\n let name = mutations[i].attributeName;\n let oldValue = mutations[i].oldValue;\n let add = () => {\n if (!addedAttributes.has(el))\n addedAttributes.set(el, []);\n addedAttributes.get(el).push({ name, value: el.getAttribute(name) });\n };\n let remove = () => {\n if (!removedAttributes.has(el))\n removedAttributes.set(el, []);\n removedAttributes.get(el).push(name);\n };\n if (el.hasAttribute(name) && oldValue === null) {\n add();\n } else if (el.hasAttribute(name)) {\n remove();\n add();\n } else {\n remove();\n }\n }\n }\n removedAttributes.forEach((attrs, el) => {\n cleanupAttributes(el, attrs);\n });\n addedAttributes.forEach((attrs, el) => {\n onAttributeAddeds.forEach((i) => i(el, attrs));\n });\n for (let node of removedNodes) {\n if (addedNodes.some((i) => i.contains(node)))\n continue;\n onElRemoveds.forEach((i) => i(node));\n }\n for (let node of addedNodes) {\n if (!node.isConnected)\n continue;\n onElAddeds.forEach((i) => i(node));\n }\n addedNodes = null;\n removedNodes = null;\n addedAttributes = null;\n removedAttributes = null;\n}\n\n// packages/alpinejs/src/scope.js\nfunction scope(node) {\n return mergeProxies(closestDataStack(node));\n}\nfunction addScopeToNode(node, data2, referenceNode) {\n node._x_dataStack = [data2, ...closestDataStack(referenceNode || node)];\n return () => {\n node._x_dataStack = node._x_dataStack.filter((i) => i !== data2);\n };\n}\nfunction closestDataStack(node) {\n if (node._x_dataStack)\n return node._x_dataStack;\n if (typeof ShadowRoot === \"function\" && node instanceof ShadowRoot) {\n return closestDataStack(node.host);\n }\n if (!node.parentNode) {\n return [];\n }\n return closestDataStack(node.parentNode);\n}\nfunction mergeProxies(objects) {\n return new Proxy({ objects }, mergeProxyTrap);\n}\nvar mergeProxyTrap = {\n ownKeys({ objects }) {\n return Array.from(\n new Set(objects.flatMap((i) => Object.keys(i)))\n );\n },\n has({ objects }, name) {\n if (name == Symbol.unscopables)\n return false;\n return objects.some(\n (obj) => Object.prototype.hasOwnProperty.call(obj, name) || Reflect.has(obj, name)\n );\n },\n get({ objects }, name, thisProxy) {\n if (name == \"toJSON\")\n return collapseProxies;\n return Reflect.get(\n objects.find(\n (obj) => Reflect.has(obj, name)\n ) || {},\n name,\n thisProxy\n );\n },\n set({ objects }, name, value, thisProxy) {\n const target = objects.find(\n (obj) => Object.prototype.hasOwnProperty.call(obj, name)\n ) || objects[objects.length - 1];\n const descriptor = Object.getOwnPropertyDescriptor(target, name);\n if ((descriptor == null ? void 0 : descriptor.set) && (descriptor == null ? void 0 : descriptor.get))\n return descriptor.set.call(thisProxy, value) || true;\n return Reflect.set(target, name, value);\n }\n};\nfunction collapseProxies() {\n let keys = Reflect.ownKeys(this);\n return keys.reduce((acc, key) => {\n acc[key] = Reflect.get(this, key);\n return acc;\n }, {});\n}\n\n// packages/alpinejs/src/interceptor.js\nfunction initInterceptors(data2) {\n let isObject = (val) => typeof val === \"object\" && !Array.isArray(val) && val !== null;\n let recurse = (obj, basePath = \"\") => {\n Object.entries(Object.getOwnPropertyDescriptors(obj)).forEach(([key, { value, enumerable }]) => {\n if (enumerable === false || value === void 0)\n return;\n if (typeof value === \"object\" && value !== null && value.__v_skip)\n return;\n let path = basePath === \"\" ? key : `${basePath}.${key}`;\n if (typeof value === \"object\" && value !== null && value._x_interceptor) {\n obj[key] = value.initialize(data2, path, key);\n } else {\n if (isObject(value) && value !== obj && !(value instanceof Element)) {\n recurse(value, path);\n }\n }\n });\n };\n return recurse(data2);\n}\nfunction interceptor(callback, mutateObj = () => {\n}) {\n let obj = {\n initialValue: void 0,\n _x_interceptor: true,\n initialize(data2, path, key) {\n return callback(this.initialValue, () => get(data2, path), (value) => set(data2, path, value), path, key);\n }\n };\n mutateObj(obj);\n return (initialValue) => {\n if (typeof initialValue === \"object\" && initialValue !== null && initialValue._x_interceptor) {\n let initialize = obj.initialize.bind(obj);\n obj.initialize = (data2, path, key) => {\n let innerValue = initialValue.initialize(data2, path, key);\n obj.initialValue = innerValue;\n return initialize(data2, path, key);\n };\n } else {\n obj.initialValue = initialValue;\n }\n return obj;\n };\n}\nfunction get(obj, path) {\n return path.split(\".\").reduce((carry, segment) => carry[segment], obj);\n}\nfunction set(obj, path, value) {\n if (typeof path === \"string\")\n path = path.split(\".\");\n if (path.length === 1)\n obj[path[0]] = value;\n else if (path.length === 0)\n throw error;\n else {\n if (obj[path[0]])\n return set(obj[path[0]], path.slice(1), value);\n else {\n obj[path[0]] = {};\n return set(obj[path[0]], path.slice(1), value);\n }\n }\n}\n\n// packages/alpinejs/src/magics.js\nvar magics = {};\nfunction magic(name, callback) {\n magics[name] = callback;\n}\nfunction injectMagics(obj, el) {\n let memoizedUtilities = getUtilities(el);\n Object.entries(magics).forEach(([name, callback]) => {\n Object.defineProperty(obj, `$${name}`, {\n get() {\n return callback(el, memoizedUtilities);\n },\n enumerable: false\n });\n });\n return obj;\n}\nfunction getUtilities(el) {\n let [utilities, cleanup] = getElementBoundUtilities(el);\n let utils = { interceptor, ...utilities };\n onElRemoved(el, cleanup);\n return utils;\n}\n\n// packages/alpinejs/src/utils/error.js\nfunction tryCatch(el, expression, callback, ...args) {\n try {\n return callback(...args);\n } catch (e) {\n handleError(e, el, expression);\n }\n}\nfunction handleError(error2, el, expression = void 0) {\n error2 = Object.assign(\n error2 != null ? error2 : { message: \"No error message given.\" },\n { el, expression }\n );\n console.warn(`Alpine Expression Error: ${error2.message}\n\n${expression ? 'Expression: \"' + expression + '\"\\n\\n' : \"\"}`, el);\n setTimeout(() => {\n throw error2;\n }, 0);\n}\n\n// packages/alpinejs/src/evaluator.js\nvar shouldAutoEvaluateFunctions = true;\nfunction dontAutoEvaluateFunctions(callback) {\n let cache = shouldAutoEvaluateFunctions;\n shouldAutoEvaluateFunctions = false;\n let result = callback();\n shouldAutoEvaluateFunctions = cache;\n return result;\n}\nfunction evaluate(el, expression, extras = {}) {\n let result;\n evaluateLater(el, expression)((value) => result = value, extras);\n return result;\n}\nfunction evaluateLater(...args) {\n return theEvaluatorFunction(...args);\n}\nvar theEvaluatorFunction = normalEvaluator;\nfunction setEvaluator(newEvaluator) {\n theEvaluatorFunction = newEvaluator;\n}\nfunction normalEvaluator(el, expression) {\n let overriddenMagics = {};\n injectMagics(overriddenMagics, el);\n let dataStack = [overriddenMagics, ...closestDataStack(el)];\n let evaluator = typeof expression === \"function\" ? generateEvaluatorFromFunction(dataStack, expression) : generateEvaluatorFromString(dataStack, expression, el);\n return tryCatch.bind(null, el, expression, evaluator);\n}\nfunction generateEvaluatorFromFunction(dataStack, func) {\n return (receiver = () => {\n }, { scope: scope2 = {}, params = [] } = {}) => {\n let result = func.apply(mergeProxies([scope2, ...dataStack]), params);\n runIfTypeOfFunction(receiver, result);\n };\n}\nvar evaluatorMemo = {};\nfunction generateFunctionFromString(expression, el) {\n if (evaluatorMemo[expression]) {\n return evaluatorMemo[expression];\n }\n let AsyncFunction = Object.getPrototypeOf(async function() {\n }).constructor;\n let rightSideSafeExpression = /^[\\n\\s]*if.*\\(.*\\)/.test(expression.trim()) || /^(let|const)\\s/.test(expression.trim()) ? `(async()=>{ ${expression} })()` : expression;\n const safeAsyncFunction = () => {\n try {\n let func2 = new AsyncFunction(\n [\"__self\", \"scope\"],\n `with (scope) { __self.result = ${rightSideSafeExpression} }; __self.finished = true; return __self.result;`\n );\n Object.defineProperty(func2, \"name\", {\n value: `[Alpine] ${expression}`\n });\n return func2;\n } catch (error2) {\n handleError(error2, el, expression);\n return Promise.resolve();\n }\n };\n let func = safeAsyncFunction();\n evaluatorMemo[expression] = func;\n return func;\n}\nfunction generateEvaluatorFromString(dataStack, expression, el) {\n let func = generateFunctionFromString(expression, el);\n return (receiver = () => {\n }, { scope: scope2 = {}, params = [] } = {}) => {\n func.result = void 0;\n func.finished = false;\n let completeScope = mergeProxies([scope2, ...dataStack]);\n if (typeof func === \"function\") {\n let promise = func(func, completeScope).catch((error2) => handleError(error2, el, expression));\n if (func.finished) {\n runIfTypeOfFunction(receiver, func.result, completeScope, params, el);\n func.result = void 0;\n } else {\n promise.then((result) => {\n runIfTypeOfFunction(receiver, result, completeScope, params, el);\n }).catch((error2) => handleError(error2, el, expression)).finally(() => func.result = void 0);\n }\n }\n };\n}\nfunction runIfTypeOfFunction(receiver, value, scope2, params, el) {\n if (shouldAutoEvaluateFunctions && typeof value === \"function\") {\n let result = value.apply(scope2, params);\n if (result instanceof Promise) {\n result.then((i) => runIfTypeOfFunction(receiver, i, scope2, params)).catch((error2) => handleError(error2, el, value));\n } else {\n receiver(result);\n }\n } else if (typeof value === \"object\" && value instanceof Promise) {\n value.then((i) => receiver(i));\n } else {\n receiver(value);\n }\n}\n\n// packages/alpinejs/src/directives.js\nvar prefixAsString = \"x-\";\nfunction prefix(subject = \"\") {\n return prefixAsString + subject;\n}\nfunction setPrefix(newPrefix) {\n prefixAsString = newPrefix;\n}\nvar directiveHandlers = {};\nfunction directive(name, callback) {\n directiveHandlers[name] = callback;\n return {\n before(directive2) {\n if (!directiveHandlers[directive2]) {\n console.warn(String.raw`Cannot find directive \\`${directive2}\\`. \\`${name}\\` will use the default order of execution`);\n return;\n }\n const pos = directiveOrder.indexOf(directive2);\n directiveOrder.splice(pos >= 0 ? pos : directiveOrder.indexOf(\"DEFAULT\"), 0, name);\n }\n };\n}\nfunction directiveExists(name) {\n return Object.keys(directiveHandlers).includes(name);\n}\nfunction directives(el, attributes, originalAttributeOverride) {\n attributes = Array.from(attributes);\n if (el._x_virtualDirectives) {\n let vAttributes = Object.entries(el._x_virtualDirectives).map(([name, value]) => ({ name, value }));\n let staticAttributes = attributesOnly(vAttributes);\n vAttributes = vAttributes.map((attribute) => {\n if (staticAttributes.find((attr) => attr.name === attribute.name)) {\n return {\n name: `x-bind:${attribute.name}`,\n value: `\"${attribute.value}\"`\n };\n }\n return attribute;\n });\n attributes = attributes.concat(vAttributes);\n }\n let transformedAttributeMap = {};\n let directives2 = attributes.map(toTransformedAttributes((newName, oldName) => transformedAttributeMap[newName] = oldName)).filter(outNonAlpineAttributes).map(toParsedDirectives(transformedAttributeMap, originalAttributeOverride)).sort(byPriority);\n return directives2.map((directive2) => {\n return getDirectiveHandler(el, directive2);\n });\n}\nfunction attributesOnly(attributes) {\n return Array.from(attributes).map(toTransformedAttributes()).filter((attr) => !outNonAlpineAttributes(attr));\n}\nvar isDeferringHandlers = false;\nvar directiveHandlerStacks = /* @__PURE__ */ new Map();\nvar currentHandlerStackKey = Symbol();\nfunction deferHandlingDirectives(callback) {\n isDeferringHandlers = true;\n let key = Symbol();\n currentHandlerStackKey = key;\n directiveHandlerStacks.set(key, []);\n let flushHandlers = () => {\n while (directiveHandlerStacks.get(key).length)\n directiveHandlerStacks.get(key).shift()();\n directiveHandlerStacks.delete(key);\n };\n let stopDeferring = () => {\n isDeferringHandlers = false;\n flushHandlers();\n };\n callback(flushHandlers);\n stopDeferring();\n}\nfunction getElementBoundUtilities(el) {\n let cleanups = [];\n let cleanup = (callback) => cleanups.push(callback);\n let [effect3, cleanupEffect] = elementBoundEffect(el);\n cleanups.push(cleanupEffect);\n let utilities = {\n Alpine: alpine_default,\n effect: effect3,\n cleanup,\n evaluateLater: evaluateLater.bind(evaluateLater, el),\n evaluate: evaluate.bind(evaluate, el)\n };\n let doCleanup = () => cleanups.forEach((i) => i());\n return [utilities, doCleanup];\n}\nfunction getDirectiveHandler(el, directive2) {\n let noop = () => {\n };\n let handler4 = directiveHandlers[directive2.type] || noop;\n let [utilities, cleanup] = getElementBoundUtilities(el);\n onAttributeRemoved(el, directive2.original, cleanup);\n let fullHandler = () => {\n if (el._x_ignore || el._x_ignoreSelf)\n return;\n handler4.inline && handler4.inline(el, directive2, utilities);\n handler4 = handler4.bind(handler4, el, directive2, utilities);\n isDeferringHandlers ? directiveHandlerStacks.get(currentHandlerStackKey).push(handler4) : handler4();\n };\n fullHandler.runCleanups = cleanup;\n return fullHandler;\n}\nvar startingWith = (subject, replacement) => ({ name, value }) => {\n if (name.startsWith(subject))\n name = name.replace(subject, replacement);\n return { name, value };\n};\nvar into = (i) => i;\nfunction toTransformedAttributes(callback = () => {\n}) {\n return ({ name, value }) => {\n let { name: newName, value: newValue } = attributeTransformers.reduce((carry, transform) => {\n return transform(carry);\n }, { name, value });\n if (newName !== name)\n callback(newName, name);\n return { name: newName, value: newValue };\n };\n}\nvar attributeTransformers = [];\nfunction mapAttributes(callback) {\n attributeTransformers.push(callback);\n}\nfunction outNonAlpineAttributes({ name }) {\n return alpineAttributeRegex().test(name);\n}\nvar alpineAttributeRegex = () => new RegExp(`^${prefixAsString}([^:^.]+)\\\\b`);\nfunction toParsedDirectives(transformedAttributeMap, originalAttributeOverride) {\n return ({ name, value }) => {\n let typeMatch = name.match(alpineAttributeRegex());\n let valueMatch = name.match(/:([a-zA-Z0-9\\-_:]+)/);\n let modifiers = name.match(/\\.[^.\\]]+(?=[^\\]]*$)/g) || [];\n let original = originalAttributeOverride || transformedAttributeMap[name] || name;\n return {\n type: typeMatch ? typeMatch[1] : null,\n value: valueMatch ? valueMatch[1] : null,\n modifiers: modifiers.map((i) => i.replace(\".\", \"\")),\n expression: value,\n original\n };\n };\n}\nvar DEFAULT = \"DEFAULT\";\nvar directiveOrder = [\n \"ignore\",\n \"ref\",\n \"data\",\n \"id\",\n \"anchor\",\n \"bind\",\n \"init\",\n \"for\",\n \"model\",\n \"modelable\",\n \"transition\",\n \"show\",\n \"if\",\n DEFAULT,\n \"teleport\"\n];\nfunction byPriority(a, b) {\n let typeA = directiveOrder.indexOf(a.type) === -1 ? DEFAULT : a.type;\n let typeB = directiveOrder.indexOf(b.type) === -1 ? DEFAULT : b.type;\n return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB);\n}\n\n// packages/alpinejs/src/utils/dispatch.js\nfunction dispatch(el, name, detail = {}) {\n el.dispatchEvent(\n new CustomEvent(name, {\n detail,\n bubbles: true,\n // Allows events to pass the shadow DOM barrier.\n composed: true,\n cancelable: true\n })\n );\n}\n\n// packages/alpinejs/src/utils/walk.js\nfunction walk(el, callback) {\n if (typeof ShadowRoot === \"function\" && el instanceof ShadowRoot) {\n Array.from(el.children).forEach((el2) => walk(el2, callback));\n return;\n }\n let skip = false;\n callback(el, () => skip = true);\n if (skip)\n return;\n let node = el.firstElementChild;\n while (node) {\n walk(node, callback, false);\n node = node.nextElementSibling;\n }\n}\n\n// packages/alpinejs/src/utils/warn.js\nfunction warn(message, ...args) {\n console.warn(`Alpine Warning: ${message}`, ...args);\n}\n\n// packages/alpinejs/src/lifecycle.js\nvar started = false;\nfunction start() {\n if (started)\n warn(\"Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems.\");\n started = true;\n if (!document.body)\n warn(\"Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `