From 4c8be4bec5d6786b16f201823a965e7533285dfe Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Wed, 16 Sep 2026 10:31:50 +0100 Subject: [PATCH 1/5] =?UTF-8?q?feat(panel):=20draft=20slices=20=E2=80=94?= =?UTF-8?q?=20namespaced=20contributions=20to=20first-party=20edit=20draft?= =?UTF-8?q?s=20(spec=200086)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces the DraftSlice contract and ComposedDraftResource so a namespaced surface (first-party or add-on) declares its fields, rules, labels and commit in its own terms and the panel composes it into the resource's draft under `{namespace}:{field}`. Sections register first-party slices via draftSlices(); add-ons via draftExtensions(), which lands them under the reserved `addon:{key}` namespace. The product's attribute, availability and sole-variant surfaces move onto the contract with no key changes. - Server: DraftSlice contract/abstract, ComposedDraftResource, PanelManager registration with duplicate/reserved/malformed namespace rejection, lazy composition in draftableFor(), discard fan-out through the EditDraft deleting event (EditDraft is now Prunable), stale stored keys dropped at commit. - Seeding: shared `draftSliceValues` Inertia prop resolved from the deepest route-bound record; the product page's hand-built value props go away. - Client: useEditDraft merges the shared prop (opt-out via `slices: false`), exposes dirtyKeys and provides itself; new useDraftSlice / useAddonDraftSlice composable exported to add-ons as `useDraftSlice`. - Example add-on: LoyaltyTierSlice + LoyaltyCard in customers.edit:main:after, README guide section, end-to-end tests against the real customer routes. - Spec 0087 (product editing through the draft) included for review; its implementation follows separately. Co-Authored-By: Claude Fable 5.1 --- TODO.md | 2 + packages/panel-addon-example/README.md | 116 +++++++ .../panel-addon-example/resources/js/addon.ts | 2 + .../resources/js/components/LoyaltyCard.vue | 32 ++ .../resources/lang/en/example.php | 8 + .../resources/lang/fr/example.php | 8 + .../src/Drafts/LoyaltyTierSlice.php | 78 +++++ .../src/ExampleSection.php | 20 ++ .../js/components/DraftActions.test.ts | 1 + .../js/composables/useDraftSlice.test.ts | 172 ++++++++++ .../resources/js/composables/useDraftSlice.ts | 108 ++++++ .../js/composables/useEditDraft.test.ts | 41 ++- .../resources/js/composables/useEditDraft.ts | 60 +++- .../resources/js/pages/products/Edit.vue | 8 +- packages/panel/resources/js/ui.ts | 4 + .../panel/resources/panel-package/index.js | 1 + packages/panel/src/Contracts/DraftSlice.php | 83 +++++ .../src/Drafts/ComposedDraftResource.php | 184 ++++++++++ .../Drafts/Concerns/NormalizesDraftValues.php | 99 ++++++ packages/panel/src/Drafts/DraftManager.php | 20 +- packages/panel/src/Drafts/DraftSlice.php | 50 +++ .../Products/ProductEditController.php | 19 +- .../Middleware/HandlePanelInertiaRequests.php | 27 ++ packages/panel/src/Models/EditDraft.php | 7 +- packages/panel/src/PanelManager.php | 111 +++++- packages/panel/src/PanelServiceProvider.php | 2 + .../src/Sections/Catalog/CatalogSection.php | 16 + .../Sections/Catalog/ProductDraftResource.php | 227 +------------ .../Catalog/Slices/AvailabilitySlice.php | 69 ++++ .../Catalog/Slices/ProductAttributeSlice.php | 87 +++++ .../Catalog/Slices/ProductChannelSlice.php | 29 ++ .../Slices/ProductCustomerGroupSlice.php | 29 ++ .../Catalog/Slices/SoleVariantSlice.php | 99 ++++++ .../panel/src/Sections/ProvidesNavigation.php | 17 + packages/panel/src/Sections/Section.php | 27 ++ .../panel/src/Sections/SectionExtension.php | 23 ++ .../panel/src/Support/AvailabilitySchema.php | 126 +++++-- specs/0086-panel-draft-slices.md | 321 ++++++++++++++++++ .../0087-product-editing-through-the-draft.md | 256 ++++++++++++++ specs/README.md | 2 + .../panel/Feature/Drafts/DraftSlicesTest.php | 274 +++++++++++++++ tests/panel/Feature/ExampleAddonTest.php | 80 +++++ .../Feature/Products/ProductEditTest.php | 2 +- tests/panel/Fixtures/Drafts/MemoSlice.php | 85 +++++ 44 files changed, 2743 insertions(+), 289 deletions(-) create mode 100644 packages/panel-addon-example/resources/js/components/LoyaltyCard.vue create mode 100644 packages/panel-addon-example/src/Drafts/LoyaltyTierSlice.php create mode 100644 packages/panel/resources/js/composables/useDraftSlice.test.ts create mode 100644 packages/panel/resources/js/composables/useDraftSlice.ts create mode 100644 packages/panel/src/Contracts/DraftSlice.php create mode 100644 packages/panel/src/Drafts/ComposedDraftResource.php create mode 100644 packages/panel/src/Drafts/Concerns/NormalizesDraftValues.php create mode 100644 packages/panel/src/Drafts/DraftSlice.php create mode 100644 packages/panel/src/Sections/Catalog/Slices/AvailabilitySlice.php create mode 100644 packages/panel/src/Sections/Catalog/Slices/ProductAttributeSlice.php create mode 100644 packages/panel/src/Sections/Catalog/Slices/ProductChannelSlice.php create mode 100644 packages/panel/src/Sections/Catalog/Slices/ProductCustomerGroupSlice.php create mode 100644 packages/panel/src/Sections/Catalog/Slices/SoleVariantSlice.php create mode 100644 specs/0086-panel-draft-slices.md create mode 100644 specs/0087-product-editing-through-the-draft.md create mode 100644 tests/panel/Feature/Drafts/DraftSlicesTest.php create mode 100644 tests/panel/Fixtures/Drafts/MemoSlice.php diff --git a/TODO.md b/TODO.md index 8a81b47be2..ec134f3554 100644 --- a/TODO.md +++ b/TODO.md @@ -8,6 +8,8 @@ Items tagged _(judgement)_ are genuine line-calls worth revisiting. ## Outstanding +- Panel draft slices — one contract for namespaced draft contributions, used by first-party surfaces and by add-on slot components alike (spec 0086) +- Product editing through the draft — associations, slugs, prices, and media join the product draft as slices; the few operations that stay immediate say so (spec 0087) - Panel order screen improvements — stock visibility, activity pagination, timeline money events, address polish (spec 0069) - Default professional customer notifications for the order lifecycle (spec 0036) _(judgement)_ - Bulk order operations — goal-oriented bulk actions on the orders table (spec 0026) diff --git a/packages/panel-addon-example/README.md b/packages/panel-addon-example/README.md index 49ac49f271..468edf0dcf 100644 --- a/packages/panel-addon-example/README.md +++ b/packages/panel-addon-example/README.md @@ -371,6 +371,119 @@ error, because `SlotRegistry::forPage()` just won't find a match. If your slot isn't appearing, this is the first thing to check (see [Troubleshooting](#troubleshooting)). +## Adding fields to a first-party edit draft + +A slot component on an edit page can take part in that page's save. First-party +edit pages (customers, products, brands, collections, product types, variants) +are driven by an autosaving **edit draft**: dirty fields persist server-side as +staff type, restore when they come back, and commit with field-level conflict +detection. An add-on joins that draft with a **draft slice**: a server-side +class declaring its fields, rules and commit, plus a component that binds to it. + +The panel places every add-on slice under a namespace of its own, +`addon:{key}:`, so it can add to the draft but can never read or write the +resource's own fields, or another add-on's. That is the rule the whole +extension surface follows: add-ons add, only the host subtracts. + +`src/Drafts/LoyaltyTierSlice.php`: + +```php +class LoyaltyTierSlice extends DraftSlice +{ + public function __construct(protected UpdatesCustomer $updatesCustomer) {} + + public function model(): string { return Customer::class; } + + // Bare key; the panel places the slice under addon:example-addon: + public function key(): string { return 'example-addon'; } + + public function fields(Model $record): array { return ['tier']; } + + public function currentValues(Model $record): array + { + return ['tier' => $record->meta['loyalty_tier'] ?? null]; + } + + public function rules(Model $record): array + { + return ['tier' => ['nullable', Rule::in(['bronze', 'silver', 'gold'])]]; + } + + // Runs after the customer's own commit, inside the same transaction, with + // every field present (current values overlaid with the draft). + public function commit(Model $record, array $values): void + { + $meta = $record->meta?->getArrayCopy() ?? []; + $meta['loyalty_tier'] = $values['tier'] ?? null; + + $this->updatesCustomer->execute($record, ['meta' => $meta]); + } + + public function labels(): array + { + return ['tier' => 'example-addon::example.loyalty_tier']; + } +} +``` + +Register it from the section, and register the slot component that edits it: + +```php +public function draftExtensions(): array +{ + return [LoyaltyTierSlice::class]; +} + +public function slots(SlotRegistry $registry): void +{ + $registry->add(new Slot( + zone: 'customers.edit:main:after', + component: 'example-addon::LoyaltyCard', + )); +} +``` + +`resources/js/components/LoyaltyCard.vue` binds to the slice with +`useDraftSlice`, passing the slice's key. The composable finds the page's form +above it in the component tree (every slot zone on an edit page is inside it) +and returns a view scoped to the add-on's namespace: + +```vue + + + +``` + +`slice.values` is a reactive object holding only this slice's fields. +`slice.errors` carries commit-time validation errors keyed by bare field name, +`slice.field('tier')` gives a writable ref for one field, and `slice.isDirty`, +`slice.saving` and `slice.committing` mirror the page form. Writing to a field +the slice did not declare throws. + +What the add-on gets for free: autosave and restore, the dirty-navigation +guard, per-field conflict detection (a conflicting tier shows in the page's +conflict dialog under the label from `labels()`), 422 mapping, and an atomic +commit with the customer's own fields. If the slice's `commit()` throws, the +customer's changes roll back too. + +Slices only apply on edit pages. Create pages post a plain form and redirect; +there is no draft and no record until the store succeeds. + +Optional: implement `discard(Model $record, EditDraft $draft)` when a slice +holds state outside the draft's JSON columns (staged uploads, for instance). +It is called when a draft holding the slice's keys is discarded or pruned. + ## Registering a table extension A `TableExtension` bundles one or more `TableColumn`s (plus optional filters @@ -931,6 +1044,9 @@ pages, not only in an isolated fixture. actions injected into the first-party customers table. - `src/Actions/ImportPageAction.php` / `AuditPageAction.php` — the listing- and record-page header actions. +- `src/Drafts/LoyaltyTierSlice.php` / `resources/js/components/LoyaltyCard.vue` + — the draft slice added to the customer edit draft and the slot component + that edits it. - `resources/js/addon.ts` — the IIFE entry point. - `resources/js/pages/Widgets/Index.vue`, `resources/js/components/InfoBanner.vue` — the example page and slot component. diff --git a/packages/panel-addon-example/resources/js/addon.ts b/packages/panel-addon-example/resources/js/addon.ts index 7b0aa93e6c..2e80500681 100644 --- a/packages/panel-addon-example/resources/js/addon.ts +++ b/packages/panel-addon-example/resources/js/addon.ts @@ -2,6 +2,7 @@ import WidgetsIndexPage from './pages/Widgets/Index.vue'; import SettingsIndexPage from './pages/Settings/Index.vue'; import CustomerCountWidgetComponent from './components/CustomerCountWidget.vue'; import InfoBannerComponent from './components/InfoBanner.vue'; +import LoyaltyCardComponent from './components/LoyaltyCard.vue'; import SeoCardComponent from './components/SeoCard.vue'; // Register eagerly. The panel's app.ts publishes window.LunarPanel and is emitted @@ -18,5 +19,6 @@ window.LunarPanel.registerPages({ window.LunarPanel.registerComponents('example-addon', { CustomerCountWidget: CustomerCountWidgetComponent, InfoBanner: InfoBannerComponent, + LoyaltyCard: LoyaltyCardComponent, SeoCard: SeoCardComponent, }); diff --git a/packages/panel-addon-example/resources/js/components/LoyaltyCard.vue b/packages/panel-addon-example/resources/js/components/LoyaltyCard.vue new file mode 100644 index 0000000000..568d375d3a --- /dev/null +++ b/packages/panel-addon-example/resources/js/components/LoyaltyCard.vue @@ -0,0 +1,32 @@ + + + diff --git a/packages/panel-addon-example/resources/lang/en/example.php b/packages/panel-addon-example/resources/lang/en/example.php index 2106de7646..b1d03003ee 100644 --- a/packages/panel-addon-example/resources/lang/en/example.php +++ b/packages/panel-addon-example/resources/lang/en/example.php @@ -18,4 +18,12 @@ 'widget_description' => 'Total customers, and how many joined in the selected range.', 'widget_total' => 'Total customers', 'widget_recent' => 'New in range', + 'loyalty_title' => 'Loyalty', + 'loyalty_description' => 'A field contributed by the example add-on. It saves with the rest of the customer through the edit draft.', + 'loyalty_tier' => 'Loyalty tier', + 'loyalty_tier_none' => 'No tier', + 'loyalty_tier_bronze' => 'Bronze', + 'loyalty_tier_silver' => 'Silver', + 'loyalty_tier_gold' => 'Gold', + 'loyalty_unsaved' => 'Changes save with the customer.', ]; diff --git a/packages/panel-addon-example/resources/lang/fr/example.php b/packages/panel-addon-example/resources/lang/fr/example.php index e066567e3d..a3051aacf4 100644 --- a/packages/panel-addon-example/resources/lang/fr/example.php +++ b/packages/panel-addon-example/resources/lang/fr/example.php @@ -18,4 +18,12 @@ 'widget_description' => 'Le total des clients, et combien ont rejoint sur la période sélectionnée.', 'widget_total' => 'Clients au total', 'widget_recent' => 'Nouveaux sur la période', + 'loyalty_title' => 'Fidélité', + 'loyalty_description' => 'Un champ fourni par l\'add-on d\'exemple. Il s\'enregistre avec le reste du client via le brouillon de modification.', + 'loyalty_tier' => 'Niveau de fidélité', + 'loyalty_tier_none' => 'Aucun niveau', + 'loyalty_tier_bronze' => 'Bronze', + 'loyalty_tier_silver' => 'Argent', + 'loyalty_tier_gold' => 'Or', + 'loyalty_unsaved' => 'Les modifications s\'enregistrent avec le client.', ]; diff --git a/packages/panel-addon-example/src/Drafts/LoyaltyTierSlice.php b/packages/panel-addon-example/src/Drafts/LoyaltyTierSlice.php new file mode 100644 index 0000000000..1dd16a80b7 --- /dev/null +++ b/packages/panel-addon-example/src/Drafts/LoyaltyTierSlice.php @@ -0,0 +1,78 @@ + $record->meta['loyalty_tier'] ?? null]; + } + + public function normalize(array $data): array + { + // The select submits '' for "no tier"; the stored value is null. + if (array_key_exists('tier', $data) && $data['tier'] === '') { + $data['tier'] = null; + } + + return $data; + } + + public function rules(Model $record): array + { + return ['tier' => ['nullable', Rule::in(self::TIERS)]]; + } + + public function commit(Model $record, array $values): void + { + /** @var Customer $record */ + $meta = $record->meta?->getArrayCopy() ?? []; + $meta['loyalty_tier'] = $values['tier'] ?? null; + + $this->updatesCustomer->execute($record, ['meta' => $meta]); + } + + public function labels(): array + { + return ['tier' => 'example-addon::example.loyalty_tier']; + } +} diff --git a/packages/panel-addon-example/src/ExampleSection.php b/packages/panel-addon-example/src/ExampleSection.php index 9c04d11125..a5e00cb436 100644 --- a/packages/panel-addon-example/src/ExampleSection.php +++ b/packages/panel-addon-example/src/ExampleSection.php @@ -15,6 +15,7 @@ use LunarPanelExample\Actions\AuditPageAction; use LunarPanelExample\Actions\ImportPageAction; use LunarPanelExample\Dashboard\CustomerCountWidget; +use LunarPanelExample\Drafts\LoyaltyTierSlice; use LunarPanelExample\Search\CustomerEmailSearchSource; use LunarPanelExample\Search\PingWidgetsCommand; use LunarPanelExample\Tables\ExampleTableExtension; @@ -159,6 +160,14 @@ public function slots(SlotRegistry $registry): void props: ['message' => 'This banner was injected by the example add-on via a slot.'], )); + // A slot component that takes part in the page's save: LoyaltyCard binds + // to the LoyaltyTierSlice below through useDraftSlice('example-addon'), + // so its field autosaves and commits with the customer's own. + $registry->add(new Slot( + zone: 'customers.edit:main:after', + component: 'example-addon::LoyaltyCard', + )); + // The canonical slot example (spec 0049/0057): the product edit page // deliberately ships no SEO section — an add-on injects one into the // content-adjacent zone between the content cluster and the variants @@ -174,6 +183,17 @@ public function tableExtensions(): array return ['customers.index' => ExampleTableExtension::class]; } + /** + * Contribute fields to a first-party edit draft. The panel places each + * slice under `addon:{key}` (here `addon:example-addon:`), so it can add + * to the customer draft but never reach the customer's own fields or + * another add-on's. + */ + public function draftExtensions(): array + { + return [LoyaltyTierSlice::class]; + } + /** * Contribute a dashboard widget. Staff reorder, hide, and re-add it like * any first-party widget; its data ships as a deferred Inertia prop. diff --git a/packages/panel/resources/js/components/DraftActions.test.ts b/packages/panel/resources/js/components/DraftActions.test.ts index b5cef05ddd..3f6bd183ea 100644 --- a/packages/panel/resources/js/components/DraftActions.test.ts +++ b/packages/panel/resources/js/components/DraftActions.test.ts @@ -10,6 +10,7 @@ function fakeForm(overrides: Partial> = {}): EditDraftFo errors: ref({}), conflicts: ref([]), isDirty: computed(() => false), + dirtyKeys: computed(() => []), saving: ref(false), committing: ref(false), savedAt: ref(null), diff --git a/packages/panel/resources/js/composables/useDraftSlice.test.ts b/packages/panel/resources/js/composables/useDraftSlice.test.ts new file mode 100644 index 0000000000..02b8806e49 --- /dev/null +++ b/packages/panel/resources/js/composables/useDraftSlice.test.ts @@ -0,0 +1,172 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { defineComponent, h, nextTick } from 'vue'; +import { mount } from '@vue/test-utils'; +import { ValidationError } from '../lib/http'; +import { useEditDraft, type EditDraftForm } from './useEditDraft'; +import { bindDraftSlice, useAddonDraftSlice, useDraftSlice, type DraftSlice } from './useDraftSlice'; + +const { httpMock, pageProps } = vi.hoisted(() => ({ + httpMock: { + patch: vi.fn(), + post: vi.fn(), + delete: vi.fn(), + }, + pageProps: {} as Record, +})); + +vi.mock('../lib/http', async (importOriginal) => ({ + ...(await importOriginal()), + http: httpMock, +})); + +vi.mock('@inertiajs/vue3', () => ({ + router: { reload: vi.fn(), on: vi.fn(() => () => {}) }, + usePage: () => ({ props: pageProps }), +})); + +const urls = { draft: '/customers/1/draft', commit: '/customers/1/draft/commit' }; + +function buildForm(): EditDraftForm> { + return useEditDraft({ + initial: { first_name: 'Ada', 'addon:loyalty:tier': 'bronze', 'addon:loyalty:note': '', 'association:up-sell': [1, 2] }, + draft: null, + urls, + }); +} + +describe('bindDraftSlice', () => { + beforeEach(() => { + vi.useFakeTimers(); + httpMock.patch.mockResolvedValue({ data: {}, updated_at: null }); + httpMock.post.mockResolvedValue({ committed: true }); + }); + + afterEach(() => { + vi.clearAllMocks(); + vi.useRealTimers(); + }); + + it('scopes reads, writes and enumeration to the namespace', () => { + const form = buildForm(); + const slice = bindDraftSlice<{ tier: string; note: string }>(form, 'addon:loyalty'); + + expect(slice.values.tier).toBe('bronze'); + expect(Object.keys(slice.values)).toEqual(['tier', 'note']); + expect('tier' in slice.values).toBe(true); + expect((slice.values as Record).first_name).toBeUndefined(); + + slice.values.tier = 'gold'; + + expect(form.values['addon:loyalty:tier']).toBe('gold'); + expect(form.dirtyKeys.value).toEqual(['addon:loyalty:tier']); + expect(slice.isDirty.value).toBe(true); + }); + + it('stays clean while only other namespaces change', () => { + const form = buildForm(); + const slice = bindDraftSlice(form, 'addon:loyalty'); + + form.values.first_name = 'Grace'; + form.values['association:up-sell'] = [2]; + + expect(form.isDirty.value).toBe(true); + expect(slice.isDirty.value).toBe(false); + }); + + it('refuses a field the namespace does not declare', () => { + const slice = bindDraftSlice(buildForm(), 'addon:loyalty'); + + expect(() => { + (slice.values as Record).points = 10; + }).toThrow('Draft slice [addon:loyalty] has no field [points].'); + }); + + it('exposes a writable ref per field', async () => { + const form = buildForm(); + const tier = bindDraftSlice<{ tier: string }>(form, 'addon:loyalty').field('tier'); + + expect(tier.value).toBe('bronze'); + + tier.value = 'silver'; + await nextTick(); + + expect(tier.value).toBe('silver'); + expect(form.values['addon:loyalty:tier']).toBe('silver'); + }); + + it('maps commit errors back to bare field names', async () => { + const form = buildForm(); + const slice = bindDraftSlice(form, 'addon:loyalty'); + + httpMock.post.mockRejectedValueOnce( + new ValidationError({ 'addon:loyalty:tier': ['The tier is invalid.'], first_name: ['Required.'] }), + ); + + await expect(form.commit()).resolves.toBe(false); + + expect(slice.errors.value).toEqual({ tier: 'The tier is invalid.' }); + }); +}); + +describe('useDraftSlice', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + function mountWith(child: ReturnType) { + const Page = defineComponent({ + setup() { + useEditDraft({ initial: { 'addon:loyalty:tier': 'bronze', 'association:up-sell': [] }, draft: null, urls }); + + return () => h('div', [h(child)]); + }, + }); + + return mount(Page); + } + + it('injects the enclosing page form and prefixes an add-on key', () => { + let slice: DraftSlice<{ tier: string }> | undefined; + + const Child = defineComponent({ + setup() { + slice = useAddonDraftSlice<{ tier: string }>('loyalty'); + + return () => h('span', slice?.values.tier); + }, + }); + + const wrapper = mountWith(Child); + + expect(wrapper.text()).toBe('bronze'); + expect(slice?.values.tier).toBe('bronze'); + }); + + it('binds a first-party namespace as given', () => { + let keys: string[] = []; + + const Child = defineComponent({ + setup() { + keys = Object.keys(useDraftSlice('association').values); + + return () => null; + }, + }); + + mountWith(Child); + + expect(keys).toEqual(['up-sell']); + }); + + it('throws a descriptive error outside a draft-backed page', () => { + const Orphan = defineComponent({ + setup() { + useAddonDraftSlice('loyalty'); + + return () => null; + }, + }); + + expect(() => mount(Orphan)).toThrow("useDraftSlice('addon:loyalty') needs a page driven by useEditDraft"); + }); +}); diff --git a/packages/panel/resources/js/composables/useDraftSlice.ts b/packages/panel/resources/js/composables/useDraftSlice.ts new file mode 100644 index 0000000000..b46e6146ca --- /dev/null +++ b/packages/panel/resources/js/composables/useDraftSlice.ts @@ -0,0 +1,108 @@ +import { computed, inject, type ComputedRef, type Ref, type WritableComputedRef } from 'vue'; +import { editDraftFormKey, type EditDraftForm } from './useEditDraft'; + +/** + * A namespaced view onto the page's edit draft. Reads and writes go to the + * form's `{namespace}:{field}` keys, so the slice's fields autosave, restore, + * conflict-check and commit with the resource's own; nothing here reaches + * another namespace or the underlying form. + */ +export interface DraftSlice = Record> { + /** Reactive values scoped to the namespace; `v-model="slice.values.tier"` works. */ + values: T; + /** Commit-time validation errors for this namespace, keyed by bare field. */ + errors: ComputedRef>; + /** A writable ref for one field. */ + field: (name: K) => WritableComputedRef; + /** Whether any of the namespace's fields differ from their pristine value. */ + isDirty: ComputedRef; + saving: Ref; + committing: Ref; +} + +/** + * Scope a form to one namespace. Exported for the composable's tests; page + * code goes through useDraftSlice() / useAddonDraftSlice(). + */ +export function bindDraftSlice>( + form: EditDraftForm>, + namespace: string, +): DraftSlice { + const prefix = `${namespace}:`; + const target = form.values; + + const known = (prop: string | symbol): prop is string => typeof prop === 'string' && `${prefix}${prop}` in target; + + // A Proxy rather than a copy: reads track the form's reactive values and + // writes land on them, so autosave and dirty state see the change. + const values = new Proxy({} as T, { + get: (_, prop) => (known(prop) ? target[`${prefix}${prop}`] : undefined), + set: (_, prop, value) => { + if (!known(prop)) { + throw new Error(`Draft slice [${namespace}] has no field [${String(prop)}].`); + } + + target[`${prefix}${prop}`] = value; + + return true; + }, + has: (_, prop) => known(prop), + ownKeys: () => + Object.keys(target) + .filter((key) => key.startsWith(prefix)) + .map((key) => key.slice(prefix.length)), + getOwnPropertyDescriptor: (_, prop) => + known(prop) + ? { enumerable: true, configurable: true, writable: true, value: target[`${prefix}${prop}`] } + : undefined, + }); + + const errors = computed>(() => + Object.fromEntries( + Object.entries(form.errors.value) + .filter(([key]) => key.startsWith(prefix)) + .map(([key, message]) => [key.slice(prefix.length), message]), + ), + ); + + const isDirty = computed(() => form.dirtyKeys.value.some((key) => key.startsWith(prefix))); + + const field = (name: K): WritableComputedRef => + computed({ + get: () => values[name], + set: (value: T[K]) => { + values[name] = value; + }, + }); + + return { values, errors, field, isDirty, saving: form.saving, committing: form.committing }; +} + +function injectForm(namespace: string): EditDraftForm> { + const form = inject(editDraftFormKey, null); + + if (!form) { + throw new Error( + `useDraftSlice('${namespace}') needs a page driven by useEditDraft above it in the component tree.`, + ); + } + + return form; +} + +/** + * Bind to a first-party slice by its bare namespace, e.g. `association`. + */ +export function useDraftSlice = Record>(namespace: string): DraftSlice { + return bindDraftSlice(injectForm(namespace), namespace); +} + +/** + * Bind to an add-on slice by its key. Slices registered through + * Section::draftExtensions() live under `addon:{key}`, and this is the form + * `@lunarphp/panel` exports as useDraftSlice, so an add-on component only + * ever names its own key. + */ +export function useAddonDraftSlice = Record>(key: string): DraftSlice { + return useDraftSlice(`addon:${key}`); +} diff --git a/packages/panel/resources/js/composables/useEditDraft.test.ts b/packages/panel/resources/js/composables/useEditDraft.test.ts index 536e9211c1..f2d47cceec 100644 --- a/packages/panel/resources/js/composables/useEditDraft.test.ts +++ b/packages/panel/resources/js/composables/useEditDraft.test.ts @@ -3,7 +3,7 @@ import { nextTick } from 'vue'; import { DraftConflictError, ValidationError } from '../lib/http'; import { useEditDraft } from './useEditDraft'; -const { httpMock, reloadMock, routerOnMock } = vi.hoisted(() => ({ +const { httpMock, reloadMock, routerOnMock, pageProps } = vi.hoisted(() => ({ httpMock: { patch: vi.fn(), post: vi.fn(), @@ -11,6 +11,7 @@ const { httpMock, reloadMock, routerOnMock } = vi.hoisted(() => ({ }, reloadMock: vi.fn(), routerOnMock: vi.fn((_event: string, _handler: unknown) => () => {}), + pageProps: {} as Record, })); vi.mock('../lib/http', async (importOriginal) => ({ @@ -20,6 +21,7 @@ vi.mock('../lib/http', async (importOriginal) => ({ vi.mock('@inertiajs/vue3', () => ({ router: { reload: reloadMock, on: routerOnMock }, + usePage: () => ({ props: pageProps }), })); const urls = { draft: '/customers/1/draft', commit: '/customers/1/draft/commit' }; @@ -41,6 +43,43 @@ describe('useEditDraft', () => { afterEach(() => { vi.clearAllMocks(); vi.useRealTimers(); + delete pageProps.draftSliceValues; + }); + + it('seeds draft slice values from the shared page prop', async () => { + pageProps.draftSliceValues = { 'addon:loyalty:tier': 'bronze' }; + + const form = useEditDraft({ + initial: { first_name: 'Original' }, + draft: { data: { 'addon:loyalty:tier': 'gold' }, updated_at: null }, + urls, + }); + + const values = form.values as Record; + + // Restored from the draft because the key was seeded, and diffed + // against the seeded pristine value. + expect(values['addon:loyalty:tier']).toBe('gold'); + expect(form.dirtyKeys.value).toEqual(['addon:loyalty:tier']); + + values['addon:loyalty:tier'] = 'bronze'; + await flushAutosave(); + + expect(httpMock.delete).toHaveBeenCalledWith(urls.draft); + }); + + it('skips slice seeding when the page opts out', () => { + pageProps.draftSliceValues = { 'addon:loyalty:tier': 'bronze' }; + + const form = useEditDraft({ + initial: { first_name: 'Original' }, + draft: { data: { 'addon:loyalty:tier': 'gold' }, updated_at: null }, + urls, + slices: false, + }); + + expect('addon:loyalty:tier' in form.values).toBe(false); + expect(form.isDirty.value).toBe(false); }); it('overlays a stored draft onto the initial values', () => { diff --git a/packages/panel/resources/js/composables/useEditDraft.ts b/packages/panel/resources/js/composables/useEditDraft.ts index 1d4ff0e72a..92cfcc2d87 100644 --- a/packages/panel/resources/js/composables/useEditDraft.ts +++ b/packages/panel/resources/js/composables/useEditDraft.ts @@ -1,5 +1,17 @@ -import { computed, getCurrentInstance, getCurrentScope, onScopeDispose, reactive, ref, watch, type ComputedRef, type Ref } from 'vue'; -import { router } from '@inertiajs/vue3'; +import { + computed, + getCurrentInstance, + getCurrentScope, + onScopeDispose, + provide, + reactive, + ref, + watch, + type ComputedRef, + type InjectionKey, + type Ref, +} from 'vue'; +import { router, usePage } from '@inertiajs/vue3'; import { useI18n } from 'vue-i18n'; import { DraftConflictError, ValidationError, http, type DraftConflict } from '../lib/http'; @@ -16,6 +28,12 @@ export interface EditDraftOptions> { commit: string; }; debounceMs?: number; + /** + * Seed the form with the page's shared `draftSliceValues` prop (the + * prefixed current values of every draft slice on the route's deepest + * record). Off for a page that drafts some other record. + */ + slices?: boolean; } export interface EditDraftForm> { @@ -23,6 +41,8 @@ export interface EditDraftForm> { errors: Ref>; conflicts: Ref; isDirty: ComputedRef; + /** The keys whose value differs from pristine. */ + dirtyKeys: ComputedRef; saving: Ref; committing: Ref; savedAt: Ref; @@ -33,6 +53,21 @@ export interface EditDraftForm> { discard: () => Promise; } +/** + * The page's form, provided by useEditDraft() so components further down the + * tree (first-party cards, add-on slot components) can bind a draft slice. + */ +export const editDraftFormKey: InjectionKey>> = Symbol('lunar-panel:edit-draft-form'); + +// The shared prop is absent outside an Inertia page (unit tests, tooling). +function sharedSliceValues(): Record { + try { + return (usePage().props.draftSliceValues as Record | undefined) ?? {}; + } catch { + return {}; + } +} + // JSON round-trip rather than structuredClone: draft values are JSON-shaped // by construction, and this also unwraps Vue reactive proxies safely. function clone(value: T): T { @@ -76,11 +111,16 @@ function encode(value: unknown): string { export function useEditDraft>(options: EditDraftOptions): EditDraftForm { const debounceMs = options.debounceMs ?? 750; + // Slice values seed alongside the page's own initial values so their + // keys autosave, restore, diff and guard like any other; the page's + // explicit initial wins where both name a key. + const initial = { ...(options.slices === false ? {} : sharedSliceValues()), ...options.initial } as T; + // Reactive so isDirty recomputes when a successful commit re-baselines // pristine to the committed values — a plain object would leave the stale // dirty state cached until the next keystroke. - const pristine = reactive>(clone(options.initial)); - const values = reactive(clone(options.initial)) as T; + const pristine = reactive>(clone(initial)); + const values = reactive(clone(initial)) as T; for (const [key, value] of Object.entries(options.draft?.data ?? {})) { if (key in values) { @@ -110,7 +150,8 @@ export function useEditDraft>(options: EditDra return changed; }; - const isDirty = computed(() => Object.keys(diff()).length > 0); + const dirtyKeys = computed(() => Object.keys(diff())); + const isDirty = computed(() => dirtyKeys.value.length > 0); // Leaving the page with uncommitted changes prompts first — the edits // survive as a draft, but staff shouldn't navigate away believing they @@ -284,11 +325,12 @@ export function useEditDraft>(options: EditDra conflicts.value = []; }; - return { + const form: EditDraftForm = { values, errors, conflicts, isDirty, + dirtyKeys, saving, committing, savedAt, @@ -298,4 +340,10 @@ export function useEditDraft>(options: EditDra resolve, discard, }; + + if (getCurrentInstance()) { + provide(editDraftFormKey, form as EditDraftForm>); + } + + return form; } diff --git a/packages/panel/resources/js/pages/products/Edit.vue b/packages/panel/resources/js/pages/products/Edit.vue index be1152e020..9eb0ce85a0 100644 --- a/packages/panel/resources/js/pages/products/Edit.vue +++ b/packages/panel/resources/js/pages/products/Edit.vue @@ -91,7 +91,6 @@ const props = defineProps<{ stock: { aggregate: StockAggregate; levels: StockLevelRow[] }; urls: { pricesStore: string; stockAdjust: string }; } | null; - variantValues: Record; variantAttributeGroups: AttributeGroup[]; currencies: CurrencyOption[]; customerGroups: { id: number; name: string }[]; @@ -102,9 +101,7 @@ const props = defineProps<{ mediaGroups: MediaGroup[]; productUrls: UrlRow[]; attributeGroups: AttributeGroup[]; - attributeValues: Record; availability: { channels: AvailabilityRow[]; customer_groups: AvailabilityRow[] }; - availabilityValues: Record; brandOptions: { value: number; label: string }[]; typeOptions: { value: number; label: string }[]; collections: CollectionOption[]; @@ -153,9 +150,8 @@ const draftForm = useEditDraft({ // Mapped attribute values ride the same draft under attribute:{handle} // keys; availability rows under channel:{id} / customer_group:{id}; // on the simple shape the sole variant's fields under variant:{field}. - ...props.attributeValues, - ...props.availabilityValues, - ...props.variantValues, + // All three are draft slices, seeded from the shared draftSliceValues + // prop by useEditDraft. }, draft: props.draft, urls: { draft: props.urls.draft, commit: props.urls.draftCommit }, diff --git a/packages/panel/resources/js/ui.ts b/packages/panel/resources/js/ui.ts index 9c1ad0cee8..87e1fc4fff 100644 --- a/packages/panel/resources/js/ui.ts +++ b/packages/panel/resources/js/ui.ts @@ -59,6 +59,10 @@ export { default as DraftActions } from './components/DraftActions.vue'; export { default as DraftConflictDialog } from './components/DraftConflictDialog.vue'; export { useEditDraft } from './composables/useEditDraft'; export type { DraftState, EditDraftForm, EditDraftOptions } from './composables/useEditDraft'; +// Add-on slices live under `addon:{key}`; the exported composable applies +// that prefix so a slot component only ever names its own key. +export { useAddonDraftSlice as useDraftSlice } from './composables/useDraftSlice'; +export type { DraftSlice } from './composables/useDraftSlice'; export { DraftConflictError, HttpError, ValidationError, http } from './lib/http'; export type { DraftConflict } from './lib/http'; diff --git a/packages/panel/resources/panel-package/index.js b/packages/panel/resources/panel-package/index.js index d4e1d75184..2ca1047018 100644 --- a/packages/panel/resources/panel-package/index.js +++ b/packages/panel/resources/panel-package/index.js @@ -55,6 +55,7 @@ export const ValuePreviewChip = ui().ValuePreviewChip; export const DraftActions = ui().DraftActions; export const DraftConflictDialog = ui().DraftConflictDialog; export const useEditDraft = (...args) => ui().useEditDraft(...args); +export const useDraftSlice = (...args) => ui().useDraftSlice(...args); export const http = { get: (...args) => ui().http.get(...args), post: (...args) => ui().http.post(...args), diff --git a/packages/panel/src/Contracts/DraftSlice.php b/packages/panel/src/Contracts/DraftSlice.php new file mode 100644 index 0000000000..f7400cb2df --- /dev/null +++ b/packages/panel/src/Contracts/DraftSlice.php @@ -0,0 +1,83 @@ + */ + public function model(): string; + + /** + * The namespace this slice owns on the model: `[a-z0-9_-]+`, unique per + * model. `addon` is reserved for the public extension hook. + */ + public function key(): string; + + /** + * The unprefixed field names for this record. Row-shaped slices derive + * the set from data (which channels exist, which currencies are enabled). + * + * @return array + */ + public function fields(Model $record): array; + + /** + * The current, normalised stored value of every field, keyed by + * unprefixed field name. + * + * @return array + */ + public function currentValues(Model $record): array; + + /** + * Normalise incoming values into the shape currentValues() reports so + * equality comparison holds. Receives only this slice's keys. + * + * @param array $data + * @return array + */ + public function normalize(array $data): array; + + /** + * Validation rules keyed by unprefixed field (`.*` entries included); + * the composer prefixes the keys. Rule parameters pass through verbatim, + * so a parameter naming one of this slice's own fields needs the full key. + * + * @return array + */ + public function rules(Model $record): array; + + /** + * Persist this slice's values: every field present, current values + * overlaid with the draft, unprefixed. Runs after the resource's own + * commit inside the same transaction, and delegates to core actions. + * + * @param array $values + */ + public function commit(Model $record, array $values): void; + + /** + * Unprefixed field to lang key, for the conflict dialog and validation + * messages. Fields without an entry fall back to their raw key. + * + * @return array + */ + public function labels(): array; + + /** + * Called when a draft holding this slice's keys is discarded or pruned, + * for slices that keep state outside the draft's JSON columns. + */ + public function discard(Model $record, EditDraft $draft): void; +} diff --git a/packages/panel/src/Drafts/ComposedDraftResource.php b/packages/panel/src/Drafts/ComposedDraftResource.php new file mode 100644 index 0000000000..9798d4f845 --- /dev/null +++ b/packages/panel/src/Drafts/ComposedDraftResource.php @@ -0,0 +1,184 @@ + $slices keyed by namespace, in registration order + */ + public function __construct( + protected DraftableResource $resource, + protected array $slices, + protected Model $record, + ) {} + + public function resource(): DraftableResource + { + return $this->resource; + } + + /** @return array */ + public function slices(): array + { + return $this->slices; + } + + public function model(): string + { + return $this->resource->model(); + } + + public function fields(): array + { + $fields = $this->resource->fields(); + + foreach ($this->slices as $namespace => $slice) { + foreach ($slice->fields($this->record) as $field) { + $fields[] = $this->prefix($namespace, $field); + } + } + + return $fields; + } + + public function currentValues(Model $record): array + { + return [...$this->resource->currentValues($record), ...$this->sliceValues($record)]; + } + + /** + * The prefixed current values of every slice, without the resource's + * own: what an edit page seeds its form with. + * + * @return array + */ + public function sliceValues(Model $record): array + { + $values = []; + + foreach ($this->slices as $namespace => $slice) { + foreach ($slice->currentValues($record) as $field => $value) { + $values[$this->prefix($namespace, $field)] = $value; + } + } + + return $values; + } + + public function normalize(array $data): array + { + $partitioned = $this->partition($data); + + $normalized = $this->resource->normalize($partitioned['resource']); + + foreach ($partitioned['slices'] as $namespace => $values) { + foreach ($this->slices[$namespace]->normalize($values) as $field => $value) { + $normalized[$this->prefix($namespace, $field)] = $value; + } + } + + return $normalized; + } + + public function rules(Model $record): array + { + $rules = $this->resource->rules($record); + + foreach ($this->slices as $namespace => $slice) { + foreach ($slice->rules($record) as $field => $fieldRules) { + $rules[$this->prefix($namespace, $field)] = $fieldRules; + } + } + + return $rules; + } + + /** + * The resource commits first with its own keys, then each slice with its + * unprefixed values in registration order. DraftManager wraps the whole + * call in one transaction, so a failing slice rolls the resource back. + */ + public function commit(Model $record, array $values): void + { + $partitioned = $this->partition($values); + + $this->resource->commit($record, $partitioned['resource']); + + foreach ($this->slices as $namespace => $slice) { + $slice->commit($record, $partitioned['slices'][$namespace] ?? []); + } + } + + public function labels(): array + { + $labels = $this->resource->labels(); + + foreach ($this->slices as $namespace => $slice) { + foreach ($slice->labels() as $field => $label) { + $labels[$this->prefix($namespace, $field)] = $label; + } + } + + return $labels; + } + + /** + * Fan a discarded or pruned draft out to the slices whose keys it held. + */ + public function discard(Model $record, EditDraft $draft): void + { + $partitioned = $this->partition($draft->data ?? []); + + foreach ($partitioned['slices'] as $namespace => $values) { + $this->slices[$namespace]->discard($record, $draft); + } + } + + protected function prefix(string $namespace, string $field): string + { + return "{$namespace}:{$field}"; + } + + /** + * Split a flat key set into the resource's own keys and each slice's + * unprefixed keys. Namespaces are unique per model and the `addon:` form + * is reserved, so a key matches at most one prefix. + * + * @param array $data + * @return array{resource: array, slices: array>} + */ + protected function partition(array $data): array + { + $resource = []; + $slices = []; + + foreach ($data as $key => $value) { + foreach ($this->slices as $namespace => $slice) { + $prefix = $this->prefix($namespace, ''); + + if (str_starts_with($key, $prefix)) { + $slices[$namespace][substr($key, strlen($prefix))] = $value; + + continue 2; + } + } + + $resource[$key] = $value; + } + + return ['resource' => $resource, 'slices' => $slices]; + } +} diff --git a/packages/panel/src/Drafts/Concerns/NormalizesDraftValues.php b/packages/panel/src/Drafts/Concerns/NormalizesDraftValues.php new file mode 100644 index 0000000000..e9f017dc73 --- /dev/null +++ b/packages/panel/src/Drafts/Concerns/NormalizesDraftValues.php @@ -0,0 +1,99 @@ + $ids + * @return array + */ + protected function sortedIds(array $ids): array + { + $ids = array_values(array_unique(array_map('intval', $ids))); + + sort($ids); + + return $ids; + } + + /** + * Attribute values arrive in whatever shape their field type stores; + * translated-text maps get key-sorted with blank entries dropped so + * equality against the stored value holds. Sequential arrays keep their + * order, as do keyed list values. + */ + protected function normalizeAttributeValue(mixed $value, ?string $token = null): mixed + { + if (! is_array($value)) { + return $value; + } + + if (array_is_list($value)) { + return $value; + } + + if ($token === 'list') { + return array_map(fn (mixed $item) => is_string($item) ? $item : (string) $item, $value); + } + + return $this->translationMap($value); + } + + /** + * Normalise a `{locale: text}` translation map so equality against the + * stored value holds: empty values are dropped and keys are sorted. + * + * @param array $map + * @return array + */ + protected function translationMap(array $map): array + { + $map = array_filter( + array_map(fn (mixed $value) => is_string($value) ? $value : (string) $value, $map), + fn (string $value) => $value !== '', + ); + + ksort($map); + + return $map; + } + + /** + * Re-key a prefixed map (`attribute:handle` => value) by the bare field + * name, for slices built on helpers that speak in full draft keys. + * + * @template TValue + * + * @param array $keyed + * @return array + */ + protected function stripPrefix(array $keyed, string $prefix): array + { + $stripped = []; + + foreach ($keyed as $key => $value) { + $stripped[str_starts_with($key, $prefix) ? substr($key, strlen($prefix)) : $key] = $value; + } + + return $stripped; + } + + /** + * Strip a prefix from a list of full draft keys. + * + * @param array $keys + * @return array + */ + protected function stripPrefixFromList(array $keys, string $prefix): array + { + return array_values(array_map( + fn (string $key) => str_starts_with($key, $prefix) ? substr($key, strlen($prefix)) : $key, + $keys, + )); + } +} diff --git a/packages/panel/src/Drafts/DraftManager.php b/packages/panel/src/Drafts/DraftManager.php index 4f5c2d3f9f..52812c8b5f 100644 --- a/packages/panel/src/Drafts/DraftManager.php +++ b/packages/panel/src/Drafts/DraftManager.php @@ -63,8 +63,10 @@ public function commit(DraftableResource $resource, Model $draftable, Authentica // Overlay the request's final diff onto the stored draft — unlike // autosave's wholesale replace, commit must not drop fields another - // tab may have drafted since this client last loaded. - $merged = [...($draft?->data ?? []), ...$data]; + // tab may have drafted since this client last loaded. Stored keys the + // resource no longer declares (a removed add-on, a deleted channel + // row) are dropped rather than left to block the commit. + $merged = [...$this->knownFields($resource, $draft?->data ?? []), ...$data]; if ($merged === []) { return CommitResult::committed(); @@ -106,7 +108,10 @@ public function commit(DraftableResource $resource, Model $draftable, Authentica $this->db->connection()->transaction(function () use ($resource, $draftable, $current, $merged, $draft): void { $resource->commit($draftable, [...$current, ...$merged]); - $draft->delete(); + + // Quietly: a committed draft is consumed, not discarded, so the + // slice discard hooks wired to the deleting event must not fire. + $draft->deleteQuietly(); }); return CommitResult::committed(); @@ -214,6 +219,15 @@ protected function sortKeys(mixed $value): mixed return $value; } + /** + * @param array $data + * @return array + */ + protected function knownFields(DraftableResource $resource, array $data): array + { + return array_intersect_key($data, array_flip($resource->fields())); + } + /** * @param array $data * diff --git a/packages/panel/src/Drafts/DraftSlice.php b/packages/panel/src/Drafts/DraftSlice.php new file mode 100644 index 0000000000..ab6698b545 --- /dev/null +++ b/packages/panel/src/Drafts/DraftSlice.php @@ -0,0 +1,50 @@ + $data + * @return array + */ + public function normalize(array $data): array + { + return $data; + } + + /** @return array */ + public function labels(): array + { + return []; + } + + public function discard(Model $record, EditDraft $draft): void {} + + public function bindNamespace(string $namespace): static + { + $this->namespace = $namespace; + + return $this; + } + + /** + * The full draft key of one of this slice's own fields, for rule + * parameters that must reference it (`required_with:` and the like). + */ + protected function field(string $name): string + { + return ($this->namespace ?? $this->key()).':'.$name; + } +} diff --git a/packages/panel/src/Http/Controllers/Products/ProductEditController.php b/packages/panel/src/Http/Controllers/Products/ProductEditController.php index fa86a402df..7a5b29efa5 100644 --- a/packages/panel/src/Http/Controllers/Products/ProductEditController.php +++ b/packages/panel/src/Http/Controllers/Products/ProductEditController.php @@ -29,12 +29,11 @@ use Lunar\Panel\Contracts\DraftManager; use Lunar\Panel\Http\Requests\Products\ProductRequest; use Lunar\Panel\PanelManager; -use Lunar\Panel\Sections\Catalog\ProductDraftResource; +use Lunar\Panel\Sections\Catalog\Slices\SoleVariantSlice; use Lunar\Panel\Support\AttributeSchema; use Lunar\Panel\Support\AvailabilitySchema; use Lunar\Panel\Support\Media\MediaGroups; use Lunar\Panel\Support\TimelineActivity; -use Lunar\Panel\Support\VariantFields; use Spatie\Activitylog\Models\Activity; class ProductEditController @@ -45,7 +44,6 @@ public function edit( DraftManager $drafts, AttributeSchema $attributeSchema, AvailabilitySchema $availabilitySchema, - VariantFields $variantFields, ): Response { $availabilitySchema = $availabilitySchema->withPurchasable(); @@ -181,11 +179,6 @@ public function edit( 'edit_url' => route('panel.products.variants.edit', [$product, $variant]), ]), 'variant' => $soleVariant ? $this->variantPayload($product, $soleVariant) : null, - 'variantValues' => $soleVariant - ? collect($variantFields->values($soleVariant)) - ->mapWithKeys(fn (mixed $value, string $key) => [ProductDraftResource::VARIANT_PREFIX.$key => $value]) - ->all() - : (object) [], 'variantAttributeGroups' => $soleVariant ? $this->prefixedGroups($attributeSchema->groups($soleVariant)) : [], @@ -227,10 +220,10 @@ public function edit( ->get(['id', 'code', 'name', 'default']), 'mediaGroups' => MediaGroups::for($product, 'panel.products'), 'productUrls' => $urls, + // Attribute, availability and sole-variant values seed the form + // through the shared draftSliceValues prop. 'attributeGroups' => $attributeSchema->groups($product), - 'attributeValues' => $attributeSchema->values($product) ?: (object) [], 'availability' => $availabilitySchema->rows(), - 'availabilityValues' => $availabilitySchema->values($product) ?: (object) [], 'brandOptions' => Brand::query()->orderBy('name')->get(['id', 'name']) ->map(fn (Brand $brand) => ['value' => $brand->id, 'label' => $brand->name]), // Active types plus the product's current one, so a since-drafted @@ -371,8 +364,8 @@ protected function variantPayload(Product $product, ProductVariant $variant): ar } /** - * Attribute groups whose field keys carry the simple-shape variant - * prefix, so AttributeFields reads and writes the product draft's + * Attribute groups whose field keys carry the sole-variant slice's + * namespace, so AttributeFields reads and writes the product draft's * variant:attribute:{handle} keys untouched. * * @param array> $groups @@ -382,7 +375,7 @@ protected function prefixedGroups(array $groups): array { return array_map(function (array $group): array { $group['fields'] = array_map(function (array $field): array { - $field['key'] = ProductDraftResource::VARIANT_PREFIX.$field['key']; + $field['key'] = SoleVariantSlice::KEY.':'.$field['key']; return $field; }, $group['fields']); diff --git a/packages/panel/src/Http/Middleware/HandlePanelInertiaRequests.php b/packages/panel/src/Http/Middleware/HandlePanelInertiaRequests.php index 09f05a8aff..896e442455 100644 --- a/packages/panel/src/Http/Middleware/HandlePanelInertiaRequests.php +++ b/packages/panel/src/Http/Middleware/HandlePanelInertiaRequests.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; use Inertia\Middleware; +use Lunar\Panel\Drafts\ComposedDraftResource; use Lunar\Panel\PanelManager; use Lunar\Panel\Support\Gravatar; @@ -54,9 +55,35 @@ public function share(Request $request): array 'visitedRecord' => fn () => ($record = $this->currentRecord($request)) ? $this->manager->resolveSearchSources()->rowFor($record) : null, + // Prefixed current values of every draft slice on the page's + // record, so useEditDraft can seed them without each edit + // controller knowing which slices apply. + 'draftSliceValues' => fn () => $this->draftSliceValues($request), ]); } + /** + * Slice values for the draft target: the deepest route-bound model, the + * same record EditDraftController drafts (a product's variant on the + * variant page, not the product). Empty when nothing is bound, no + * resource covers it, or it has no slices. + * + * @return array|object + */ + protected function draftSliceValues(Request $request): array|object + { + $record = collect($request->route()?->parameters() ?? []) + ->last(fn (mixed $parameter): bool => $parameter instanceof Model); + + $resource = $record ? $this->manager->draftableFor($record) : null; + + if (! $resource instanceof ComposedDraftResource) { + return (object) []; + } + + return $resource->sliceValues($record) ?: (object) []; + } + protected function currentPagePrefix(Request $request): string { $name = (string) $request->route()?->getName(); diff --git a/packages/panel/src/Models/EditDraft.php b/packages/panel/src/Models/EditDraft.php index 48acfffd98..503c7c87c0 100644 --- a/packages/panel/src/Models/EditDraft.php +++ b/packages/panel/src/Models/EditDraft.php @@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\MassPrunable; +use Illuminate\Database\Eloquent\Prunable; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; use Lunar\Core\Models\Base; @@ -19,7 +19,7 @@ class EditDraft extends Base { use HasFactory; - use MassPrunable; + use Prunable; /** @var array */ protected $guarded = []; @@ -48,7 +48,8 @@ public function staff(): BelongsTo /** * Drafts untouched beyond the configured TTL: their base snapshots are too * stale for trustworthy conflict comparison, so they prune rather than - * resume. + * resume. Pruned one model at a time so each fires its deleting event and + * the slice discard fan-out runs. * * @return Builder */ diff --git a/packages/panel/src/PanelManager.php b/packages/panel/src/PanelManager.php index 083d5c46b2..26966a8d87 100644 --- a/packages/panel/src/PanelManager.php +++ b/packages/panel/src/PanelManager.php @@ -7,10 +7,14 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; +use InvalidArgumentException; use Lunar\Panel\Actions\PageActionResolver; use Lunar\Panel\Contracts\DiscountTypeForm; use Lunar\Panel\Contracts\DraftableResource; +use Lunar\Panel\Contracts\DraftSlice; use Lunar\Panel\Dashboard\WidgetRegistry; +use Lunar\Panel\Drafts\ComposedDraftResource; +use Lunar\Panel\Drafts\DraftSlice as BaseDraftSlice; use Lunar\Panel\Models\EditDraft; use Lunar\Panel\Navigation\NavigationRegistry; use Lunar\Panel\Search\SearchCommand; @@ -51,6 +55,9 @@ class PanelManager /** @var array, DraftableResource> */ protected array $draftables = []; + /** @var array, array> */ + protected array $draftSlices = []; + /** @var array> */ protected array $discountTypeForms = []; @@ -161,6 +168,14 @@ private function processEntity(string $sectionKey, ProvidesNavigation $entity): $this->draftable($definitionClass); } + foreach ($entity->draftSlices() as $sliceClass) { + $this->draftSlice($sliceClass); + } + + foreach ($entity->draftExtensions() as $sliceClass) { + $this->draftSlice($sliceClass, addon: true); + } + foreach ($entity->discountTypeForms() as $discountType => $formClass) { $this->discountTypeForm($discountType, $formClass); } @@ -338,11 +353,14 @@ public function draftable(string $definitionClass): static if (isset($this->draftables[$model])) { Log::warning("Lunar Panel: draftable resource for [{$model}] is already registered and will be overwritten."); } else { + // Model deletes rather than a mass delete, so each draft's + // discard fan-out runs against the record still in hand. $model::deleted(function (Model $record): void { EditDraft::query() ->where('draftable_type', $record->getMorphClass()) ->where('draftable_id', $record->getKey()) - ->delete(); + ->get() + ->each(fn (EditDraft $draft) => $draft->setRelation('draftable', $record)->delete()); }); } @@ -351,9 +369,98 @@ public function draftable(string $definitionClass): static return $this; } + /** + * Register a draft slice, indexed by its model then namespace. Sections + * register first-party slices under their bare key; the public + * draftExtensions() hook passes $addon so the slice lands under + * `addon:{key}` and can never claim a first-party namespace. + * + * @param class-string $sliceClass + * + * @throws InvalidArgumentException on a malformed, reserved, or duplicate namespace + */ + public function draftSlice(string $sliceClass, bool $addon = false): static + { + /** @var DraftSlice $slice */ + $slice = app($sliceClass); + + $key = $slice->key(); + + if (! preg_match('/^[a-z0-9_-]+$/', $key)) { + throw new InvalidArgumentException("Lunar Panel: draft slice [{$sliceClass}] key [{$key}] must match [a-z0-9_-]+."); + } + + if (! $addon && $key === 'addon') { + throw new InvalidArgumentException("Lunar Panel: draft slice [{$sliceClass}] cannot claim the reserved [addon] namespace."); + } + + $namespace = $addon ? "addon:{$key}" : $key; + $model = $slice->model(); + + // Re-registering the same class (sections processed twice) is a + // no-op; only a different class claiming the namespace is an error. + if (($existing = $this->draftSlices[$model][$namespace] ?? null) && $existing::class !== $sliceClass) { + $existingClass = $existing::class; + + throw new InvalidArgumentException( + "Lunar Panel: draft slice namespace [{$namespace}] on [{$model}] is claimed by both [{$existingClass}] and [{$sliceClass}]." + ); + } + + if ($slice instanceof BaseDraftSlice) { + $slice->bindNamespace($namespace); + } + + $this->draftSlices[$model][$namespace] = $slice; + + return $this; + } + + /** + * @param class-string $model + * @return array + */ + public function draftSlicesFor(string $model): array + { + return $this->draftSlices[$model] ?? []; + } + + /** + * The record's draftable definition: the registered resource alone, or + * composed with the model's slices when it has any. Slices are kept + * apart from resources so registration order between the panel's own + * sections and add-on sections does not matter. + */ public function draftableFor(Model $model): ?DraftableResource { - return $this->draftables[$model::class] ?? null; + $resource = $this->draftables[$model::class] ?? null; + + if (! $resource || ! ($slices = $this->draftSlicesFor($model::class))) { + return $resource; + } + + return new ComposedDraftResource($resource, $slices, $model); + } + + /** + * Give a draft being deleted (discarded, pruned, or orphaned by its + * record's deletion) to the slices whose keys it holds. Committed drafts + * are consumed rather than discarded and delete quietly, so they never + * arrive here. + */ + public function draftDiscarded(EditDraft $draft): void + { + $record = $draft->draftable; + + if (! $record instanceof Model) { + return; + } + + $resource = $this->draftableFor($record); + + if ($resource instanceof ComposedDraftResource) { + $resource->discard($record, $draft); + } } /** diff --git a/packages/panel/src/PanelServiceProvider.php b/packages/panel/src/PanelServiceProvider.php index 752c6b9c86..1781535b42 100644 --- a/packages/panel/src/PanelServiceProvider.php +++ b/packages/panel/src/PanelServiceProvider.php @@ -98,6 +98,8 @@ public function boot(): void $this->registerPermissionGate(); + EditDraft::deleting(fn (EditDraft $draft) => $this->app->make(PanelManager::class)->draftDiscarded($draft)); + Panel::section(new DashboardSection); Panel::section(new CatalogSection); Panel::section(new SalesSection); diff --git a/packages/panel/src/Sections/Catalog/CatalogSection.php b/packages/panel/src/Sections/Catalog/CatalogSection.php index e2510d6bd7..3e7902d863 100644 --- a/packages/panel/src/Sections/Catalog/CatalogSection.php +++ b/packages/panel/src/Sections/Catalog/CatalogSection.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Support\Facades\Route; use Lunar\Panel\Contracts\DraftableResource; +use Lunar\Panel\Contracts\DraftSlice; use Lunar\Panel\Http\Controllers\Brands\BrandBulkStatusController; use Lunar\Panel\Http\Controllers\Brands\BrandCreateController; use Lunar\Panel\Http\Controllers\Brands\BrandEditController; @@ -53,6 +54,10 @@ use Lunar\Panel\Search\Sources\BrandSearchSource; use Lunar\Panel\Search\Sources\CollectionSearchSource; use Lunar\Panel\Search\Sources\ProductSearchSource; +use Lunar\Panel\Sections\Catalog\Slices\ProductAttributeSlice; +use Lunar\Panel\Sections\Catalog\Slices\ProductChannelSlice; +use Lunar\Panel\Sections\Catalog\Slices\ProductCustomerGroupSlice; +use Lunar\Panel\Sections\Catalog\Slices\SoleVariantSlice; use Lunar\Panel\Sections\Catalog\Tables\BrandsTableExtension; use Lunar\Panel\Sections\Catalog\Tables\CollectionsTableExtension; use Lunar\Panel\Sections\Catalog\Tables\ProductsTableExtension; @@ -154,6 +159,17 @@ public function draftables(): array ]; } + /** @return array> */ + public function draftSlices(): array + { + return [ + ProductAttributeSlice::class, + ProductChannelSlice::class, + ProductCustomerGroupSlice::class, + SoleVariantSlice::class, + ]; + } + /** @return array> */ public function searchSources(): array { diff --git a/packages/panel/src/Sections/Catalog/ProductDraftResource.php b/packages/panel/src/Sections/Catalog/ProductDraftResource.php index 2b0b51886f..fd13e39c12 100644 --- a/packages/panel/src/Sections/Catalog/ProductDraftResource.php +++ b/packages/panel/src/Sections/Catalog/ProductDraftResource.php @@ -4,39 +4,21 @@ use Illuminate\Database\Eloquent\Model; use Lunar\Core\Contracts\Actions\Products\UpdatesProduct; -use Lunar\Core\Contracts\Actions\Products\UpdatesProductVariant; use Lunar\Core\Models\Product; -use Lunar\Core\Models\ProductVariant; +use Lunar\Panel\Drafts\Concerns\NormalizesDraftValues; use Lunar\Panel\Drafts\DraftableResource; use Lunar\Panel\Http\Requests\Products\ProductRequest; -use Lunar\Panel\Support\AttributeSchema; -use Lunar\Panel\Support\AvailabilitySchema; -use Lunar\Panel\Support\VariantFields; +/** + * The product's own columns and relations. Attribute values, availability + * rows and the simple-shape sole variant ride the same draft as slices (see + * Slices/), composed onto this resource by the panel. + */ class ProductDraftResource extends DraftableResource { - /** - * Simple-shape products (one variant, no options) edit their sole - * variant inline, so its fields ride this draft under this prefix and - * keep a single save cluster. - */ - public const VARIANT_PREFIX = 'variant:'; - - /** @var array|null */ - protected ?array $attributeTokens = null; + use NormalizesDraftValues; - protected AvailabilitySchema $availabilitySchema; - - public function __construct( - protected UpdatesProduct $updatesProduct, - protected UpdatesProductVariant $updatesProductVariant, - protected AttributeSchema $attributeSchema, - protected VariantFields $variantFields, - AvailabilitySchema $availabilitySchema, - ) { - // Product customer-group rows carry the pivot's extra purchasable flag. - $this->availabilitySchema = $availabilitySchema->withPurchasable(); - } + public function __construct(protected UpdatesProduct $updatesProduct) {} public function model(): string { @@ -54,14 +36,6 @@ public function fields(): array 'description', 'tags', 'collection_ids', - // The morph-wide superset: which attributes actually apply - // depends on the record's product type, which rules() enforces. - ...$this->attributeSchema->fieldsForMorph(Product::morphName()), - ...$this->availabilitySchema->fields(), - ...array_map( - fn (string $field) => self::VARIANT_PREFIX.$field, - $this->variantFields->fields(), - ), ]; } @@ -77,40 +51,9 @@ public function currentValues(Model $record): array 'description' => $this->translationMap($record->description?->all() ?? []), 'tags' => $this->sortedTags($record->tags()->pluck('value')->all()), 'collection_ids' => $this->sortedIds($record->collections()->allRelatedIds()->all()), - ...collect($this->attributeSchema->values($record)) - ->map(fn (mixed $value, string $key) => $this->normalizeAttributeValue($value, $this->attributeTokens($record)[$key] ?? null)) - ->all(), - ...$this->availabilitySchema->values($record), - ...$this->soleVariantValues($record), ]; } - /** - * The sole variant's prefixed field values on simple-shape products; - * multi-variant products edit variants on their own pages instead. - * - * @return array - */ - protected function soleVariantValues(Product $record): array - { - $variant = $this->soleVariant($record); - - if (! $variant) { - return []; - } - - return collect($this->variantFields->values($variant)) - ->mapWithKeys(fn (mixed $value, string $key) => [self::VARIANT_PREFIX.$key => $value]) - ->all(); - } - - protected function soleVariant(Product $record): ?ProductVariant - { - $variants = $record->variants()->limit(2)->get(); - - return $variants->count() === 1 ? $variants->first() : null; - } - public function normalize(array $data): array { foreach (['name', 'short_description', 'description'] as $field) { @@ -127,70 +70,18 @@ public function normalize(array $data): array $data['collection_ids'] = $this->sortedIds((array) $data['collection_ids']); } - foreach ($data as $key => $value) { - if (str_starts_with($key, AttributeSchema::PREFIX)) { - $data[$key] = $this->normalizeAttributeValue($value); - } - - if (str_starts_with($key, AvailabilitySchema::CHANNEL_PREFIX) - || str_starts_with($key, AvailabilitySchema::CUSTOMER_GROUP_PREFIX)) { - $data[$key] = $this->availabilitySchema->normalizeValue((array) $value); - } - - if (str_starts_with($key, self::VARIANT_PREFIX)) { - $field = substr($key, strlen(self::VARIANT_PREFIX)); - - $data[$key] = str_starts_with($field, AttributeSchema::PREFIX) - ? $this->normalizeAttributeValue($value) - : $this->variantFields->normalizeValue($field, $value); - } - } - return $data; } public function rules(Model $record): array { /** @var Product $record */ - $variant = $this->soleVariant($record); - - // The variant surface only exists on simple-shape products; with - // multiple variants every variant key is refused outright. - $variantRules = $variant - ? collect($this->variantFields->rules($variant)) - ->mapWithKeys(fn (array $rules, string $field) => [self::VARIANT_PREFIX.$field => $rules]) - ->all() - : collect($this->variantFields->fields()) - ->mapWithKeys(fn (string $field) => [self::VARIANT_PREFIX.$field => ['prohibited']]) - ->all(); - - return [ - ...ProductRequest::rulesFor($record), - ...$this->attributeSchema->rules($record), - ...$this->availabilitySchema->rules(), - ...$variantRules, - ]; + return ProductRequest::rulesFor($record); } public function commit(Model $record, array $values): void { /** @var Product $record */ - // Drafted availability rows split off and rebuild the full pivot maps - // (untouched rows ride along — the sync replaces the whole set). - $availability = $this->availabilitySchema->extract($record, $values); - - $values = $availability['attributes']; - - // Simple-shape variant fields split off and commit through the - // variant action on the sole variant. - $variantValues = collect($values) - ->filter(fn (mixed $value, string $key) => str_starts_with($key, self::VARIANT_PREFIX)) - ->mapWithKeys(fn (mixed $value, string $key) => [substr($key, strlen(self::VARIANT_PREFIX)) => $value]); - - $values = collect($values) - ->reject(fn (mixed $value, string $key) => str_starts_with($key, self::VARIANT_PREFIX)) - ->all(); - $tags = array_key_exists('tags', $values) ? array_map('strval', (array) $values['tags']) : null; @@ -199,40 +90,12 @@ public function commit(Model $record, array $values): void ? array_map('intval', (array) $values['collection_ids']) : null; - $attributeValues = collect($values) - ->filter(fn (mixed $value, string $key) => str_starts_with($key, AttributeSchema::PREFIX)); - - $attributes = collect($values) - ->except([...$attributeValues->keys(), 'tags', 'collection_ids']) - ->all(); - - if ($attributeValues->isNotEmpty()) { - // Overlay the drafted values onto the stored set so attributes the - // draft never touched survive the whole-column write. - $data = ($record->attribute_data ?? collect())->all(); - - foreach ($attributeValues as $key => $value) { - $data[substr($key, strlen(AttributeSchema::PREFIX))] = $value; - } - - $attributes['attribute_data'] = $data; - } - $this->updatesProduct->execute( $record, - $attributes, + collect($values)->except(['tags', 'collection_ids'])->all(), $tags, $collectionIds, - $availability['channels'], - $availability['customerGroups'], ); - - if ($variantValues->isNotEmpty() && ($variant = $this->soleVariant($record))) { - $this->updatesProductVariant->execute( - $variant, - $this->variantFields->commitPayload($variant, $variantValues->all()), - ); - } } public function labels(): array @@ -246,27 +109,9 @@ public function labels(): array 'description' => 'panel::products.field_description', 'tags' => 'panel::products.field_tags', 'collection_ids' => 'panel::products.side_collections', - ...$this->attributeSchema->labelsForMorph(Product::morphName()), - ...$this->availabilitySchema->labels(), - ...collect($this->variantFields->labels()) - ->mapWithKeys(fn (string $label, string $field) => [self::VARIANT_PREFIX.$field => $label]) - ->all(), ]; } - /** - * @param array $ids - * @return array - */ - protected function sortedIds(array $ids): array - { - $ids = array_values(array_unique(array_map('intval', $ids))); - - sort($ids); - - return $ids; - } - /** * Tags compare as an uppercased sorted set — the Tag model uppercases * values on write, so drafted input must normalise the same way for @@ -286,56 +131,4 @@ protected function sortedTags(array $tags): array return $tags; } - - /** - * Attribute values arrive in whatever shape their field type stores; - * translated-text maps get key-sorted with blank entries dropped so - * equality against the stored value holds. Sequential arrays keep their - * order, as do keyed list values. - */ - protected function normalizeAttributeValue(mixed $value, ?string $token = null): mixed - { - if (! is_array($value)) { - return $value; - } - - if (array_is_list($value)) { - return $value; - } - - if ($token === 'list') { - return array_map(fn (mixed $item) => is_string($item) ? $item : (string) $item, $value); - } - - return $this->translationMap($value); - } - - /** - * Field-type tokens per draft field key, memoized per request. - * - * @return array - */ - protected function attributeTokens(Product $record): array - { - return $this->attributeTokens ??= $this->attributeSchema->tokens($record); - } - - /** - * Normalise a `{locale: text}` translation map so equality against the - * stored value holds: empty values are dropped and keys are sorted. - * - * @param array $map - * @return array - */ - protected function translationMap(array $map): array - { - $map = array_filter( - array_map(fn (mixed $value) => is_string($value) ? $value : (string) $value, $map), - fn (string $value) => $value !== '', - ); - - ksort($map); - - return $map; - } } diff --git a/packages/panel/src/Sections/Catalog/Slices/AvailabilitySlice.php b/packages/panel/src/Sections/Catalog/Slices/AvailabilitySlice.php new file mode 100644 index 0000000000..c2f8c61383 --- /dev/null +++ b/packages/panel/src/Sections/Catalog/Slices/AvailabilitySlice.php @@ -0,0 +1,69 @@ +availabilitySchema = $availabilitySchema->withPurchasable(); + } + + /** The schema prefix this slice's rows carry, e.g. `channel:`. */ + abstract protected function side(): string; + + public function model(): string + { + return Product::class; + } + + public function key(): string + { + return rtrim($this->side(), ':'); + } + + public function fields(Model $record): array + { + return $this->stripPrefixFromList($this->availabilitySchema->fields($this->side()), $this->side()); + } + + public function currentValues(Model $record): array + { + return $this->stripPrefix($this->availabilitySchema->values($record, $this->side()), $this->side()); + } + + public function normalize(array $data): array + { + return array_map(fn (mixed $value) => $this->availabilitySchema->normalizeValue((array) $value), $data); + } + + public function rules(Model $record): array + { + return $this->stripPrefix($this->availabilitySchema->rules($this->side()), $this->side()); + } + + public function labels(): array + { + return $this->stripPrefix($this->availabilitySchema->labels($this->side()), $this->side()); + } +} diff --git a/packages/panel/src/Sections/Catalog/Slices/ProductAttributeSlice.php b/packages/panel/src/Sections/Catalog/Slices/ProductAttributeSlice.php new file mode 100644 index 0000000000..ac945e3ed4 --- /dev/null +++ b/packages/panel/src/Sections/Catalog/Slices/ProductAttributeSlice.php @@ -0,0 +1,87 @@ +stripPrefixFromList( + $this->attributeSchema->fieldsForMorph(Product::morphName()), + AttributeSchema::PREFIX, + ); + } + + public function currentValues(Model $record): array + { + $tokens = $this->stripPrefix($this->attributeSchema->tokens($record), AttributeSchema::PREFIX); + + return collect($this->stripPrefix($this->attributeSchema->values($record), AttributeSchema::PREFIX)) + ->map(fn (mixed $value, string $handle) => $this->normalizeAttributeValue($value, $tokens[$handle] ?? null)) + ->all(); + } + + public function normalize(array $data): array + { + return array_map(fn (mixed $value) => $this->normalizeAttributeValue($value), $data); + } + + public function rules(Model $record): array + { + return $this->stripPrefix($this->attributeSchema->rules($record), AttributeSchema::PREFIX); + } + + public function commit(Model $record, array $values): void + { + /** @var Product $record */ + if ($values === []) { + return; + } + + // Overlay the drafted values onto the stored set so attributes the + // draft never touched survive the whole-column write. + $data = ($record->attribute_data ?? collect())->all(); + + foreach ($values as $handle => $value) { + $data[$handle] = $value; + } + + $this->updatesProduct->execute($record, ['attribute_data' => $data]); + } + + public function labels(): array + { + return $this->stripPrefix($this->attributeSchema->labelsForMorph(Product::morphName()), AttributeSchema::PREFIX); + } +} diff --git a/packages/panel/src/Sections/Catalog/Slices/ProductChannelSlice.php b/packages/panel/src/Sections/Catalog/Slices/ProductChannelSlice.php new file mode 100644 index 0000000000..a9b1b622df --- /dev/null +++ b/packages/panel/src/Sections/Catalog/Slices/ProductChannelSlice.php @@ -0,0 +1,29 @@ +updatesProduct->execute( + $record, + [], + channels: $this->availabilitySchema->pivotRows($this->side(), $values), + ); + } +} diff --git a/packages/panel/src/Sections/Catalog/Slices/ProductCustomerGroupSlice.php b/packages/panel/src/Sections/Catalog/Slices/ProductCustomerGroupSlice.php new file mode 100644 index 0000000000..7150bca566 --- /dev/null +++ b/packages/panel/src/Sections/Catalog/Slices/ProductCustomerGroupSlice.php @@ -0,0 +1,29 @@ +updatesProduct->execute( + $record, + [], + customerGroups: $this->availabilitySchema->pivotRows($this->side(), $values), + ); + } +} diff --git a/packages/panel/src/Sections/Catalog/Slices/SoleVariantSlice.php b/packages/panel/src/Sections/Catalog/Slices/SoleVariantSlice.php new file mode 100644 index 0000000000..039013642d --- /dev/null +++ b/packages/panel/src/Sections/Catalog/Slices/SoleVariantSlice.php @@ -0,0 +1,99 @@ +variantFields->fields(); + } + + public function currentValues(Model $record): array + { + /** @var Product $record */ + $variant = $this->soleVariant($record); + + return $variant ? $this->variantFields->values($variant) : []; + } + + public function normalize(array $data): array + { + foreach ($data as $field => $value) { + $data[$field] = str_starts_with($field, AttributeSchema::PREFIX) + ? $this->normalizeAttributeValue($value) + : $this->variantFields->normalizeValue($field, $value); + } + + return $data; + } + + public function rules(Model $record): array + { + /** @var Product $record */ + if ($variant = $this->soleVariant($record)) { + return $this->variantFields->rules($variant); + } + + return array_fill_keys($this->variantFields->fields(), ['prohibited']); + } + + public function commit(Model $record, array $values): void + { + /** @var Product $record */ + if ($values === [] || ! ($variant = $this->soleVariant($record))) { + return; + } + + $this->updatesProductVariant->execute( + $variant, + $this->variantFields->commitPayload($variant, $values), + ); + } + + public function labels(): array + { + return $this->variantFields->labels(); + } + + protected function soleVariant(Product $record): ?ProductVariant + { + $variants = $record->variants()->limit(2)->get(); + + return $variants->count() === 1 ? $variants->first() : null; + } +} diff --git a/packages/panel/src/Sections/ProvidesNavigation.php b/packages/panel/src/Sections/ProvidesNavigation.php index d89e3ecd43..bbaefba70f 100644 --- a/packages/panel/src/Sections/ProvidesNavigation.php +++ b/packages/panel/src/Sections/ProvidesNavigation.php @@ -5,6 +5,7 @@ use Closure; use Lunar\Panel\Contracts\DiscountTypeForm; use Lunar\Panel\Contracts\DraftableResource; +use Lunar\Panel\Contracts\DraftSlice; use Lunar\Panel\Dashboard\Widget; use Lunar\Panel\Navigation\NavigationRegistry; use Lunar\Panel\Slots\SlotRegistry; @@ -39,6 +40,22 @@ public function pageActions(): array; */ public function draftables(): array; + /** + * First-party draft slices, each owning a bare namespace on its model, + * e.g. [ProductAttributeSlice::class]. + * + * @return array> + */ + public function draftSlices(): array; + + /** + * Draft slices contributed to another section's resources; placed under + * the reserved `addon:{key}` namespace, e.g. [LoyaltyTierSlice::class]. + * + * @return array> + */ + public function draftExtensions(): array; + /** * Panel forms for discount types, keyed by the discount type class, e.g. * [PercentageOff::class => PercentageOffForm::class]. diff --git a/packages/panel/src/Sections/Section.php b/packages/panel/src/Sections/Section.php index c8647702c4..47a1e04779 100644 --- a/packages/panel/src/Sections/Section.php +++ b/packages/panel/src/Sections/Section.php @@ -5,6 +5,7 @@ use Closure; use Lunar\Panel\Contracts\DiscountTypeForm; use Lunar\Panel\Contracts\DraftableResource; +use Lunar\Panel\Contracts\DraftSlice; use Lunar\Panel\Dashboard\Widget; use Lunar\Panel\Navigation\NavigationRegistry; use Lunar\Panel\Search\SearchCommand; @@ -64,6 +65,32 @@ public function draftables(): array return []; } + /** + * Return draft slices this section owns: namespaced contributions to a + * draftable resource's edit draft, each under a bare namespace of its + * own, e.g. [ProductAttributeSlice::class]. Reserved for the panel's own + * sections; an add-on uses draftExtensions(). + * + * @return array> + */ + public function draftSlices(): array + { + return []; + } + + /** + * Return draft slices this section contributes to a resource it does not + * own, e.g. [LoyaltyTierSlice::class]. Each lands under `addon:{key}`, so + * its fields autosave, restore, conflict-check and commit with the + * resource's own without being able to touch them. + * + * @return array> + */ + public function draftExtensions(): array + { + return []; + } + /** * Return panel forms for discount types this section owns, keyed by the * discount type class, e.g. diff --git a/packages/panel/src/Sections/SectionExtension.php b/packages/panel/src/Sections/SectionExtension.php index 01c49a4191..42165de840 100644 --- a/packages/panel/src/Sections/SectionExtension.php +++ b/packages/panel/src/Sections/SectionExtension.php @@ -5,6 +5,7 @@ use Closure; use Lunar\Panel\Contracts\DiscountTypeForm; use Lunar\Panel\Contracts\DraftableResource; +use Lunar\Panel\Contracts\DraftSlice; use Lunar\Panel\Dashboard\Widget; use Lunar\Panel\Navigation\NavigationRegistry; use Lunar\Panel\Search\SearchCommand; @@ -56,6 +57,28 @@ public function draftables(): array return []; } + /** + * Return draft slices this extension owns under a bare namespace; see + * Section::draftSlices(). Add-ons use draftExtensions(). + * + * @return array> + */ + public function draftSlices(): array + { + return []; + } + + /** + * Return draft slices this extension contributes under `addon:{key}`; + * see Section::draftExtensions(). + * + * @return array> + */ + public function draftExtensions(): array + { + return []; + } + /** * Return panel forms for discount types this extension contributes, keyed * by the discount type class, e.g. diff --git a/packages/panel/src/Support/AvailabilitySchema.php b/packages/panel/src/Support/AvailabilitySchema.php index ee324d03eb..5a624dcab0 100644 --- a/packages/panel/src/Support/AvailabilitySchema.php +++ b/packages/panel/src/Support/AvailabilitySchema.php @@ -37,60 +37,79 @@ public function withPurchasable(): static } /** - * Every draftable availability field key. + * Every draftable availability field key, or one side's when a prefix + * is given. * * @return array */ - public function fields(): array + public function fields(?string $side = null): array { return [ - ...Channel::query()->pluck('id')->map(fn (int $id) => static::CHANNEL_PREFIX.$id), - ...CustomerGroup::query()->pluck('id')->map(fn (int $id) => static::CUSTOMER_GROUP_PREFIX.$id), + ...($this->includes($side, static::CHANNEL_PREFIX) + ? Channel::query()->pluck('id')->map(fn (int $id) => static::CHANNEL_PREFIX.$id) + : []), + ...($this->includes($side, static::CUSTOMER_GROUP_PREFIX) + ? CustomerGroup::query()->pluck('id')->map(fn (int $id) => static::CUSTOMER_GROUP_PREFIX.$id) + : []), ]; } /** - * Current pivot state per draft field key. + * Current pivot state per draft field key, or one side's when a prefix + * is given. * * @return array> */ - public function values(Model $model): array + public function values(Model $model, ?string $side = null): array { - $channelPivots = $model->channels()->get()->keyBy('id'); - $groupPivots = $model->customerGroups()->get()->keyBy('id'); - $values = []; - foreach (Channel::query()->get(['id']) as $channel) { - $pivot = $channelPivots->get($channel->id)?->pivot; + if ($this->includes($side, static::CHANNEL_PREFIX)) { + $channelPivots = $model->channels()->get()->keyBy('id'); + + foreach (Channel::query()->get(['id']) as $channel) { + $pivot = $channelPivots->get($channel->id)?->pivot; - $values[static::CHANNEL_PREFIX.$channel->id] = $this->normalizeValue([ - 'enabled' => (bool) ($pivot->enabled ?? false), - 'starts_at' => $pivot->starts_at ?? null, - 'ends_at' => $pivot->ends_at ?? null, - ]); + $values[static::CHANNEL_PREFIX.$channel->id] = $this->normalizeValue([ + 'enabled' => (bool) ($pivot->enabled ?? false), + 'starts_at' => $pivot->starts_at ?? null, + 'ends_at' => $pivot->ends_at ?? null, + ]); + } } - foreach (CustomerGroup::query()->get(['id']) as $group) { - $pivot = $groupPivots->get($group->id)?->pivot; + if ($this->includes($side, static::CUSTOMER_GROUP_PREFIX)) { + $groupPivots = $model->customerGroups()->get()->keyBy('id'); - $value = [ - 'enabled' => (bool) ($pivot->enabled ?? false), - 'visible' => (bool) ($pivot->visible ?? true), - 'starts_at' => $pivot->starts_at ?? null, - 'ends_at' => $pivot->ends_at ?? null, - ]; + foreach (CustomerGroup::query()->get(['id']) as $group) { + $pivot = $groupPivots->get($group->id)?->pivot; - if ($this->withPurchasable) { - $value['purchasable'] = (bool) ($pivot->purchasable ?? true); - } + $value = [ + 'enabled' => (bool) ($pivot->enabled ?? false), + 'visible' => (bool) ($pivot->visible ?? true), + 'starts_at' => $pivot->starts_at ?? null, + 'ends_at' => $pivot->ends_at ?? null, + ]; - $values[static::CUSTOMER_GROUP_PREFIX.$group->id] = $this->normalizeValue($value); + if ($this->withPurchasable) { + $value['purchasable'] = (bool) ($pivot->purchasable ?? true); + } + + $values[static::CUSTOMER_GROUP_PREFIX.$group->id] = $this->normalizeValue($value); + } } return $values; } + /** + * Whether a side selector (null for both) covers the given prefix. + */ + protected function includes(?string $side, string $prefix): bool + { + return $side === null || $side === $prefix; + } + /** * The rows the availability card renders: id, name and the draft key. * @@ -115,15 +134,16 @@ public function rows(): array } /** - * Validation rules for every availability field. + * Validation rules for every availability field, or one side's when a + * prefix is given. * * @return array> */ - public function rules(): array + public function rules(?string $side = null): array { $rules = []; - foreach ($this->fields() as $field) { + foreach ($this->fields($side) as $field) { $rules[$field] = ['nullable', 'array']; $rules["{$field}.enabled"] = ['boolean']; $rules["{$field}.starts_at"] = ['nullable', 'date']; @@ -142,25 +162,57 @@ public function rules(): array } /** - * Conflict-dialog labels per field key. + * Conflict-dialog labels per field key, or one side's when a prefix is + * given. * * @return array */ - public function labels(): array + public function labels(?string $side = null): array { $labels = []; - foreach (Channel::query()->get(['id', 'name']) as $channel) { - $labels[static::CHANNEL_PREFIX.$channel->id] = __('panel::availability.channels').' — '.$channel->name; + if ($this->includes($side, static::CHANNEL_PREFIX)) { + foreach (Channel::query()->get(['id', 'name']) as $channel) { + $labels[static::CHANNEL_PREFIX.$channel->id] = __('panel::availability.channels').' — '.$channel->name; + } } - foreach (CustomerGroup::query()->get(['id', 'name']) as $group) { - $labels[static::CUSTOMER_GROUP_PREFIX.$group->id] = __('panel::availability.customer_groups').' — '.$group->name; + if ($this->includes($side, static::CUSTOMER_GROUP_PREFIX)) { + foreach (CustomerGroup::query()->get(['id', 'name']) as $group) { + $labels[static::CUSTOMER_GROUP_PREFIX.$group->id] = __('panel::availability.customer_groups').' — '.$group->name; + } } return $labels; } + /** + * Turn one side's full value set (keyed by bare row id, every row + * present) into the pivot map the update actions sync: id => canonical + * row, with keys the side's pivot lacks dropped. + * + * @param array $values + * @return array> + */ + public function pivotRows(string $side, array $values): array + { + $rows = []; + + foreach ($values as $id => $value) { + $value = $this->normalizeValue((array) $value); + + if ($side === static::CHANNEL_PREFIX) { + unset($value['visible'], $value['purchasable']); + } elseif (! $this->withPurchasable) { + unset($value['purchasable']); + } + + $rows[(int) $id] = $value; + } + + return $rows; + } + /** * Canonicalise an availability value so draft equality holds: keys are * sorted, booleans are real booleans and dates share one format. diff --git a/specs/0086-panel-draft-slices.md b/specs/0086-panel-draft-slices.md new file mode 100644 index 0000000000..5b2ecd47f8 --- /dev/null +++ b/specs/0086-panel-draft-slices.md @@ -0,0 +1,321 @@ +# 0086 — Panel draft slices: namespaced contributions to first-party edit drafts + +- Status: proposed +- Author: Glenn Jacobs +- Created: 2026-09-15 +- TODO item: Panel draft slices — one contract for namespaced draft contributions, used by first-party surfaces and by add-on slot components alike (spec 0086) + +## Problem + +Two problems share a cause. + +**Add-ons cannot take part in a save.** An add-on can put a component on a first-party +edit page through a slot zone, but `PanelSlot` hands it only the slot's static props plus +whatever the page binds to the zone. It cannot store values in the page's draft, so what +staff type into it never autosaves and is lost on navigation. It cannot commit alongside +the record, so its only option is a side request to its own endpoint, outside the commit +transaction and with ordering left to chance. It gets none of the conflict detection, +restore banner, dirty guard, or validation-error plumbing the draft layer gives +first-party fields. The pressure this creates is the request in +[lunar#2736](https://github.com/lunarphp/lunar/discussions/2736) for a field-level hook +into first-party forms, the Filament `extendForm` model. That is the wrong shape for the +panel: once an add-on can reach into a form it does not own, two add-ons installed +together can silently break each other. The panel's extension surface is additive by +design, and this spec keeps it that way. + +**First-party sub-surfaces are hand-rolled.** `ProductDraftResource` already composes +several namespaced slices into one draft: attributes under `attribute:{handle}`, +availability rows under `channel:{id}` and `customer_group:{id}`, the simple-shape sole +variant under `variant:{field}`. Each has its own current values, normalisation, rules, +labels, and commit path, but they are wired by hand inside the resource with prefix +checks in every method. Adding another slice (associations, slugs, prices, media, see +[[0087-product-editing-through-the-draft]]) means more of the same, and none of it is +reusable by the brand, collection, or product-type resources that share the same +editing components. + +Both need the same thing: a contract for a namespaced draft contribution, and one +composer that assembles a resource from its slices. + +## Proposal + +A **draft slice** is a registered, namespaced contribution to a draftable resource. It +declares its own fields, current values, normalisation, rules, labels, and commit, all in +unprefixed terms; the panel composes it into the resource under a prefix the slice never +sees and cannot escape. First-party surfaces and add-ons use the same contract. The +difference is the prefix: a first-party slice owns a bare namespace such as +`association:`; anything registered through the public section hook is placed under +`addon:{key}:`. + +On the client, a component binds to a slice through a scoped composable, and from then +on autosave, restore, dirty state, conflict detection, 422 error mapping, and the atomic +commit apply to the slice's fields exactly as they apply to the resource's own columns. +A first-party card and an add-on's slot component take the same path. + +### Key scheme + +Every slice field key is `{namespace}:{field}`: + +- `{namespace}` is the slice's key, `[a-z0-9_-]+`, unique per model. First-party slices + registered by the panel's own sections use a bare namespace (`attribute`, `channel`, + `association`, `url`, `price`, `media`). Slices registered through the public + `Section::draftExtensions()` hook are namespaced `addon:{key}`, so their full keys are + `addon:{key}:{field}`. The `addon:` prefix is reserved and cannot be claimed as a bare + namespace. +- `{field}` is the slice's own field name, free-form. A slice with one value per row + (an availability channel, a price tuple, a URL language) uses the row identity as the + field name, which is how the existing `channel:{id}` keys already work; a slice that + nests another surface (the sole variant's `attribute:{handle}` values) carries that + surface's keys as its field names. +- The slice handles only `{field}`. Prefixing and unprefixing happen in the composer, so + a slice has no API through which it could name a key outside its namespace. The + isolation guarantee is structural, not a runtime check. + +There is no technical way to stop an add-on's service provider from registering a bare +namespace, since it runs with the same privileges as the panel's own. The public hook +simply does not offer it. That is the same trust model the panel already relies on for +container bindings and page overrides. + +### Server: the `DraftSlice` contract + +New `Lunar\Panel\Contracts\DraftSlice`, with an abstract `Lunar\Panel\Drafts\DraftSlice` +supplying `normalize()` and `labels()` no-op defaults as `Drafts\DraftableResource` does: + +- `model(): class-string` — the draftable model this contributes to. +- `key(): string` — the namespace. +- `fields(Model $record): array` — the unprefixed field names for this + record. Takes the record because row-shaped slices derive their field set from data + (which channels exist, which currencies are enabled, which collections the model's + media definition declares). +- `currentValues(Model $record): array` — the current, normalised stored + value per field, keyed by unprefixed name. +- `normalize(array $data): array` — same contract as the resource's, over the slice's + own values. +- `rules(Model $record): array` — rules keyed by unprefixed field. The + composer prefixes the keys. Rule parameters pass through verbatim, so a rule may + reference a resource field by its real key (`required_if:brand_id,...`), which is + read-only by construction. Referencing one of the slice's own fields in a parameter + needs the full key; `DraftSlice::field(string $name): string` returns it. +- `commit(Model $record, array $values): void` — receives the slice's own values, + unprefixed, every field present (current values overlaid with the draft), after the + resource's own commit, inside the same transaction. Persists through core actions. +- `labels(): array` — unprefixed field to lang key for the conflict + dialog and validation messages. +- `discard(Model $record, EditDraft $draft): void` — optional hook, default no-op, + called when a draft holding this slice's keys is discarded or pruned. Exists for + slices that hold state outside the JSON columns (staged media uploads in + [[0087-product-editing-through-the-draft]]). + +### Server: registration and composition + +- First-party slices register from the panel's own sections through + `Section::draftSlices(): array>`. Add-ons register + through `Section::draftExtensions()` with the same return type; `PanelManager` places + those under `addon:{key}`. Both hooks follow the optional-hook pattern of + `draftables()`. +- `PanelManager::draftSlice(string $class, bool $addon)` resolves the class from the + container and indexes it by `model()` then namespace. A duplicate namespace on the + same model throws at boot, naming both classes. +- Slices are stored separately from draftables so registration order between the + panel's own sections and add-on sections does not matter. `draftableFor()` composes + lazily: a model with no slices returns its `DraftableResource` unchanged; otherwise a + `Drafts\ComposedDraftResource` wrapping the resource and its slices. +- `ComposedDraftResource` implements `DraftableResource` and is the only place prefixing + lives: + - `fields()` — the resource's fields plus every slice's, prefixed. + - `currentValues()` / `rules()` / `labels()` — the resource's plus each slice's with + keys prefixed. + - `normalize()` — routes each namespace's keys to its slice, everything else to the + resource. + - `commit()` — the resource's `commit()` with its own keys, then each slice's + `commit()` with its unprefixed values, in registration order. `DraftManager::commit()` + already wraps the resource commit in a transaction, so the composed commit is atomic + with no change to the manager. +- Discard fans out through the `EditDraft` model's `deleting` event, wired in the panel's + service provider, so every path that removes a draft (the discard endpoint, pruning, the + record-deleted cleanup) reaches the slices whose keys the draft held. A committed draft + is consumed rather than discarded and deletes quietly. Pruning therefore moves + `EditDraft` from `MassPrunable` to `Prunable`, and the record-deleted cleanup deletes + drafts one model at a time, so each is an instance the hook can receive. +- `EditDraftController`, the routes, and the 200/409/422 payloads do not change. The + manager only ever sees a `DraftableResource`. + +### Server: stale slice keys + +A stored draft can carry keys whose namespace is no longer registered (an add-on was +removed, a channel was deleted, a slice dropped a field). Today such a key surfaces at +commit as a spurious conflict or an unknown field, leaving every record with such a draft +unsavable. `DraftManager::commit()` drops stored keys the resource no longer declares +before overlaying the request's diff, so the draft's remaining fields commit normally. +Incoming data is still checked against the declared field set as it is now; that path +guards against a bad client, not a removed package or a deleted row. Autosave already +self-heals, since the client only restores and resends keys the page seeded. + +### Server: seeding the page + +`useEditDraft` restores a draft only into keys already present in `initial`, and diffs +only those keys, so the page must know the slice fields and their current values up +front. Rather than touch every edit controller, `HandlePanelInertiaRequests` shares a +lazy `draftSliceValues` prop: the prefixed current values of every slice on the current +record, or an empty object when the page has no record or no slices apply. The record +is the deepest route-bound model, matching `EditDraftController::draftable()`. The +middleware's existing `currentRecord()` returns the first bound model, which is the +parent on nested routes such as a product's variant, so this prop must not reuse it. + +Edit controllers that hand-build slice values today (`attributeValues`, +`availabilityValues`, `variantValues` on the product page) stop doing so once those +surfaces become slices; the shared prop replaces them. + +### Client: `useEditDraft` changes + +- Merges `draftSliceValues` into `initial` before building `pristine` and `values`, so + slice keys autosave, restore, diff, and guard like any other. A `slices` option + (default `true`) opts a form out, for a page that drafts a record other than the + route's deepest binding. +- Provides its own `EditDraftForm` under an injection key when created inside a + component, so components further down the tree can find the page's form. + +### Client: `useDraftSlice(namespace)` + +New `resources/js/composables/useDraftSlice.ts`, exported on `ui.ts` and the mirrored +`@lunarphp/panel` index. First-party cards call it with a bare namespace +(`useDraftSlice('association')`); an add-on's slot component calls it with its key and +the composable applies the `addon:` prefix (`useDraftSlice('example-addon')` resolves +to `addon:example-addon:`). Callable from any component inside the page's tree, which +every `PageZone` on an edit page is: + +- Injects the page's draft form; throws a descriptive error when the page has none. +- Returns `{ values, errors, field, isDirty, saving, committing }`: + - `values` — a typed reactive proxy scoped to the namespace, so + `v-model="slice.values.tier"` reads and writes `addon:example-addon:tier` on the + page's form. Enumeration lists only the namespace's fields. + - `errors` — the form's 422 errors filtered to the namespace and unprefixed. + - `field(name)` — a `WritableComputedRef` for one field. + - `isDirty` — whether any of the namespace's fields differ from pristine. + - `saving` / `committing` — the form's refs, passed through. +- The composable never exposes the underlying form, so a component has no path to + another namespace's keys or to resource keys. The server enforces the same boundary + independently. + +### Conflicts and validation + +A conflicting slice field appears in the existing `DraftConflictDialog` with the slice's +translated label; a 422 maps to `errors[field]` in the bound component. Structured +values (a media list, a price tuple) need a readable presentation in the dialog, which +[[0087-product-editing-through-the-draft]] specifies alongside the slices that need it. + +### Example add-on + +`packages/panel-addon-example` gains a `LoyaltyTierSlice` on `Customer` storing a +loyalty tier under the customer's `meta` column (via `UpdatesCustomer`, so the example +stays schema-free), and a `LoyaltyCard.vue` slot component in the +`customers.edit:main:after` zone that binds to it with `useDraftSlice('example-addon')`. +The README's extension guide gains a section walking through both, and +`tests/panel/Feature/ExampleAddonTest.php` exercises the whole path against the real +customer routes: autosave stores the prefixed key, a concurrent change to the tier +surfaces as a conflict, and a clean commit persists it in the same transaction as the +first-party fields. + +## Alternatives considered + +- **A field-level hook into first-party forms** (hide, disable, relax required, replace; + the Filament `extendForm` model and the ask in lunar#2736) — rejected. Mutation of a + structure the add-on does not own is exactly the operation that cannot compose across + add-ons. Hiding a first-party field is a per-store decision that belongs to the host + and is out of scope here (see References). +- **Lifecycle hooks for slot components** (before-save, after-save, discard events; the + slot persists to its own endpoint) — rejected. Hooks leave ordering and atomicity to + each add-on, give the add-on's data none of the draft layer's guarantees, and need a + separate dirty-state registration. Riding the draft gets all of it, and the slice's + `commit()` is the hook. For "react after a save" a server-side commit event is the + right tool and can be added independently. +- **An add-on-only contract, leaving first-party slices hand-rolled** — rejected. The + product resource already contains three slices written by hand, the product page needs + four more, and the brand, collection, and product-type pages share two of them. One + contract used by both sides is smaller, and first-party use is the proof that the + add-on surface is complete. +- **Slices declare prefixed keys themselves** — rejected. It turns a structural + guarantee into a runtime check and makes every slice method deal in prefixes. +- **Per-page explicit seeding** (each edit controller merges slice values into its own + props) — rejected. Every controller would need the same lines, and the middleware + already resolves the current record. One shared prop keeps slice support automatic + for any draft-backed page, first-party or add-on. +- **Slices on create pages** — deferred. Create pages post a plain form and redirect; + there is no draft and no record until the store succeeds. +- **Do nothing** — rejected. Add-ons fork the page by registering a component under the + first-party page name, which breaks on every panel upgrade, and first-party + sub-surfaces keep saving outside the draft. + +## Migration impact + +- **Database**: none. Slice values live in the existing `edit_drafts` JSON columns under + prefixed keys, and a slice persists committed values wherever it chooses. +- **Breaking changes**: none to the public surface. `ProductDraftResource` shrinks as its + hand-rolled slices migrate to the contract, but it is internal. `EditDraft` moves from + `MassPrunable` to `Prunable`. The product edit page's `attributeValues`, + `availabilityValues` and `variantValues` props go away, replaced by the shared + `draftSliceValues` prop. +- **Upgrade path**: none required. +- **Translations**: no new panel copy. A slice's labels come from its own lang group + (an add-on's via `Section::langNamespaces()`). The example add-on's `en` and `fr` + groups gain the loyalty-tier label. +- **Filament / admin impact**: none. +- **Public contract surface** (treated as contract from first release): the + `DraftSlice` contract and abstract, `Section::draftSlices()` and + `Section::draftExtensions()`, the `{namespace}:{field}` and `addon:{key}:{field}` key + schemes, the `draftSliceValues` shared prop, `useDraftSlice` and its return shape, and + the `slices` option on `useEditDraft`. `ComposedDraftResource` is internal. + +## Open questions + +- **Permission on a slice.** The page route's `can:` middleware already gates every + draft endpoint, and a slot carries its own `permission`. Is a per-slice permission + worth adding so a staff member who cannot see the component also cannot commit its + keys? Owner: Glenn. Lean: route gate is enough; add later if an add-on needs it. +- **Cross-field rules inside a slice.** Prefixed parameters via `DraftSlice::field()` + are workable but easy to forget. Should the composer rewrite bare parameters that + match a slice field name? Owner: Glenn. Lean: no; the helper is explicit. +- **Example add-on storage.** Writing the loyalty tier into `Customer::$meta` keeps the + example schema-free but demonstrates a pattern a real add-on should avoid. Confirm + this is acceptable for a reference implementation, or give the example its own table. + Owner: Glenn. + +## References + +- [lunar#2736](https://github.com/lunarphp/lunar/discussions/2736) — the request that + prompted this spec, and the reply setting out the "add-ons add, the host subtracts" + rule. +- [[0087-product-editing-through-the-draft]] — the first-party consumer: associations, + slugs, prices, and media as slices, and the operations that stay immediate. +- [[0051-panel-edit-drafts]] — the draft layer this composes into. +- [[0049-inertia-panel]] — the additive extension surface, `Section` hooks, slots and + zones, `ui.ts` exports, and the rejection of a tabs extension point on the same + grounds this spec applies to forms. +- [[0057-panel-products-section]] — `ProductDraftResource`, whose attribute, + availability, and variant prefixes are the precedent for namespaced slices. +- Host-level hiding of first-party fields is the other half of the discussion reply. It + is a separate, config-driven change and gets its own spec if it goes ahead. + +## Implementation plan + +- [x] Slice 1 — Server composition: `DraftSlice` contract and abstract, + `ComposedDraftResource` (prefixing, routing, stale-key pruning, ordered commit, + discard fan-out), `Section::draftSlices()` / `draftExtensions()` and + `PanelManager::draftSlice()` with lazy composition in `draftableFor()`, namespace and + field validation at registration, `EditDraft` to `Prunable`; unit tests covering prefix + round-trips, duplicate-namespace rejection, reserved `addon:` namespace, stale-key + pruning, rule-key prefixing, commit ordering inside one transaction, and discard + fan-out on discard and prune. +- [x] Slice 2 — Page seeding: `draftSliceValues` shared prop resolved from the deepest + route-bound model; feature test that a product and a variant edit page each seed the + right record's slice values. +- [x] Slice 3 — Client: `useEditDraft` merges the shared prop (with the `slices` + opt-out) and provides itself; `useDraftSlice` with the scoped proxy, errors, `field()`, + and dirty/saving passthroughs; `ui.ts` and `@lunarphp/panel` exports; vitest coverage + for scoping, the `addon:` resolution, restore, and error mapping. +- [x] Slice 4 — Migrate the product resource's hand-rolled surfaces (attributes, + availability, sole variant) onto `DraftSlice` classes registered via + `draftSlices()`, and drop the per-page value props they replace. No behaviour change; + existing draft tests must pass unchanged. +- [x] Slice 5 — Example add-on and guide: `LoyaltyTierSlice`, `LoyaltyCard.vue`, + README section, `en`/`fr` label, and the end-to-end path in `ExampleAddonTest` + (autosave, conflict, commit). diff --git a/specs/0087-product-editing-through-the-draft.md b/specs/0087-product-editing-through-the-draft.md new file mode 100644 index 0000000000..5dabc246d2 --- /dev/null +++ b/specs/0087-product-editing-through-the-draft.md @@ -0,0 +1,256 @@ +# 0087 — Product editing through the draft + +- Status: proposed +- Author: Glenn Jacobs +- Created: 2026-09-16 +- TODO item: Product editing through the draft — associations, slugs, prices, and media join the product draft as slices; the few operations that stay immediate say so (spec 0087) + +## Problem + +The product edit page has two save models and does not say which one a given control +uses. Name, status, type, brand, descriptions, tags, collections, attributes, +availability, and the simple-shape variant fields go into the draft and land with the +save cluster. Associations, URL slugs, prices, and media persist the moment they are +touched, with no toast, no entry in the dirty state, and no place in the restore banner +or the conflict dialog. They sit on the same form as the drafted fields and look +identical. + +Staff reasonably expect that nothing on an edit page takes effect until they save. Today +that is true for some cards and false for others, and the false ones include the +irreversible action of deleting an image. + +The products spec chose immediate persistence for these surfaces on the grounds that +rows without an identity until saved merge badly. That holds for the design it had in +hand. It does not hold once each surface has a stable key: an association type is a +sorted id list exactly like `collection_ids`; a URL row is identified by its language; +a price row by its currency, customer group, and minimum quantity; a media item by its +id, with uploads staged before they have one. With [[0086-panel-draft-slices]] +providing the slice contract, each of these is a small class rather than a special case. + +The brand, collection, and product-type edit pages share the media and slug components, +so they have the same split and get the same fix. + +## Proposal + +One rule, stated on the page: **an edit is part of the draft and lands when you save; an +operation applies immediately and says so.** Every control on the product and variant +edit pages is one or the other. + +Edits: everything that changes the record's own state, including its associations, +slugs, prices, and media. These become `DraftSlice` classes composed into the product +and variant resources (and, for media and slugs, the brand, collection, and product-type +resources). + +Operations: the things that change the world beyond the record's fields. Recording a +stock movement, generating or regenerating variants, collapsing to the simple shape, +bulk variant actions, duplicating, and deleting. These keep their immediate endpoints and +gain a consistent affordance so nobody mistakes them for drafted edits. + +### Slices + +Each slice follows the [[0086-panel-draft-slices]] contract. Field names are the +row identity; the value is the row's editable state; a null value marks a row for +removal at commit. Commit diffs the drafted set against the current rows and applies +creates, updates, and deletes through the existing core actions. + +**`association`** — on `Product`. One field per association type (`alternate`, +`cross-sell`, `up-sell`), value an ordered list of product ids. Normalisation drops +duplicates and the product's own id. Commit syncs the relation per type and writes the +order as position. Conflicts are per type. Commits through the existing association +actions. + +**`url`** — on every `HasUrls` draftable (product, variant, brand, collection, +product type). One field per language handle, value an ordered list of +`{id: int|null, slug: string, default: bool}`. New rows have a null id and are created at +commit; rows absent from the list are deleted. Rules enforce one default per language, +slug format, and uniqueness against other elements' URLs for that language at commit +time, so the conflict is reported as a validation error rather than a database +exception. Commits through `CreatesUrl`, `UpdatesUrl`, `DeletesUrl`. + +**`price`** — on `ProductVariant`, riding the product draft under the `variant:` slice +on simple-shape products the way the variant's other fields do. One field per +`{currency_code}:{customer_group_id|0}:{min_quantity}` tuple, value +`{price: int, list_price: int|null}` in minor units. Changing a tier's quantity or group +is a removal plus an addition, which is what it is from the other editor's point of view +too. Rules require a base row (group 0, quantity 1) per enabled currency. The composer +guarantees the tuple is unique by construction, so the duplicate-tier case the products +spec worried about cannot arise. Commits through the price actions. + +**`media`** — on every `HasMedia` draftable. One field per media collection the model's +media definition declares, value an ordered list of +`{id: int, primary: bool, properties: object}` where `properties` carries alt text and +any custom properties `UpdatesMedia` manages. Removal from the list deletes the media at +commit; this is where the irreversible step moves to. + +Uploads are staged. The upload endpoint attaches the file to the staff member's draft +row for the record (creating the draft if none exists) rather than to the record, and +returns the staged item's id and preview URL; the client appends it to the collection's +list. `EditDraft` implements `HasMedia` for this. Nothing outside the draft can see a +staged file: it is not in the record's collections, so storefront and search are +unaffected. On commit the slice moves each staged item onto the record with the media +library's move operation, then applies order, primary, and properties through +`ReordersMedia` and `UpdatesMedia`. On discard or prune, the slice's `discard()` hook +deletes the draft's staged media. Conversions run at upload as they do now, so +thumbnails work while staged. + +**`variant_media`** — on `ProductVariant`. A single ordered list of media ids from the +product's pool with a primary flag, replacing the immediate sync endpoint. Rules require +every id to belong to the parent product. + +### Components + +The affected components keep their markup and interaction design and swap their +persistence: instead of posting, each binds to its slice through `useDraftSlice()` and +mutates the values object. Reordering associations, editing a slug, typing a price, or +dragging an image now dirties the form, shows in the save cluster, survives navigation +through the restore banner, and lands atomically with the rest of the record. + +- `UrlSlugs`, `PricingEditor`, `MediaGroups`, and the associations card on the product + page drop their `router.*` calls and debounce timers. +- `MediaGroups` keeps its upload control, which posts to the staging endpoint and + appends the result to the slice; everything after upload is a draft edit. +- The variant page's media picker binds to `variant_media`. +- Optimistic reorder handling, which existed to stop rows snapping back mid-request, + goes away because there is no request. + +### Conflict dialog for structured values + +`DraftConflictDialog` today renders a field's mine/base/theirs values as text and offers +a manual merge input. That is right for scalars and translated text and wrong for a list +of media items or a price tuple. The dialog gains a per-namespace summary presentation: +a short human description of each side (a slug, an amount with currency, a count of +items with thumbnails for media, product names for associations) and keep-mine / +take-theirs only, with no manual merge input. First-party slices ship their summary +renderer; a slice without one falls back to the JSON-ish text rendering, which is what +add-ons get until they register one. Registering a renderer for an add-on slice is out +of scope here and can be added additively. + +### Operations that stay immediate + +| Operation | Where | Why it is an operation | +|---|---|---| +| Stock adjustment | Inventory card, both pages | Records a movement in the ledger | +| Generate, regenerate, collapse to simple | Options builder | Creates and destroys variant rows | +| Bulk enable, disable, delete, set price, adjust stock | Variants table | Acts on many records at once | +| Duplicate product | Page actions | Creates a new record | +| Delete product, delete variant | Page actions | Destroys the record | +| File upload | Media groups | The bytes land in storage; the link is drafted | + +Every card or section containing one of these carries the same affordance: a small +"Applies immediately" marker next to the control (new `drafts.applies_immediately` +lang key, with a tooltip explaining that the action is not part of the draft), +destructive ones confirm before running as they do today, and every one flashes a +success message on completion. Bulk set-price is the one operation that looks like a +value edit; it keeps a confirmation step for that reason. + +File upload is the exception to the marker: from the user's point of view it is part of +the draft, because nothing about the record changes until save. Only the file's arrival +in storage is immediate, and that is invisible. + +### Routes + +The nested `associations.*`, `urls.*`, `media.update|reorder|destroy`, +`variants.prices.*`, and `variants.media.sync` routes are removed. `media.store` becomes +the staging upload endpoint. The draft trio, `options.generate`, `variants.bulk`, +`variants.stock.adjust`, `duplicate`, and `destroy` are unchanged. + +## Alternatives considered + +- **Keep the split and label the immediate cards** — rejected. Labelling would make the + current behaviour honest but not sensible: deleting an image would still be + irreversible on click, and two staff editing prices would still overwrite each other + with no conflict detection. +- **Draft everything, including stock and generation** — rejected. A stock adjustment + is a movement with its own audit trail, not a field, and drafting it would mean + drafting a ledger entry. Generation restructures rows other staff may be editing and + cannot merge field-wise; the products spec's reasoning stands. +- **Draft the media link but keep uploads on the record** — rejected. A file attached to + the record on upload is visible to the storefront before the draft is saved, which is + the very thing staff expect not to happen. Staging on the draft row costs one `HasMedia` + implementation and a move at commit. +- **Stage uploads on the staff member rather than the draft** — rejected. The draft row + is the natural owner: it already has the record and staff identity, its lifetime is + the staging lifetime, and its prune and discard paths are where cleanup belongs. +- **Whole-table keys for prices** (`prices` as one list) — rejected. One key means any + two staff touching pricing conflict on everything. Per-tuple keys conflict only on the + row both touched, which is the draft layer's whole point. +- **Manual merge for structured conflicts** — rejected for now. A merge editor for a + media list or a price row is a feature in itself; keep-mine / take-theirs covers the + realistic case and matches how the products spec already treats availability rows. + +## Migration impact + +- **Database**: none. Staged media use the media table's existing polymorphic owner + columns. Drafts stay in `edit_drafts`. +- **Breaking changes**: the removed nested product routes were listed as public surface + in the brands and products specs. v2 is unreleased, so this is a pre-release contract + change rather than a break for consumers; add-ons that called those endpoints directly + (none known) move to the draft. `EditDraft` gains `HasMedia` and becomes `Prunable` + (from [[0086-panel-draft-slices]]). +- **Upgrade path**: none required. +- **Translations**: new `drafts.applies_immediately` and its tooltip, plus the conflict + summary phrases for the four first-party slices, across all 16 panel locales. The + removed endpoints' flash keys (`pricing.flash_*`, `products.flash_associations_added`, + `media.flash_updated|deleted|reordered`) are deleted; `media.flash_uploaded` is kept + for the staging response. +- **Filament / admin impact**: none. The Filament admin's product resource is untouched. + A Filament user editing prices while a panel user drafts them is exactly the + concurrent case the commit check now catches for prices too. +- **Public contract surface**: the slice namespaces `association`, `url`, `price`, + `media`, and `variant_media` and their value shapes; the staging upload endpoint and + its response shape; the `drafts.applies_immediately` affordance as the convention for + any future immediate operation on an edit page. + +## Open questions + +- **Staged media and the media manager.** A draft's staged uploads appear only on the + page where they were uploaded. Should the media manager, if it grows a library view, + show staged items with a "pending" state, or hide them? Owner: Glenn. Lean: hide; + they are not the record's media yet. +- **Bulk set-price as an operation.** It is the one immediate control that edits values. + Keeping it immediate with confirmation is the proposal; revisit if staff find it + surprising in use. +- **Slice migration order.** Media staging is the largest piece. Ship associations and + slugs first to prove the pattern, then prices, then media? Or land all four together + so the page has one save model from the first release that changes it? Owner: Glenn. + Lean: ship in the plan's order below; each slice leaves the page consistent for the + surfaces it covers, and the marker makes the remaining immediate ones explicit in the + interim. + +## References + +- [[0086-panel-draft-slices]] — the `DraftSlice` contract, composer, seeding, + `useDraftSlice`, and the discard hook this spec relies on. +- [[0051-panel-edit-drafts]] — the draft layer, key scheme, conflict and rebase protocol. +- [[0057-panel-products-section]] — the products page and the original reasoning for + immediate sub-resources, revisited here. +- [[0052-panel-brands-section]] — the shared media and slug surfaces and their actions. +- [[0060-panel-media-groups]] — media collections on catalog edit screens. +- [lunar#2736](https://github.com/lunarphp/lunar/discussions/2736) — the discussion + that surfaced the split save model. + +## Implementation plan + +- [ ] Slice 1 — Affordance and rule: the `drafts.applies_immediately` marker with + tooltip on every immediate operation on the product and variant pages, confirmation + on bulk set-price, flash on every operation; 16-locale copy; page tests asserting each + operation carries the marker. Lands first so the page is honest before any surface + moves. +- [ ] Slice 2 — `association` slice, the associations card on `useDraftSlice`, removal + of the association routes and controller; draft feature tests (autosave, per-type + conflict, commit ordering) and a vitest pass over the card. +- [ ] Slice 3 — `url` slice on every `HasUrls` draftable, `UrlSlugs` on `useDraftSlice`, + removal of the URL routes; uniqueness and one-default rules; tests across products, + variants, brands, collections, product types. +- [ ] Slice 4 — `price` slice on the variant resource and under `variant:` on the + product resource, `PricingEditor` on `useDraftSlice`, removal of the price routes; + tuple-key round-trip, base-row rules, minor-unit normalisation, conflict per tuple. +- [ ] Slice 5 — Media staging: `EditDraft` as `HasMedia`, the staging upload endpoint, + the `media` slice on every `HasMedia` draftable with move-on-commit and + delete-on-discard, `variant_media` slice, `MediaGroups` and the variant picker on + `useDraftSlice`, removal of the media update/reorder/destroy and variant sync routes; + tests for staging visibility, commit move, discard and prune cleanup, conflict per + collection. +- [ ] Slice 6 — Conflict dialog summary presentation for the four first-party slices; + vitest coverage and a page test that a structured conflict renders the summary and + offers no manual merge. diff --git a/specs/README.md b/specs/README.md index 9ca366bf1e..9650d307e8 100644 --- a/specs/README.md +++ b/specs/README.md @@ -93,3 +93,5 @@ Each spec carries a `Status:` line in its frontmatter / header: | 0072 | Panel Discounts section | accepted | | 0073 | Split `AmountOff` into `PercentageOff` and `FixedAmountOff` | implemented | | 0074 | Panel global search (command palette) | implemented | +| 0086 | Panel draft slices: namespaced contributions to first-party edit drafts | proposed | +| 0087 | Product editing through the draft | proposed | diff --git a/tests/panel/Feature/Drafts/DraftSlicesTest.php b/tests/panel/Feature/Drafts/DraftSlicesTest.php new file mode 100644 index 0000000000..8057984d9e --- /dev/null +++ b/tests/panel/Feature/Drafts/DraftSlicesTest.php @@ -0,0 +1,274 @@ +staff = Staff::factory()->create(['admin' => true]); + $this->actingAs($this->staff, 'staff'); + + $this->customer = Customer::factory()->create(['first_name' => 'Ada', 'meta' => ['memo' => 'hello']]); + + app(PanelManager::class)->draftSlice(MemoSlice::class); +}); + +it('composes a resource with its slices under prefixed keys', function () { + $resource = app(PanelManager::class)->draftableFor($this->customer); + + expect($resource)->toBeInstanceOf(ComposedDraftResource::class) + ->and($resource->resource())->toBeInstanceOf(CustomerDraftResource::class) + ->and($resource->fields())->toContain('first_name', 'notes:memo') + ->and($resource->currentValues($this->customer)['notes:memo'])->toBe('hello') + ->and($resource->sliceValues($this->customer))->toBe(['notes:memo' => 'hello']) + ->and($resource->rules($this->customer))->toHaveKey('notes:memo') + ->and($resource->labels()['notes:memo'])->toBe('Memo'); +}); + +it('returns the plain resource for a model without slices', function () { + expect(app(PanelManager::class)->draftableFor(Brand::factory()->make())) + ->toBeInstanceOf(BrandDraftResource::class); +}); + +it('places extension slices under the reserved addon namespace', function () { + $manager = app(PanelManager::class)->draftSlice(RivalMemoSlice::class, addon: true); + + expect(array_keys($manager->draftSlicesFor(Customer::class)))->toBe(['notes', 'addon:notes']); +}); + +it('rejects a second class claiming the same namespace', function () { + app(PanelManager::class)->draftSlice(RivalMemoSlice::class); +})->throws(InvalidArgumentException::class, 'claimed by both'); + +it('ignores the same class registering twice', function () { + $manager = app(PanelManager::class)->draftSlice(MemoSlice::class); + + expect($manager->draftSlicesFor(Customer::class))->toHaveCount(1); +}); + +it('rejects a first-party slice claiming the addon namespace', function () { + app(PanelManager::class)->draftSlice(AddonReservedSlice::class); +})->throws(InvalidArgumentException::class, 'reserved [addon] namespace'); + +it('rejects a malformed namespace', function () { + app(PanelManager::class)->draftSlice(BadKeySlice::class); +})->throws(InvalidArgumentException::class, 'must match [a-z0-9_-]+'); + +it('autosaves a slice field under its prefixed key, normalised by the slice', function () { + $this->patchJson(route('panel.customers.draft.update', $this->customer), [ + 'data' => ['first_name' => 'Grace', 'notes:memo' => ''], + ])->assertOk()->assertJsonPath('data.notes:memo', null); + + expect(EditDraft::sole()->base_snapshot)->toBe(['first_name' => 'Ada', 'notes:memo' => 'hello']); +}); + +it('validates slice fields under their prefixed key', function () { + $this->patchJson(route('panel.customers.draft.update', $this->customer), [ + 'data' => ['notes:memo' => str_repeat('x', 21)], + ])->assertOk(); + + $this->postJson(route('panel.customers.draft.commit', $this->customer), ['data' => [], 'rebase' => []]) + ->assertUnprocessable() + ->assertJsonValidationErrorFor('notes:memo'); +}); + +it('commits the resource then the slice inside one transaction', function () { + $this->patchJson(route('panel.customers.draft.update', $this->customer), [ + 'data' => ['first_name' => 'Grace', 'notes:memo' => 'updated'], + ])->assertOk(); + + $this->postJson(route('panel.customers.draft.commit', $this->customer), ['data' => [], 'rebase' => []]) + ->assertOk(); + + $this->customer->refresh(); + + expect($this->customer->first_name)->toBe('Grace') + ->and($this->customer->meta['memo'])->toBe('updated') + ->and(MemoSlice::$committed)->toBe(['slice']) + ->and(EditDraft::count())->toBe(0) + // A committed draft is consumed, not discarded. + ->and(MemoSlice::$discarded)->toBe([]); +}); + +it('rolls the resource commit back when a slice commit fails', function () { + $this->patchJson(route('panel.customers.draft.update', $this->customer), [ + 'data' => ['first_name' => 'Grace', 'notes:memo' => 'boom'], + ])->assertOk(); + + $this->withoutExceptionHandling(); + + try { + $this->postJson(route('panel.customers.draft.commit', $this->customer), ['data' => [], 'rebase' => []]); + } catch (RuntimeException $exception) { + expect($exception->getMessage())->toBe('Memo slice commit failed.'); + } + + expect($this->customer->refresh()->first_name)->toBe('Ada') + ->and(EditDraft::count())->toBe(1); +}); + +it('reports a slice conflict with the slice label', function () { + $this->patchJson(route('panel.customers.draft.update', $this->customer), [ + 'data' => ['notes:memo' => 'mine'], + ])->assertOk(); + + $this->customer->update(['meta' => ['memo' => 'theirs']]); + + $this->postJson(route('panel.customers.draft.commit', $this->customer), ['data' => [], 'rebase' => []]) + ->assertConflict() + ->assertJsonPath('conflicts.0.key', 'notes:memo') + ->assertJsonPath('conflicts.0.label', 'Memo') + ->assertJsonPath('conflicts.0.theirs', 'theirs'); +}); + +it('drops stored keys the resource no longer declares instead of blocking the commit', function () { + EditDraft::factory()->create([ + 'draftable_type' => $this->customer->getMorphClass(), + 'draftable_id' => $this->customer->id, + 'staff_id' => $this->staff->id, + 'data' => ['first_name' => 'Grace', 'addon:removed:field' => 'orphan'], + 'base_snapshot' => ['first_name' => 'Ada', 'addon:removed:field' => 'old'], + ]); + + $this->postJson(route('panel.customers.draft.commit', $this->customer), ['data' => [], 'rebase' => []]) + ->assertOk(); + + expect($this->customer->refresh()->first_name)->toBe('Grace'); +}); + +it('fans a discard out to the slices whose keys the draft held', function () { + $this->patchJson(route('panel.customers.draft.update', $this->customer), [ + 'data' => ['notes:memo' => 'mine'], + ])->assertOk(); + + $draft = EditDraft::sole(); + + $this->deleteJson(route('panel.customers.draft.destroy', $this->customer))->assertNoContent(); + + expect(MemoSlice::$discarded)->toBe([$draft->id]); +}); + +it('skips the discard hook for drafts that never held the slice', function () { + $this->patchJson(route('panel.customers.draft.update', $this->customer), [ + 'data' => ['first_name' => 'Grace'], + ])->assertOk(); + + $this->deleteJson(route('panel.customers.draft.destroy', $this->customer))->assertNoContent(); + + expect(MemoSlice::$discarded)->toBe([]); +}); + +it('fans pruning and record deletion out to the slices', function () { + $this->patchJson(route('panel.customers.draft.update', $this->customer), [ + 'data' => ['notes:memo' => 'stale'], + ])->assertOk(); + + $stale = EditDraft::sole(); + + $this->travel(8)->days(); + + $this->artisan('model:prune', ['--model' => [EditDraft::class]]); + + expect(EditDraft::count())->toBe(0) + ->and(MemoSlice::$discarded)->toBe([$stale->id]); + + $this->patchJson(route('panel.customers.draft.update', $this->customer), [ + 'data' => ['notes:memo' => 'orphaned'], + ])->assertOk(); + + $orphaned = EditDraft::sole(); + + $this->customer->delete(); + + expect(EditDraft::count())->toBe(0) + ->and(MemoSlice::$discarded)->toBe([$stale->id, $orphaned->id]); +}); + +it('seeds edit pages with the slice values of the deepest route-bound record', function () { + Language::factory()->create(['default' => true, 'code' => 'en']); + $channel = Channel::factory()->create(); + + $product = Product::factory()->create(); + $variant = ProductVariant::factory()->create(['product_id' => $product->id, 'sku' => 'WID-1']); + $product->channels()->sync([$channel->id => ['enabled' => true]]); + + $this->get(route('panel.products.edit', $product)) + ->assertOk() + ->assertInertia(fn (Assert $page) => $page + ->where("draftSliceValues.channel:{$channel->id}.enabled", true) + ->where('draftSliceValues.variant:sku', 'WID-1')); + + // The variant page drafts the variant, which has no slices: the product's + // values must not leak in from the parent binding. + $this->get(route('panel.products.variants.edit', [$product, $variant])) + ->assertOk() + ->assertInertia(fn (Assert $page) => $page + ->where('draftSliceValues', [])); + + $this->get(route('panel.customers.edit', $this->customer)) + ->assertOk() + ->assertInertia(fn (Assert $page) => $page->where('draftSliceValues.notes:memo', 'hello')); + + $this->get(route('panel.customers.index')) + ->assertOk() + ->assertInertia(fn (Assert $page) => $page->where('draftSliceValues', [])); +}); diff --git a/tests/panel/Feature/ExampleAddonTest.php b/tests/panel/Feature/ExampleAddonTest.php index 48c4b3aac0..8eea048930 100644 --- a/tests/panel/Feature/ExampleAddonTest.php +++ b/tests/panel/Feature/ExampleAddonTest.php @@ -6,6 +6,7 @@ use Lunar\Core\Models\Language; use Lunar\Core\Models\Product; use Lunar\Core\Models\Staff; +use Lunar\Panel\Models\EditDraft; use Lunar\Panel\PanelManager; use Lunar\Tests\Panel\Fixtures\ExampleAddonTestCase; @@ -396,3 +397,82 @@ expect($rows)->toBeEmpty(); }); + +it('shares the loyalty card slot and its slice values on the customer edit page', function () { + $this->actingAs(Staff::factory()->create(['admin' => true]), 'staff'); + + $customer = Customer::factory()->create(['meta' => ['loyalty_tier' => 'silver']]); + + $this->get(route('panel.customers.edit', $customer)) + ->assertOk() + ->assertInertia(fn (Assert $page) => $page + ->where('slots', fn ($slots) => collect($slots->get('customers.edit:main:after')) + ->contains(fn ($entry) => $entry['component'] === 'example-addon::LoyaltyCard')) + ->where('draftSliceValues.addon:example-addon:tier', 'silver')); +}); + +it('drafts, conflicts and commits the loyalty tier with the customer through the real draft routes', function () { + $staff = Staff::factory()->create(['admin' => true]); + $this->actingAs($staff, 'staff'); + + $customer = Customer::factory()->create(['first_name' => 'Ada']); + + // Autosave stores the slice field under its addon-prefixed key, normalised + // by the slice ('' for "no tier" becomes null). + $this->patchJson(route('panel.customers.draft.update', $customer), [ + 'data' => ['first_name' => 'Grace', 'addon:example-addon:tier' => 'gold'], + ])->assertOk()->assertJsonPath('data.addon:example-addon:tier', 'gold'); + + expect(EditDraft::sole()->base_snapshot)->toBe(['first_name' => 'Ada', 'addon:example-addon:tier' => null]); + + // A concurrent change to the tier surfaces as a conflict labelled by the + // add-on's own lang key, and nothing commits. + $customer->update(['meta' => ['loyalty_tier' => 'silver']]); + + $this->postJson(route('panel.customers.draft.commit', $customer), ['data' => [], 'rebase' => []]) + ->assertConflict() + ->assertJsonPath('conflicts.0.key', 'addon:example-addon:tier') + ->assertJsonPath('conflicts.0.label', 'Loyalty tier') + ->assertJsonPath('conflicts.0.theirs', 'silver'); + + expect($customer->refresh()->first_name)->toBe('Ada'); + + // Resolved against the current value, the commit lands both the + // customer's own field and the add-on's in one go. + $this->postJson(route('panel.customers.draft.commit', $customer), [ + 'data' => ['addon:example-addon:tier' => 'gold'], + 'rebase' => ['addon:example-addon:tier' => 'silver'], + ])->assertOk(); + + $customer->refresh(); + + expect($customer->first_name)->toBe('Grace') + ->and($customer->meta['loyalty_tier'])->toBe('gold') + ->and(EditDraft::count())->toBe(0); +}); + +it('validates the loyalty tier under its prefixed key', function () { + $this->actingAs(Staff::factory()->create(['admin' => true]), 'staff'); + + $customer = Customer::factory()->create(); + + $this->patchJson(route('panel.customers.draft.update', $customer), [ + 'data' => ['addon:example-addon:tier' => 'platinum'], + ])->assertOk(); + + $this->postJson(route('panel.customers.draft.commit', $customer), ['data' => [], 'rebase' => []]) + ->assertUnprocessable() + ->assertJsonValidationErrorFor('addon:example-addon:tier'); +}); + +it('refuses an add-on slice key outside its own namespace', function () { + $this->actingAs(Staff::factory()->create(['admin' => true]), 'staff'); + + $customer = Customer::factory()->create(); + + // The add-on registered under `addon:example-addon`; the bare namespace + // is not a field the customer draft knows. + $this->patchJson(route('panel.customers.draft.update', $customer), [ + 'data' => ['example-addon:tier' => 'gold'], + ])->assertUnprocessable(); +}); diff --git a/tests/panel/Feature/Products/ProductEditTest.php b/tests/panel/Feature/Products/ProductEditTest.php index 73b1a610f9..a05d2feac8 100644 --- a/tests/panel/Feature/Products/ProductEditTest.php +++ b/tests/panel/Feature/Products/ProductEditTest.php @@ -51,7 +51,7 @@ ->assertInertia(fn (Assert $page) => $page ->where('shape', 'simple') ->where('variant.id', $this->variant->id) - ->where('variantValues.variant:sku', 'WID-1') + ->where('draftSliceValues.variant:sku', 'WID-1') ->has('variant.stock.levels') ->has('currencies') ->has('taxClasses') diff --git a/tests/panel/Fixtures/Drafts/MemoSlice.php b/tests/panel/Fixtures/Drafts/MemoSlice.php new file mode 100644 index 0000000000..4dcbdab682 --- /dev/null +++ b/tests/panel/Fixtures/Drafts/MemoSlice.php @@ -0,0 +1,85 @@ + */ + public static array $committed = []; + + /** @var array */ + public static array $discarded = []; + + public function __construct(protected UpdatesCustomer $updatesCustomer) {} + + public function model(): string + { + return Customer::class; + } + + public function key(): string + { + return 'notes'; + } + + public function fields(Model $record): array + { + return ['memo']; + } + + public function currentValues(Model $record): array + { + /** @var Customer $record */ + return ['memo' => $record->meta['memo'] ?? null]; + } + + public function normalize(array $data): array + { + if (array_key_exists('memo', $data) && $data['memo'] === '') { + $data['memo'] = null; + } + + return $data; + } + + public function rules(Model $record): array + { + return ['memo' => ['nullable', 'string', 'max:20']]; + } + + public function commit(Model $record, array $values): void + { + /** @var Customer $record */ + if (($values['memo'] ?? null) === 'boom') { + throw new RuntimeException('Memo slice commit failed.'); + } + + self::$committed[] = 'slice'; + + $this->updatesCustomer->execute($record, [ + 'meta' => [...($record->meta?->getArrayCopy() ?? []), 'memo' => $values['memo'] ?? null], + ]); + } + + public function labels(): array + { + return ['memo' => 'Memo']; + } + + public function discard(Model $record, EditDraft $draft): void + { + self::$discarded[] = (int) $draft->getKey(); + } +} From e0c1b5763c562944ecab8f1540879dcd23d0ffb0 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Wed, 16 Sep 2026 10:31:50 +0100 Subject: [PATCH 2/5] chore(panel): rebuild compiled assets Co-Authored-By: Claude Fable 5.1 --- .../panel/public/build/assets/app-C3gs_Or0.js | 272 ----------------- .../public/build/assets/app-D1QRsHyg.css | 1 + .../panel/public/build/assets/app-D5mvwWco.js | 277 ++++++++++++++++++ .../public/build/assets/app-DrcNYi_n.css | 1 - packages/panel/public/build/manifest.json | 6 +- 5 files changed, 281 insertions(+), 276 deletions(-) delete mode 100644 packages/panel/public/build/assets/app-C3gs_Or0.js create mode 100644 packages/panel/public/build/assets/app-D1QRsHyg.css create mode 100644 packages/panel/public/build/assets/app-D5mvwWco.js delete mode 100644 packages/panel/public/build/assets/app-DrcNYi_n.css diff --git a/packages/panel/public/build/assets/app-C3gs_Or0.js b/packages/panel/public/build/assets/app-C3gs_Or0.js deleted file mode 100644 index ff81bdd4a7..0000000000 --- a/packages/panel/public/build/assets/app-C3gs_Or0.js +++ /dev/null @@ -1,272 +0,0 @@ -var lP=Object.defineProperty;var e_=e=>{throw TypeError(e)};var iP=(e,t,n)=>t in e?lP(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var Re=(e,t,n)=>iP(e,typeof t!="symbol"?t+"":t,n),aP=(e,t,n)=>t.has(e)||e_("Cannot "+n);var Ha=(e,t,n)=>(aP(e,t,"read from private field"),n?n.call(e):t.get(e)),za=(e,t,n)=>t.has(e)?e_("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n);/* empty css *//** -* @vue/shared v3.5.42 -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**/function nm(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const bt={},Hi=[],qr=()=>{},Sw=()=>!1,Pc=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),rm=e=>e.startsWith("onUpdate:"),Nt=Object.assign,Ay=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},uP=Object.prototype.hasOwnProperty,Ot=(e,t)=>uP.call(e,t),Ke=Array.isArray,Jo=e=>Pa(e)==="[object Map]",Es=e=>Pa(e)==="[object Set]",t_=e=>Pa(e)==="[object Date]",cP=e=>Pa(e)==="[object RegExp]",nt=e=>typeof e=="function",jt=e=>typeof e=="string",wr=e=>typeof e=="symbol",Pt=e=>e!==null&&typeof e=="object",Py=e=>(Pt(e)||nt(e))&&nt(e.then)&&nt(e.catch),Ew=Object.prototype.toString,Pa=e=>Ew.call(e),dP=e=>Pa(e).slice(8,-1),sm=e=>Pa(e)==="[object Object]",om=e=>jt(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Ul=nm(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),lm=e=>{const t=Object.create(null);return(n=>t[n]||(t[n]=e(n)))},fP=/-\w/g,fn=lm(e=>e.replace(fP,t=>t.slice(1).toUpperCase())),pP=/\B([A-Z])/g,dr=lm(e=>e.replace(pP,"-$1").toLowerCase()),Mc=lm(e=>e.charAt(0).toUpperCase()+e.slice(1)),zi=lm(e=>e?`on${Mc(e)}`:""),On=(e,t)=>!Object.is(e,t),Ki=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},im=e=>{const t=parseFloat(e);return isNaN(t)?e:t},vf=e=>{const t=jt(e)?Number(e):NaN;return isNaN(t)?e:t};let n_;const am=()=>n_||(n_=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{}),mP="Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol",hP=nm(mP);function kt(e){if(Ke(e)){const t={};for(let n=0;n{if(n){const r=n.split(vP);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function pe(e){let t="";if(jt(e))t=e;else if(Ke(e))for(let n=0;nKr(n,t))}const Aw=e=>!!(e&&e.__v_isRef===!0),m=e=>jt(e)?e:e==null?"":Ke(e)||Pt(e)&&(e.toString===Ew||!nt(e.toString))?Aw(e)?m(e.value):JSON.stringify(e,Pw,2):String(e),Pw=(e,t)=>Aw(t)?Pw(e,t.value):Jo(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[r,s],o)=>(n[yh(r,o)+" =>"]=s,n),{})}:Es(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>yh(n))}:wr(t)?yh(t):Pt(t)&&!Ke(t)&&!sm(t)?String(t):t,yh=(e,t="")=>{var n;return wr(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};function wP(e){return e==null?"initial":typeof e=="string"?e===""?" ":e:String(e)}/** -* @vue/reactivity v3.5.42 -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**/let Cn;class My{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this._warnOnRun=!0,this.__v_skip=!0,!t&&Cn&&(Cn.active?(this.parent=Cn,this.index=(Cn.scopes||(Cn.scopes=[])).push(this)-1):(this._active=!1,this._warnOnRun=!1))}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes){const r=this.scopes.slice();for(t=0,n=r.length;t0&&--this._on===0){if(Cn===this)Cn=this.prevScope;else{let t=Cn;for(;t;){if(t.prevScope===this){t.prevScope=this.prevScope;break}t=t.prevScope}}this.prevScope=void 0}}stop(t){if(this._active){this._active=!1;let n,r;for(n=0,r=this.effects.length;n0)return;if(wu){let t=wu;for(wu=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;ku;){let t=ku;for(ku=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(r){e||(e=r)}t=n}}if(e)throw e}function Vw(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function Iw(e){let t,n=e.depsTail,r=n;for(;r;){const s=r.prevDep;r.version===-1?(r===n&&(n=s),Iy(r),$P(r)):t=r,r.dep.activeLink=r.prevActiveLink,r.prevActiveLink=void 0,r=s}e.deps=t,e.depsTail=n}function zg(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Rw(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Rw(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Gu)||(e.globalVersion=Gu,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!zg(e))))return;e.flags|=2;const t=e.dep,n=Kt,r=es;Kt=e,es=!0;try{Vw(e);const s=e.fn(e._value);(t.version===0||On(s,e._value))&&(e.flags|=128,e._value=s,t.version++)}catch(s){throw t.version++,s}finally{Kt=n,es=r,Iw(e),e.flags&=-3}}function Iy(e,t=!1){const{dep:n,prevSub:r,nextSub:s}=e;if(r&&(r.nextSub=s,e.prevSub=void 0),s&&(s.prevSub=r,e.nextSub=void 0),n.subs===e&&(n.subs=r,!r&&n.computed)){n.computed.flags&=-5;for(let o=n.computed.deps;o;o=o.nextDep)Iy(o,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function $P(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}function CP(e,t){e.effect instanceof Wu&&(e=e.effect.fn);const n=new Wu(e);t&&Nt(n,t);try{n.run()}catch(s){throw n.stop(),s}const r=n.run.bind(n);return r.effect=n,r}function SP(e){e.effect.stop()}let es=!0;const Nw=[];function ao(){Nw.push(es),es=!1}function uo(){const e=Nw.pop();es=e===void 0?!0:e}function s_(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=Kt;Kt=void 0;try{t()}finally{Kt=n}}}let Gu=0,EP=class{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}};class dm{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!Kt||!es||Kt===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==Kt)n=this.activeLink=new EP(Kt,this),Kt.deps?(n.prevDep=Kt.depsTail,Kt.depsTail.nextDep=n,Kt.depsTail=n):Kt.deps=Kt.depsTail=n,Lw(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const r=n.nextDep;r.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=r),n.prevDep=Kt.depsTail,n.nextDep=void 0,Kt.depsTail.nextDep=n,Kt.depsTail=n,Kt.deps===n&&(Kt.deps=r)}return n}trigger(t){this.version++,Gu++,this.notify(t)}notify(t){Dy();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{Vy()}}}function Lw(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let r=t.deps;r;r=r.nextDep)Lw(r)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const yf=new WeakMap,jl=Symbol(""),Kg=Symbol(""),Ju=Symbol("");function qn(e,t,n){if(es&&Kt){let r=yf.get(e);r||yf.set(e,r=new Map);let s=r.get(n);s||(r.set(n,s=new dm),s.map=r,s.key=n),s.track()}}function Hs(e,t,n,r,s,o){const l=yf.get(e);if(!l){Gu++;return}const a=u=>{u&&u.trigger()};if(Dy(),t==="clear")l.forEach(a);else{const u=Ke(e),c=u&&om(n);if(u&&n==="length"){const d=Number(r);l.forEach((f,p)=>{(p==="length"||p===Ju||!wr(p)&&p>=d)&&a(f)})}else switch((n!==void 0||l.has(void 0))&&a(l.get(n)),c&&a(l.get(Ju)),t){case"add":u?c&&a(l.get("length")):(a(l.get(jl)),Jo(e)&&a(l.get(Kg)));break;case"delete":u||(a(l.get(jl)),Jo(e)&&a(l.get(Kg)));break;case"set":Jo(e)&&a(l.get(jl));break}}Vy()}function OP(e,t){const n=yf.get(e);return n&&n.get(t)}function _i(e){const t=$t(e);return t===e?t:(qn(t,"iterate",Ju),mr(e)?t:t.map(rs))}function fm(e){return qn(e=$t(e),"iterate",Ju),e}function ks(e,t){return Os(e)?oa(Cs(e)?rs(t):t):rs(t)}const TP={__proto__:null,[Symbol.iterator](){return _h(this,Symbol.iterator,e=>ks(this,e))},concat(...e){return _i(this).concat(...e.map(t=>Ke(t)?_i(t):t))},entries(){return _h(this,"entries",e=>(e[1]=ks(this,e[1]),e))},every(e,t){return Ds(this,"every",e,t,void 0,arguments)},filter(e,t){return Ds(this,"filter",e,t,n=>n.map(r=>ks(this,r)),arguments)},find(e,t){return Ds(this,"find",e,t,n=>ks(this,n),arguments)},findIndex(e,t){return Ds(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return Ds(this,"findLast",e,t,n=>ks(this,n),arguments)},findLastIndex(e,t){return Ds(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return Ds(this,"forEach",e,t,void 0,arguments)},includes(...e){return xh(this,"includes",e)},indexOf(...e){return xh(this,"indexOf",e)},join(e){return _i(this).join(e)},lastIndexOf(...e){return xh(this,"lastIndexOf",e)},map(e,t){return Ds(this,"map",e,t,void 0,arguments)},pop(){return Ka(this,"pop")},push(...e){return Ka(this,"push",e)},reduce(e,...t){return o_(this,"reduce",e,t)},reduceRight(e,...t){return o_(this,"reduceRight",e,t)},shift(){return Ka(this,"shift")},some(e,t){return Ds(this,"some",e,t,void 0,arguments)},splice(...e){return Ka(this,"splice",e)},toReversed(){return _i(this).toReversed()},toSorted(e){return _i(this).toSorted(e)},toSpliced(...e){return _i(this).toSpliced(...e)},unshift(...e){return Ka(this,"unshift",e)},values(){return _h(this,"values",e=>ks(this,e))}};function _h(e,t,n){const r=fm(e),s=r[t]();return r!==e&&!mr(e)&&(s._next=s.next,s.next=()=>{const o=s._next();return o.done||(o.value=n(o.value)),o}),s}const AP=Array.prototype;function Ds(e,t,n,r,s,o){const l=fm(e),a=l!==e&&!mr(e),u=l[t];if(u!==AP[t]){const f=u.apply(e,o);return a?rs(f):f}let c=n;l!==e&&(a?c=function(f,p){return n.call(this,ks(e,f),p,e)}:n.length>2&&(c=function(f,p){return n.call(this,f,p,e)}));const d=u.call(l,c,r);return a&&s?s(d):d}function o_(e,t,n,r){const s=fm(e),o=s!==e&&!mr(e);let l=n,a=!1;s!==e&&(o?(a=r.length===0,l=function(c,d,f){return a&&(a=!1,c=ks(e,c)),n.call(this,c,ks(e,d),f,e)}):n.length>3&&(l=function(c,d,f){return n.call(this,c,d,f,e)}));const u=s[t](l,...r);return a?ks(e,u):u}function xh(e,t,n){const r=$t(e);qn(r,"iterate",Ju);const s=r[t](...n);return(s===-1||s===!1)&&Vc(n[0])?(n[0]=$t(n[0]),r[t](...n)):s}function Ka(e,t,n=[]){ao(),Dy();const r=$t(e)[t].apply(e,n);return Vy(),uo(),r}const PP=nm("__proto__,__v_isRef,__isVue"),Fw=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(wr));function MP(e){wr(e)||(e=String(e));const t=$t(this);return qn(t,"has",e),t.hasOwnProperty(e)}class Bw{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,r){if(n==="__v_skip")return t.__v_skip;const s=this._isReadonly,o=this._isShallow;if(n==="__v_isReactive")return!s;if(n==="__v_isReadonly")return s;if(n==="__v_isShallow")return o;if(n==="__v_raw")return r===(s?o?Kw:zw:o?Hw:jw).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(r)?t:void 0;const l=Ke(t);if(!s){let u;if(l&&(u=TP[n]))return u;if(n==="hasOwnProperty")return MP}const a=Reflect.get(t,n,Et(t)?t:r);if((wr(n)?Fw.has(n):PP(n))||(s||qn(t,"get",n),o))return a;if(Et(a)){const u=l&&om(n)?a:a.value;return s&&Pt(u)?bf(u):u}return Pt(a)?s?bf(a):rt(a):a}}class qw extends Bw{constructor(t=!1){super(!1,t)}set(t,n,r,s){let o=t[n];const l=Ke(t)&&om(n);if(!this._isShallow){const c=Os(o);if(!mr(r)&&!Os(r)&&(o=$t(o),r=$t(r)),!l&&Et(o)&&!Et(r))return c||(o.value=r),!0}const a=l?Number(n)e,dd=e=>Reflect.getPrototypeOf(e);function NP(e,t,n){return function(...r){const s=this.__v_raw,o=$t(s),l=Jo(o),a=e==="entries"||e===Symbol.iterator&&l,u=e==="keys"&&l,c=s[e](...r),d=n?Wg:t?oa:rs;return!t&&qn(o,"iterate",u?Kg:jl),Nt(Object.create(c),{next(){const{value:f,done:p}=c.next();return p?{value:f,done:p}:{value:a?[d(f[0]),d(f[1])]:d(f),done:p}}})}}function fd(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function LP(e,t){const n={get(s){const o=this.__v_raw,l=$t(o),a=$t(s);e||(On(s,a)&&qn(l,"get",s),qn(l,"get",a));const{has:u}=dd(l),c=t?Wg:e?oa:rs;if(u.call(l,s))return c(o.get(s));if(u.call(l,a))return c(o.get(a));o!==l&&o.get(s)},get size(){const s=this.__v_raw;return!e&&qn($t(s),"iterate",jl),s.size},has(s){const o=this.__v_raw,l=$t(o),a=$t(s);return e||(On(s,a)&&qn(l,"has",s),qn(l,"has",a)),s===a?o.has(s):o.has(s)||o.has(a)},forEach(s,o){const l=this,a=l.__v_raw,u=$t(a),c=t?Wg:e?oa:rs;return!e&&qn(u,"iterate",jl),a.forEach((d,f)=>s.call(o,c(d),c(f),l))}};return Nt(n,e?{add:fd("add"),set:fd("set"),delete:fd("delete"),clear:fd("clear")}:{add(s){const o=$t(this),l=dd(o),a=$t(s),u=!t&&!mr(s)&&!Os(s)?a:s;return l.has.call(o,u)||On(s,u)&&l.has.call(o,s)||On(a,u)&&l.has.call(o,a)||(o.add(u),Hs(o,"add",u,u)),this},set(s,o){!t&&!mr(o)&&!Os(o)&&(o=$t(o));const l=$t(this),{has:a,get:u}=dd(l);let c=a.call(l,s);c||(s=$t(s),c=a.call(l,s));const d=u.call(l,s);return l.set(s,o),c?On(o,d)&&Hs(l,"set",s,o):Hs(l,"add",s,o),this},delete(s){const o=$t(this),{has:l,get:a}=dd(o);let u=l.call(o,s);u||(s=$t(s),u=l.call(o,s)),a&&a.call(o,s);const c=o.delete(s);return u&&Hs(o,"delete",s,void 0),c},clear(){const s=$t(this),o=s.size!==0,l=s.clear();return o&&Hs(s,"clear",void 0,void 0),l}}),["keys","values","entries",Symbol.iterator].forEach(s=>{n[s]=NP(s,e,t)}),n}function pm(e,t){const n=LP(e,t);return(r,s,o)=>s==="__v_isReactive"?!e:s==="__v_isReadonly"?e:s==="__v_raw"?r:Reflect.get(Ot(n,s)&&s in r?n:r,s,o)}const FP={get:pm(!1,!1)},BP={get:pm(!1,!0)},qP={get:pm(!0,!1)},UP={get:pm(!0,!0)},jw=new WeakMap,Hw=new WeakMap,zw=new WeakMap,Kw=new WeakMap;function jP(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function rt(e){return Os(e)?e:mm(e,!1,DP,FP,jw)}function Ww(e){return mm(e,!1,IP,BP,Hw)}function bf(e){return mm(e,!0,VP,qP,zw)}function Ro(e){return mm(e,!0,RP,UP,Kw)}function mm(e,t,n,r,s){if(!Pt(e)||e.__v_raw&&!(t&&e.__v_isReactive)||e.__v_skip||!Object.isExtensible(e))return e;const o=s.get(e);if(o)return o;const l=jP(dP(e));if(l===0)return e;const a=new Proxy(e,l===2?r:n);return s.set(e,a),a}function Cs(e){return Os(e)?Cs(e.__v_raw):!!(e&&e.__v_isReactive)}function Os(e){return!!(e&&e.__v_isReadonly)}function mr(e){return!!(e&&e.__v_isShallow)}function Vc(e){return e?!!e.__v_raw:!1}function $t(e){const t=e&&e.__v_raw;return t?$t(t):e}function sa(e){return!Ot(e,"__v_skip")&&Object.isExtensible(e)&&Ow(e,"__v_skip",!0),e}const rs=e=>Pt(e)?rt(e):e,oa=e=>Pt(e)?bf(e):e;function Et(e){return e?e.__v_isRef===!0:!1}function U(e){return Gw(e,!1)}function yo(e){return Gw(e,!0)}function Gw(e,t){return Et(e)?e:new HP(e,t)}class HP{constructor(t,n){this.dep=new dm,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:$t(t),this._value=n?t:rs(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,r=this.__v_isShallow||mr(t)||Os(t);t=r?t:$t(t),On(t,n)&&(this._rawValue=t,this._value=r?t:rs(t),this.dep.trigger())}}function Jw(e){e.dep&&e.dep.trigger()}function i(e){return Et(e)?e.value:e}function Ln(e){return nt(e)?e():i(e)}const zP={get:(e,t,n)=>t==="__v_raw"?e:i(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const s=e[t];return Et(s)&&!Et(n)?(s.value=n,!0):Reflect.set(e,t,n,r)}};function Ry(e){return Cs(e)?e:new Proxy(e,zP)}class KP{constructor(t){this.__v_isRef=!0,this._value=void 0;const n=this.dep=new dm,{get:r,set:s}=t(n.track.bind(n),n.trigger.bind(n));this._get=r,this._set=s}get value(){return this._value=this._get()}set value(t){this._set(t)}}function hm(e){return new KP(e)}function ln(e){const t=Ke(e)?new Array(e.length):{};for(const n in e)t[n]=Xw(e,n);return t}class WP{constructor(t,n,r){this._object=t,this._defaultValue=r,this.__v_isRef=!0,this._value=void 0,this._key=wr(n)?n:String(n),this._raw=$t(t);let s=!0,o=t;if(!Ke(t)||wr(this._key)||!om(this._key))do s=!Vc(o)||mr(o);while(s&&(o=o.__v_raw));this._shallow=s}get value(){let t=this._object[this._key];return this._shallow&&(t=i(t)),this._value=t===void 0?this._defaultValue:t}set value(t){if(this._shallow&&Et(this._raw[this._key])){const n=this._object[this._key];if(Et(n)){n.value=t;return}}this._object[this._key]=t}get dep(){return OP(this._raw,this._key)}}class GP{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}function Yw(e,t,n){return Et(e)?e:nt(e)?new GP(e):Pt(e)&&arguments.length>1?Xw(e,t,n):U(e)}function Xw(e,t,n){return new WP(e,t,n)}class JP{constructor(t,n,r){this.fn=t,this.setter=n,this._value=void 0,this.dep=new dm(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Gu-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=r}notify(){if(this.flags|=16,!(this.flags&8)&&Kt!==this)return Dw(this,!0),!0}get value(){const t=this.dep.track();return Rw(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function YP(e,t,n=!1){let r,s;return nt(e)?r=e:(r=e.get,s=e.set),new JP(r,s,n)}const XP={GET:"get",HAS:"has",ITERATE:"iterate"},QP={SET:"set",ADD:"add",DELETE:"delete",CLEAR:"clear"},pd={},_f=new WeakMap;let No;function ZP(){return No}function Qw(e,t=!1,n=No){if(n){let r=_f.get(n);r||_f.set(n,r=[]),r.push(e)}}function e5(e,t,n=bt){const{immediate:r,deep:s,once:o,scheduler:l,augmentJob:a,call:u}=n,c=E=>s?E:mr(E)||s===!1||s===0?zs(E,1):zs(E);let d,f,p,h,b=!1,x=!1;if(Et(e)?(f=()=>e.value,b=mr(e)):Cs(e)?(f=()=>c(e),b=!0):Ke(e)?(x=!0,b=e.some(E=>Cs(E)||mr(E)),f=()=>e.map(E=>{if(Et(E))return E.value;if(Cs(E))return c(E);if(nt(E))return u?u(E,2):E()})):nt(e)?t?f=u?()=>u(e,2):e:f=()=>{if(p){ao();try{p()}finally{uo()}}const E=No;No=d;try{return u?u(e,3,[h]):e(h)}finally{No=E}}:f=qr,t&&s){const E=f,T=s===!0?1/0:s;f=()=>zs(E(),T)}const w=Ma(),k=()=>{d.stop(),w&&w.active&&Ay(w.effects,d)};if(o&&t){const E=t;t=(...T)=>{const O=E(...T);return k(),O}}let C=x?new Array(e.length).fill(pd):pd;const S=E=>{if(!(!(d.flags&1)||!d.dirty&&!E))if(t){const T=d.run();if(E||s||b||(x?T.some((O,P)=>On(O,C[P])):On(T,C))){p&&p();const O=No;No=d;try{const P=[T,C===pd?void 0:x&&C[0]===pd?[]:C,h];C=T,u?u(t,3,P):t(...P)}finally{No=O}}}else d.run()};return a&&a(S),d=new Wu(f),d.scheduler=l?()=>l(S,!1):S,h=E=>Qw(E,!1,d),p=d.onStop=()=>{const E=_f.get(d);if(E){if(u)u(E,4);else for(const T of E)T();_f.delete(d)}},t?r?S(!0):C=d.run():l?l(S.bind(null,!0),!0):d.run(),k.pause=d.pause.bind(d),k.resume=d.resume.bind(d),k.stop=k,k}function zs(e,t=1/0,n){if(t<=0||!Pt(e)||e.__v_skip||(n=n||new Map,(n.get(e)||0)>=t))return e;if(n.set(e,t),t--,Et(e))zs(e.value,t,n);else if(Ke(e))for(let r=0;r{zs(r,t,n)});else if(sm(e)){for(const r in e)zs(e[r],t,n);for(const r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&zs(e[r],t,n)}return e}/** -* @vue/runtime-core v3.5.42 -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**/const Zw=[];function t5(e){Zw.push(e)}function n5(){Zw.pop()}function r5(e,t){}const s5={SETUP_FUNCTION:0,0:"SETUP_FUNCTION",RENDER_FUNCTION:1,1:"RENDER_FUNCTION",NATIVE_EVENT_HANDLER:5,5:"NATIVE_EVENT_HANDLER",COMPONENT_EVENT_HANDLER:6,6:"COMPONENT_EVENT_HANDLER",VNODE_HOOK:7,7:"VNODE_HOOK",DIRECTIVE_HOOK:8,8:"DIRECTIVE_HOOK",TRANSITION_HOOK:9,9:"TRANSITION_HOOK",APP_ERROR_HANDLER:10,10:"APP_ERROR_HANDLER",APP_WARN_HANDLER:11,11:"APP_WARN_HANDLER",FUNCTION_REF:12,12:"FUNCTION_REF",ASYNC_COMPONENT_LOADER:13,13:"ASYNC_COMPONENT_LOADER",SCHEDULER:14,14:"SCHEDULER",COMPONENT_UPDATE:15,15:"COMPONENT_UPDATE",APP_UNMOUNT_CLEANUP:16,16:"APP_UNMOUNT_CLEANUP"},o5={sp:"serverPrefetch hook",bc:"beforeCreate hook",c:"created hook",bm:"beforeMount hook",m:"mounted hook",bu:"beforeUpdate hook",u:"updated",bum:"beforeUnmount hook",um:"unmounted hook",a:"activated hook",da:"deactivated hook",ec:"errorCaptured hook",rtc:"renderTracked hook",rtg:"renderTriggered hook",0:"setup function",1:"render function",2:"watcher getter",3:"watcher callback",4:"watcher cleanup function",5:"native event handler",6:"component event handler",7:"vnode hook",8:"directive hook",9:"transition hook",10:"app errorHandler",11:"app warnHandler",12:"ref function",13:"async component loader",14:"scheduler flush",15:"component update",16:"app unmount cleanup function"};function Da(e,t,n,r){try{return r?e(...r):e()}catch(s){fi(s,t,n)}}function $r(e,t,n,r){if(nt(e)){const s=Da(e,t,n,r);return s&&Py(s)&&s.catch(o=>{fi(o,t,n)}),s}if(Ke(e)){const s=[];for(let o=0;o>>1,s=Xn[r],o=Xu(s);o=Xu(n)?Xn.push(e):Xn.splice(i5(t),0,e),e.flags|=1,t2()}}function t2(){xf||(xf=e2.then(n2))}function Yu(e){if(!Ke(e))Lo&&e.id===-1?Lo.splice(Ai+1,0,e):e.flags&1||(Wi.push(e),e.flags|=1);else for(let t=0;tXu(n)-Xu(r));if(Wi.length=0,Lo){for(let n=0;ne.id==null?e.flags&2?-1:1/0:e.id;function n2(e){try{for(ms=0;msPi.emit(s,...o)),md=[]):typeof window<"u"&&window.HTMLElement&&!((r=(n=window.navigator)==null?void 0:n.userAgent)!=null&&r.includes("jsdom"))?((t.__VUE_DEVTOOLS_HOOK_REPLAY__=t.__VUE_DEVTOOLS_HOOK_REPLAY__||[]).push(o=>{r2(o,t)}),setTimeout(()=>{Pi||(t.__VUE_DEVTOOLS_HOOK_REPLAY__=null,md=[])},3e3)):md=[]}let Rn=null,gm=null;function Qu(e){const t=Rn;return Rn=e,gm=e&&e.type.__scopeId||null,t}function a5(e){gm=e}function u5(){gm=null}const c5=e=>_;function _(e,t=Rn,n){if(!t||e._n)return e;const r=(...s)=>{r._d&&tc(-1);const o=Qu(t),l=ro.length;let a;try{a=e(...s)}finally{for(let u=ro.length;u>l;u--)Cm();Qu(o),r._d&&tc(1)}return a};return r._n=!0,r._c=!0,r._d=!0,r}function jn(e,t){if(Rn===null)return e;const n=Fc(Rn),r=e.dirs||(e.dirs=[]);for(let s=0;s1)return n&&nt(t)?t.call(r&&r.proxy):t}}function d5(){return!!(zt()||Hl)}const s2=Symbol.for("v-scx"),o2=()=>eo(s2);function Cr(e,t){return Rc(e,null,t)}function Ly(e,t){return Rc(e,null,{flush:"post"})}function Fy(e,t){return Rc(e,null,{flush:"sync"})}function ye(e,t,n){return Rc(e,t,n)}function Rc(e,t,n=bt){const{immediate:r,deep:s,flush:o,once:l}=n,a=Nt({},n),u=t&&r||!t&&o!=="post";let c;if(Zl){if(o==="sync"){const h=o2();c=h.__watcherHandles||(h.__watcherHandles=[])}else if(!u){const h=()=>{};return h.stop=qr,h.resume=qr,h.pause=qr,h}}const d=In;a.call=(h,b,x)=>$r(h,d,b,x);let f=!1;o==="post"?a.scheduler=h=>{bn(h,d&&d.suspense)}:o!=="sync"&&(f=!0,a.scheduler=(h,b)=>{b?h():Ny(h)}),a.augmentJob=h=>{t&&(h.flags|=4),f&&(h.flags|=2,d&&(h.id=d.uid,h.i=d))};const p=e5(e,t,a);return Zl&&(c?c.push(p):u&&p()),p}function f5(e,t,n){const r=this.proxy,s=jt(e)?e.includes(".")?l2(r,e):()=>r[e]:e.bind(r,r);let o;nt(t)?o=t:(o=t.handler,n=t);const l=Va(this),a=Rc(s,o.bind(r),n);return l(),a}function l2(e,t){const n=t.split(".");return()=>{let r=e;for(let s=0;se.__isTeleport,Pl=e=>e&&(e.disabled||e.disabled===""),p5=e=>e&&(e.defer||e.defer===""),i_=e=>typeof SVGElement<"u"&&e instanceof SVGElement,a_=e=>typeof MathMLElement=="function"&&e instanceof MathMLElement,Gg=(e,t)=>{const n=e&&e.to;return jt(n)?t?t(n):null:n},m5={name:"Teleport",__isTeleport:!0,process(e,t,n,r,s,o,l,a,u,c){const{mc:d,pc:f,pbc:p,o:{insert:h,querySelector:b,createText:x,createComment:w,parentNode:k}}=c,C=Pl(t.props);let{dynamicChildren:S}=t;const E=(P,I,M)=>{P.shapeFlag&16&&d(P.children,I,M,s,o,l,a,u)},T=(P=t)=>{const I=Pl(P.props),M=P.target=Gg(P.props,b),D=Jg(M,P,x,h);M&&(l!=="svg"&&i_(M)?l="svg":l!=="mathml"&&a_(M)&&(l="mathml"),s&&s.isCE&&(s.ce._teleportTargets||(s.ce._teleportTargets=new Set)).add(M),I||(E(P,M,D),cu(P,!1)))},O=P=>{const I=()=>{if(Po.get(P)===I){if(Po.delete(P),Pl(P.props)){const M=k(P.el)||n;E(P,M,P.anchor),cu(P,!0)}T(P)}};Po.set(P,I),bn(I,o)};if(e==null){const P=t.el=x(""),I=t.anchor=x("");if(h(P,n,r),h(I,n,r),p5(t.props)||o&&o.pendingBranch){O(t);return}C&&(E(t,n,I),cu(t,!0)),T()}else{t.el=e.el;const P=t.anchor=e.anchor,I=Po.get(e);if(I){I.flags|=8,Po.delete(e),O(t);return}t.targetStart=e.targetStart;const M=t.target=e.target,D=t.targetAnchor=e.targetAnchor,L=Pl(e.props),F=L?n:M,N=L?P:D;if(l==="svg"||i_(M)?l="svg":(l==="mathml"||a_(M))&&(l="mathml"),S?(p(e.dynamicChildren,S,F,s,o,l,a),e1(e,t,!0)):u||f(e,t,F,N,s,o,l,a,!1),C)L?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):hd(t,n,P,c,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const j=Gg(t.props,b);j&&(t.target=j,hd(t,j,null,c,0))}else L&&hd(t,M,D,c,1);cu(t,C)}},remove(e,t,n,{um:r,o:{remove:s}},o){const{shapeFlag:l,children:a,anchor:u,targetStart:c,targetAnchor:d,target:f,props:p}=e,h=Pl(p),b=o||!h,x=Po.get(e);if(x&&(x.flags|=8,Po.delete(e)),f&&(s(c),s(d)),o&&s(u),!x&&(h||f)&&l&16)for(let w=0;w{e.isMounted=!0}),Kn(()=>{e.isUnmounting=!0}),e}const Or=[Function,Array],Uy={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Or,onEnter:Or,onAfterEnter:Or,onEnterCancelled:Or,onBeforeLeave:Or,onLeave:Or,onAfterLeave:Or,onLeaveCancelled:Or,onBeforeAppear:Or,onAppear:Or,onAfterAppear:Or,onAppearCancelled:Or},a2=e=>{const t=e.subTree;return t.component?a2(t.component):t},g5={name:"BaseTransition",props:Uy,setup(e,{slots:t}){const n=zt(),r=qy();return()=>{const s=t.default&&ym(t.default(),!0),o=s&&s.length?u2(s):n.subTree?R():void 0;if(!o)return;const l=$t(e),{mode:a}=l;if(r.isLeaving)return kh(o);const u=wf(o);if(!u)return kh(o);let c=la(u,l,r,n,f=>c=f);u.type!==dn&&co(u,c);let d=n.subTree&&wf(n.subTree);if(d&&d.type!==dn&&!Yr(d,u)&&a2(n).type!==dn){let f=la(d,l,r,n);if(co(d,f),a==="out-in"&&u.type!==dn)return r.isLeaving=!0,f.afterLeave=()=>{r.isLeaving=!1,n.job.flags&8||n.update(),delete f.afterLeave,d=void 0},kh(o);a==="in-out"&&u.type!==dn?f.delayLeave=(p,h,b)=>{const x=d2(r,d);x[String(d.key)]=d,p[Dr]=()=>{h(),p[Dr]=void 0,delete c.delayedLeave,d=void 0},c.delayedLeave=()=>{b(),delete c.delayedLeave,d=void 0}}:d=void 0}else d&&(d=void 0);return o}}};function u2(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==dn){t=n;break}}return t}const c2=g5;function d2(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function la(e,t,n,r,s){const{appear:o,mode:l,persisted:a=!1,onBeforeEnter:u,onEnter:c,onAfterEnter:d,onEnterCancelled:f,onBeforeLeave:p,onLeave:h,onAfterLeave:b,onLeaveCancelled:x,onBeforeAppear:w,onAppear:k,onAfterAppear:C,onAppearCancelled:S}=t,E=String(e.key),T=d2(n,e),O=(M,D)=>{M&&$r(M,r,9,D)},P=(M,D)=>{const L=D[1];O(M,D),Ke(M)?M.every(F=>F.length<=1)&&L():M.length<=1&&L()},I={mode:l,persisted:a,beforeEnter(M){let D=u;if(!n.isMounted)if(o)D=w||u;else return;M[Dr]&&M[Dr](!0);const L=T[E];L&&Yr(e,L)&&L.el[Dr]&&L.el[Dr](),O(D,[M])},enter(M){if(T[E]===e)return;let D=c,L=d,F=f;if(!n.isMounted)if(o)D=k||c,L=C||d,F=S||f;else return;let N=!1;M[Wa]=q=>{N||(N=!0,q?O(F,[M]):O(L,[M]),I.delayedLeave&&I.delayedLeave(),M[Wa]=void 0)};const j=M[Wa].bind(null,!1);D?P(D,[M,j]):j()},leave(M,D){const L=String(e.key);if(M[Wa]&&M[Wa](!0),n.isUnmounting)return D();O(p,[M]);let F=!1;M[Dr]=j=>{F||(F=!0,D(),j?O(x,[M]):O(b,[M]),M[Dr]=void 0,T[L]===e&&delete T[L])};const N=M[Dr].bind(null,!1);T[L]=e,h?P(h,[M,N]):N()},clone(M){const D=la(M,t,n,r,s);return s&&s(D),D}};return I}function kh(e){if(Nc(e))return e=ss(e),e.children=null,e}function wf(e){if(!Nc(e))return vm(e.type)&&e.children?u2(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&nt(n.default))return n.default()}}function co(e,t){if(e.shapeFlag&6&&e.component){e.transition=t;const n=e.component.subTree;co(vm(n.type)&&wf(n)||n,t)}else e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function ym(e,t=!1,n){let r=[],s=0;for(let o=0;o1)for(let o=0;on.value,set:o=>n.value=o})}return n}function u_(e,t){let n;return!!((n=Object.getOwnPropertyDescriptor(e,t))&&!n.configurable)}const $f=new WeakMap;function Gi(e,t,n,r,s=!1){if(Ke(e)){e.forEach((x,w)=>Gi(x,t&&(Ke(t)?t[w]:t),n,r,s));return}if(to(r)&&!s){r.shapeFlag&512&&r.type.__asyncResolved&&r.component.subTree.component&&Gi(e,t,n,r.component.subTree);return}const o=r.shapeFlag&4?Fc(r.component):r.el,l=s?null:o,{i:a,r:u}=e,c=t&&t.r,d=a.refs===bt?a.refs={}:a.refs,f=a.setupState,p=$t(f),h=f===bt?Sw:x=>u_(d,x)?!1:Ot(p,x),b=(x,w)=>!(w&&u_(d,w));if(c!=null&&c!==u){if(c_(t),jt(c))d[c]=null,h(c)&&(f[c]=null);else if(Et(c)){const x=t;b(c,x.k)&&(c.value=null),x.k&&(d[x.k]=null)}}if(nt(u))Da(u,a,12,[l,d]);else{const x=jt(u),w=Et(u);if(x||w){const k=()=>{if(e.f){const C=x?h(u)?f[u]:d[u]:b()||!e.k?u.value:d[e.k];if(s)Ke(C)&&Ay(C,o);else if(Ke(C))C.includes(o)||C.push(o);else if(x)d[u]=[o],h(u)&&(f[u]=d[u]);else{const S=[o];b(u,e.k)&&(u.value=S),e.k&&(d[e.k]=S)}}else x?(d[u]=l,h(u)&&(f[u]=l)):w&&(b(u,e.k)&&(u.value=l),e.k&&(d[e.k]=l))};if(l){const C=()=>{k(),$f.delete(e)};C.id=-1,$f.set(e,C),bn(C,n)}else c_(e),k()}}}function c_(e){const t=$f.get(e);t&&(t.flags|=8,$f.delete(e))}let d_=!1;const xi=()=>{d_||(console.error("Hydration completed but contains mismatches."),d_=!0)},y5=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",b5=e=>e.namespaceURI.includes("MathML"),gd=e=>{if(e.nodeType===1){if(y5(e))return"svg";if(b5(e))return"mathml"}},qi=e=>e.nodeType===8;function _5(e){const{mt:t,p:n,o:{patchProp:r,createText:s,nextSibling:o,parentNode:l,remove:a,insert:u,createComment:c}}=e,d=(S,E)=>{if(!E.hasChildNodes()){n(null,S,E),kf(),E._vnode=S;return}f(E.firstChild,S,null,null,null),kf(),E._vnode=S},f=(S,E,T,O,P,I=!1)=>{I=I||!!E.dynamicChildren;const M=qi(S)&&S.data==="[",D=()=>x(S,E,T,O,P,M),{type:L,ref:F,shapeFlag:N,patchFlag:j}=E;let q=S.nodeType;E.el=S,j===-2&&(I=!1,E.dynamicChildren=null);let W=null;switch(L){case no:q!==3?E.children===""?(u(E.el=s(""),l(S),S),W=S):W=D():(S.data!==E.children&&(xi(),S.data=E.children),W=o(S));break;case dn:C(S)?(W=o(S),k(E.el=S.content.firstChild,S,T)):q!==8||M?W=D():W=o(S);break;case Yo:if(M&&(S=o(S),q=S.nodeType),q===1||q===3){W=S;const X=!E.children.length;for(let ee=0;ee{I=I||!!E.dynamicChildren;const{type:M,dynamicProps:D,props:L,patchFlag:F,shapeFlag:N,dirs:j,transition:q}=E,W=M==="input"||M==="option",X=!!D;if(W||X||F!==-1){j&&_s(E,null,T,"created");let ee=!1;if(C(S)){ee=L2(null,q)&&T&&T.vnode.props&&T.vnode.props.appear;const fe=S.content.firstChild;if(ee){const le=fe.getAttribute("class");le&&(fe.$cls=le),q.beforeEnter(fe)}k(fe,S,T),E.el=S=fe}if(N&16&&!(L&&(L.innerHTML||L.textContent))){let fe=h(S.firstChild,E,S,T,O,P,I);for(fe&&!Xd(S,1)&&xi();fe;){const le=fe;fe=fe.nextSibling,a(le)}}else if(N&8){let fe=E.children;fe[0]===` -`&&(S.tagName==="PRE"||S.tagName==="TEXTAREA")&&(fe=fe.slice(1));const{textContent:le}=S;le!==fe&&le!==fe.replace(/\r\n|\r/g,` -`)&&(Xd(S,0)||xi(),S.textContent=E.children)}if(L){if(W||X||!I||F&48){const fe=S.tagName.includes("-"),le=S.namespaceURI.includes("svg")?"svg":S.namespaceURI.includes("MathML")?"mathml":void 0;for(const re in L)if(W&&(re.endsWith("value")||re==="indeterminate")||Pc(re)&&!Ul(re)||re[0]==="."||fe&&!Ul(re)||D&&D.includes(re)){if(k5(S,re,L[re]))continue;r(S,re,null,L[re],le,T)}}else if(L.onClick)r(S,"onClick",null,L.onClick,void 0,T);else if(F&4&&Cs(L.style))for(const fe in L.style)L.style[fe]}let ce;(ce=L&&L.onVnodeBeforeMount)&&lr(ce,T,E),j&&_s(E,null,T,"beforeMount"),((ce=L&&L.onVnodeMounted)||j||ee)&&U2(()=>{ce&&lr(ce,T,E),ee&&q.enter(S),j&&_s(E,null,T,"mounted")},O)}return S.nextSibling},h=(S,E,T,O,P,I,M)=>{M=M||!!E.dynamicChildren;const D=E.children,L=D.length;let F=!1;for(let N=0;N{const{slotScopeIds:M}=E;M&&(P=P?P.concat(M):M);const D=l(S),L=h(o(S),E,D,T,O,P,I);return L&&qi(L)&&L.data==="]"?o(E.anchor=L):(xi(),u(E.anchor=c("]"),D,L),L)},x=(S,E,T,O,P,I)=>{if($5(S,E)||xi(),E.el=null,I){const L=w(S);for(;;){const F=o(S);if(F&&F!==L)a(F);else break}}const M=o(S),D=l(S);return a(S),n(null,E,D,M,T,O,gd(D),P),T&&(T.vnode.el=E.el,$m(T,E.el)),M},w=(S,E="[",T="]")=>{let O=0;for(;S;)if(S=o(S),S&&qi(S)&&(S.data===E&&O++,S.data===T)){if(O===0)return o(S);O--}return S},k=(S,E,T)=>{const O=E.parentNode;O&&O.replaceChild(S,E);let P=T;for(;P;)P.vnode.el===E&&(P.vnode.el=P.subTree.el=S),P=P.parent},C=S=>S.nodeType===1&&S.tagName==="TEMPLATE";return[d,f]}const x5=new Set(["src","srcset","href","poster"]);function k5(e,t,n){return x5.has(t)?e.getAttribute(t)===(n==null?null:`${n}`):!1}const Cf="data-allow-mismatch",w5={0:"text",1:"children",2:"class",3:"style",4:"attribute"};function Xd(e,t){if(t===0||t===1)for(;e&&!e.hasAttribute(Cf);)e=e.parentElement;return Hy(e&&e.getAttribute(Cf),t)}function Hy(e,t){if(e==null)return!1;if(e==="")return!0;{const n=e.split(",");return t===0&&n.includes("children")?!0:n.includes(w5[t])}}function $5(e,t){return Xd(e.parentElement,1)||C5(e)||S5(t)}function C5(e){return e.nodeType===1&&Hy(e.getAttribute(Cf),1)}function S5({props:e}){const t=e&&e[Cf];return typeof t=="string"&&Hy(t,1)}const E5=am().requestIdleCallback||(e=>setTimeout(e,1)),O5=am().cancelIdleCallback||(e=>clearTimeout(e)),T5=(e=1e4)=>t=>{const n=E5(t,{timeout:e});return()=>O5(n)};function A5(e){const{top:t,left:n,bottom:r,right:s}=e.getBoundingClientRect(),{innerHeight:o,innerWidth:l}=window;return(t>0&&t0&&r0&&n0&&s(t,n)=>{const r=new IntersectionObserver(s=>{for(const o of s)if(o.isIntersecting){r.disconnect(),t();break}},e);return n(s=>{if(s instanceof Element){if(A5(s))return t(),r.disconnect(),!1;r.observe(s)}}),()=>r.disconnect()},M5=e=>t=>{if(e){const n=matchMedia(e);if(n.matches)t();else return n.addEventListener("change",t,{once:!0}),()=>n.removeEventListener("change",t)}},D5=(e=[])=>(t,n)=>{jt(e)&&(e=[e]);let r=!1;const s=l=>{r||(r=!0,o(),t(),l.target.dispatchEvent(new l.constructor(l.type,l)))},o=()=>{n(l=>{for(const a of e)l.removeEventListener(a,s)})};return n(l=>{for(const a of e)l.addEventListener(a,s,{once:!0})}),o};function V5(e,t){if(qi(e)&&e.data==="["){let n=1,r=e.nextSibling;for(;r;){if(r.nodeType===1){if(t(r)===!1)break}else if(qi(r))if(r.data==="]"){if(--n===0)break}else r.data==="["&&n++;r=r.nextSibling}}else t(e)}const to=e=>!!e.type.__asyncLoader;function I5(e){nt(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:r,delay:s=200,hydrate:o,timeout:l,suspensible:a=!0,onError:u}=e;let c=null,d,f=0;const p=()=>(f++,c=null,h()),h=()=>{let b;return c||(b=c=t().catch(x=>{if(x=x instanceof Error?x:new Error(String(x)),u)return new Promise((w,k)=>{u(x,()=>w(p()),()=>k(x),f+1)});throw x}).then(x=>b!==c&&c?c:(x&&(x.__esModule||x[Symbol.toStringTag]==="Module")&&(x=x.default),d=x,x)))};return K({name:"AsyncComponentWrapper",__asyncLoader:h,__asyncHydrate(b,x,w){const k=b.isConnected;let C=!1;(x.bu||(x.bu=[])).push(()=>C=!0);const S=()=>{C||!b.parentNode||k&&!b.isConnected||w()},E=o?()=>{const T=o(S,O=>V5(b,O));T&&(x.bum||(x.bum=[])).push(T)}:S;d?E():h().then(()=>!x.isUnmounted&&E())},get __asyncResolved(){return d},setup(){const b=In;if(jy(b),d)return()=>vd(d,b);const x=T=>{c=null,fi(T,b,13,!r)};if(a&&b.suspense||Zl)return h().then(T=>()=>vd(T,b)).catch(T=>(x(T),()=>r?v(r,{error:T}):null));const w=U(!1),k=U(),C=U(!!s);let S,E;return nn(()=>{S!=null&&clearTimeout(S),E!=null&&clearTimeout(E)}),s&&(E=setTimeout(()=>{b.isUnmounted||(C.value=!1)},s)),l!=null&&(S=setTimeout(()=>{if(!b.isUnmounted&&!w.value&&!k.value){const T=new Error(`Async component timed out after ${l}ms.`);x(T),k.value=T}},l)),h().then(()=>{b.isUnmounted||(w.value=!0,b.parent&&Nc(b.parent.vnode)&&b.parent.update())}).catch(T=>{if(b.isUnmounted){c=null;return}x(T),k.value=T}),()=>{if(w.value&&d)return vd(d,b);if(k.value&&r)return v(r,{error:k.value});if(n&&!C.value)return vd(n,b)}}})}function vd(e,t){const{ref:n,props:r,children:s,ce:o}=t.vnode,l=v(e,r,s);return l.ref=n,l.ce=o,delete t.vnode.ce,l}const Nc=e=>e.type.__isKeepAlive,R5={name:"KeepAlive",__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){const n=zt(),r=n.ctx;if(!r.renderer)return()=>{const C=t.default&&t.default();return C&&C.length===1?C[0]:C};const s=new Map,o=new Set;let l=null;const a=n.suspense,{renderer:{p:u,m:c,um:d,o:{createElement:f}}}=r,p=f("div");r.activate=(C,S,E,T,O)=>{const P=C.component;c(C,S,E,0,a),u(P.vnode,C,S,E,P,a,T,C.slotScopeIds,O),bn(()=>{P.isDeactivated=!1,P.a&&Ki(P.a);const I=C.props&&C.props.onVnodeMounted;I&&lr(I,P.parent,C)},a)},r.deactivate=C=>{const S=C.component;Ef(S.m),Ef(S.a),c(C,p,null,1,a),bn(()=>{S.da&&Ki(S.da);const E=C.props&&C.props.onVnodeUnmounted;E&&lr(E,S.parent,C),S.isDeactivated=!0},a)};function h(C){wh(C),d(C,n,a,!0)}function b(C){s.forEach((S,E)=>{const T=sv(to(S)?S.type.__asyncResolved||{}:S.type);T&&!C(T)&&x(E)})}function x(C){const S=s.get(C);S&&(!l||!Yr(S,l))?h(S):l&&wh(l),s.delete(C),o.delete(C)}ye(()=>[e.include,e.exclude],([C,S])=>{C&&b(E=>du(C,E)),S&&b(E=>!du(S,E))},{flush:"post",deep:!0});let w=null;const k=()=>{w!=null&&(Of(n.subTree.type)?bn(()=>{const C=yd(n.subTree);C.component&&s.set(w,C)},n.subTree.suspense):s.set(w,yd(n.subTree)))};return mt(k),Lc(k),Kn(()=>{s.forEach(C=>{const{subTree:S,suspense:E}=n,T=yd(S);if(C.type===T.type&&C.key===T.key){wh(T);const O=T.component.da;O&&bn(O,E);return}h(C)})}),()=>{if(w=null,!t.default)return l=null;const C=t.default(),S=C[0];if(C.length>1)return l=null,C;if(!fo(S)||!(S.shapeFlag&4)&&!(S.shapeFlag&128))return l=null,S;let E=yd(S);if(E.type===dn)return l=null,E;const T=E.type,O=sv(to(E)?E.type.__asyncResolved||{}:T),{include:P,exclude:I,max:M}=e;if(P&&(!O||!du(P,O))||I&&O&&du(I,O))return E.shapeFlag&=-257,l=E,S;const D=E.key==null?T:E.key,L=s.get(D);return E.el&&(E=ss(E),S.shapeFlag&128&&(S.ssContent=E)),w=D,L?(E.el=L.el,E.component=L.component,E.transition&&co(E,E.transition),E.shapeFlag|=512,o.delete(D),o.add(D)):(o.add(D),M&&o.size>parseInt(M,10)&&x(o.values().next().value)),E.shapeFlag|=256,l=E,Of(S.type)?S:E}}},N5=R5;function du(e,t){return Ke(e)?e.some(n=>du(n,t)):jt(e)?e.split(",").includes(t):cP(e)?(e.lastIndex=0,e.test(t)):!1}function f2(e,t){m2(e,"a",t)}function p2(e,t){m2(e,"da",t)}function m2(e,t,n=In){const r=e.__wdc||(e.__wdc=()=>{let s=n;for(;s;){if(s.isDeactivated)return;s=s.parent}return e()});if(bm(t,r,n),n){let s=n.parent;for(;s&&s.parent;)Nc(s.parent.vnode)&&L5(r,t,n,s),s=s.parent}}function L5(e,t,n,r){const s=bm(t,e,r,!0);nn(()=>{Ay(r[t],s)},n)}function wh(e){e.shapeFlag&=-257,e.shapeFlag&=-513}function yd(e){return e.shapeFlag&128?e.ssContent:e}function bm(e,t,n=In,r=!1){if(n){const s=n[e]||(n[e]=[]),o=t.__weh||(t.__weh=(...l)=>{ao();const a=Va(n),u=$r(t,n,e,l);return a(),uo(),u});return r?s.unshift(o):s.push(o),o}}const bo=e=>(t,n=In)=>{(!Zl||e==="sp")&&bm(e,(...r)=>t(...r),n)},h2=bo("bm"),mt=bo("m"),zy=bo("bu"),Lc=bo("u"),Kn=bo("bum"),nn=bo("um"),g2=bo("sp"),v2=bo("rtg"),y2=bo("rtc");function b2(e,t=In){bm("ec",e,t)}const Ky="components",F5="directives";function Wy(e,t){return Gy(Ky,e,!0,t)||e}const _2=Symbol.for("v-ndc");function Pn(e){return jt(e)?Gy(Ky,e,!1)||e:e||_2}function B5(e){return Gy(F5,e)}function Gy(e,t,n=!0,r=!1){const s=Rn||In;if(s){const o=s.type;if(e===Ky){const a=sv(o,!1);if(a&&(a===t||a===fn(t)||a===Mc(fn(t))))return o}const l=f_(s[e]||o[e],t)||f_(s.appContext[e],t);return!l&&r?o:l}}function f_(e,t){return e&&(e[t]||e[fn(t)]||e[Mc(fn(t))])}function ne(e,t,n,r){let s;const o=n&&n[r],l=Ke(e);if(l||jt(e)){const a=l&&Cs(e);let u=!1,c=!1;a&&(u=!mr(e),c=Os(e),e=fm(e)),s=new Array(e.length);for(let d=0,f=e.length;dt(a,u,void 0,o&&o[u]));else{const a=Object.keys(e);s=new Array(a.length);for(let u=0,c=a.length;u{const o=r.fn(...s);return o&&(o.key=r.key),o}:r.fn)}return e}function he(e,t,n,r,s,o){if(n==null&&(n={}),Rn.ce||Rn.parent&&to(Rn.parent)&&Rn.parent.ce){const c=o!=null&&n.key==null?Nt({},n,{key:o}):n,d=Object.keys(c).length>0;return t!=="default"&&(c.name=t),y(),B(H,null,[v("slot",c,r&&r())],d?-2:64)}let l=e[t];l&&l._c&&(l._d=!1);const a=ro.length;y();let u;try{const c=l&&Jy(l(n)),d=n.key||o||c&&c.key;u=B(H,{key:(d&&!wr(d)?d:`_${t}`)+(!c&&r?"_fb":"")},c||(r?r():[]),c&&e._===1?64:-2)}catch(c){for(let d=ro.length;d>a;d--)Cm();throw c}finally{l&&l._c&&(l._d=!0)}return!s&&u.scopeId&&(u.slotScopeIds=[u.scopeId+"-s"]),u}function Jy(e){return e.some(t=>fo(t)?!(t.type===dn||t.type===H&&!Jy(t.children)):!0)?e:null}function _m(e,t){const n={};for(const r in e)n[t&&/[A-Z]/.test(r)?`on:${r}`:zi(r)]=e[r];return n}const Yg=e=>e?K2(e)?Fc(e):Yg(e.parent):null,$u=Nt(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Yg(e.parent),$root:e=>Yg(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Xy(e),$forceUpdate:e=>e.f||(e.f=()=>{Ny(e.update)}),$nextTick:e=>e.n||(e.n=ot.bind(e.proxy)),$watch:e=>f5.bind(e)}),$h=(e,t)=>e!==bt&&!e.__isScriptSetup&&Ot(e,t),Xg={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:r,data:s,props:o,accessCache:l,type:a,appContext:u}=e;if(t[0]!=="$"){const p=l[t];if(p!==void 0)switch(p){case 1:return r[t];case 2:return s[t];case 4:return n[t];case 3:return o[t]}else{if($h(r,t))return l[t]=1,r[t];if(s!==bt&&Ot(s,t))return l[t]=2,s[t];if(Ot(o,t))return l[t]=3,o[t];if(n!==bt&&Ot(n,t))return l[t]=4,n[t];Qg&&(l[t]=0)}}const c=$u[t];let d,f;if(c)return t==="$attrs"&&qn(e.attrs,"get",""),c(e);if((d=a.__cssModules)&&(d=d[t]))return d;if(n!==bt&&Ot(n,t))return l[t]=4,n[t];if(f=u.config.globalProperties,Ot(f,t))return f[t]},set({_:e},t,n){const{data:r,setupState:s,ctx:o}=e;return $h(s,t)?(s[t]=n,!0):r!==bt&&Ot(r,t)?(r[t]=n,!0):Ot(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(o[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:s,props:o,type:l}},a){let u;return!!(n[a]||e!==bt&&a[0]!=="$"&&Ot(e,a)||$h(t,a)||Ot(o,a)||Ot(r,a)||Ot($u,a)||Ot(s.config.globalProperties,a)||(u=l.__cssModules)&&u[a])},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:Ot(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}},q5=Nt({},Xg,{get(e,t){if(t!==Symbol.unscopables)return Xg.get(e,t,e)},has(e,t){return t[0]!=="_"&&!hP(t)}});function U5(){return null}function j5(){return null}function H5(e){}function z5(e){}function K5(){return null}function W5(){}function G5(e,t){return null}function xm(){return x2().slots}function km(){return x2().attrs}function x2(e){const t=zt();return t.setupContext||(t.setupContext=J2(t))}function Zu(e){return Ke(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}function Yy(e,t){const n=Zu(e);for(const r in t){if(r.startsWith("__skip"))continue;let s=n[r];s?Ke(s)||nt(s)?s=n[r]={type:s,default:t[r]}:s.default=t[r]:s===null&&(s=n[r]={default:t[r]}),s&&t[`__skip_${r}`]&&(s.skipFactory=!0)}return n}function J5(e,t){return!e||!t?e||t:Ke(e)&&Ke(t)?e.concat(t):Nt({},Zu(e),Zu(t))}function Y5(e,t){const n={};for(const r in e)t.includes(r)||Object.defineProperty(n,r,{enumerable:!0,get:()=>e[r]});return n}function X5(e){const t=zt(),n=Zl;let r=e();nc(),n&&Xo(!1);const s=()=>{Va(t),n&&Xo(!0)},o=()=>{zt()!==t&&t.scope.off(),nc(),n&&Xo(!1)};return Py(r)&&(r=r.catch(l=>{throw s(),Promise.resolve().then(()=>Promise.resolve().then(o)),l})),[r,()=>{s(),Promise.resolve().then(o)}]}let Qg=!0;function Q5(e){const t=Xy(e),n=e.proxy,r=e.ctx;Qg=!1,t.beforeCreate&&p_(t.beforeCreate,e,"bc");const{data:s,computed:o,methods:l,watch:a,provide:u,inject:c,created:d,beforeMount:f,mounted:p,beforeUpdate:h,updated:b,activated:x,deactivated:w,beforeDestroy:k,beforeUnmount:C,destroyed:S,unmounted:E,render:T,renderTracked:O,renderTriggered:P,errorCaptured:I,serverPrefetch:M,expose:D,inheritAttrs:L,components:F,directives:N,filters:j}=t;if(c&&Z5(c,r,null),l)for(const X in l){const ee=l[X];nt(ee)&&(r[X]=ee.bind(n))}if(s){const X=s.call(n,n);Pt(X)&&(e.data=rt(X))}if(Qg=!0,o)for(const X in o){const ee=o[X],ce=nt(ee)?ee.bind(n,n):nt(ee.get)?ee.get.bind(n,n):qr,fe=!nt(ee)&&nt(ee.set)?ee.set.bind(n):qr,le=V({get:ce,set:fe});Object.defineProperty(r,X,{enumerable:!0,configurable:!0,get:()=>le.value,set:re=>le.value=re})}if(a)for(const X in a)k2(a[X],r,n,X);if(u){const X=nt(u)?u.call(n):u;Reflect.ownKeys(X).forEach(ee=>{Ic(ee,X[ee])})}d&&p_(d,e,"c");function W(X,ee){Ke(ee)?ee.forEach(ce=>X(ce.bind(n))):ee&&X(ee.bind(n))}if(W(h2,f),W(mt,p),W(zy,h),W(Lc,b),W(f2,x),W(p2,w),W(b2,I),W(y2,O),W(v2,P),W(Kn,C),W(nn,E),W(g2,M),Ke(D))if(D.length){const X=e.exposed||(e.exposed={});D.forEach(ee=>{Object.defineProperty(X,ee,{get:()=>n[ee],set:ce=>n[ee]=ce,enumerable:!0})})}else e.exposed||(e.exposed={});T&&e.render===qr&&(e.render=T),L!=null&&(e.inheritAttrs=L),F&&(e.components=F),N&&(e.directives=N),M&&jy(e)}function Z5(e,t,n=qr){Ke(e)&&(e=Zg(e));for(const r in e){const s=e[r];let o;Pt(s)?"default"in s?o=eo(s.from||r,s.default,!0):o=eo(s.from||r):o=eo(s),Et(o)?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>o.value,set:l=>o.value=l}):t[r]=o}}function p_(e,t,n){$r(Ke(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}function k2(e,t,n,r){let s=r.includes(".")?l2(n,r):()=>n[r];if(jt(e)){const o=t[e];nt(o)&&ye(s,o)}else if(nt(e))ye(s,e.bind(n));else if(Pt(e))if(Ke(e))e.forEach(o=>k2(o,t,n,r));else{const o=nt(e.handler)?e.handler.bind(n):t[e.handler];nt(o)&&ye(s,o,e)}}function Xy(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:s,optionsCache:o,config:{optionMergeStrategies:l}}=e.appContext,a=o.get(t);let u;return a?u=a:!s.length&&!n&&!r?u=t:(u={},s.length&&s.forEach(c=>Sf(u,c,l,!0)),Sf(u,t,l)),Pt(t)&&o.set(t,u),u}function Sf(e,t,n,r=!1){const{mixins:s,extends:o}=t;o&&Sf(e,o,n,!0),s&&s.forEach(l=>Sf(e,l,n,!0));for(const l in t)if(!(r&&l==="expose")){const a=e8[l]||n&&n[l];e[l]=a?a(e[l],t[l]):t[l]}return e}const e8={data:m_,props:h_,emits:h_,methods:fu,computed:fu,beforeCreate:Gn,created:Gn,beforeMount:Gn,mounted:Gn,beforeUpdate:Gn,updated:Gn,beforeDestroy:Gn,beforeUnmount:Gn,destroyed:Gn,unmounted:Gn,activated:Gn,deactivated:Gn,errorCaptured:Gn,serverPrefetch:Gn,components:fu,directives:fu,watch:n8,provide:m_,inject:t8};function m_(e,t){return t?e?function(){return Nt(nt(e)?e.call(this,this):e,nt(t)?t.call(this,this):t)}:t:e}function t8(e,t){return fu(Zg(e),Zg(t))}function Zg(e){if(Ke(e)){const t={};for(let n=0;n{let d,f=bt,p;return Fy(()=>{const h=e[s];On(d,h)&&(d=h,c())}),{get(){return u(),n.get?n.get(d):d},set(h){const b=n.set?n.set(h):h;if(!On(b,d)&&!(f!==bt&&On(h,f)))return;const x=r.vnode.props,w=!!(x&&(t in x||s in x||o in x)&&(`onUpdate:${t}`in x||`onUpdate:${s}`in x||`onUpdate:${o}`in x));w||(d=h,c()),r.emit(`update:${t}`,b),On(h,f)&&(On(h,b)&&!On(b,p)||w&&f!==bt&&!On(b,d))&&c(),f=h,p=b}}});return a[Symbol.iterator]=()=>{let u=0;return{next(){return u<2?{value:u++?l||bt:a,done:!1}:{done:!0}}}},a}const $2=(e,t)=>t==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${fn(t)}Modifiers`]||e[`${dr(t)}Modifiers`];function l8(e,t,...n){if(e.isUnmounted)return;const r=e.vnode.props||bt;let s=n;const o=t.startsWith("update:"),l=o&&$2(r,t.slice(7));l&&(l.trim&&(s=n.map(d=>jt(d)?d.trim():d)),l.number&&(s=s.map(im)));let a,u=r[a=zi(t)]||r[a=zi(fn(t))];!u&&o&&(u=r[a=zi(dr(t))]),u&&$r(u,e,6,s);const c=r[a+"Once"];if(c){if(!e.emitted)e.emitted={};else if(e.emitted[a])return;e.emitted[a]=!0,$r(c,e,6,s)}}const i8=new WeakMap;function C2(e,t,n=!1){const r=n?i8:t.emitsCache,s=r.get(e);if(s!==void 0)return s;const o=e.emits;let l={},a=!1;if(!nt(e)){const u=c=>{const d=C2(c,t,!0);d&&(a=!0,Nt(l,d))};!n&&t.mixins.length&&t.mixins.forEach(u),e.extends&&u(e.extends),e.mixins&&e.mixins.forEach(u)}return!o&&!a?(Pt(e)&&r.set(e,null),null):(Ke(o)?o.forEach(u=>l[u]=null):Nt(l,o),Pt(e)&&r.set(e,l),l)}function wm(e,t){return!e||!Pc(t)?!1:(t=t.slice(2),t=t==="Once"?t:t.replace(/Once$/,""),Ot(e,t[0].toLowerCase()+t.slice(1))||Ot(e,dr(t))||Ot(e,t))}function Qd(e){const{type:t,vnode:n,proxy:r,withProxy:s,propsOptions:[o],slots:l,attrs:a,emit:u,render:c,renderCache:d,props:f,data:p,setupState:h,ctx:b,inheritAttrs:x}=e,w=Qu(e);let k,C;try{if(n.shapeFlag&4){const E=s||r,T=E;k=ir(c.call(T,E,d,f,h,p,b)),C=a}else{const E=t;k=ir(E.length>1?E(f,{attrs:a,slots:l,emit:u}):E(f,null)),C=t.props?a:u8(a)}}catch(E){ro.length=0,fi(E,e,1),k=v(dn)}let S=k;if(C&&x!==!1){const E=Object.keys(C),{shapeFlag:T}=S;E.length&&T&7&&(o&&E.some(rm)&&(C=c8(C,o)),S=ss(S,C,!1,!0))}if(n.dirs&&(S=ss(S,null,!1,!0),S.dirs=S.dirs?S.dirs.concat(n.dirs):n.dirs),n.transition){const E=vm(S.type)&&wf(S)||S;co(E,n.transition)}return k=S,Qu(w),k}function a8(e,t=!0){let n;for(let r=0;r{let t;for(const n in e)(n==="class"||n==="style"||Pc(n))&&((t||(t={}))[n]=e[n]);return t},c8=(e,t)=>{const n={};for(const r in e)(!rm(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function d8(e,t,n){const{props:r,children:s,component:o}=e,{props:l,children:a,patchFlag:u}=t,c=o.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&u>=0){if(u&1024)return!0;if(u&16)return r?g_(r,l,c):!!l;if(u&8){const d=t.dynamicProps;for(let f=0;fObject.create(E2),T2=e=>Object.getPrototypeOf(e)===E2;function f8(e,t,n,r=!1){const s={},o=O2();e.propsDefaults=Object.create(null),A2(e,t,s,o);for(const l in e.propsOptions[0])l in s||(s[l]=void 0);n?e.props=r?s:Ww(s):e.type.props?e.props=s:e.props=o,e.attrs=o}function p8(e,t,n,r){const{props:s,attrs:o,vnode:{patchFlag:l}}=e,a=$t(s),[u]=e.propsOptions;let c=!1;if((r||l>0)&&!(l&16)){if(l&8){const d=e.vnode.dynamicProps;for(let f=0;f{u=!0;const[p,h]=P2(f,t,!0);Nt(l,p),h&&a.push(...h)};!n&&t.mixins.length&&t.mixins.forEach(d),e.extends&&d(e.extends),e.mixins&&e.mixins.forEach(d)}if(!o&&!u)return Pt(e)&&r.set(e,Hi),Hi;if(Ke(o))for(let d=0;de==="_"||e==="_ctx"||e==="$stable",Zy=e=>Ke(e)?e.map(ir):[ir(e)],h8=(e,t,n)=>{if(t._n)return t;const r=_((...s)=>Zy(t(...s)),n);return r._c=!1,r},M2=(e,t,n)=>{const r=e._ctx;for(const s in e){if(Qy(s))continue;const o=e[s];if(nt(o))t[s]=h8(s,o,r);else if(o!=null){const l=Zy(o);t[s]=()=>l}}},D2=(e,t)=>{const n=Zy(t);e.slots.default=()=>n},V2=(e,t,n)=>{for(const r in t)(n||!Qy(r))&&(e[r]=t[r])},g8=(e,t,n)=>{const r=e.slots=O2();if(e.vnode.shapeFlag&32){const s=t._;s?(V2(r,t,n),n&&Ow(r,"_",s,!0)):M2(t,r)}else t&&D2(e,t)},v8=(e,t,n)=>{const{vnode:r,slots:s}=e;let o=!0,l=bt;if(r.shapeFlag&32){const a=t._;a?n&&a===1?o=!1:V2(s,t,n):(o=!t.$stable,M2(t,s)),l=t}else t&&(D2(e,t),l={default:1});if(o)for(const a in s)!Qy(a)&&l[a]==null&&delete s[a]},bn=U2;function I2(e){return N2(e)}function R2(e){return N2(e,_5)}function N2(e,t){const n=am();n.__VUE__=!0;const{insert:r,remove:s,patchProp:o,createElement:l,createText:a,createComment:u,setText:c,setElementText:d,parentNode:f,nextSibling:p,setScopeId:h=qr,insertStaticContent:b}=e,x=(G,se,_e,we=null,Oe=null,z=null,J=void 0,ae=null,ve=!!se.dynamicChildren)=>{if(G===se)return;G&&!Yr(G,se)&&(we=Le(G),re(G,Oe,z,!0),G=null),se.patchFlag===-2&&(ve=!1,se.dynamicChildren=null);const{type:$e,ref:Pe,shapeFlag:oe}=se;switch($e){case no:w(G,se,_e,we);break;case dn:k(G,se,_e,we);break;case Yo:G==null&&C(se,_e,we,J);break;case H:F(G,se,_e,we,Oe,z,J,ae,ve);break;default:oe&1?T(G,se,_e,we,Oe,z,J,ae,ve):oe&6?N(G,se,_e,we,Oe,z,J,ae,ve):(oe&64||oe&128)&&$e.process(G,se,_e,we,Oe,z,J,ae,ve,ue)}Pe!=null&&Oe?Gi(Pe,G&&G.ref,z,se||G,!se):Pe==null&&G&&G.ref!=null&&Gi(G.ref,null,z,G,!0)},w=(G,se,_e,we)=>{if(G==null)r(se.el=a(se.children),_e,we);else{const Oe=se.el=G.el;se.children!==G.children&&c(Oe,se.children)}},k=(G,se,_e,we)=>{G==null?r(se.el=u(se.children||""),_e,we):se.el=G.el},C=(G,se,_e,we)=>{[G.el,G.anchor]=b(G.children,se,_e,we,G.el,G.anchor)},S=({el:G,anchor:se},_e,we)=>{let Oe;for(;G&&G!==se;)Oe=p(G),r(G,_e,we),G=Oe;r(se,_e,we)},E=({el:G,anchor:se})=>{let _e;for(;G&&G!==se;)_e=p(G),s(G),G=_e;s(se)},T=(G,se,_e,we,Oe,z,J,ae,ve)=>{if(se.type==="svg"?J="svg":se.type==="math"&&(J="mathml"),G==null)O(se,_e,we,Oe,z,J,ae,ve);else{const $e=G.el&&G.el._isVueCE?G.el:null;try{$e&&$e._beginPatch(),M(G,se,Oe,z,J,ae,ve)}finally{$e&&$e._endPatch()}}},O=(G,se,_e,we,Oe,z,J,ae)=>{let ve,$e;const{props:Pe,shapeFlag:oe,transition:be,dirs:Fe}=G;if(ve=G.el=l(G.type,z,Pe&&Pe.is,Pe),oe&8?d(ve,G.children):oe&16&&I(G.children,ve,null,we,Oe,Ch(G,z),J,ae),Fe&&_s(G,null,we,"created"),P(ve,G,G.scopeId,J,we),Pe){for(const ht in Pe)ht!=="value"&&!Ul(ht)&&o(ve,ht,null,Pe[ht],z,we);"value"in Pe&&o(ve,"value",null,Pe.value,z),($e=Pe.onVnodeBeforeMount)&&lr($e,we,G)}Fe&&_s(G,null,we,"beforeMount");const Xe=L2(Oe,be);Xe&&be.beforeEnter(ve),r(ve,se,_e),(($e=Pe&&Pe.onVnodeMounted)||Xe||Fe)&&bn(()=>{try{$e&&lr($e,we,G),Xe&&be.enter(ve),Fe&&_s(G,null,we,"mounted")}finally{}},Oe)},P=(G,se,_e,we,Oe)=>{if(_e&&h(G,_e),we)for(let z=0;z{for(let $e=ve;$e{const ae=se.el=G.el;let{patchFlag:ve,dynamicChildren:$e,dirs:Pe}=se;ve|=G.patchFlag&16;const oe=G.props||bt,be=se.props||bt;let Fe;if(_e&&$l(_e,!1),(Fe=be.onVnodeBeforeUpdate)&&lr(Fe,_e,se,G),Pe&&_s(se,G,_e,"beforeUpdate"),_e&&$l(_e,!0),$e&&(!G.dynamicChildren||G.dynamicChildren.length!==$e.length)&&(ve=0,J=!1,$e=null),(oe.innerHTML&&be.innerHTML==null||oe.textContent&&be.textContent==null)&&d(ae,""),$e?D(G.dynamicChildren,$e,ae,_e,we,Ch(se,Oe),z):J||ee(G,se,ae,null,_e,we,Ch(se,Oe),z,!1),ve>0){if(ve&16)L(ae,oe,be,_e,Oe);else if(ve&2&&oe.class!==be.class&&o(ae,"class",null,be.class,Oe),ve&4&&o(ae,"style",oe.style,be.style,Oe),ve&8){const Xe=se.dynamicProps;for(let ht=0;ht{Fe&&lr(Fe,_e,se,G),Pe&&_s(se,G,_e,"updated")},we)},D=(G,se,_e,we,Oe,z,J)=>{for(let ae=0;ae{if(se!==_e){if(se!==bt)for(const z in se)!Ul(z)&&!(z in _e)&&o(G,z,se[z],null,Oe,we);for(const z in _e){if(Ul(z))continue;const J=_e[z],ae=se[z];J!==ae&&z!=="value"&&o(G,z,ae,J,Oe,we)}"value"in _e&&o(G,"value",se.value,_e.value,Oe)}},F=(G,se,_e,we,Oe,z,J,ae,ve)=>{const $e=se.el=G?G.el:a(""),Pe=se.anchor=G?G.anchor:a("");let{patchFlag:oe,dynamicChildren:be,slotScopeIds:Fe}=se;Fe&&(ae=ae?ae.concat(Fe):Fe),G==null?(r($e,_e,we),r(Pe,_e,we),I(se.children||[],_e,Pe,Oe,z,J,ae,ve)):oe>0&&oe&64&&be&&G.dynamicChildren&&G.dynamicChildren.length===be.length?(D(G.dynamicChildren,be,_e,Oe,z,J,ae),(se.key!=null||Oe&&se===Oe.subTree)&&e1(G,se,!0)):ee(G,se,_e,Pe,Oe,z,J,ae,ve)},N=(G,se,_e,we,Oe,z,J,ae,ve)=>{se.slotScopeIds=ae,G==null?se.shapeFlag&512?Oe.ctx.activate(se,_e,we,J,ve):j(se,_e,we,Oe,z,J,ve):q(G,se,ve)},j=(G,se,_e,we,Oe,z,J)=>{const ae=G.component=z2(G,we,Oe);if(Nc(G)&&(ae.ctx.renderer=ue),W2(ae,!1,J),ae.asyncDep){if(Oe&&Oe.registerDep(ae,W,J),!G.el){const ve=ae.subTree=v(dn);k(null,ve,se,_e),G.placeholder=ve.el}}else W(ae,G,se,_e,Oe,z,J)},q=(G,se,_e)=>{const we=se.component=G.component;if(d8(G,se,_e))if(we.asyncDep&&!we.asyncResolved){X(we,se,_e);return}else we.next=se,we.update();else se.el=G.el,we.vnode=se},W=(G,se,_e,we,Oe,z,J)=>{const ae=()=>{if(G.isMounted){let{next:oe,bu:be,u:Fe,parent:Xe,vnode:ht}=G;{const Ae=F2(G);if(Ae){oe&&(oe.el=ht.el,X(G,oe,J)),Ae.asyncDep.then(()=>{bn(()=>{G.isUnmounted||$e()},Oe)});return}}let it=oe,_t;$l(G,!1),oe?(oe.el=ht.el,X(G,oe,J)):oe=ht,be&&Ki(be),(_t=oe.props&&oe.props.onVnodeBeforeUpdate)&&lr(_t,Xe,oe,ht),$l(G,!0);const Zt=Qd(G),Ce=G.subTree;G.subTree=Zt,x(Ce,Zt,f(Ce.el),Le(Ce),G,Oe,z),oe.el=Zt.el,it===null&&$m(G,Zt.el),Fe&&bn(Fe,Oe),(_t=oe.props&&oe.props.onVnodeUpdated)&&bn(()=>lr(_t,Xe,oe,ht),Oe)}else{let oe;const{el:be,props:Fe}=se,{bm:Xe,m:ht,parent:it,root:_t,type:Zt}=G,Ce=to(se);if($l(G,!1),Xe&&Ki(Xe),!Ce&&(oe=Fe&&Fe.onVnodeBeforeMount)&&lr(oe,it,se),$l(G,!0),be&&ge){const Ae=()=>{G.subTree=Qd(G),ge(be,G.subTree,G,Oe,null)};Ce&&Zt.__asyncHydrate?Zt.__asyncHydrate(be,G,Ae):Ae()}else{_t.ce&&_t.ce._hasShadowRoot()&&_t.ce._injectChildStyle(Zt,G.parent?G.parent.type:void 0);const Ae=G.subTree=Qd(G);x(null,Ae,_e,we,G,Oe,z),se.el=Ae.el}if(ht&&bn(ht,Oe),!Ce&&(oe=Fe&&Fe.onVnodeMounted)){const Ae=se;bn(()=>lr(oe,it,Ae),Oe)}(se.shapeFlag&256||it&&to(it.vnode)&&it.vnode.shapeFlag&256)&&G.a&&bn(G.a,Oe),G.isMounted=!0,se=_e=we=null}};G.scope.on();const ve=G.effect=new Wu(ae);G.scope.off();const $e=G.update=ve.run.bind(ve),Pe=G.job=ve.runIfDirty.bind(ve);Pe.i=G,Pe.id=G.uid,ve.scheduler=()=>Ny(Pe),$l(G,!0),$e()},X=(G,se,_e)=>{se.component=G;const we=G.vnode.props;G.vnode=se,G.next=null,p8(G,se.props,we,_e),v8(G,se.children,_e),ao(),l_(G),uo()},ee=(G,se,_e,we,Oe,z,J,ae,ve=!1)=>{const $e=G&&G.children,Pe=G?G.shapeFlag:0,oe=se.children,{patchFlag:be,shapeFlag:Fe}=se;if(be>0){if(be&128){fe($e,oe,_e,we,Oe,z,J,ae,ve);return}else if(be&256){ce($e,oe,_e,we,Oe,z,J,ae,ve);return}}Fe&8?(Pe&16&&Se($e,Oe,z),oe!==$e&&d(_e,oe)):Pe&16?Fe&16?fe($e,oe,_e,we,Oe,z,J,ae,ve):Se($e,Oe,z,!0):(Pe&8&&d(_e,""),Fe&16&&I(oe,_e,we,Oe,z,J,ae,ve))},ce=(G,se,_e,we,Oe,z,J,ae,ve)=>{G=G||Hi,se=se||Hi;const $e=G.length,Pe=se.length,oe=Math.min($e,Pe);let be;for(be=0;bePe?Se(G,Oe,z,!0,!1,oe):I(se,_e,we,Oe,z,J,ae,ve,oe)},fe=(G,se,_e,we,Oe,z,J,ae,ve)=>{let $e=0;const Pe=se.length;let oe=G.length-1,be=Pe-1;for(;$e<=oe&&$e<=be;){const Fe=G[$e],Xe=se[$e]=ve?js(se[$e]):ir(se[$e]);if(Yr(Fe,Xe))x(Fe,Xe,_e,null,Oe,z,J,ae,ve);else break;$e++}for(;$e<=oe&&$e<=be;){const Fe=G[oe],Xe=se[be]=ve?js(se[be]):ir(se[be]);if(Yr(Fe,Xe))x(Fe,Xe,_e,null,Oe,z,J,ae,ve);else break;oe--,be--}if($e>oe){if($e<=be){const Fe=be+1,Xe=Febe)for(;$e<=oe;)re(G[$e],Oe,z,!0),$e++;else{const Fe=$e,Xe=$e,ht=new Map;for($e=Xe;$e<=be;$e++){const yr=se[$e]=ve?js(se[$e]):ir(se[$e]);yr.key!=null&&ht.set(yr.key,$e)}let it,_t=0;const Zt=be-Xe+1;let Ce=!1,Ae=0;const Ie=new Array(Zt);for($e=0;$e=Zt){re(yr,Oe,z,!0);continue}let us;if(yr.key!=null)us=ht.get(yr.key);else for(it=Xe;it<=be;it++)if(Ie[it-Xe]===0&&Yr(yr,se[it])){us=it;break}us===void 0?re(yr,Oe,z,!0):(Ie[us-Xe]=$e+1,us>=Ae?Ae=us:Ce=!0,x(yr,se[us],_e,null,Oe,z,J,ae,ve),_t++)}const Co=Ce?y8(Ie):Hi;for(it=Co.length-1,$e=Zt-1;$e>=0;$e--){const yr=Xe+$e,us=se[yr],Qb=se[yr+1],Zb=yr+1{const{el:z,type:J,transition:ae,children:ve,shapeFlag:$e}=G;if($e&6){le(G.component.subTree,se,_e,we);return}if($e&128){G.suspense.move(se,_e,we);return}if($e&64){J.move(G,se,_e,ue);return}if(J===H){r(z,se,_e);for(let oe=0;oeae.enter(z),Oe));else{const{leave:oe,delayLeave:be,afterLeave:Fe}=ae,Xe=()=>{G.ctx.isUnmounted?s(z):r(z,se,_e)},ht=()=>{const it=z._isLeaving||!!z[Dr];z._isLeaving&&z[Dr](!0),ae.persisted&&!it?Xe():oe(z,()=>{Xe(),Fe&&Fe()})};be?be(z,Xe,ht):ht()}else r(z,se,_e)},re=(G,se,_e,we=!1,Oe=!1)=>{const{type:z,props:J,ref:ae,children:ve,dynamicChildren:$e,shapeFlag:Pe,patchFlag:oe,dirs:be,cacheIndex:Fe,memo:Xe}=G;if(oe===-2&&(Oe=!1),ae!=null&&(ao(),Gi(ae,null,_e,G,!0),uo()),Fe!=null&&(se.renderCache[Fe]=void 0),Pe&256){se.ctx.deactivate(G);return}const ht=Pe&1&&be,it=!to(G);let _t;if(it&&(_t=J&&J.onVnodeBeforeUnmount)&&lr(_t,se,G),Pe&6)De(G.component,_e,we);else{if(Pe&128){G.suspense.unmount(_e,we);return}ht&&_s(G,null,se,"beforeUnmount"),Pe&64?G.type.remove(G,se,_e,ue,we):$e&&!$e.hasOnce&&(z!==H||oe>0&&oe&64)?Se($e,se,_e,!1,!0):(z===H&&oe&384||!Oe&&Pe&16)&&Se(ve,se,_e),we&&Q(G)}const Zt=Xe!=null&&Fe==null;(it&&(_t=J&&J.onVnodeUnmounted)||ht||Zt)&&bn(()=>{_t&&lr(_t,se,G),ht&&_s(G,null,se,"unmounted"),Zt&&(G.el=null)},_e)},Q=G=>{const{type:se,el:_e,anchor:we,transition:Oe}=G;if(se===H){Ee(_e,we);return}if(se===Yo){E(G);return}const z=()=>{s(_e),Oe&&!Oe.persisted&&Oe.afterLeave&&Oe.afterLeave()};if(G.shapeFlag&1&&Oe&&!Oe.persisted){const{leave:J,delayLeave:ae}=Oe,ve=()=>J(_e,z);ae?ae(G.el,z,ve):ve()}else z()},Ee=(G,se)=>{let _e;for(;G!==se;)_e=p(G),s(G),G=_e;s(se)},De=(G,se,_e)=>{const{bum:we,scope:Oe,job:z,subTree:J,um:ae,m:ve,a:$e}=G;Ef(ve),Ef($e),we&&Ki(we),Oe.stop(),z&&(z.flags|=8,re(J,G,se,_e)),ae&&bn(ae,se),bn(()=>{G.isUnmounted=!0},se)},Se=(G,se,_e,we=!1,Oe=!1,z=0)=>{for(let J=z;J{if(G.shapeFlag&6)return Le(G.component.subTree);if(G.shapeFlag&128)return G.suspense.next();const se=p(G.anchor||G.el),_e=se&&se[i2];return _e?p(_e):se};let Be=!1;const me=(G,se,_e)=>{let we;G==null?se._vnode&&(re(se._vnode,null,null,!0),we=se._vnode.component):x(se._vnode||null,G,se,null,null,null,_e),se._vnode=G,Be||(Be=!0,l_(we),kf(),Be=!1)},ue={p:x,um:re,m:le,r:Q,mt:j,mc:I,pc:ee,pbc:D,n:Le,o:e};let te,ge;return t&&([te,ge]=t(ue)),{render:me,hydrate:te,createApp:s8(me,te)}}function Ch({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function $l({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function L2(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function e1(e,t,n=!1){const r=e.children,s=t.children;if(Ke(r)&&Ke(s))for(let o=0;o>1,e[n[a]]0&&(t[r]=n[o-1]),n[o]=r)}}for(o=n.length,l=n[o-1];o-- >0;)n[o]=l,l=t[l];return n}function F2(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:F2(t)}function Ef(e){if(e)for(let t=0;te.__isSuspense;let tv=0;const b8={name:"Suspense",__isSuspense:!0,process(e,t,n,r,s,o,l,a,u,c){if(e==null)x8(t,n,r,s,o,l,a,u,c);else{if(o&&o.deps>0&&!e.suspense.isInFallback){t.suspense=e.suspense,t.suspense.vnode=t,t.el=e.el;return}k8(e,t,n,r,s,l,a,u,c)}},hydrate:w8,normalize:$8},_8=b8;function ec(e,t){const n=e.props&&e.props[t];nt(n)&&n()}function x8(e,t,n,r,s,o,l,a,u){const{p:c,o:{createElement:d}}=u,f=d("div"),p=e.suspense=q2(e,s,r,t,f,n,o,l,a,u);c(null,p.pendingBranch=e.ssContent,f,null,r,p,o,l),p.deps>0?(ec(e,"onPending"),ec(e,"onFallback"),c(null,e.ssFallback,t,n,r,null,o,l),Ji(p,e.ssFallback)):p.resolve(!1,!0)}function k8(e,t,n,r,s,o,l,a,{p:u,um:c,o:{createElement:d}}){const f=t.suspense=e.suspense;f.vnode=t,t.el=e.el;const p=t.ssContent,h=t.ssFallback,{activeBranch:b,pendingBranch:x,isInFallback:w,isHydrating:k}=f;if(x)f.pendingBranch=p,Yr(x,p)?(u(x,p,f.hiddenContainer,null,s,f,o,l,a),f.deps<=0?f.resolve():w&&!k&&!f.isFallbackMountPending&&(u(b,h,n,r,s,null,o,l,a),Ji(f,h))):(f.pendingId=tv++,k?(f.isHydrating=!1,f.activeBranch=x):c(x,s,f),f.deps=0,f.effects.length=0,f.hiddenContainer=d("div"),w?(u(null,p,f.hiddenContainer,null,s,f,o,l,a),f.deps<=0?f.resolve():f.isFallbackMountPending||(u(b,h,n,r,s,null,o,l,a),Ji(f,h))):b&&Yr(b,p)?(u(b,p,n,r,s,f,o,l,a),f.resolve(!0)):(u(null,p,f.hiddenContainer,null,s,f,o,l,a),f.deps<=0&&f.resolve()));else if(b&&Yr(b,p))u(b,p,n,r,s,f,o,l,a),Ji(f,p);else if(ec(t,"onPending"),f.pendingBranch=p,p.shapeFlag&512?f.pendingId=p.component.suspenseId:f.pendingId=tv++,u(null,p,f.hiddenContainer,null,s,f,o,l,a),f.deps<=0)f.resolve();else{const{timeout:C,pendingId:S}=f;C>0?setTimeout(()=>{f.pendingId===S&&f.fallback(h)},C):C===0&&f.fallback(h)}}function q2(e,t,n,r,s,o,l,a,u,c,d=!1){const{p:f,m:p,um:h,n:b,o:{parentNode:x,remove:w}}=c;let k;const C=C8(e);C&&t&&t.pendingBranch&&(k=t.pendingId,t.deps++);const S=e.props?vf(e.props.timeout):void 0,E=o,T={vnode:e,parent:t,parentComponent:n,namespace:l,container:r,hiddenContainer:s,deps:0,pendingId:tv++,timeout:typeof S=="number"?S:-1,activeBranch:null,isFallbackMountPending:!1,pendingBranch:null,isInFallback:!d,isHydrating:d,isUnmounted:!1,effects:[],resolve(O=!1,P=!1){const{vnode:I,activeBranch:M,pendingBranch:D,pendingId:L,effects:F,parentComponent:N,container:j,isInFallback:q}=T;let W=!1;if(T.isHydrating)T.isHydrating=!1;else if(!O){W=M&&D.transition&&D.transition.mode==="out-in";let ce=!1;W&&(M.transition.afterLeave=()=>{L===T.pendingId&&(p(D,j,o===E&&!ce?b(M):o,0),Yu(F),q&&I.ssFallback&&(I.ssFallback.el=null))}),M&&!T.isFallbackMountPending&&(x(M.el)===j&&(o=b(M),ce=!0),h(M,N,T,!0),!W&&q&&I.ssFallback&&bn(()=>I.ssFallback.el=null,T)),W||p(D,j,o,0)}T.isFallbackMountPending=!1,Ji(T,D),T.pendingBranch=null,T.isInFallback=!1;let X=T.parent,ee=!1;for(;X;){if(X.pendingBranch){for(let ce=0;ce{if(T.isFallbackMountPending=!1,!T.isInFallback)return;const q=T.vnode.ssFallback;f(null,q,D,F,M,null,L,a,u),Ji(T,q)},j=O.transition&&O.transition.mode==="out-in";j&&(T.isFallbackMountPending=!0,I.transition.afterLeave=N),T.isInFallback=!0,h(I,M,null,!0),j||N()},move(O,P,I){T.activeBranch&&p(T.activeBranch,O,P,I),T.container=O},next(){return T.activeBranch&&b(T.activeBranch)},registerDep(O,P,I){const M=!!T.pendingBranch;M&&T.deps++;const D=O.vnode.el;O.asyncDep.catch(L=>{fi(L,O,0)}).then(L=>{if(O.isUnmounted||T.isUnmounted||T.pendingId!==O.suspenseId)return;nc(),O.asyncResolved=!0;const{vnode:F}=O;nv(O,L,!1),D&&(F.el=D);const N=!D&&O.subTree.el;P(O,F,x(D||O.subTree.el),D?null:b(O.subTree),T,l,I),N&&(F.placeholder=null,w(N)),$m(O,F.el),M&&--T.deps===0&&T.resolve()})},unmount(O,P){T.isUnmounted=!0,T.activeBranch&&h(T.activeBranch,n,O,P),T.pendingBranch&&h(T.pendingBranch,n,O,P)}};return T}function w8(e,t,n,r,s,o,l,a,u){const c=t.suspense=q2(t,r,n,e.parentNode,document.createElement("div"),null,s,o,l,a,!0),d=u(e,c.pendingBranch=t.ssContent,n,c,o,l);return c.deps===0&&c.resolve(!1,!0),d}function $8(e){const{shapeFlag:t,children:n}=e,r=t&32;e.ssContent=y_(r?n.default:n),e.ssFallback=r?y_(n.fallback):v(dn)}function y_(e){let t;if(nt(e)){const n=Ql&&e._c;n&&(e._d=!1,y()),e=e(),n&&(e._d=!0,t=Un,Cm())}return Ke(e)&&(e=a8(e)),e=ir(e),t&&!e.dynamicChildren&&(e.dynamicChildren=t.filter(n=>n!==e)),e}function U2(e,t){t&&t.pendingBranch?Ke(e)?t.effects.push(...e):t.effects.push(e):Yu(e)}function Ji(e,t){e.activeBranch=t;const{vnode:n,parentComponent:r}=e;let s=t.el;for(;!s&&t.component;)t=t.component.subTree,s=t.el;n.el=s,r&&r.subTree===n&&(r.vnode.el=s,$m(r,s))}function C8(e){const t=e.props&&e.props.suspensible;return t!=null&&t!==!1}const H=Symbol.for("v-fgt"),no=Symbol.for("v-txt"),dn=Symbol.for("v-cmt"),Yo=Symbol.for("v-stc"),ro=[];let Un=null;function y(e=!1){ro.push(Un=e?null:[])}function Cm(){ro.pop(),Un=ro[ro.length-1]||null}let Ql=1;function tc(e,t=!1){Ql+=e,e<0&&Un&&t&&(Un.hasOnce=!0)}function j2(e){return e.dynamicChildren=Ql>0?Un||Hi:null,Cm(),Ql>0&&Un&&Un.push(e),e}function $(e,t,n,r,s,o){return j2(g(e,t,n,r,s,o,!0))}function B(e,t,n,r,s){return j2(v(e,t,n,r,s,!0))}function fo(e){return e?e.__v_isVNode===!0:!1}function Yr(e,t){return e.type===t.type&&e.key===t.key}function S8(e){}const H2=({key:e})=>e??null,Zd=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?jt(e)||Et(e)||nt(e)?{i:Rn,r:e,k:t,f:!!n}:e:null);function g(e,t=null,n=null,r=0,s=null,o=e===H?0:1,l=!1,a=!1){const u={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&H2(t),ref:t&&Zd(t),scopeId:gm,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:o,patchFlag:r,dynamicProps:s,dynamicChildren:null,appContext:null,ctx:Rn};return a?(Tf(u,n),o&128&&e.normalize(u)):n&&(u.shapeFlag|=jt(n)?8:16),Ql>0&&!l&&Un&&(u.patchFlag>0||o&6)&&u.patchFlag!==32&&Un.push(u),u}const v=E8;function E8(e,t=null,n=null,r=0,s=null,o=!1){if((!e||e===_2)&&(e=dn),fo(e)){const a=ss(e,t,!0);return n&&Tf(a,n),Ql>0&&!o&&Un&&(a.shapeFlag&6?Un[Un.indexOf(e)]=a:Un.push(a)),a.patchFlag=-2,a}if(I8(e)&&(e=e.__vccOpts),t){t=ct(t);let{class:a,style:u}=t;a&&!jt(a)&&(t.class=pe(a)),Pt(u)&&(Vc(u)&&!Ke(u)&&(u=Nt({},u)),t.style=kt(u))}const l=jt(e)?1:Of(e)?128:vm(e)?64:Pt(e)?4:nt(e)?2:0;return g(e,t,n,r,s,l,o,!0)}function ct(e){return e?Vc(e)||T2(e)?Nt({},e):e:null}function ss(e,t,n=!1,r=!1){const{props:s,ref:o,patchFlag:l,children:a,transition:u}=e,c=t?je(s||{},t):s,d={__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&H2(c),ref:t&&t.ref?n&&o?Ke(o)?o.concat(Zd(t)):[o,Zd(t)]:Zd(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:a,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==H?l===-1?16:l|16:l,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:u,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&ss(e.ssContent),ssFallback:e.ssFallback&&ss(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return u&&r&&co(d,u.clone(d)),d}function A(e=" ",t=0){return v(no,null,e,t)}function O8(e,t){const n=v(Yo,null,e);return n.staticCount=t,n}function R(e="",t=!1){return t?(y(),B(dn,null,e)):v(dn,null,e)}function ir(e){return e==null||typeof e=="boolean"?v(dn):Ke(e)?v(H,null,e.slice()):fo(e)?js(e):v(no,null,String(e))}function js(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:ss(e)}function Tf(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(Ke(t))n=16;else if(typeof t=="object")if(r&65){const s=t.default;s&&(s._c&&(s._d=!1),Tf(e,s()),s._c&&(s._d=!0));return}else{n=32;const s=t._;!s&&!T2(t)?t._ctx=Rn:s===3&&Rn&&(Rn.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else if(nt(t)){if(r&65){Tf(e,{default:t});return}t={default:t,_ctx:Rn},n=32}else t=String(t),r&64?(n=16,t=[A(t)]):n=8;e.children=t,e.shapeFlag|=n}function je(...e){const t={};for(let n=0;nIn||Rn;let Af,Xo;{const e=am(),t=(n,r)=>{let s;return(s=e[n])||(s=e[n]=[]),s.push(r),o=>{s.length>1?s.forEach(l=>l(o)):s[0](o)}};Af=t("__VUE_INSTANCE_SETTERS__",n=>In=n),Xo=t("__VUE_SSR_SETTERS__",n=>Zl=n)}const Va=e=>{const t=In;return Af(e),e.scope.on(),()=>{e.scope.off(),Af(t)}},nc=()=>{In&&In.scope.off(),Af(null)};function K2(e){return e.vnode.shapeFlag&4}let Zl=!1;function W2(e,t=!1,n=!1){t&&Xo(t);const{props:r,children:s}=e.vnode,o=K2(e);f8(e,r,o,t),g8(e,s,n||t);const l=o?P8(e,t):void 0;return t&&Xo(!1),l}function P8(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Xg);const{setup:r}=n;if(r){ao();const s=e.setupContext=r.length>1?J2(e):null,o=Va(e),l=Da(r,e,0,[e.props,s]),a=Py(l);if(uo(),o(),(a||e.sp)&&!to(e)&&jy(e),a){if(l.then(nc,nc),t)return l.then(u=>{Xo(!0);try{nv(e,u,t)}finally{Xo(!1)}}).catch(u=>{fi(u,e,0)});e.asyncDep=l}else nv(e,l,t)}else G2(e,t)}function nv(e,t,n){nt(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:Pt(t)&&(e.setupState=Ry(t)),G2(e,n)}let Pf,rv;function M8(e){Pf=e,rv=t=>{t.render._rc&&(t.withProxy=new Proxy(t.ctx,q5))}}const D8=()=>!Pf;function G2(e,t,n){const r=e.type;if(!e.render){if(!t&&Pf&&!r.render){const s=r.template||Xy(e).template;if(s){const{isCustomElement:o,compilerOptions:l}=e.appContext.config,{delimiters:a,compilerOptions:u}=r,c=Nt(Nt({isCustomElement:o,delimiters:a},l),u);r.render=Pf(s,c)}}e.render=r.render||qr,rv&&rv(e)}{const s=Va(e);ao();try{Q5(e)}finally{uo(),s()}}}const V8={get(e,t){return qn(e,"get",""),e[t]}};function J2(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,V8),slots:e.slots,emit:e.emit,expose:t}}function Fc(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(Ry(sa(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in $u)return $u[n](e)},has(t,n){return n in t||n in $u}})):e.proxy}function sv(e,t=!0){return nt(e)?e.displayName||e.name:e.name||t&&e.__name}function I8(e){return nt(e)&&"__vccOpts"in e}const V=(e,t)=>YP(e,t,Zl);function Rt(e,t,n){try{tc(-1);const r=arguments.length;return r===2?Pt(t)&&!Ke(t)?fo(t)?v(e,null,[t]):v(e,t):v(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&fo(n)&&(n=[n]),v(e,t,n))}finally{tc(1)}}function R8(){}function Sm(e,t,n,r){const s=n[r];if(s&&Y2(s,e))return s;const o=t();return o.memo=e.slice(),o.cacheIndex=r,n[r]=o}function Y2(e,t){const n=e.memo;if(n.length!=t.length)return!1;for(let r=0;r0&&Un&&Un.push(e),!0}const X2="3.5.42",N8=qr,L8=o5,F8=Pi,B8=r2,q8={createComponentInstance:z2,setupComponent:W2,renderComponentRoot:Qd,setCurrentRenderingInstance:Qu,isVNode:fo,normalizeVNode:ir,getComponentPublicInstance:Fc,ensureValidVNode:Jy,pushWarningContext:t5,popWarningContext:n5},U8=q8,j8=null,H8=null,z8=null;/** -* @vue/runtime-dom v3.5.42 -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**/let ov;const b_=typeof window<"u"&&window.trustedTypes;if(b_)try{ov=b_.createPolicy("vue",{createHTML:e=>e})}catch{}const Q2=ov?e=>ov.createHTML(e):e=>e,K8="http://www.w3.org/2000/svg",W8="http://www.w3.org/1998/Math/MathML",qs=typeof document<"u"?document:null,__=qs&&qs.createElement("template"),Z2={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const s=t==="svg"?qs.createElementNS(K8,e):t==="mathml"?qs.createElementNS(W8,e):n?qs.createElement(e,{is:n}):qs.createElement(e);return e==="select"&&r&&r.multiple!=null&&s.setAttribute("multiple",r.multiple),s},createText:e=>qs.createTextNode(e),createComment:e=>qs.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>qs.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,s,o){const l=n?n.previousSibling:t.lastChild;if(s&&(s===o||s.nextSibling))for(;t.insertBefore(s.cloneNode(!0),n),!(s===o||!(s=s.nextSibling)););else{__.innerHTML=Q2(r==="svg"?`${e}`:r==="mathml"?`${e}`:e);const a=__.content;if(r==="svg"||r==="mathml"){const u=a.firstChild;for(;u.firstChild;)a.appendChild(u.firstChild);a.removeChild(u)}t.insertBefore(a,n)}return[l?l.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},So="transition",Ga="animation",ia=Symbol("_vtc"),e$={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},t$=Nt({},Uy,e$),G8=e=>(e.displayName="Transition",e.props=t$,e),J8=G8((e,{slots:t})=>Rt(c2,n$(e),t)),Cl=(e,t=[])=>{Ke(e)?e.forEach(n=>n(...t)):e&&e(...t)},x_=e=>e?Ke(e)?e.some(t=>t.length>1):e.length>1:!1;function n$(e){const t={};for(const F in e)F in e$||(t[F]=e[F]);if(e.css===!1)return t;const{name:n="v",type:r,duration:s,enterFromClass:o=`${n}-enter-from`,enterActiveClass:l=`${n}-enter-active`,enterToClass:a=`${n}-enter-to`,appearFromClass:u=o,appearActiveClass:c=l,appearToClass:d=a,leaveFromClass:f=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=e,b=Y8(s),x=b&&b[0],w=b&&b[1],{onBeforeEnter:k,onEnter:C,onEnterCancelled:S,onLeave:E,onLeaveCancelled:T,onBeforeAppear:O=k,onAppear:P=C,onAppearCancelled:I=S}=t,M=(F,N,j,q)=>{F._enterCancelled=q,Mo(F,N?d:a),Mo(F,N?c:l),j&&j()},D=(F,N)=>{F._isLeaving=!1,Mo(F,f),Mo(F,h),Mo(F,p),N&&N()},L=F=>(N,j)=>{const q=F?P:C,W=()=>M(N,F,j);Cl(q,[N,W]),k_(()=>{Mo(N,F?u:o),ps(N,F?d:a),x_(q)||w_(N,r,x,W)})};return Nt(t,{onBeforeEnter(F){Cl(k,[F]),ps(F,o),ps(F,l)},onBeforeAppear(F){Cl(O,[F]),ps(F,u),ps(F,c)},onEnter:L(!1),onAppear:L(!0),onLeave(F,N){F._isLeaving=!0;const j=()=>D(F,N);ps(F,f),F._enterCancelled?(ps(F,p),lv(F)):(lv(F),ps(F,p)),k_(()=>{F._isLeaving&&(Mo(F,f),ps(F,h),x_(E)||w_(F,r,w,j))}),Cl(E,[F,j])},onEnterCancelled(F){M(F,!1,void 0,!0),Cl(S,[F])},onAppearCancelled(F){M(F,!0,void 0,!0),Cl(I,[F])},onLeaveCancelled(F){D(F),Cl(T,[F])}})}function Y8(e){if(e==null)return null;if(Pt(e))return[Sh(e.enter),Sh(e.leave)];{const t=Sh(e);return[t,t]}}function Sh(e){return vf(e)}function ps(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[ia]||(e[ia]=new Set)).add(t)}function Mo(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const n=e[ia];n&&(n.delete(t),n.size||(e[ia]=void 0))}function k_(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let X8=0;function w_(e,t,n,r){const s=e._endId=++X8,o=()=>{s===e._endId&&r()};if(n!=null)return setTimeout(o,n);const{type:l,timeout:a,propCount:u}=r$(e,t);if(!l)return r();const c=l+"end";let d=0;const f=()=>{e.removeEventListener(c,p),o()},p=h=>{h.target===e&&++d>=u&&f()};setTimeout(()=>{d(n[b]||"").split(", "),s=r(`${So}Delay`),o=r(`${So}Duration`),l=$_(s,o),a=r(`${Ga}Delay`),u=r(`${Ga}Duration`),c=$_(a,u);let d=null,f=0,p=0;t===So?l>0&&(d=So,f=l,p=o.length):t===Ga?c>0&&(d=Ga,f=c,p=u.length):(f=Math.max(l,c),d=f>0?l>c?So:Ga:null,p=d?d===So?o.length:u.length:0);const h=d===So&&/\b(?:transform|all)(?:,|$)/.test(r(`${So}Property`).toString());return{type:d,timeout:f,propCount:p,hasTransform:h}}function $_(e,t){for(;e.lengthC_(n)+C_(e[r])))}function C_(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function lv(e){return(e?e.ownerDocument:document).body.offsetHeight}function Q8(e,t,n){const r=e[ia];r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Mf=Symbol("_vod"),t1=Symbol("_vsh"),sl={name:"show",beforeMount(e,{value:t},{transition:n}){e[Mf]=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):Ja(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),Ja(e,!0),r.enter(e)):r.leave(e,()=>{Ja(e,!1)}):Ja(e,t))},beforeUnmount(e,{value:t}){Ja(e,t)}};function Ja(e,t){e.style.display=t?e[Mf]:"none",e[t1]=!t}function Z8(){sl.getSSRProps=({value:e})=>{if(!e)return{style:{display:"none"}}}}const s$=Symbol("");function e3(e){const t=zt();if(!t)return;const n=t.ut=(s=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach(o=>Df(o,s))},r=()=>{const s=e(t.proxy);t.ce?Df(t.ce,s):iv(t.subTree,s),n(s)};zy(()=>{Yu(r)}),mt(()=>{ye(r,qr,{flush:"post"});const s=new MutationObserver(r);s.observe(t.subTree.el.parentNode,{childList:!0}),nn(()=>s.disconnect())})}function iv(e,t){if(e.shapeFlag&128){const n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push(()=>{iv(n.activeBranch,t)})}for(;e.component;)e=e.component.subTree;if(e.shapeFlag&1&&e.el)Df(e.el,t);else if(e.type===H)e.children.forEach(n=>iv(n,t));else if(e.type===Yo){let{el:n,anchor:r}=e;for(;n&&(Df(n,t),n!==r);)n=n.nextSibling}}function Df(e,t){if(e.nodeType===1){const n=e.style;let r="";for(const s in t){const o=wP(t[s]);n.setProperty(`--${s}`,o),r+=`--${s}: ${o};`}n[s$]=r}}const t3=/(?:^|;)\s*display\s*:/;function n3(e,t,n){const r=e.style,s=jt(n);let o=!1;if(n&&!s){if(t)if(jt(t))for(const l of t.split(";")){const a=l.slice(0,l.indexOf(":")).trim();n[a]==null&&pu(r,a,"")}else for(const l in t)n[l]==null&&pu(r,l,"");for(const l in n){l==="display"&&(o=!0);const a=n[l];a!=null?s3(e,l,!jt(t)&&t?t[l]:void 0,a)||pu(r,l,a):pu(r,l,"")}}else if(s){if(t!==n){const l=r[s$];l&&(n+=";"+l),r.cssText=n,o=t3.test(n)}}else t&&e.removeAttribute("style");Mf in e&&(e[Mf]=o?r.display:"",e[t1]&&(r.display="none"))}const bd=/\s*!important$/;function pu(e,t,n){if(Ke(n))n.forEach(r=>pu(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))bd.test(n)?e.setProperty(t,n.replace(bd,""),"important"):e.setProperty(t,n);else{const r=r3(e,t);bd.test(n)?e.setProperty(dr(r),n.replace(bd,""),"important"):e[r]=n}}const S_=["Webkit","Moz","ms"],Eh={};function r3(e,t){const n=Eh[t];if(n)return n;let r=fn(t);if(r!=="filter"&&r in e)return Eh[t]=r;r=Mc(r);for(let s=0;sOh||(c3.then(()=>Oh=0),Oh=Date.now());function f3(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;const s=n.value;if(Ke(s)){const o=r.stopImmediatePropagation;r.stopImmediatePropagation=()=>{o.call(r),r._stopped=!0};const l=s.slice(),a=[r];for(let u=0;ue.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,o$=(e,t,n,r,s,o)=>{const l=s==="svg";t==="class"?Q8(e,r,l):t==="style"?n3(e,n,r):Pc(t)?rm(t)||l3(e,t,n,r,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):p3(e,t,r,l))?(T_(e,t,r),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&O_(e,t,r,l,o,t!=="value")):e._isVueCE&&(m3(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!jt(r)))?T_(e,fn(t),r,o,t):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),O_(e,t,r,l))};function p3(e,t,n,r){if(r)return!!(t==="innerHTML"||t==="textContent"||t in e&&P_(t)&&nt(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const s=e.tagName;if(s==="IMG"||s==="VIDEO"||s==="CANVAS"||s==="SOURCE")return!1}return P_(t)&&jt(n)?!1:t in e}function m3(e,t){const n=e._def.props;if(!n)return!1;const r=fn(t);return Array.isArray(n)?n.some(s=>fn(s)===r):Object.keys(n).some(s=>fn(s)===r)}const M_={};function l$(e,t,n){let r=K(e,t);sm(r)&&(r=Nt({},r,t));class s extends Em{constructor(l){super(r,l,n)}}return s.def=r,s}const h3=((e,t)=>l$(e,t,If)),g3=typeof HTMLElement<"u"?HTMLElement:class{};class Em extends g3{constructor(t,n={},r=sc){super(),this._def=t,this._props=n,this._createApp=r,this._isVueCE=!0,this._instance=null,this._app=null,this._nonce=this._def.nonce,this._connected=!1,this._resolved=!1,this._patching=!1,this._dirty=!1,this._numberProps=null,this._styleChildren=new WeakSet,this._styleAnchors=new WeakMap,this._ob=null,this.shadowRoot&&r!==sc?this._root=this.shadowRoot:t.shadowRoot!==!1?(this.attachShadow(Nt({},t.shadowRootOptions,{mode:"open"})),this._root=this.shadowRoot):this._root=this}connectedCallback(){if(!this.isConnected)return;!this.shadowRoot&&!this._resolved&&this._parseSlots(),this._connected=!0;let t=this;for(;t=t&&(t.assignedSlot||t.parentNode||t.host);)if(t instanceof Em){this._parent=t;break}this._instance||(this._resolved?this._mount(this._def):t&&t._pendingResolve?this._pendingResolve=t._pendingResolve.then(()=>{if(this._pendingResolve=void 0,this.isConnected)return this._resolveDef()}):this._resolveDef())}_setParent(t=this._parent){t&&(this._instance.parent=t._instance,this._inheritParentContext(t))}_inheritParentContext(t=this._parent){t&&this._app&&Object.setPrototypeOf(this._app._context.provides,t._instance.provides)}disconnectedCallback(){this._connected=!1,ot(()=>{this._connected||(this._ob&&(this._ob.disconnect(),this._ob=null),this._app&&this._app.unmount(),this._instance&&(this._instance.ce=void 0),this._app=this._instance=null,this._teleportTargets&&(this._teleportTargets.clear(),this._teleportTargets=void 0))})}_processMutations(t){for(const n of t)this._setAttr(n.attributeName)}_resolveDef(){if(this._pendingResolve)return this._pendingResolve;for(let r=0;r{this._resolved=!0,this._pendingResolve=void 0;const{props:o,styles:l}=r;let a;if(o&&!Ke(o))for(const u in o){const c=o[u];(c===Number||c&&c.type===Number)&&(u in this._props&&(this._props[u]=vf(this._props[u])),(a||(a=Object.create(null)))[fn(u)]=!0)}this._numberProps=a,this._resolveProps(r),this.shadowRoot&&this._applyStyles(l),this._mount(r)},n=this._def.__asyncLoader;if(n)return this._pendingResolve=n().then(r=>{r.configureApp=this._def.configureApp,t(this._def=r,!0)}),this._pendingResolve;t(this._def)}_mount(t){this._app=this._createApp(t),this._inheritParentContext(),t.configureApp&&t.configureApp(this._app),this._app._ceVNode=this._createVNode(),this._app.mount(this._root);const n=this._instance&&this._instance.exposed;if(n)for(const r in n)Ot(this,r)||Object.defineProperty(this,r,{get:()=>i(n[r])})}_resolveProps(t){const{props:n}=t,r=Ke(n)?n:Object.keys(n||{});for(const s of Object.keys(this))s[0]!=="_"&&r.includes(s)&&this._setProp(s,this[s]);for(const s of r.map(fn))Object.defineProperty(this,s,{get(){return this._getProp(s)},set(o){this._setProp(s,o,!0,!this._patching)}})}_setAttr(t){if(t.startsWith("data-v-"))return;const n=this.hasAttribute(t);let r=n?this.getAttribute(t):M_;const s=fn(t);n&&this._numberProps&&this._numberProps[s]&&(r=vf(r)),this._setProp(s,r,!1,!0)}_getProp(t){return this._props[t]}_setProp(t,n,r=!0,s=!1){if(n!==this._props[t]&&(this._dirty=!0,n===M_?delete this._props[t]:(this._props[t]=n,t==="key"&&this._app&&(this._app._ceVNode.key=n)),s&&this._instance&&this._update(),r)){const o=this._ob;o&&(this._processMutations(o.takeRecords()),o.disconnect()),n===!0?this.setAttribute(dr(t),""):typeof n=="string"||typeof n=="number"?this.setAttribute(dr(t),n+""):n||this.removeAttribute(dr(t)),o&&o.observe(this,{attributes:!0})}}_update(){const t=this._createVNode();this._app&&(t.appContext=this._app._context),y$(t,this._root)}_createVNode(){const t={};this.shadowRoot||(t.onVnodeMounted=t.onVnodeUpdated=this._renderSlots.bind(this));const n=v(this._def,Nt(t,this._props));return this._instance||(n.ce=r=>{this._instance=r,r.ce=this,r.isCE=!0;const s=(o,l)=>{this.dispatchEvent(new CustomEvent(o,sm(l[0])?Nt({detail:l},l[0]):{detail:l}))};r.emit=(o,...l)=>{s(o,l),dr(o)!==o&&s(dr(o),l)},this._setParent()}),n}_applyStyles(t,n,r){if(!t)return;if(n){if(n===this._def||this._styleChildren.has(n))return;this._styleChildren.add(n)}const s=this._nonce,o=this.shadowRoot,l=r?this._getStyleAnchor(r)||this._getStyleAnchor(this._def):this._getRootStyleInsertionAnchor(o);let a=null;for(let u=t.length-1;u>=0;u--){const c=document.createElement("style");s&&c.setAttribute("nonce",s),c.textContent=t[u],o.insertBefore(c,a||l),a=c,u===0&&(r||this._styleAnchors.set(this._def,c),n&&this._styleAnchors.set(n,c))}}_getStyleAnchor(t){if(!t)return null;const n=this._styleAnchors.get(t);return n&&n.parentNode===this.shadowRoot?n:(n&&this._styleAnchors.delete(t),null)}_getRootStyleInsertionAnchor(t){for(let n=0;n(delete e.props.mode,e),_3=b3({name:"TransitionGroup",props:Nt({},t$,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=zt(),r=qy();let s,o;return Lc(()=>{if(!s.length)return;const l=e.moveClass||`${e.name||"v"}-move`;if(!$3(s[0].el,n.vnode.el,l)){s=[];return}s.forEach(x3),s.forEach(k3);const a=s.filter(w3);lv(n.vnode.el),a.forEach(u=>{const c=u.el,d=c.style;ps(c,l),d.transform=d.webkitTransform=d.transitionDuration="";const f=c[Vf]=p=>{p&&p.target!==c||(!p||p.propertyName.endsWith("transform"))&&(c.removeEventListener("transitionend",f),c[Vf]=null,Mo(c,l))};c.addEventListener("transitionend",f)}),s=[]}),()=>{const l=$t(e),a=n$(l);let u=l.tag||H;if(s=[],o)for(let c=0;c{a.split(/\s+/).forEach(u=>u&&r.classList.remove(u))}),n.split(/\s+/).forEach(a=>a&&r.classList.add(a)),r.style.display="none";const o=t.nodeType===1?t:t.parentNode;o.appendChild(r);const{hasTransform:l}=r$(r);return o.removeChild(r),l}const ol=e=>{const t=e.props["onUpdate:modelValue"]||!1;return Ke(t)?n=>Ki(t,n):t};function C3(e){e.target.composing=!0}function V_(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const _r=Symbol("_assign"),_d=Symbol("_initialValue");function Th(e,t,n){return t&&(e=e.trim()),n&&(e=im(e)),e}const po={created(e,{modifiers:{lazy:t,trim:n,number:r}},s){e.parentNode&&(e.type==="text"?e[_d]=e.defaultValue.replace(/[\r\n]/g,""):e.type==="textarea"&&(e[_d]=e.defaultValue.replace(/\r\n?/g,` -`))),e[_r]=ol(s);const o=r||s.props&&s.props.type==="number";Ks(e,t?"change":"input",l=>{l.target.composing||e[_r](Th(e.value,n,o))}),(n||o)&&Ks(e,"change",()=>{e.value=Th(e.value,n,o)}),t||(Ks(e,"compositionstart",C3),Ks(e,"compositionend",V_),Ks(e,"change",V_))},mounted(e,{value:t,modifiers:{trim:n,number:r}}){const s=t??"",o=e[_d];delete e[_d],o!==void 0&&(e.type==="text"||e.type==="textarea")&&e.value!==o?e[_r](Th(e.value,n,r)):e.value=s},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:s,number:o}},l){if(e[_r]=ol(l),e.composing)return;const a=(o||e.type==="number")&&!/^0\d/.test(e.value)?im(e.value):e.value,u=t??"";if(a===u)return;const c=e.getRootNode();(c instanceof Document||c instanceof ShadowRoot)&&c.activeElement===e&&e.type!=="range"&&(r&&t===n||s&&e.value.trim()===u)||(e.value=u)}},n1={deep:!0,created(e,t,n){e[_r]=ol(n),Ks(e,"change",()=>{const r=e._modelValue,s=aa(e),o=e.checked,l=e[_r];if(Ke(r)){const a=um(r,s),u=a!==-1;if(o&&!u)l(r.concat(s));else if(!o&&u){const c=[...r];c.splice(a,1),l(c)}}else if(Es(r)){const a=new Set(r);o?a.add(s):a.delete(s),l(a)}else l(f$(e,o))})},mounted:I_,beforeUpdate(e,t,n){e[_r]=ol(n),I_(e,t,n)}};function I_(e,{value:t,oldValue:n},r){e._modelValue=t;let s;if(Ke(t))s=um(t,r.props.value)>-1;else if(Es(t))s=t.has(r.props.value);else{if(t===n)return;s=Kr(t,f$(e,!0))}e.checked!==s&&(e.checked=s)}const Om={created(e,{value:t},n){e.checked=Kr(t,n.props.value),e[_r]=ol(n),Ks(e,"change",()=>{e[_r](aa(e))})},beforeUpdate(e,{value:t,oldValue:n},r){e[_r]=ol(r),t!==n&&(e.checked=Kr(t,r.props.value))}},rc={deep:!0,created(e,{value:t,modifiers:{number:n}},r){e._modelValue=t,Ks(e,"change",()=>{const s=Array.prototype.filter.call(e.options,u=>u.selected).map(u=>n?im(aa(u)):aa(u)),o=e.multiple,l=o?Es(e._modelValue)?new Set(s):s:s[0],a=e._pendingValue=[o,o?Ke(l)?s.slice():s:l];try{e[_r](l)}finally{ot(()=>{e._pendingValue===a&&(e._pendingValue=void 0)})}}),e[_r]=ol(r)},mounted(e,{value:t}){R_(e,t)},beforeUpdate(e,{value:t},n){e._modelValue=t,e[_r]=ol(n)},updated(e,{value:t}){const n=e._pendingValue;e._pendingValue=void 0,(!n||n[0]!==e.multiple||!S3(t,n[1],n[0]))&&R_(e,t)}};function S3(e,t,n){if(!n||Ke(e))return Kr(e,t);if(Es(e)){if(e.size!==t.length)return!1;for(const r of t)if(!e.has(r))return!1;return!0}return!1}function R_(e,t){const n=e.multiple,r=Ke(t);if(!(n&&!r&&!Es(t))){for(let s=0,o=e.options.length;sString(c)===String(a)):l.selected=um(t,a)>-1}else l.selected=t.has(a);else if(Kr(aa(l),t)){e.selectedIndex!==s&&(e.selectedIndex=s);return}}!n&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function aa(e){return"_value"in e?e._value:e.value}function f$(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const p$={created(e,t,n){xd(e,t,n,null,"created")},mounted(e,t,n){xd(e,t,n,null,"mounted")},beforeUpdate(e,t,n,r){xd(e,t,n,r,"beforeUpdate")},updated(e,t,n,r){xd(e,t,n,r,"updated")}};function m$(e,t){switch(e){case"SELECT":return rc;case"TEXTAREA":return po;default:switch(t){case"checkbox":return n1;case"radio":return Om;default:return po}}}function xd(e,t,n,r,s){const l=m$(e.tagName,n.props&&n.props.type)[s];l&&l(e,t,n,r)}function E3(){po.getSSRProps=({value:e})=>({value:e}),Om.getSSRProps=({value:e},t)=>{if(t.props&&Kr(t.props.value,e))return{checked:!0}},n1.getSSRProps=({value:e},t)=>{if(Ke(e)){if(t.props&&um(e,t.props.value)>-1)return{checked:!0}}else if(Es(e)){if(t.props&&e.has(t.props.value))return{checked:!0}}else if(e)return{checked:!0}},p$.getSSRProps=(e,t)=>{if(typeof t.type!="string")return;const n=m$(t.type.toUpperCase(),t.props&&t.props.type);if(n.getSSRProps)return n.getSSRProps(e,t)}}const O3=["ctrl","shift","alt","meta"],T3={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>O3.some(n=>e[`${n}Key`]&&!t.includes(n))},Ne=(e,t)=>{if(!e)return e;const n=e._withMods||(e._withMods={}),r=t.join(".");return n[r]||(n[r]=((s,...o)=>{for(let l=0;l{const n=e._withKeys||(e._withKeys={}),r=t.join(".");return n[r]||(n[r]=(s=>{if(!("key"in s))return;const o=dr(s.key);if(t.some(l=>l===o||A3[l]===o))return e(s)}))},h$=Nt({patchProp:o$},Z2);let Cu,N_=!1;function g$(){return Cu||(Cu=I2(h$))}function v$(){return Cu=N_?Cu:R2(h$),N_=!0,Cu}const y$=((...e)=>{g$().render(...e)}),P3=((...e)=>{v$().hydrate(...e)}),sc=((...e)=>{const t=g$().createApp(...e),{mount:n}=t;return t.mount=r=>{const s=_$(r);if(!s)return;const o=t._component;!nt(o)&&!o.render&&!o.template&&(o.template=s.innerHTML),s.nodeType===1&&(s.textContent="");const l=n(s,!1,b$(s));return s instanceof Element&&(s.removeAttribute("v-cloak"),s.setAttribute("data-v-app","")),l},t}),If=((...e)=>{const t=v$().createApp(...e),{mount:n}=t;return t.mount=r=>{const s=_$(r);if(s)return n(s,!0,b$(s))},t});function b$(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function _$(e){return jt(e)?document.querySelector(e):e}let L_=!1;const M3=()=>{L_||(L_=!0,E3(),Z8())};/** -* vue v3.5.42 -* (c) 2018-present Yuxi (Evan) You and Vue contributors -* @license MIT -**/const D3=()=>{},V3=Object.freeze(Object.defineProperty({__proto__:null,BaseTransition:c2,BaseTransitionPropsValidators:Uy,Comment:dn,DeprecationTypes:z8,EffectScope:My,ErrorCodes:s5,ErrorTypeStrings:L8,Fragment:H,KeepAlive:N5,ReactiveEffect:Wu,Static:Yo,Suspense:_8,Teleport:By,Text:no,TrackOpTypes:XP,Transition:J8,TransitionGroup:c$,TriggerOpTypes:QP,VueElement:Em,assertNumber:r5,callWithAsyncErrorHandling:$r,callWithErrorHandling:Da,camelize:fn,capitalize:Mc,cloneVNode:ss,compatUtils:H8,compile:D3,computed:V,createApp:sc,createBlock:B,createCommentVNode:R,createElementBlock:$,createElementVNode:g,createHydrationRenderer:R2,createPropsRestProxy:Y5,createRenderer:I2,createSSRApp:If,createSlots:pl,createStaticVNode:O8,createTextVNode:A,createVNode:v,customRef:hm,defineAsyncComponent:I5,defineComponent:K,defineCustomElement:l$,defineEmits:j5,defineExpose:H5,defineModel:W5,defineOptions:z5,defineProps:U5,defineSSRCustomElement:h3,defineSlots:K5,devtools:F8,effect:CP,effectScope:cm,getCurrentInstance:zt,getCurrentScope:Ma,getCurrentWatcher:ZP,getTransitionRawChildren:ym,guardReactiveProps:ct,h:Rt,handleError:fi,hasInjectionContext:d5,hydrate:P3,hydrateOnIdle:T5,hydrateOnInteraction:D5,hydrateOnMediaQuery:M5,hydrateOnVisible:P5,initCustomFormatter:R8,initDirectivesForSSR:M3,inject:eo,isMemoSame:Y2,isProxy:Vc,isReactive:Cs,isReadonly:Os,isRef:Et,isRuntimeOnly:D8,isShallow:mr,isVNode:fo,markRaw:sa,mergeDefaults:Yy,mergeModels:J5,mergeProps:je,nextTick:ot,nodeOps:Z2,normalizeClass:pe,normalizeProps:lt,normalizeStyle:kt,onActivated:f2,onBeforeMount:h2,onBeforeUnmount:Kn,onBeforeUpdate:zy,onDeactivated:p2,onErrorCaptured:b2,onMounted:mt,onRenderTracked:y2,onRenderTriggered:v2,onScopeDispose:Dc,onServerPrefetch:g2,onUnmounted:nn,onUpdated:Lc,onWatcherCleanup:Qw,openBlock:y,patchProp:o$,popScopeId:u5,provide:Ic,proxyRefs:Ry,pushScopeId:a5,queuePostFlushCb:Yu,reactive:rt,readonly:bf,ref:U,registerRuntimeCompiler:M8,render:y$,renderList:ne,renderSlot:he,resolveComponent:Wy,resolveDirective:B5,resolveDynamicComponent:Pn,resolveFilter:j8,resolveTransitionHooks:la,setBlockTracking:tc,setDevtoolsHook:B8,setTransitionHooks:co,shallowReactive:Ww,shallowReadonly:Ro,shallowRef:yo,ssrContextKey:s2,ssrUtils:U8,stop:SP,toDisplayString:m,toHandlerKey:zi,toHandlers:_m,toRaw:$t,toRef:Yw,toRefs:ln,toValue:Ln,transformVNodeArgs:S8,triggerRef:Jw,unref:i,useAttrs:km,useCssModule:y3,useCssVars:e3,useHost:i$,useId:Yd,useModel:o8,useSSRContext:o2,useShadowRoot:v3,useSlots:xm,useTemplateRef:v5,useTransitionState:qy,vModelCheckbox:n1,vModelDynamic:p$,vModelRadio:Om,vModelSelect:rc,vModelText:po,vShow:sl,version:X2,warn:N8,watch:ye,watchEffect:Cr,watchPostEffect:Ly,watchSyncEffect:Fy,withAsyncContext:X5,withCtx:_,withDefaults:G5,withDirectives:jn,withKeys:Wt,withMemo:Sm,withModifiers:Ne,withScopeId:c5},Symbol.toStringTag,{value:"Module"})),F_=typeof globalThis=="object"&&globalThis||typeof window=="object"&&window||typeof self=="object"&&self||typeof global=="object"&&global||(function(){return this})();function I3(e,t,{signal:n,edges:r}={}){let s,o=null;const l=r!=null&&r.includes("leading"),a=r==null||r.includes("trailing"),u=()=>{o!==null&&(e.apply(s,o),s=void 0,o=null)},c=()=>{a&&u(),h()};let d=null;const f=()=>{d!=null&&clearTimeout(d),d=setTimeout(()=>{d=null,c()},t)},p=()=>{d!==null&&(clearTimeout(d),d=null)},h=()=>{p(),s=void 0,o=null},b=()=>{u()},x=function(...w){if(n!=null&&n.aborted)return;s=this,o=w;const k=d==null;f(),l&&k&&u()};return x.schedule=f,x.cancel=h,x.flush=b,n==null||n.addEventListener("abort",h,{once:!0}),x}function x$(){}function r1(e){return e==null||typeof e!="object"&&typeof e!="function"}function s1(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}function R3(e){if(r1(e))return e;if(Array.isArray(e)||s1(e)||e instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&e instanceof SharedArrayBuffer)return e.slice(0);const t=Object.getPrototypeOf(e);if(t==null)return Object.assign(Object.create(t),e);const n=t.constructor;if(e instanceof Date||e instanceof Map||e instanceof Set)return new n(e);if(e instanceof RegExp){const r=new n(e);return r.lastIndex=e.lastIndex,r}if(e instanceof DataView)return new n(e.buffer.slice(0));if(e instanceof Error){let r;return e instanceof AggregateError?r=new n(e.errors,e.message,{cause:e.cause}):r=new n(e.message,{cause:e.cause}),r.stack=e.stack,Object.assign(r,e),r}return typeof File<"u"&&e instanceof File?new n([e],e.name,{type:e.type,lastModified:e.lastModified}):typeof e=="object"?Object.assign(Object.create(t),e):e}function Rf(e){return typeof F_.Buffer<"u"&&F_.Buffer.isBuffer(e)}function Nf(e){return Object.getOwnPropertySymbols(e).filter(t=>Object.prototype.propertyIsEnumerable.call(e,t))}function oc(e){return e==null?e===void 0?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}const k$="[object RegExp]",o1="[object String]",l1="[object Number]",i1="[object Boolean]",w$="[object Arguments]",$$="[object Symbol]",C$="[object Date]",S$="[object Map]",E$="[object Set]",O$="[object Array]",N3="[object Function]",T$="[object ArrayBuffer]",ef="[object Object]",L3="[object Error]",A$="[object DataView]",P$="[object Uint8Array]",M$="[object Uint8ClampedArray]",D$="[object Uint16Array]",V$="[object Uint32Array]",F3="[object BigUint64Array]",I$="[object Int8Array]",R$="[object Int16Array]",N$="[object Int32Array]",B3="[object BigInt64Array]",L$="[object Float32Array]",F$="[object Float64Array]";function q3(e,t){return Vl(e,void 0,e,new Map,t)}function Vl(e,t,n,r=new Map,s=void 0){const o=s==null?void 0:s(e,t,n,r);if(o!==void 0)return o;if(r1(e))return e;if(r.has(e))return r.get(e);if(Array.isArray(e)){const l=new Array(e.length);r.set(e,l);for(let a=0;amu(f,h,void 0,e,t,n,r));if(p===-1)return!1;c.splice(p,1)}return!0}case O$:case P$:case M$:case D$:case V$:case F3:case I$:case R$:case N$:case B3:case L$:case F$:if(Rf(e)!==Rf(t)||e.length!==t.length)return!1;for(let u=0;u=0}const z3={"&":"&","<":"<",">":">",'"':""","'":"'"};function K3(e){return e.replace(/[&<>"']/g,t=>z3[t])}function W3(e){return e!=null&&typeof e!="function"&&H3(e.length)}function q$(e){return typeof e=="symbol"||e instanceof Symbol}function U$(e){return e==null?"":j$(e)}function j$(e){if(typeof e=="string")return e;if(Array.isArray(e))return e.map(j$).join(",");if(q$(e))return e.toString();const t=e+"";return t==="0"&&Object.is(Number(e),-0)?"-0":t}function Tm(e){var t;return typeof e=="string"||typeof e=="symbol"?e:Object.is((t=e==null?void 0:e.valueOf)==null?void 0:t.call(e),-0)?"-0":String(e)}function Am(e){if(Array.isArray(e))return e.map(Tm);if(typeof e=="symbol")return[e];e=U$(e);const t=[],n=e.length;if(n===0)return t;let r=0,s="",o="",l=!1,a=!1;const u=/^-?\d+(?:\.\d+)?$/;for(e.charCodeAt(0)===46&&t.push("");r{if(typeof e=="object"){if(oc(e)==="[object Object]"&&typeof e.constructor!="function"){const l={};return o.set(e,l),Jr(l,e,s,o),l}switch(Object.prototype.toString.call(e)){case l1:case o1:case i1:{const l=new e.constructor(e==null?void 0:e.valueOf());return Jr(l,e),l}case w$:{const l={};return Jr(l,e),l.length=e.length,l[Symbol.iterator]=e[Symbol.iterator],l}default:return}}})}function U_(e){return Y3(e)}function av(e){return e!==null&&typeof e=="object"&&oc(e)==="[object Arguments]"}const X3=/^(?:0|[1-9]\d*)$/;function z$(e,t=Number.MAX_SAFE_INTEGER){switch(typeof e){case"number":return Number.isInteger(e)&&e>=0&&e{const r=e[t];(!(Object.hasOwn(e,t)&&B$(r,n))||n===void 0&&!(t in e))&&(e[t]=n)};function rM(e){return e==="__proto__"||e==="constructor"||e==="prototype"}function sM(e,t,n,r){if(e==null&&!q_(e))return e;let s;tM(t,e)?s=[t]:Array.isArray(t)?s=t:s=Am(t);const o=n(qt(e,s));let l=e;for(let a=0;an,()=>{})}function oM(e,t=0,n={}){typeof n!="object"&&(n={});const{leading:r=!1,trailing:s=!0,maxWait:o}=n,l=Array(2);r&&(l[0]="leading"),s&&(l[1]="trailing");let a,u=null;const c=I3(function(...p){a=e.apply(this,p),u=null},t,{edges:l}),d=function(...p){return o!=null&&(u===null&&(u=Date.now()),Date.now()-u>=o)?((r||s)&&(a=e.apply(this,p)),u=Date.now(),c.cancel(),c.schedule(),a):(c.apply(this,p),a)},f=()=>(c.flush(),a);return d.cancel=c.cancel,d.flush=f,d}function lM(e,...t){const n=t.slice(0,-1),r=t[t.length-1];let s=e;for(let o=0;otypeof File<"u"&&e instanceof File||e instanceof Blob||typeof FileList<"u"&&e instanceof FileList&&e.length>0,Pm=e=>e instanceof FormData?!0:a1(e)||typeof e=="object"&&e!==null&&Object.values(e).some(t=>Pm(t));let Bf=class extends Error{constructor(n){super(`HTTP error ${n.status}`);Re(this,"response");this.name="HttpResponseError",this.response=n}},G$=class extends Error{constructor(t="Request was cancelled"){super(t),this.name="HttpCancelledError"}},iM=class extends Error{constructor(t="Network error"){super(t),this.name="HttpNetworkError"}};function aM(e){const t=new URLSearchParams;return Object.entries(e).forEach(([n,r])=>{r!=null&&(Array.isArray(r)?r.forEach(s=>t.append(`${n}[]`,String(s))):typeof r=="object"?t.append(n,JSON.stringify(r)):t.append(n,String(r)))}),t.toString()}function uM(e,t,n){if(t&&!e.startsWith("http://")&&!e.startsWith("https://")&&(e=t.replace(/\/$/,"")+"/"+e.replace(/^\//,"")),n&&Object.keys(n).length>0){const r=aM(n);r&&(e+=(e.includes("?")?"&":"?")+r)}return e}function cM(){var e,t,n,r;return typeof window>"u"?null:((r=(n=(t=(e=window.axios)==null?void 0:e.defaults)==null?void 0:t.headers)==null?void 0:n.common)==null?void 0:r["X-Requested-With"])??null}function J$(e,t=new FormData,n=null){for(const r in e)Object.prototype.hasOwnProperty.call(e,r)&&Y$(t,n?`${n}[${r}]`:r,e[r]);return t}function Y$(e,t,n){if(Array.isArray(n))return n.forEach((r,s)=>Y$(e,`${t}[${s}]`,r));if(n instanceof Date)return e.append(t,n.toISOString());if(typeof File<"u"&&n instanceof File)return e.append(t,n,n.name);if(n instanceof Blob)return e.append(t,n);if(typeof n=="boolean")return e.append(t,n?"1":"0");if(typeof n=="string")return e.append(t,n);if(typeof n=="number")return e.append(t,`${n}`);if(n==null)return e.append(t,"");J$(n,e,t)}function dM(e,t){var n;if(e!=null)return e instanceof FormData?e:typeof e=="object"&&Pm(e)?J$(e):typeof e=="object"||(n=t["Content-Type"])!=null&&n.includes("application/json")?JSON.stringify(e):String(e)}function fM(e){const t={};return e.forEach((n,r)=>{t[r.toLowerCase()]=n}),t}function pM(e={}){let t=e.xsrfCookieName??"XSRF-TOKEN",n=e.xsrfHeaderName??"X-XSRF-TOKEN";function r(){if(typeof document>"u")return null;const s=document.cookie.match(new RegExp("(^|;\\s*)"+t+"=([^;]*)"));return s?decodeURIComponent(s[2]):null}return{setXsrfCookieName(s){t=s},setXsrfHeaderName(s){n=s},async request(s){const o=uM(s.url,s.baseURL,s.params),l=s.method.toUpperCase(),a={},u=cM();u&&(a["X-Requested-With"]=u),s.data!==void 0&&!["GET","DELETE"].includes(l)&&!(s.data instanceof FormData)&&!Pm(s.data)&&(a["Content-Type"]="application/json"),s.headers&&Object.entries(s.headers).forEach(([b,x])=>{x!==void 0&&(a[b]=String(x))});const c=r();c&&!["GET","HEAD","OPTIONS"].includes(l)&&(a[n]=c);let d=s.signal,f;const p=s.timeout??3e4;if(p>0&&!d){const b=new AbortController;d=b.signal,f=setTimeout(()=>b.abort(),p)}const h=["GET","DELETE"].includes(l)?void 0:dM(s.data,a);h instanceof FormData&&delete a["Content-Type"];try{const b=await fetch(o,{method:l,headers:a,body:h,signal:d,credentials:s.credentials??"same-origin"});f&&clearTimeout(f);let x;const w=b.headers.get("content-type");w!=null&&w.includes("application/json")?x=await b.json():x=await b.text();const k={status:b.status,data:x,headers:fM(b.headers)};if(!b.ok)throw new Bf(k);return k}catch(b){throw f&&clearTimeout(f),b instanceof Bf?b:b instanceof DOMException&&b.name==="AbortError"?new G$:b instanceof TypeError?new iM(b.message):b}}}}const cv=pM();let u1=cv,c1,X$,Q$="same-origin",Z$=e=>`${e.method}:${e.baseURL??c1??""}${e.url}`,eC=e=>e.status===204&&e.headers["precognition-success"]==="true";const qf={},Yn={get:(e,t={},n={})=>Xa(Ya("get",e,t,n)),post:(e,t={},n={})=>Xa(Ya("post",e,t,n)),patch:(e,t={},n={})=>Xa(Ya("patch",e,t,n)),put:(e,t={},n={})=>Xa(Ya("put",e,t,n)),delete:(e,t={},n={})=>Xa(Ya("delete",e,t,n)),useHttpClient(e){return u1=e,Yn},withBaseURL(e){return c1=e,Yn},withTimeout(e){return X$=e,Yn},withCredentials(e){return Q$=typeof e=="string"?e:e?"include":"omit",Yn},fingerprintRequestsUsing(e){return Z$=e===null?()=>null:e,Yn},determineSuccessUsing(e){return eC=e,Yn},withXsrfCookieName(e){return cv.setXsrfCookieName(e),Yn},withXsrfHeaderName(e){return cv.setXsrfHeaderName(e),Yn}},Ya=(e,t,n,r)=>({url:t,method:e,...r,...["get","delete"].includes(e)?{params:Ff({},n,r==null?void 0:r.params)}:{data:Ff({},n,r==null?void 0:r.data)}}),Xa=(e={})=>{const t=[mM,gM,vM].reduce((n,r)=>r(n),e);return(t.onBefore??(()=>!0))()===!1?Promise.resolve(null):((t.onStart??(()=>null))(),u1.request({method:t.method,url:t.url,baseURL:t.baseURL??c1,data:t.data,params:t.params,headers:t.headers,signal:t.signal,timeout:t.timeout,credentials:Q$}).then(async n=>{t.precognitive&&j_(n);const r=n.status;let s=n;return t.precognitive&&t.onPrecognitionSuccess&&eC(n)&&(s=await Promise.resolve(t.onPrecognitionSuccess(n)??s)),t.onSuccess&&hM(r)&&(s=await Promise.resolve(t.onSuccess(s)??s)),(H_(t,r)??(l=>l))(s)??s},n=>{if(yM(n))return Promise.reject(n);const r=n;return t.precognitive&&j_(r.response),(H_(t,r.response.status)??((o,l)=>Promise.reject(l)))(r.response,r)}).finally(t.onFinish??(()=>null)))},mM=e=>{const t=e.only??e.validate;return{...e,timeout:e.timeout??X$,precognitive:e.precognitive!==!1,fingerprint:typeof e.fingerprint>"u"?Z$(e,u1):e.fingerprint,headers:{...e.headers,Accept:"application/json","Content-Type":bM(e),...e.precognitive!==!1?{Precognition:!0}:{},...t?{"Precognition-Validate-Only":Array.from(t).join()}:{}}}},hM=e=>e>=200&&e<300,gM=e=>{var t;return typeof e.fingerprint!="string"||((t=qf[e.fingerprint])==null||t.abort(),delete qf[e.fingerprint]),e},vM=e=>typeof e.fingerprint!="string"||e.signal||!e.precognitive?e:(qf[e.fingerprint]=new AbortController,{...e,signal:qf[e.fingerprint].signal}),j_=e=>{var t;if(((t=e.headers)==null?void 0:t.precognition)!=="true")throw Error("Did not receive a Precognition response. Ensure you have the Precognition middleware in place for the route.")},yM=e=>{var t;return!(e instanceof Bf)||typeof((t=e.response)==null?void 0:t.status)!="number"},H_=(e,t)=>({401:e.onUnauthorized,403:e.onForbidden,404:e.onNotFound,409:e.onConflict,422:e.onValidationError,423:e.onLocked})[t],bM=e=>{var t,n,r;return((t=e.headers)==null?void 0:t["Content-Type"])??((n=e.headers)==null?void 0:n["Content-type"])??((r=e.headers)==null?void 0:r["content-type"])??(Pm(e.data)?"multipart/form-data":"application/json")},_M=(e,t)=>{if(!e.includes("*"))return[e];const n=e.split(".");let r=[""];for(const s of n)if(s==="*"){const o=[];for(const l of r){const a=l?qt(t,l):t;if(Array.isArray(a))for(let u=0;uo?`${o}.${s}`:s);return r},xM=(e,t)=>t.includes("*")?new RegExp("^"+t.replace(/\./g,"\\.").replace(/\*/g,"[^.]+")+"$").test(e):e===t,z_=(e,t)=>Object.fromEntries(Object.entries(e).filter(([n])=>!t.some(r=>xM(n,r)))),kM=(e,t={})=>{const n={errorsChanged:[],touchedChanged:[],validatingChanged:[],validatedChanged:[]};let r=!1,s=!1;const o=L=>L!==s?(s=L,n.validatingChanged):[];let l=[];const a=L=>{const F=[...new Set(L)];return l.length!==F.length||!F.every(N=>l.includes(N))?(l=F,n.validatedChanged):[]},u=()=>l.filter(L=>typeof f[L]>"u");let c=[];const d=L=>{const F=[...new Set(L)];return c.length!==F.length||!F.every(N=>c.includes(N))?(c=F,n.touchedChanged):[]};let f={};const p=L=>{const F=wM(L);return ts(f,F)?[]:(f=F,n.errorsChanged)},h=L=>{const F={...f};return delete F[Eu(L)],p(F)},b=()=>Object.keys(f).length>0;let x=1500;const w=L=>{x=L,O.cancel(),O=T()};let k=t,C=null,S=[],E=null;const T=()=>oM(L=>{e({get:(F,N={},j={})=>Yn.get(F,M(N),P(j,L,N)),post:(F,N={},j={})=>Yn.post(F,M(N),P(j,L,N)),patch:(F,N={},j={})=>Yn.patch(F,M(N),P(j,L,N)),put:(F,N={},j={})=>Yn.put(F,M(N),P(j,L,N)),delete:(F,N={},j={})=>Yn.delete(F,M(N),P(j,L,N))}).catch(F=>{var N;return F instanceof G$||F instanceof Bf&&((N=F.response)==null?void 0:N.status)===422?null:Promise.reject(F)})},x,{leading:!0,trailing:!0});let O=T();const P=(L,F,N={})=>{const j={...L,...F},q=Array.from(j.only??j.validate??c);return{...F,...Ff({},L,F),only:q,timeout:j.timeout??5e3,onValidationError:(W,X)=>([...a([...l,...q]),...p(Ff(z_({...f},q),W.data.errors))].forEach(ee=>ee()),j.onValidationError?j.onValidationError(W,X):Promise.reject(X)),onSuccess:W=>(a([...l,...q]).forEach(X=>X()),j.onSuccess?j.onSuccess(W):W),onPrecognitionSuccess:W=>([...a([...l,...q]),...p(z_({...f},q))].forEach(X=>X()),j.onPrecognitionSuccess?j.onPrecognitionSuccess(W):W),onBefore:()=>{const W=c.some(ce=>ce.includes("*")),X=W?[...new Set(c.flatMap(ce=>_M(ce,N)))]:c;return j.onBeforeValidation&&j.onBeforeValidation({data:N,touched:X},{data:k,touched:S})===!1||(j.onBefore||(()=>!0))()===!1?!1:(W&&d(X).forEach(ce=>ce()),E=c,C=N,!0)},onStart:()=>{o(!0).forEach(W=>W()),(j.onStart??(()=>null))()},onFinish:()=>{o(!1).forEach(W=>W()),S=E,k=C,E=C=null,(j.onFinish??(()=>null))()}}},I=(L,F,N)=>{if(typeof L>"u"){const j=Array.from((N==null?void 0:N.only)??(N==null?void 0:N.validate)??[]);d([...c,...j]).forEach(q=>q()),O(N??{});return}if(a1(F)&&!r){console.warn('Precognition file validation is not active. Call the "validateFiles" function on your form to enable it.');return}L=Eu(L),(L.includes("*")||qt(k,L)!==F)&&(d([L,...c]).forEach(j=>j()),O(N??{}))},M=L=>r===!1?dv(L):L,D={touched:()=>c,validate(L,F,N){return typeof L=="object"&&!("target"in L)&&(N=L,L=F=void 0),I(L,F,N),D},touch(L){const F=Array.isArray(L)?L:[Eu(L)];return d([...c,...F]).forEach(N=>N()),D},validating:()=>s,valid:u,errors:()=>f,hasErrors:b,setErrors(L){return p(L).forEach(F=>F()),D},forgetError(L){return h(L).forEach(F=>F()),D},defaults(L){return t=L,k=L,D},reset(...L){if(L.length===0)d([]).forEach(F=>F());else{const F=[...c];L.forEach(N=>{F.includes(N)&&F.splice(F.indexOf(N),1),fr(k,N,qt(t,N))}),d(F).forEach(N=>N())}return D},setTimeout(L){return w(L),D},on(L,F){return n[L].push(F),D},validateFiles(){return r=!0,D},withoutFileValidation(){return r=!1,D}};return D},tC=e=>Object.keys(e).reduce((t,n)=>({...t,[n]:Array.isArray(e[n])?e[n][0]:e[n]}),{}),wM=e=>Object.keys(e).reduce((t,n)=>({...t,[n]:typeof e[n]=="string"?[e[n]]:e[n]}),{}),Eu=e=>typeof e!="string"?e.target.name:e,dv=e=>{const t={...e};return Object.keys(t).forEach(n=>{const r=t[n];if(r!==null){if(a1(r)){delete t[n];return}if(Array.isArray(r)){t[n]=Object.values(dv({...r}));return}if(typeof r=="object"){t[n]=dv(t[n]);return}}}),t};var $M=class{constructor(e){Re(this,"config",{});Re(this,"defaults");this.defaults=e}extend(e){return e&&(this.defaults={...this.defaults,...e}),this}replace(e){this.config=e}get(e){return K$(this.config,e)?qt(this.config,e):qt(this.defaults,e)}set(e,t){typeof e=="string"?fr(this.config,e,t):Object.entries(e).forEach(([n,r])=>{fr(this.config,n,r)})}},ei=new $M({form:{recentlySuccessfulDuration:2e3,forceIndicesArrayFormatInFormData:!0,withAllErrors:!1},prefetch:{cacheFor:3e4,hoverDelay:75}});function lc(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}function rr(e,t){return document.dispatchEvent(new CustomEvent(`inertia:${e}`,t))}var K_=e=>rr("before",{cancelable:!0,detail:{visit:e}}),CM=(e,{page:t,visitId:n}={})=>rr("error",{detail:{errors:e,page:t,visitId:n}}),SM=e=>rr("networkError",{cancelable:!0,detail:{error:e}}),EM=e=>rr("finish",{detail:{visit:e}}),W_=e=>rr("httpException",{cancelable:!0,detail:{response:e}}),OM=e=>rr("beforeUpdate",{detail:{page:e}}),Uf=(e,{cached:t=!1,visitId:n}={})=>rr("navigate",{detail:{page:e,cached:t,visitId:n}}),TM=(e,{replace:t,visitId:n})=>rr("clientVisit",{detail:{page:e,replace:t,visitId:n}}),AM=e=>rr("progress",{detail:{progress:e}}),PM=e=>rr("start",{detail:{visit:e}}),MM=(e,{visitId:t}={})=>rr("success",{detail:{page:e,visitId:t}}),DM=(e,t)=>rr("prefetched",{detail:{fetchedAt:Date.now(),response:e,visit:t}}),VM=e=>rr("prefetching",{detail:{visit:e}}),jf=e=>rr("flash",{detail:{flash:e}}),IM=(e,t)=>rr("location",{cancelable:!0,detail:{url:e,versionChange:t}}),Hg,ar=(Hg=class{static set(e,t){typeof window<"u"&&window.sessionStorage.setItem(e,JSON.stringify(t))}static get(e){if(typeof window<"u")return JSON.parse(window.sessionStorage.getItem(e)||"null")}static merge(e,t){const n=this.get(e);n===null?this.set(e,t):this.set(e,{...n,...t})}static remove(e){typeof window<"u"&&window.sessionStorage.removeItem(e)}static removeNested(e,t){const n=this.get(e);n!==null&&(delete n[t],this.set(e,n))}static exists(e){try{return this.get(e)!==null}catch{return!1}}static clear(){typeof window<"u"&&window.sessionStorage.clear()}},Re(Hg,"locationVisitKey","inertiaLocationVisit"),Hg),RM=async e=>{if(typeof window>"u")throw new Error("Unable to encrypt history");const t=nC(),n=await rC(),r=await UM(n);if(!r)throw new Error("Unable to encrypt history");return await LM(t,r,e)},ua={key:"historyKey",iv:"historyIv"},NM=async e=>{const t=nC(),n=await rC();if(!n)throw new Error("Unable to decrypt history");return await FM(t,n,e)},LM=async(e,t,n)=>{if(typeof window>"u")throw new Error("Unable to encrypt history");if(typeof window.crypto.subtle>"u")return console.warn("Encryption is not supported in this environment. SSL is required."),Promise.resolve(n);const r=new TextEncoder,s=JSON.stringify(n),o=new Uint8Array(s.length*3),l=r.encodeInto(s,o);return window.crypto.subtle.encrypt({name:"AES-GCM",iv:e},t,o.subarray(0,l.written))},FM=async(e,t,n)=>{if(typeof window.crypto.subtle>"u")return console.warn("Decryption is not supported in this environment. SSL is required."),Promise.resolve(n);const r=await window.crypto.subtle.decrypt({name:"AES-GCM",iv:e},t,n);return JSON.parse(new TextDecoder().decode(r))},nC=()=>{const e=ar.get(ua.iv);if(e)return new Uint8Array(e);const t=window.crypto.getRandomValues(new Uint8Array(12));return ar.set(ua.iv,Array.from(t)),t},BM=async()=>typeof window.crypto.subtle>"u"?(console.warn("Encryption is not supported in this environment. SSL is required."),Promise.resolve(null)):window.crypto.subtle.generateKey({name:"AES-GCM",length:256},!0,["encrypt","decrypt"]),qM=async e=>{if(typeof window.crypto.subtle>"u")return console.warn("Encryption is not supported in this environment. SSL is required."),Promise.resolve();const t=await window.crypto.subtle.exportKey("raw",e);ar.set(ua.key,Array.from(new Uint8Array(t)))},UM=async e=>{if(e)return e;const t=await BM();return t?(await qM(t),t):null},rC=async()=>{const e=ar.get(ua.key);return e?await window.crypto.subtle.importKey("raw",new Uint8Array(e),{name:"AES-GCM",length:256},!0,["encrypt","decrypt"]):null},G_=e=>{const t={};for(const n of Object.keys(e))e[n]!==void 0&&(t[n]=e[n]);return t},sC=(e,t,n)=>{if(e===t)return!0;for(const r in e)if(!n.includes(r)&&e[r]!==t[r]&&!jM(e[r],t[r]))return!1;for(const r in t)if(!n.includes(r)&&!(r in e))return!1;return!0},jM=(e,t)=>{switch(typeof e){case"object":return sC(e,t,[]);case"function":return e.toString()===t.toString();default:return e===t}},HM=(e,t,n)=>{const r=Am(t);if(r.length===0)return e;const s=(o,l)=>{if(l===r.length)return n;const a=r[l],u=Array.isArray(o)?[...o]:o&&typeof o=="object"?{...o}:/^(?:0|[1-9]\d*)$/.test(a)?[]:{};return u[a]=s(o==null?void 0:o[a],l+1),u};return s(e,0)},zM={ms:1,s:1e3,m:1e3*60,h:1e3*60*60,d:1e3*60*60*24},J_=e=>{if(typeof e=="number")return e;for(const[t,n]of Object.entries(zM))if(e.endsWith(t))return parseFloat(e)*n;return parseInt(e)},KM=class{constructor(){Re(this,"cached",[]);Re(this,"inFlightRequests",[]);Re(this,"removalTimers",[]);Re(this,"currentUseId",null)}add(e,t,{cacheFor:n,cacheTags:r}){if(this.findInFlight(e))return Promise.resolve();const o=this.findCached(e);if(!e.fresh&&o&&o.staleTimestamp>Date.now())return Promise.resolve();const[l,a]=this.extractStaleValues(n),u=new Promise((c,d)=>{t({...e,onCancel:()=>{this.remove(e),e.onCancel(),d()},onError:f=>{this.remove(e),e.onError(f),d()},onPrefetching(f){e.onPrefetching(f)},onPrefetched(f,p){e.onPrefetched(f,p)},onPrefetchResponse(f){c(f)},onPrefetchError(f){hs.removeFromInFlight(e),d(f)}})}).then(c=>{this.remove(e);const d=c.getPageResponse();Ve.mergeOncePropsIntoResponse(d),this.cached.push({params:{...e},staleTimestamp:Date.now()+l,expiresAt:Date.now()+a,response:u,singleUse:a===0,timestamp:Date.now(),inFlight:!1,tags:Array.isArray(r)?r:[r]});const f=this.getShortestOncePropTtl(d);return this.scheduleForRemoval(e,f?Math.min(a,f):a),this.removeFromInFlight(e),c.handlePrefetch(),c});return this.inFlightRequests.push({params:{...e},response:u,staleTimestamp:null,inFlight:!0}),u}removeAll(){this.cached=[],this.removalTimers.forEach(e=>{clearTimeout(e.timer)}),this.removalTimers=[]}removeByTags(e){this.cached=this.cached.filter(t=>!t.tags.some(n=>e.includes(n)))}remove(e){this.cached=this.cached.filter(t=>!this.paramsAreEqual(t.params,e)),this.clearTimer(e)}removeFromInFlight(e){this.inFlightRequests=this.inFlightRequests.filter(t=>!this.paramsAreEqual(t.params,e))}extractStaleValues(e){const[t,n]=this.cacheForToStaleAndExpires(e);return[J_(t),J_(n)]}cacheForToStaleAndExpires(e){if(!Array.isArray(e))return[e,e];switch(e.length){case 0:return[0,0];case 1:return[e[0],e[0]];default:return[e[0],e[1]]}}clearTimer(e){const t=this.removalTimers.find(n=>this.paramsAreEqual(n.params,e));t&&(clearTimeout(t.timer),this.removalTimers=this.removalTimers.filter(n=>n!==t))}scheduleForRemoval(e,t){if(!(typeof window>"u")&&(this.clearTimer(e),t>0)){const n=window.setTimeout(()=>this.remove(e),t);this.removalTimers.push({params:e,timer:n})}}get(e){return this.findCached(e)||this.findInFlight(e)}use(e,t){const n=`${t.url.pathname}-${Date.now()}-${Math.random().toString(36).substring(7)}`;this.currentUseId=n;const r={...t,cached:!0};return e.response.then(s=>{if(this.currentUseId===n)return s.mergeParams({...r,onPrefetched:()=>{}}),this.removeSingleUseItems(t),s.handle()})}removeSingleUseItems(e){this.cached=this.cached.filter(t=>this.paramsAreEqual(t.params,e)?!t.singleUse:!0)}findCached(e){return this.cached.find(t=>this.paramsAreEqual(t.params,e))||null}findInFlight(e){return this.inFlightRequests.find(t=>this.paramsAreEqual(t.params,e))||null}withoutPurposePrefetchHeader(e){const t=Tt(e);return t.headers.Purpose==="prefetch"&&delete t.headers.Purpose,t}paramsAreEqual(e,t){return sC(this.withoutPurposePrefetchHeader(e),this.withoutPurposePrefetchHeader(t),["id","showProgress","replace","prefetch","preserveScroll","preserveState","onBefore","onBeforeUpdate","onStart","onProgress","onFinish","onCancel","onSuccess","onError","onFlash","onPrefetched","onCancelToken","onPrefetching","async","viewTransition","optimistic","component","pageProps","cached"])}updateCachedOncePropsFromCurrentPage(){this.cached.forEach(e=>{e.response.then(t=>{const n=t.getPageResponse();Ve.mergeOncePropsIntoResponse(n,{force:!0});for(const[l,a]of Object.entries(n.deferredProps??{})){const u=a.filter(c=>qt(n.props,c)===void 0);u.length>0?n.deferredProps[l]=u:delete n.deferredProps[l]}const r=this.getShortestOncePropTtl(n);if(r===null)return;const s=e.expiresAt-Date.now(),o=Math.min(s,r);o>0?this.scheduleForRemoval(e.params,o):this.remove(e.params)})})}getShortestOncePropTtl(e){const t=Object.values(e.onceProps??{}).map(n=>n.expiresAt).filter(n=>!!n);return t.length===0?null:Math.min(...t)-Date.now()}},hs=new KM,Mh=e=>{if(e.offsetParent===null)return!1;const t=e.getBoundingClientRect(),n=t.top=0,r=t.left=0;return n&&r},WM=e=>{const t=l=>{const a=window.getComputedStyle(l);return a.overflowY==="scroll"?!0:a.overflowY!=="auto"?!1:["visible","clip"].includes(a.overflowX)?!0:r(a.maxHeight,l.style.height)||s(l,"height")},n=l=>{const a=window.getComputedStyle(l);return a.overflowX==="scroll"?!0:a.overflowX!=="auto"?!1:["visible","clip"].includes(a.overflowY)?!0:r(a.maxWidth,l.style.width)||s(l,"width")},r=(l,a)=>!!(l&&l!=="none"&&l!=="0px"||a&&a!=="auto"&&a!=="0"),s=(l,a)=>{const u=l.parentElement;if(!u)return!1;const c=window.getComputedStyle(u);if(["flex","inline-flex"].includes(c.display)){const d=["column","column-reverse"].includes(c.flexDirection);return a==="height"?d:!d}return["grid","inline-grid"].includes(c.display)};let o=e==null?void 0:e.parentElement;for(;o;){const l=t(o)||n(o);if(window.getComputedStyle(o).display!=="contents"&&l)return o;o=o.parentElement}return null},oC=(e,t)=>{if(!t)return e.filter(o=>Mh(o));const n=e.indexOf(t),r=[],s=[];for(let o=n;o>=0;o--){const l=e[o];if(Mh(l))r.push(l);else break}for(let o=n+1;o{window.requestAnimationFrame(()=>{t>1?Ou(e,t-1):e()})},GM=e=>{if(typeof window>"u")return null;const t=document.querySelector(`script[data-page="${e}"][type="application/json"]`);return t!=null&&t.textContent?JSON.parse(t.textContent):null},hu=typeof window>"u",JM=!hu&&/Firefox/i.test(window.navigator.userAgent),ur=class{static save(){xt.saveScrollPositions(this.getScrollRegions())}static getScrollRegions(){return Array.from(this.regions()).map(e=>({top:e.scrollTop,left:e.scrollLeft}))}static regions(){return document.querySelectorAll("[scroll-region]")}static scrollToTop(){if(JM&&getComputedStyle(document.documentElement).scrollBehavior==="smooth")return Ou(()=>window.scrollTo(0,0),2);window.scrollTo(0,0)}static reset(){(hu?null:window.location.hash)||this.scrollToTop(),this.regions().forEach(t=>{typeof t.scrollTo=="function"?t.scrollTo(0,0):(t.scrollTop=0,t.scrollLeft=0)}),this.save(),this.scrollToAnchor()}static scrollToAnchor(){const e=hu?null:window.location.hash;e&&setTimeout(()=>{const t=document.getElementById(e.slice(1));t?t.scrollIntoView():this.scrollToTop()})}static restore(e){hu||window.requestAnimationFrame(()=>{this.restoreDocument(),this.restoreScrollRegions(e)})}static restoreScrollRegions(e){hu||this.regions().forEach((t,n)=>{const r=e[n];r&&(typeof t.scrollTo=="function"?t.scrollTo(r.left,r.top):(t.scrollTop=r.top,t.scrollLeft=r.left))})}static restoreDocument(){const e=xt.getDocumentScrollPosition();window.scrollTo(e.left,e.top)}static onScroll(e){const t=e.target;typeof t.hasAttribute=="function"&&t.hasAttribute("scroll-region")&&this.save()}static onWindowScroll(){xt.saveDocumentScrollPosition({top:window.scrollY,left:window.scrollX})}},d1=e=>typeof File<"u"&&e instanceof File||e instanceof Blob||typeof FileList<"u"&&e instanceof FileList&&e.length>0;function Hf(e){return d1(e)||e instanceof FormData&&Array.from(e.values()).some(t=>Hf(t))||typeof e=="object"&&e!==null&&Object.values(e).some(t=>Hf(t))}var fv=e=>e instanceof FormData;function f1(e,t=new FormData,n=null,r="brackets"){e=e||{};for(const s in e)Object.prototype.hasOwnProperty.call(e,s)&&iC(t,lC(n,s,"indices"),e[s],r);return t}function lC(e,t,n){return e?n==="brackets"?`${e}[]`:`${e}[${t}]`:t}function iC(e,t,n,r){if(Array.isArray(n))return Array.from(n.keys()).forEach(s=>iC(e,lC(t,s.toString(),r),n[s],r));if(n instanceof Date)return e.append(t,n.toISOString());if(n instanceof File)return e.append(t,n,n.name);if(n instanceof Blob)return e.append(t,n);if(typeof n=="boolean")return e.append(t,n?"1":"0");if(typeof n=="string")return e.append(t,n);if(typeof n=="number")return e.append(t,`${n}`);if(n==null)return e.append(t,"");f1(n,e,t,r)}function YM(e){return/\[\d+\]/.test(decodeURIComponent(e.search))}function XM(e){if(!e||e==="?")return{};const t={};return e.replace(/^\?/,"").split("&").filter(Boolean).forEach(n=>{const[r,s]=ZM(n);eD(t,Y_(r),Y_(s))}),t}function QM(e,t){const n=[];return pv(e,"",n,t),n.length?"?"+n.join("&"):""}function ZM(e){const t=e.indexOf("=");return t===-1?[e,""]:[e.substring(0,t),e.substring(t+1)]}function Y_(e){return decodeURIComponent(e.replace(/\+/g," "))}function eD(e,t,n){const r=tD(t);if(r.some(l=>l==="__proto__"))return;let s=e;for(;r.length>1;){const l=r.shift(),a=r[0]==="";(typeof s[l]!="object"||s[l]===null)&&(s[l]=a?[]:{}),s=s[l]}const o=r.shift();o===""&&Array.isArray(s)?s.push(n):s[o]=n}function tD(e){const t=[],n=e.split("[")[0];n&&t.push(n);let r;const s=/\[([^\]]*)\]/g;for(;(r=s.exec(e))!==null;)t.push(r[1]);return t}function pv(e,t,n,r){if(e!==void 0){if(e===null){n.push(`${t}=`);return}if(Array.isArray(e)){e.forEach((s,o)=>{const l=r==="indices"?`${t}[${o}]`:`${t}[]`;pv(s,l,n,r)});return}if(typeof e=="object"){Object.keys(e).forEach(s=>{pv(e[s],t?`${t}[${s}]`:s,n,r)});return}n.push(`${t}=${encodeURIComponent(String(e))}`)}}function Lr(e){return new URL(e.toString(),typeof window>"u"?void 0:window.location.toString())}var nD=(e,t,n,r,s)=>{let o=typeof e=="string"?Lr(e):e;if((Hf(t)||r)&&!fv(t)&&(ei.get("form.forceIndicesArrayFormatInFormData")&&(s="indices"),t=f1(t,new FormData,null,s)),fv(t))return[o,t];const[l,a]=Bc(n,o,t,s);return[Lr(l),a]};function Bc(e,t,n,r="brackets"){const s=e==="get"&&!fv(n)&&Object.keys(n).length>0,o=uC(t.toString()),l=o||t.toString().startsWith("/")||t.toString()==="",a=!l&&!t.toString().startsWith("#")&&!t.toString().startsWith("?"),u=/^[.]{1,2}([/]|$)/.test(t.toString()),c=t.toString().includes("?")||s,d=t.toString().includes("#"),f=new URL(t.toString(),typeof window>"u"?"http://localhost":window.location.toString());if(s){const p=YM(f)?"indices":r;f.search=QM({...XM(f.search),...n},p)}return[[o?`${f.protocol}//${f.host}`:"",l?f.pathname:"",a?f.pathname.substring(u?0:1):"",c?f.search:"",d?f.hash:""].join(""),s?{}:n]}function zf(e){return e=new URL(e.href),e.hash="",e}var X_=(e,t)=>{e.hash&&!t.hash&&zf(e).href===t.href&&(t.hash=e.hash)},Kf=(e,t)=>zf(e).href===zf(t).href,mv=(e,t)=>e.origin===t.origin&&e.pathname===t.pathname;function Qr(e){return e!==null&&typeof e=="object"&&e!==void 0&&"url"in e&&"method"in e}function aC(e){return e.component?typeof e.component!="string"?(console.error(`The "component" property on the URL method pair received multiple components (${Object.keys(e.component).join(", ")}), but only a single component string is supported for instant visits. Use the withComponent() method to specify which component to use.`),null):e.component:null}function uC(e){return/^([a-z][a-z0-9+.-]*:)?\/\/[^/]/i.test(e)}function rD(e,t){const n=typeof e=="string"?Lr(e):e;return t?`${n.protocol}//${n.host}${n.pathname}${n.search}${n.hash}`:`${n.pathname}${n.search}${n.hash}`}var sD=class{constructor(){Re(this,"page");Re(this,"swapComponent");Re(this,"resolveComponent");Re(this,"onFlashCallback");Re(this,"componentId",{});Re(this,"listeners",[]);Re(this,"isFirstPageLoad",!0);Re(this,"cleared",!1);Re(this,"pendingDeferredProps",null);Re(this,"historyQuotaExceeded",!1);Re(this,"optimisticBaseline",{});Re(this,"pendingOptimistics",[]);Re(this,"optimisticCounter",0)}init({initialPage:e,swapComponent:t,resolveComponent:n,onFlash:r}){return this.page={...e,flash:e.flash??{},rescuedProps:e.rescuedProps??[]},this.swapComponent=t,this.resolveComponent=n,this.onFlashCallback=r,ws.on("historyQuotaExceeded",()=>{this.historyQuotaExceeded=!0}),this}set(e,{replace:t=!1,preserveScroll:n=!1,preserveState:r=!1,viewTransition:s=!1,cached:o=!1,initialRender:l=!1,visitId:a}={}){Object.keys(e.deferredProps||{}).length&&(this.pendingDeferredProps={deferredProps:e.deferredProps,component:e.component,url:e.url},e.initialDeferredProps===void 0&&(e.initialDeferredProps=e.deferredProps)),this.componentId={};const u=this.componentId;return e.clearHistory&&xt.clear(),this.resolve(e.component,e).then(c=>{if(u!==this.componentId)return;e.rememberedState??(e.rememberedState={});const d=typeof window>"u",f=d?new URL(e.url):window.location,p=!d&&n?ur.getScrollRegions():[];t=t||Kf(Lr(e.url),f);const h={...e,flash:{}};return new Promise(b=>t?xt.replaceState(h,b):xt.pushState(h,b)).then(()=>{const b=!this.isTheSame(e);if(!b&&Object.keys(e.props.errors||{}).length>0&&(s=!1),this.page=e,this.cleared=!1,this.hasOnceProps()&&hs.updateCachedOncePropsFromCurrentPage(),b&&this.fireEventsFor("newComponent"),this.isFirstPageLoad&&this.fireEventsFor("firstLoad"),this.isFirstPageLoad=!1,this.historyQuotaExceeded){this.historyQuotaExceeded=!1;return}return this.swap({component:c,page:e,preserveState:r,viewTransition:s,initialRender:l}).then(()=>{n?window.requestAnimationFrame(()=>ur.restoreScrollRegions(p)):ur.reset(),this.pendingDeferredProps&&this.pendingDeferredProps.component===e.component&&this.pendingDeferredProps.url===e.url&&ws.fireInternalEvent("loadDeferredProps",this.pendingDeferredProps.deferredProps),this.pendingDeferredProps=null,t||Uf(e,{cached:o,visitId:a})})})})}setQuietly(e,{preserveState:t=!1}={}){return this.resolve(e.component,e).then(n=>(this.page=e,this.cleared=!1,xt.setCurrent(e),this.swap({component:n,page:e,preserveState:t,viewTransition:!1})))}clear(){this.cleared=!0}isCleared(){return this.cleared}get(){return this.page}getWithoutFlashData(){return{...this.page,flash:{}}}hasOnceProps(){return Object.keys(this.page.onceProps??{}).length>0}merge(e){this.page={...this.page,...e}}setPropsQuietly(e){return this.page={...this.page,props:e},this.resolve(this.page.component,this.page).then(t=>this.swap({component:t,page:this.page,preserveState:!0,viewTransition:!1}))}setFlash(e){var t;this.page={...this.page,flash:e},(t=this.onFlashCallback)==null||t.call(this,e)}setUrlHash(e){this.page.url.includes(e)||(this.page.url+=e)}remember(e){this.page.rememberedState=e}swap({component:e,page:t,preserveState:n,viewTransition:r,initialRender:s=!1}){const o=()=>this.swapComponent({component:e,page:t,preserveState:n,initialRender:s});if(!r||!(document!=null&&document.startViewTransition)||document.visibilityState==="hidden")return o();const l=typeof r=="boolean"?()=>null:r;return new Promise(a=>{const u=document.startViewTransition(()=>o().then(a));u.ready.catch(()=>{}),l(u)})}resolve(e,t){return Promise.resolve(this.resolveComponent(e,t))}nextOptimisticId(){return++this.optimisticCounter}setBaseline(e,t){e in this.optimisticBaseline||(this.optimisticBaseline[e]=t)}updateBaseline(e,t){e in this.optimisticBaseline&&(this.optimisticBaseline[e]=t)}hasBaseline(e){return e in this.optimisticBaseline}registerOptimistic(e,t){this.pendingOptimistics.push({id:e,callback:t})}unregisterOptimistic(e){this.pendingOptimistics=this.pendingOptimistics.filter(t=>t.id!==e)}replayOptimistics(){const e=Object.keys(this.optimisticBaseline);if(e.length===0)return{};const t=Tt(this.page.props);for(const r of e)t[r]=Tt(this.optimisticBaseline[r]);for(const{callback:r}of this.pendingOptimistics){const s=r(Tt(t));s&&Object.assign(t,s)}const n={};for(const r of e)n[r]=t[r];return n}pendingOptimisticCount(){return this.pendingOptimistics.length}clearOptimisticState(){this.optimisticBaseline={},this.pendingOptimistics=[]}isTheSame(e){return this.page.component===e.component}on(e,t){return this.listeners.push({event:e,callback:t}),()=>{this.listeners=this.listeners.filter(n=>n.event!==e&&n.callback!==t)}}fireEventsFor(e){this.listeners.filter(t=>t.event===e).forEach(t=>t.callback())}mergeOncePropsIntoResponse(e,{force:t=!1}={}){Object.entries(e.onceProps??{}).forEach(([n,r])=>{var o;const s=(o=this.page.onceProps)==null?void 0:o[n];s!==void 0&&(t||qt(e.props,r.prop)===void 0)&&(fr(e.props,r.prop,qt(this.page.props,s.prop)),e.onceProps[n].expiresAt=s.expiresAt)})}},Ve=new sD,Mm=class{constructor(){Re(this,"items",[]);Re(this,"processingPromise",null)}add(e){return this.items.push(e),this.process()}process(){return this.processingPromise??(this.processingPromise=this.processNext().finally(()=>{this.processingPromise=null})),this.processingPromise}processNext(){const e=this.items.shift();return e?Promise.resolve(e()).then(()=>this.processNext()):Promise.resolve()}},Mi=typeof window>"u",Qa=new Mm,Q_=!Mi&&/CriOS/.test(window.navigator.userAgent),oD=class{constructor(){Re(this,"rememberedState","rememberedState");Re(this,"scrollRegions","scrollRegions");Re(this,"preserveUrl",!1);Re(this,"current",{});Re(this,"initialState",null)}remember(e,t){var n;this.replaceState({...Ve.getWithoutFlashData(),rememberedState:{...((n=Ve.get())==null?void 0:n.rememberedState)??{},[t]:e}})}restore(e){var t,n,r,s;if(!Mi)return((t=this.current[this.rememberedState])==null?void 0:t[e])!==void 0?(n=this.current[this.rememberedState])==null?void 0:n[e]:(s=(r=this.initialState)==null?void 0:r[this.rememberedState])==null?void 0:s[e]}pushState(e,t=null){if(!Mi){if(this.preserveUrl){t&&t();return}this.current=e,Qa.add(()=>this.getPageData(e).then(n=>{const r=()=>this.doPushState({page:n},e.url).then(()=>t==null?void 0:t());return Q_?new Promise(s=>{setTimeout(()=>r().then(s))}):r()}))}}clonePageProps(e){try{return structuredClone(e.props),e}catch{return{...e,props:Tt(e.props)}}}getPageData(e){const t=this.clonePageProps(e);return new Promise(n=>e.encryptHistory?RM(t).then(n):n(t))}processQueue(){return Qa.process()}decrypt(e=null){var n;if(Mi)return Promise.resolve(e??Ve.get());const t=e??((n=window.history.state)==null?void 0:n.page);return this.decryptPageData(t).then(r=>{if(!r)throw new Error("Unable to decrypt history");return this.initialState===null?this.initialState=r??void 0:this.current=r??{},r})}decryptPageData(e){return e instanceof ArrayBuffer?NM(e):Promise.resolve(e)}saveScrollPositions(e){Qa.add(()=>Promise.resolve().then(()=>{var t;if((t=window.history.state)!=null&&t.page&&!ts(this.getScrollRegions(),e))return this.doReplaceState({page:window.history.state.page,scrollRegions:e})}))}saveDocumentScrollPosition(e){Qa.add(()=>Promise.resolve().then(()=>{var t;if((t=window.history.state)!=null&&t.page&&!ts(this.getDocumentScrollPosition(),e))return this.doReplaceState({page:window.history.state.page,documentScrollPosition:e})}))}getScrollRegions(){var e;return((e=window.history.state)==null?void 0:e.scrollRegions)||[]}getDocumentScrollPosition(){var e;return((e=window.history.state)==null?void 0:e.documentScrollPosition)||{top:0,left:0}}replaceState(e,t=null){if(ts(this.current,e)){t&&t();return}const{flash:n,...r}=e;if(Ve.merge(r),!Mi){if(this.preserveUrl){t&&t();return}this.current=e,Qa.add(()=>this.getPageData(e).then(s=>{const o=()=>this.doReplaceState({page:s},e.url).then(()=>t==null?void 0:t());return Q_?new Promise(l=>{setTimeout(()=>o().then(l))}):o()}))}}isHistoryThrottleError(e){return e instanceof Error&&e.name==="SecurityError"&&(e.message.includes("history.pushState")||e.message.includes("history.replaceState"))}isQuotaExceededError(e){return e instanceof Error&&e.name==="QuotaExceededError"}withThrottleProtection(e){return Promise.resolve().then(()=>{try{return e()}catch(t){if(!this.isHistoryThrottleError(t))throw t;console.error(t.message)}})}doReplaceState(e,t){return this.withThrottleProtection(()=>{var n,r;window.history.replaceState({...e,scrollRegions:e.scrollRegions??((n=window.history.state)==null?void 0:n.scrollRegions),documentScrollPosition:e.documentScrollPosition??((r=window.history.state)==null?void 0:r.documentScrollPosition)},"",t)})}doPushState(e,t){return this.withThrottleProtection(()=>{try{window.history.pushState(e,"",t)}catch(n){if(!this.isQuotaExceededError(n))throw n;ws.fireInternalEvent("historyQuotaExceeded",t)}})}getState(e,t){var n;return((n=this.current)==null?void 0:n[e])??t}deleteState(e){this.current[e]!==void 0&&(delete this.current[e],this.replaceState(this.current))}clearInitialState(e){this.initialState&&this.initialState[e]!==void 0&&delete this.initialState[e]}browserHasHistoryEntry(){var e;return!Mi&&!!((e=window.history.state)!=null&&e.page)}clear(){ar.remove(ua.key),ar.remove(ua.iv)}setCurrent(e){this.current=e}isValidState(e){return!!e.page}getAllState(){return this.current}};typeof window<"u"&&window.history.scrollRestoration&&(window.history.scrollRestoration="manual");var xt=new oD,lD=class{constructor(){Re(this,"internalListeners",[])}init(){typeof window<"u"&&(window.addEventListener("popstate",this.handlePopstateEvent.bind(this)),window.addEventListener("pageshow",this.handlePageshowEvent.bind(this)),window.addEventListener("scroll",lc(ur.onWindowScroll.bind(ur),100),!0)),typeof document<"u"&&document.addEventListener("scroll",lc(ur.onScroll.bind(ur),100),!0)}onGlobalEvent(e,t){const n=(r=>{const s=t(r);r.cancelable&&!r.defaultPrevented&&s===!1&&r.preventDefault()});return this.registerListener(`inertia:${e}`,n)}on(e,t){return this.internalListeners.push({event:e,listener:t}),()=>{this.internalListeners=this.internalListeners.filter(n=>n.listener!==t)}}onMissingHistoryItem(){Ve.clear(),this.fireInternalEvent("missingHistoryItem")}fireInternalEvent(e,...t){this.internalListeners.filter(n=>n.event===e).forEach(n=>n.listener(...t))}registerListener(e,t){return document.addEventListener(e,t),()=>document.removeEventListener(e,t)}handlePageshowEvent(e){e.persisted&&xt.decrypt().catch(()=>this.onMissingHistoryItem())}handlePopstateEvent(e){const t=e.state||null;if(t===null){const n=Lr(Ve.get().url);n.hash=window.location.hash,xt.replaceState({...Ve.getWithoutFlashData(),url:n.href}),ur.reset();return}if(!xt.isValidState(t))return this.onMissingHistoryItem();xt.decrypt(t.page).then(n=>{if(Ve.get().version!==n.version){this.onMissingHistoryItem();return}xe.cancelAll({prefetch:!1}),Ve.setQuietly(n,{preserveState:!1}).then(()=>{ur.restore(xt.getScrollRegions()),Uf(Ve.get());const r={},s=Ve.get().props;for(const[o,l]of Object.entries(n.initialDeferredProps??n.deferredProps??{})){const a=l.filter(u=>qt(s,u)===void 0);a.length>0&&(r[o]=a)}Object.keys(r).length>0&&this.fireInternalEvent("loadDeferredProps",r)})}).catch(()=>{this.onMissingHistoryItem()})}},ws=new lD,iD=class{constructor(){Re(this,"type");this.type=this.resolveType()}resolveType(){var t;if(typeof window>"u")return"navigate";const e=(t=window.performance)==null?void 0:t.getEntriesByType("navigation")[0];return(e==null?void 0:e.type)??"navigate"}get(){return this.type}isBackForward(){return this.type==="back_forward"}isReload(){return this.type==="reload"}},Dh=new iD;function nf(){const e=typeof window<"u"?window.crypto:void 0;if(e!=null&&e.randomUUID)return e.randomUUID();const t=()=>e!=null&&e.getRandomValues?e.getRandomValues(new Uint8Array(1))[0]:Math.floor(Math.random()*256);return"10000000-1000-4000-8000-100000000000".replace(/[018]/g,n=>(+n^t()&15>>+n/4).toString(16))}var aD=class{static handle(){this.clearRememberedStateOnReload(),[this.handleBackForward,this.handleLocation,this.handleDefault].find(t=>t.bind(this)())}static clearRememberedStateOnReload(){Dh.isReload()&&(xt.deleteState(xt.rememberedState),xt.clearInitialState(xt.rememberedState))}static handleBackForward(){if(!Dh.isBackForward()||!xt.browserHasHistoryEntry())return!1;const e=xt.getScrollRegions();return xt.decrypt().then(t=>{const n=nf();Ve.set(t,{preserveScroll:!0,preserveState:!0,visitId:n}).then(()=>{ur.restore(e),Uf(Ve.get(),{visitId:n})})}).catch(()=>{ws.onMissingHistoryItem()}),!0}static handleLocation(){if(!ar.exists(ar.locationVisitKey))return!1;const e=ar.get(ar.locationVisitKey)||{};return ar.remove(ar.locationVisitKey),typeof window<"u"&&Ve.setUrlHash(window.location.hash),xt.decrypt(Ve.get()).then(()=>{const t=nf(),n=xt.getState(xt.rememberedState,{}),r=xt.getScrollRegions();Ve.remember(n),Ve.set(Ve.get(),{preserveScroll:e.preserveScroll,preserveState:!0,initialRender:!0,visitId:t}).then(()=>{e.preserveScroll&&ur.restore(r),this.fireInitialEvents(t)})}).catch(()=>{ws.onMissingHistoryItem()}),!0}static handleDefault(){typeof window<"u"&&Ve.setUrlHash(window.location.hash);const e=nf();Ve.set(Ve.get(),{preserveScroll:!0,preserveState:!0,initialRender:!0,visitId:e}).then(()=>{Dh.isReload()?ur.restore(xt.getScrollRegions()):ur.scrollToAnchor(),this.fireInitialEvents(e)})}static fireInitialEvents(e){const t=Ve.get();Uf(t,{visitId:e}),Object.keys(t.flash).length>0&&queueMicrotask(()=>jf(t.flash))}},uD=class{constructor(e,t,n){Re(this,"intervalId",null);Re(this,"timeoutId",null);Re(this,"throttle",!1);Re(this,"keepAlive",!1);Re(this,"cb");Re(this,"interval");Re(this,"cbCount",0);Re(this,"mode");Re(this,"inFlight",!1);Re(this,"currentCancel",null);Re(this,"stopped",!0);Re(this,"instanceId",0);this.keepAlive=n.keepAlive??!1,this.mode=n.mode??"overlap",this.cb=t,this.interval=e,(n.autoStart??!0)&&this.start()}stop(){this.stopped=!0,this.instanceId++,this.inFlight=!1,this.currentCancel=null,this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}start(){if(!(typeof window>"u")){if(this.stop(),this.stopped=!1,this.mode==="rest"){this.scheduleNext();return}this.intervalId=window.setInterval(()=>this.tick(),this.interval)}}isInBackground(e){this.throttle=this.keepAlive?!1:e,this.throttle&&(this.cbCount=0)}scheduleNext(){this.stopped||(this.timeoutId=window.setTimeout(()=>{this.timeoutId=null,this.tick()},this.interval))}tick(){!this.throttle||this.cbCount%10===0?this.fire():this.mode==="rest"&&this.scheduleNext(),this.throttle&&this.cbCount++}fire(){var t;this.inFlight&&this.mode==="cancel"&&((t=this.currentCancel)==null||t.call(this));const e=this.instanceId;this.cb({onStart:n=>{e===this.instanceId&&(this.inFlight=!0,this.currentCancel=n)},onFinish:()=>{e===this.instanceId&&(this.inFlight=!1,this.currentCancel=null,this.mode==="rest"&&this.scheduleNext())}})}},cD=class{constructor(){Re(this,"polls",[]);this.setupVisibilityListener()}get count(){return this.polls.length}add(e,t,n){const r=new uD(e,t,n);return this.polls.push(r),{stop:()=>r.stop(),start:()=>r.start(),destroy:()=>{r.stop(),this.polls=this.polls.filter(s=>s!==r)}}}clear(){this.polls.forEach(e=>e.stop()),this.polls=[]}setupVisibilityListener(){typeof document>"u"||document.addEventListener("visibilitychange",()=>{this.polls.forEach(e=>e.isInBackground(document.hidden))},!1)}},Z_=new cD,dD=class{constructor(){Re(this,"requestHandlers",[]);Re(this,"responseHandlers",[]);Re(this,"errorHandlers",[])}onRequest(e){return this.requestHandlers.push(e),()=>{this.requestHandlers=this.requestHandlers.filter(t=>t!==e)}}onResponse(e){return this.responseHandlers.push(e),()=>{this.responseHandlers=this.responseHandlers.filter(t=>t!==e)}}onError(e){return this.errorHandlers.push(e),()=>{this.errorHandlers=this.errorHandlers.filter(t=>t!==e)}}async processRequest(e){let t=e;for(const n of this.requestHandlers)t=await n(t);return t}async processResponse(e){let t=e;for(const n of this.responseHandlers)t=await n(t);return t}async processError(e){for(const t of this.errorHandlers)await t(e)}},Jn=new dD,p1=class extends Error{constructor(n,r,s){super(s?`${n} (${s})`:n);Re(this,"code");Re(this,"url");this.name="HttpError",this.code=r,this.url=s}},ic=class extends p1{constructor(t,n,r){super(t,"ERR_HTTP_RESPONSE",r);Re(this,"response");this.name="HttpResponseError",this.response=n}},ac=class extends p1{constructor(e="Request was cancelled",t){super(e,"ERR_CANCELLED",t),this.name="HttpCancelledError"}},e0=class extends p1{constructor(t,n,r){super(t,"ERR_NETWORK",n);Re(this,"cause");this.name="HttpNetworkError",this.cause=r}};function fD(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null}function pD(e){const t={};return e.getAllResponseHeaders().split(`\r -`).forEach(n=>{const r=n.indexOf(":");r>0&&(t[n.slice(0,r).toLowerCase().trim()]=n.slice(r+1).trim())}),t}function cC(e){return typeof FormData<"u"&&e instanceof FormData}function mD(e){return typeof e=="string"||cC(e)||typeof Blob<"u"&&e instanceof Blob||typeof ArrayBuffer<"u"&&e instanceof ArrayBuffer||typeof ArrayBuffer<"u"&&ArrayBuffer.isView(e)||typeof URLSearchParams<"u"&&e instanceof URLSearchParams}function hD(e,t){if(!t.headers)return;const n=cC(t.data);Object.entries(t.headers).forEach(([r,s])=>{(r.toLowerCase()!=="content-type"||!n)&&e.setRequestHeader(r,String(s))})}function gD(e,t){if(!t||Object.keys(t).length===0)return e;const[n]=Bc("get",e,t);return n}var dC=class{constructor(e={}){Re(this,"xsrfCookieName");Re(this,"xsrfHeaderName");this.xsrfCookieName=e.xsrfCookieName??"XSRF-TOKEN",this.xsrfHeaderName=e.xsrfHeaderName??"X-XSRF-TOKEN"}async request(e){const t=await Jn.processRequest(e);try{const n=await this.doRequest(t);return await Jn.processResponse(n)}catch(n){throw(n instanceof ic||n instanceof e0||n instanceof ac)&&await Jn.processError(n),n}}doRequest(e){return new Promise((t,n)=>{var u,c;const r=new XMLHttpRequest,s=gD(e.url,e.params);r.open(e.method.toUpperCase(),s,!0);const o=fD(this.xsrfCookieName);o&&r.setRequestHeader(this.xsrfHeaderName,o),Object.keys(e.headers??{}).some(d=>d.toLowerCase()==="x-requested-with")||r.setRequestHeader("X-Requested-With","XMLHttpRequest");let a=null;e.data!==null&&e.data!==void 0&&(mD(e.data)?a=e.data:typeof e.data=="object"?(a=JSON.stringify(e.data),!((u=e.headers)!=null&&u["Content-Type"])&&!((c=e.headers)!=null&&c["content-type"])&&r.setRequestHeader("Content-Type","application/json")):a=String(e.data)),hD(r,e),e.onUploadProgress&&(r.upload.onprogress=d=>{const f=d.lengthComputable?d.loaded/d.total:void 0;e.onUploadProgress({progress:f,percentage:f?Math.round(f*100):0,loaded:d.loaded,total:d.lengthComputable?d.total:void 0})}),e.signal&&e.signal.addEventListener("abort",()=>r.abort()),r.onabort=()=>n(new ac("Request was cancelled",e.url)),r.onerror=()=>n(new e0("Network error",e.url)),r.onload=()=>{const d={status:r.status,data:r.responseText,headers:pD(r)};r.status>=400?n(new ic(`Request failed with status ${r.status}`,d,e.url)):t(d)},r.send(a)})}},vD=new dC,Vh=vD;function yD(e){return!("request"in e)}var Dm={getClient(){return Vh},setClient(e){if(!yD(e)){Vh=e;return}Vh=new dC(e),e.xsrfCookieName&&Yn.withXsrfCookieName(e.xsrfCookieName),e.xsrfHeaderName&&Yn.withXsrfHeaderName(e.xsrfHeaderName)},onRequest:Jn.onRequest.bind(Jn),onResponse:Jn.onResponse.bind(Jn),onError:Jn.onError.bind(Jn),processRequest:Jn.processRequest.bind(Jn),processResponse:Jn.processResponse.bind(Jn),processError:Jn.processError.bind(Jn)},bD=class{constructor(){Re(this,"requestHandlers",[]);Re(this,"responseHandlers",[])}onVisitRequest(e){return this.requestHandlers.push(e),()=>{this.requestHandlers=this.requestHandlers.filter(t=>t!==e)}}onVisitResponse(e){return this.responseHandlers.push(e),()=>{this.responseHandlers=this.responseHandlers.filter(t=>t!==e)}}async processRequest(e,t){let n=t;for(const r of this.requestHandlers)n=await r(e,n);return n}async processResponse(e,t){let n=t;for(const r of this.responseHandlers)n=await r(e,n);return n}},m1=new bD;function _D(){typeof window>"u"||(window.__inertia_interceptors__=m1)}var rf=class sf{constructor(t){Re(this,"callbacks",[]);Re(this,"params");if(!t.prefetch)this.params=t;else{const n={onBefore:this.wrapCallback(t,"onBefore"),onBeforeUpdate:this.wrapCallback(t,"onBeforeUpdate"),onStart:this.wrapCallback(t,"onStart"),onProgress:this.wrapCallback(t,"onProgress"),onFinish:this.wrapCallback(t,"onFinish"),onCancel:this.wrapCallback(t,"onCancel"),onSuccess:this.wrapCallback(t,"onSuccess"),onError:this.wrapCallback(t,"onError"),onHttpException:this.wrapCallback(t,"onHttpException"),onNetworkError:this.wrapCallback(t,"onNetworkError"),onFlash:this.wrapCallback(t,"onFlash"),onCancelToken:this.wrapCallback(t,"onCancelToken"),onPrefetched:this.wrapCallback(t,"onPrefetched"),onPrefetching:this.wrapCallback(t,"onPrefetching")};this.params={...t,...n,onPrefetchResponse:t.onPrefetchResponse||(()=>{}),onPrefetchError:t.onPrefetchError||(()=>{})}}}static create(t){return new sf(t)}data(){return this.params.method==="get"?null:this.params.data}queryParams(){return this.params.method==="get"?this.params.data:{}}isPartial(){return this.params.only.length>0||this.params.except.length>0||this.params.reset.length>0}isPrefetch(){return this.params.prefetch===!0}isDeferredPropsRequest(){return this.params.deferredProps===!0}isPollRequest(){return this.params.poll===!0}onCancelToken(t){this.params.onCancelToken({cancel:t})}markAsFinished(){this.params.completed=!0,this.params.cancelled=!1,this.params.interrupted=!1}markAsCancelled({cancelled:t=!0,interrupted:n=!1}){this.params.onCancel(),this.params.completed=!1,this.params.cancelled=t,this.params.interrupted=n}wasCancelledAtAll(){return this.params.cancelled||this.params.interrupted}onFinish(){this.params.onFinish(this.params)}onStart(){this.params.onStart(this.params)}onPrefetching(){this.params.onPrefetching(this.params)}onPrefetchResponse(t){this.params.onPrefetchResponse&&this.params.onPrefetchResponse(t)}onPrefetchError(t){this.params.onPrefetchError&&this.params.onPrefetchError(t)}all(){return this.params}headers(){const t={...this.params.headers};this.isPartial()&&(t["X-Inertia-Partial-Component"]=Ve.get().component);const n=this.params.only.concat(this.params.reset);return n.length>0&&(t["X-Inertia-Partial-Data"]=n.join(",")),this.params.except.length>0&&(t["X-Inertia-Partial-Except"]=this.params.except.join(",")),this.params.reset.length>0&&(t["X-Inertia-Reset"]=this.params.reset.join(",")),this.params.errorBag&&this.params.errorBag.length>0&&(t["X-Inertia-Error-Bag"]=this.params.errorBag),t}setPreserveOptions(t){this.params.preserveScroll=sf.resolvePreserveOption(this.params.preserveScroll,t),this.params.preserveState=sf.resolvePreserveOption(this.params.preserveState,t)}runCallbacks(){this.callbacks.forEach(({name:t,args:n})=>{this.params[t](...n)})}merge(t){this.params={...this.params,...t}}wrapCallback(t,n){return(...r)=>{this.recordCallback(n,r),t[n](...r)}}recordCallback(t,n){this.callbacks.push({name:t,args:n})}static resolvePreserveOption(t,n){return typeof t=="function"?t(n):t==="errors"?Object.keys(n.props.errors||{}).length>0:t}},xD={createIframeAndPage(e){typeof e=="object"&&(e=`All Inertia requests must receive a valid Inertia response, however a plain JSON response was received.
${JSON.stringify(e)}`);const t=document.createElement("html");t.innerHTML=e,t.querySelectorAll("a").forEach(r=>r.setAttribute("target","_top"));const n=document.createElement("iframe");return n.style.backgroundColor="white",n.style.borderRadius="5px",n.style.width="100%",n.style.height="100%",n.setAttribute("sandbox","allow-scripts"),{iframe:n,page:t}},show(e){const{iframe:t,page:n}=this.createIframeAndPage(e);t.style.boxSizing="border-box",t.style.display="block";const r=document.createElement("dialog");r.id="inertia-error-dialog",Object.assign(r.style,{width:"calc(100vw - 100px)",height:"calc(100vh - 100px)",padding:"0",margin:"auto",border:"none",backgroundColor:"transparent"});const s=document.createElement("style");s.textContent=` - dialog#inertia-error-dialog::backdrop { - background-color: rgba(0, 0, 0, 0.6); - } - - dialog#inertia-error-dialog:focus { - outline: none; - } - `;const o=ei.get("nonce");o&&(s.nonce=o),document.head.appendChild(s),r.addEventListener("click",l=>{l.target===r&&r.close()}),r.addEventListener("close",()=>{s.remove(),r.remove()}),r.appendChild(t),document.body.prepend(r),r.showModal(),r.focus(),t.srcdoc=n.outerHTML}},t0=(e,t)=>e===t||e.startsWith(`${t}.`),fC=(e,t)=>{const{only:n,except:r}=e;return!(n.length===0&&r.length===0||n.length>0&&!n.some(s=>t0(t,s))||r.length>0&&r.some(s=>t0(t,s)))},kD=(e,t)=>t.some(n=>fC(e,n)),wD=new Mm,n0=class pC{constructor(t,n,r){Re(this,"requestParams");Re(this,"response");Re(this,"originatingPage");Re(this,"wasPrefetched",!1);Re(this,"processed",!1);this.requestParams=t,this.response=n,this.originatingPage=r}static create(t,n,r){return new pC(t,n,r)}isProcessed(){return this.processed}async handlePrefetch(){Kf(this.requestParams.all().url,window.location)&&this.handle()}async handle(){return wD.add(()=>this.process())}async process(){if(this.requestParams.all().prefetch)return this.wasPrefetched=!0,this.requestParams.all().prefetch=!1,this.requestParams.all().onPrefetched(this.response,this.requestParams.all()),DM(this.response,this.requestParams.all()),Promise.resolve();if(this.requestParams.runCallbacks(),this.processed=!0,!this.isInertiaResponse())return this.handleNonInertiaResponse();if(this.isHttpException()){const r={...this.response,data:this.getDataFromResponse(this.response.data)};if(this.requestParams.all().onHttpException(r)===!1||!W_(r))return}await xt.processQueue(),xt.preserveUrl=this.requestParams.all().preserveUrl,await this.setPage();const{flash:t}=Ve.get();Object.keys(t).length>0&&!this.requestParams.isDeferredPropsRequest()&&(jf(t),this.requestParams.all().onFlash(t));const n=Ve.get().props.errors||{};if(Object.keys(n).length>0){const r=this.getScopedErrors(n);return CM(r,{page:Ve.get(),visitId:this.requestParams.all().id}),this.requestParams.all().onError(r)}xe.flushByCacheTags(this.requestParams.all().invalidateCacheTags||[]),this.wasPrefetched||xe.flush(Ve.get().url),MM(Ve.get(),{visitId:this.requestParams.all().id}),await this.requestParams.all().onSuccess(Ve.get()),xt.preserveUrl=!1}mergeParams(t){this.requestParams.merge(t)}getPageResponse(){const t=this.getDataFromResponse(this.response.data);return typeof t=="object"?this.response.data={...t,flash:t.flash??{},rescuedProps:t.rescuedProps??[]}:this.response.data=t}async handleNonInertiaResponse(){if(this.isInertiaRedirect()){xe.visit(this.getHeader("x-inertia-redirect"),{...this.requestParams.all(),method:"get",data:{}});return}if(this.isLocationVisit()){const n=Lr(this.getHeader("x-inertia-location"));return X_(this.requestParams.all().url,n),this.locationVisit(n)}const t={...this.response,data:this.getDataFromResponse(this.response.data)};if(this.requestParams.all().onHttpException(t)!==!1&&W_(t))return xD.show(t.data)}isInertiaResponse(){return this.hasHeader("x-inertia")}isHttpException(){return this.response.status>=400}hasStatus(t){return this.response.status===t}getHeader(t){return this.response.headers[t]}hasHeader(t){return this.getHeader(t)!==void 0}isInertiaRedirect(){return this.hasStatus(409)&&this.hasHeader("x-inertia-redirect")}isLocationVisit(){return this.hasStatus(409)&&this.hasHeader("x-inertia-location")}locationVisit(t){try{if(typeof window>"u")return;const n=this.getHeader("x-inertia-version"),r=!!n&&n!==Ve.get().version;if(!IM(t,r)||r&&this.requestParams.all().async)return;ar.set(ar.locationVisitKey,{preserveScroll:this.requestParams.all().preserveScroll===!0}),Kf(window.location,t)?window.location.reload():window.location.href=t.href}catch{return!1}}async setPage(){const t=this.getPageResponse();return this.shouldSetPage(t)?(this.response=await m1.processResponse(this.requestParams.all(),this.response),this.mergeProps(t),Ve.mergeOncePropsIntoResponse(t),this.preserveOptimisticProps(t),this.preserveEqualProps(t),await this.setRememberedState(t),this.requestParams.setPreserveOptions(t),t.url=xt.preserveUrl?Ve.get().url:this.pageUrl(t),this.requestParams.all().onBeforeUpdate(t),OM(t),Ve.set(t,{replace:this.requestParams.all().replace,preserveScroll:this.requestParams.all().preserveScroll,preserveState:this.requestParams.all().preserveState,viewTransition:this.requestParams.all().viewTransition,cached:this.requestParams.all().cached,visitId:this.requestParams.all().id})):Promise.resolve()}getDataFromResponse(t){if(typeof t!="string")return t;try{return JSON.parse(t)}catch{return t}}shouldSetPage(t){if(!this.requestParams.all().async||this.originatingPage.component!==t.component)return!0;if(this.originatingPage.component!==Ve.get().component)return!1;const n=Lr(this.originatingPage.url),r=Lr(Ve.get().url);return n.origin===r.origin&&n.pathname===r.pathname}pageUrl(t){const n=Lr(t.url);return t.preserveFragment?n.hash=this.requestParams.all().url.hash:X_(this.requestParams.all().url,n),n.pathname+n.search+n.hash}preserveOptimisticProps(t){if(xe.hasPendingOptimistic())for(const n of Object.keys(t.props))Ve.hasBaseline(n)&&(Ve.updateBaseline(n,t.props[n]),t.props[n]=Ve.get().props[n])}preserveEqualProps(t){if(t.component!==Ve.get().component)return;const n=Ve.get().props;Object.entries(t.props).forEach(([r,s])=>{ts(s,n[r])&&(t.props[r]=n[r])})}mergeProps(t){if(!this.requestParams.isPartial()||t.component!==Ve.get().component)return;const n=t.mergeProps||[],r=t.prependProps||[],s=t.deepMergeProps||[],o=t.matchPropsOn||[],l=(c,d)=>{const f=qt(Ve.get().props,c),p=qt(t.props,c);if(Array.isArray(p)){const h=this.mergeOrMatchItems(f||[],p,c,o,d);fr(t.props,c,h)}else if(typeof p=="object"&&p!==null){const h={...f||{},...p};fr(t.props,c,h)}};n.forEach(c=>l(c,!0)),r.forEach(c=>l(c,!1)),s.forEach(c=>{const d=qt(Ve.get().props,c),f=qt(t.props,c),p=(h,b,x)=>Array.isArray(b)?this.mergeOrMatchItems(h,b,x,o):typeof b=="object"&&b!==null?Object.keys(b).reduce((w,k)=>(w[k]=p(h?h[k]:void 0,b[k],`${x}.${k}`),w),{...h}):b;fr(t.props,c,p(d,f,c))});const a=new Set([...this.requestParams.all().only,...this.requestParams.all().except].filter(c=>c.includes(".")).map(c=>c.split(".")[0]));for(const c of a){const d=Ve.get().props[c];this.isObject(d)&&this.isObject(t.props[c])&&(t.props[c]=this.deepMergeObjects(d,t.props[c]))}t.props={...Ve.get().props,...t.props},this.shouldPreserveErrors(t)&&(t.props.errors=Ve.get().props.errors),Ve.get().scrollProps&&(t.scrollProps={...Ve.get().scrollProps||{},...t.scrollProps||{}}),Ve.hasOnceProps()&&(t.onceProps={...Ve.get().onceProps||{},...t.onceProps||{}}),this.requestParams.isDeferredPropsRequest()&&(t.flash={...Ve.get().flash});const u=Ve.get().initialDeferredProps;u&&Object.keys(u).length>0&&(t.initialDeferredProps=u),t.rescuedProps=this.mergeRescuedProps(t)}mergeRescuedProps(t){const n=Ve.get().rescuedProps??[],r=t.rescuedProps??[],s=new Set(n.filter(o=>!fC(this.requestParams.all(),o)));return r.forEach(o=>s.add(o)),Array.from(s)}shouldPreserveErrors(t){if(!this.requestParams.all().preserveErrors)return!1;const n=Ve.get().props.errors;if(!n||Object.keys(n).length===0)return!1;const r=t.props.errors;return!(r&&Object.keys(r).length>0)}isObject(t){return t&&typeof t=="object"&&!Array.isArray(t)}deepMergeObjects(t,n){const r={...t};for(const s of Object.keys(n)){const o=t[s],l=n[s];this.isObject(o)&&this.isObject(l)?r[s]=this.deepMergeObjects(o,l):r[s]=l}return r}mergeOrMatchItems(t,n,r,s,o=!0){const l=Array.isArray(t)?t:[],a=s.find(d=>d.split(".").slice(0,-1).join(".")===r);if(!a)return o?[...l,...n]:[...n,...l];const u=a.split(".").pop()||"",c=new Map;return n.forEach(d=>{this.hasUniqueProperty(d,u)&&c.set(d[u],d)}),o?this.appendWithMatching(l,n,c,u):this.prependWithMatching(l,n,c,u)}appendWithMatching(t,n,r,s){const o=t.map(a=>this.hasUniqueProperty(a,s)&&r.has(a[s])?r.get(a[s]):a),l=n.filter(a=>this.hasUniqueProperty(a,s)?!t.some(u=>this.hasUniqueProperty(u,s)&&u[s]===a[s]):!0);return[...o,...l]}prependWithMatching(t,n,r,s){const o=t.filter(l=>this.hasUniqueProperty(l,s)?!r.has(l[s]):!0);return[...n,...o]}hasUniqueProperty(t,n){return t&&typeof t=="object"&&n in t}async setRememberedState(t){const n=await xt.getState(xt.rememberedState,{});this.requestParams.all().preserveState&&n&&t.component===Ve.get().component&&(t.rememberedState=n)}getScopedErrors(t){return this.requestParams.all().errorBag?t[this.requestParams.all().errorBag||""]||{}:t}},r0=class mC{constructor(t,n,{optimistic:r=!1}={}){Re(this,"page");Re(this,"response");Re(this,"cancelToken");Re(this,"requestParams");Re(this,"requestHasFinished",!1);Re(this,"optimistic");this.page=n,this.requestParams=rf.create(t),this.cancelToken=new AbortController,this.optimistic=r}static create(t,n,r){return new mC(t,n,r)}isPrefetch(){return this.requestParams.isPrefetch()}getUrl(){return this.requestParams.all().url}isOptimistic(){return this.optimistic}isPendingOptimistic(){return this.isOptimistic()&&(!this.response||!this.response.isProcessed())}async send(){this.requestParams.onCancelToken(()=>{this.response||this.cancel({cancelled:!0})}),PM(this.requestParams.all()),this.requestParams.onStart(),this.requestParams.all().prefetch&&(this.requestParams.onPrefetching(),VM(this.requestParams.all()));const t=this.requestParams.all().prefetch,n={method:this.requestParams.all().method,url:zf(this.requestParams.all().url).href,data:this.requestParams.data(),signal:this.cancelToken.signal,headers:this.getHeaders(),onUploadProgress:this.onProgress.bind(this)},r=await m1.processRequest(this.requestParams.all(),n);return Dm.getClient().request(r).then(s=>(this.response=n0.create(this.requestParams,s,this.page),this.response.handle())).catch(s=>s instanceof ic?(this.response=n0.create(this.requestParams,s.response,this.page),this.response.handle()):Promise.reject(s)).catch(s=>{if(!(s instanceof ac)&&this.requestParams.all().onNetworkError(s)!==!1&&SM(s))return t&&this.requestParams.onPrefetchError(s),Promise.reject(s)}).finally(()=>{this.finish(),t&&this.response&&this.requestParams.onPrefetchResponse(this.response)})}finish(){this.requestParams.wasCancelledAtAll()||(this.requestParams.markAsFinished(),this.fireFinishEvents())}fireFinishEvents(){this.requestHasFinished||(this.requestHasFinished=!0,EM(this.requestParams.all()),this.requestParams.onFinish())}cancel({cancelled:t=!1,interrupted:n=!1}){this.requestHasFinished||(this.cancelToken.abort(),this.requestParams.markAsCancelled({cancelled:t,interrupted:n}),this.fireFinishEvents())}onProgress(t){this.requestParams.data()instanceof FormData&&(AM(t),this.requestParams.all().onProgress(t))}getHeaders(){const t={...this.requestParams.headers(),Accept:"text/html, application/xhtml+xml","X-Requested-With":"XMLHttpRequest","X-Inertia":!0},n=Ve.get();n.version&&(t["X-Inertia-Version"]=n.version);const r=Object.entries(n.onceProps||{}).filter(([,s])=>qt(n.props,s.prop)===void 0?!1:!s.expiresAt||s.expiresAt>Date.now()).map(([s])=>s);return r.length>0&&(t["X-Inertia-Except-Once-Props"]=r.join(",")),t}},s0=class{constructor({maxConcurrent:e,interruptible:t}){Re(this,"requests",[]);Re(this,"maxConcurrent");Re(this,"interruptible");this.maxConcurrent=e,this.interruptible=t}send(e){this.requests.push(e),e.send().finally(()=>{this.requests=this.requests.filter(t=>t!==e)})}interruptInFlight(){this.cancel({interrupted:!0},!1)}cancelInFlight(e={}){const t=typeof e=="function"?e:n=>{const{prefetch:r=!0,optimistic:s=!0}=e;return(r||!n.isPrefetch())&&(s||!n.isOptimistic())};this.requests.filter(t).forEach(n=>n.cancel({cancelled:!0}))}cancel({cancelled:e=!1,interrupted:t=!1}={},n=!1){if(!n&&!this.shouldCancel())return;const r=this.requests.shift();r==null||r.cancel({cancelled:e,interrupted:t})}shouldCancel(){return this.interruptible&&this.requests.length>=this.maxConcurrent}hasPendingOptimistic(){return this.requests.some(e=>e.isPendingOptimistic())}},sr=()=>{},$D=class{constructor(){Re(this,"syncRequestStream",new s0({maxConcurrent:1,interruptible:!0}));Re(this,"asyncRequestStream",new s0({maxConcurrent:1/0,interruptible:!1}));Re(this,"clientVisitQueue",new Mm);Re(this,"pendingOptimisticCallback")}init({initialPage:e,resolveComponent:t,swapComponent:n,onFlash:r}){Ve.init({initialPage:e,resolveComponent:t,swapComponent:n,onFlash:r}),aD.handle(),ws.init(),ws.on("missingHistoryItem",()=>{typeof window<"u"&&this.visit(window.location.href,{preserveState:!0,preserveScroll:!0,replace:!0})}),ws.on("loadDeferredProps",s=>{this.loadDeferredProps(s)}),ws.on("historyQuotaExceeded",s=>{window.location.href=s})}optimistic(e){return this.pendingOptimisticCallback=e,this}get(e,t={},n={}){return this.visit(e,{...n,method:"get",data:t})}post(e,t={},n={}){return this.visit(e,{preserveState:!0,...n,method:"post",data:t})}put(e,t={},n={}){return this.visit(e,{preserveState:!0,...n,method:"put",data:t})}patch(e,t={},n={}){return this.visit(e,{preserveState:!0,...n,method:"patch",data:t})}delete(e,t={}){return this.visit(e,{preserveState:!0,...t,method:"delete"})}reload(e={}){return this.doReload(e)}doReload(e={}){if(!(typeof window>"u"))return this.visit(window.location.href,{...e,preserveScroll:!0,preserveState:!0,async:!0,headers:{...e.headers||{},"Cache-Control":"no-cache"}})}remember(e,t="default"){xt.remember(e,t)}restore(e="default"){return xt.restore(e)}on(e,t){return typeof window>"u"?()=>{}:ws.onGlobalEvent(e,t)}once(e,t){if(typeof window>"u")return()=>{};const n=this.on(e,r=>(n(),t(r)));return n}hasPendingOptimistic(){return this.asyncRequestStream.hasPendingOptimistic()}get activePolls(){return Z_.count}cancelAll({async:e=!0,prefetch:t=!0,sync:n=!0}={}){e&&this.asyncRequestStream.cancelInFlight({prefetch:t}),n&&this.syncRequestStream.cancelInFlight()}poll(e,t={},n={}){return Z_.add(e,({onStart:r,onFinish:s})=>{const o=typeof t=="function"?t():t;this.doReload({poll:!0,preserveErrors:!0,...o,onCancelToken:l=>{var a;r(l.cancel),(a=o.onCancelToken)==null||a.call(o,l)},onFinish:l=>{var a;s(),(a=o.onFinish)==null||a.call(o,l)}})},{autoStart:n.autoStart??!0,keepAlive:n.keepAlive??!1,mode:n.mode})}visit(e,t={}){t.optimistic=t.optimistic??this.pendingOptimisticCallback,this.pendingOptimisticCallback=void 0,t.optimistic&&(t.async=t.async??!0);const n=this.getPendingVisit(e,{...t,showProgress:t.showProgress??(!t.async||!!t.optimistic)}),r=this.getVisitEvents(t);if(r.onBefore(n)===!1||!K_(n))return;const s=Lr(Ve.get().url);(n.only.length>0||n.except.length>0||n.reset.length>0?mv(n.url,s):Kf(n.url,s))||this.asyncRequestStream.cancelInFlight(c=>!c.isPrefetch()&&!c.isOptimistic()&&mv(c.getUrl(),s)),n.async||this.syncRequestStream.interruptInFlight(),t.optimistic&&this.applyOptimisticUpdate(t.optimistic,r),!Ve.isCleared()&&!n.preserveUrl&&ur.save();const a={...n,...r},u=()=>{const c=hs.get(a);c?(xr.reveal(c.inFlight),hs.use(c,a)):(xr.reveal(!0),(n.async?this.asyncRequestStream:this.syncRequestStream).send(r0.create(a,Ve.get(),{optimistic:!!t.optimistic})))};Array.isArray(n.component)&&(console.error(`The "component" prop received an array of components (${n.component.join(", ")}), but only a single component string is supported for instant visits. Pass an explicit component name instead.`),n.component=null),n.component?xt.processQueue().then(()=>{this.performInstantSwap(n).then(()=>{a.preserveScroll=!0,a.preserveState=!0,a.replace=!0,a.viewTransition=!1,u()})}):u()}getCached(e,t={}){return hs.findCached(this.getPrefetchParams(e,t))}flush(e,t={}){hs.remove(this.getPrefetchParams(e,t))}flushAll(){hs.removeAll()}flushByCacheTags(e){hs.removeByTags(Array.isArray(e)?e:[e])}getPrefetching(e,t={}){return hs.findInFlight(this.getPrefetchParams(e,t))}prefetch(e,t={},n={}){if((t.method??(Qr(e)?e.method:"get"))!=="get")throw new Error("Prefetch requests must use the GET method");const s=this.getPendingVisit(e,{...t,async:!0,showProgress:!1,prefetch:!0,viewTransition:!1}),o=s.url.origin+s.url.pathname+s.url.search,l=window.location.origin+window.location.pathname+window.location.search;if(o===l)return;const a=this.getVisitEvents(t);if(a.onBefore(s)===!1||!K_(s))return;xr.hide(),this.asyncRequestStream.interruptInFlight();const u={...s,...a};new Promise(d=>{const f=()=>{Ve.get()?d():setTimeout(f,50)};f()}).then(()=>{hs.add(u,d=>{this.asyncRequestStream.send(r0.create(d,Ve.get()))},{cacheFor:ei.get("prefetch.cacheFor"),cacheTags:[],...n})})}clearHistory(){xt.clear()}decryptHistory(){return xt.decrypt()}resolveComponent(e,t){return Ve.resolve(e,t)}replace(e){this.clientVisit(e,{replace:!0})}replaceProp(e,t,n){this.replace({preserveScroll:!0,preserveState:!0,props(r){const s=typeof t=="function"?t(qt(r,e),r):t;return HM(r,e,s)},...n||{}})}appendToProp(e,t,n){this.replaceProp(e,(r,s)=>{const o=typeof t=="function"?t(r,s):t;return Array.isArray(r)||(r=r!==void 0?[r]:[]),[...r,o]},n)}prependToProp(e,t,n){this.replaceProp(e,(r,s)=>{const o=typeof t=="function"?t(r,s):t;return Array.isArray(r)||(r=r!==void 0?[r]:[]),[o,...r]},n)}push(e){this.clientVisit(e)}flash(e,t){const n=Ve.get().flash;let r;if(typeof e=="function")r=e(n);else if(typeof e=="string")r={...n,[e]:t};else if(e&&Object.keys(e).length)r={...n,...e};else return;Ve.setFlash(r),Object.keys(r).length&&jf(r)}clientVisit(e,{replace:t=!1}={}){this.clientVisitQueue.add(()=>this.performClientVisit(e,{replace:t}))}performClientVisit(e,{replace:t=!1}={}){const n=Ve.get(),r=typeof e.props=="function"?Object.fromEntries(Object.values(n.onceProps??{}).map(w=>[w.prop,qt(n.props,w.prop)])):{},s=typeof e.props=="function"?e.props(n.props,r):e.props??n.props,o=typeof e.flash=="function"?e.flash(n.flash):e.flash,{viewTransition:l,onError:a,onFinish:u,onFlash:c,onSuccess:d,...f}=e,p={...n,...f,flash:o??{},props:s},h=rf.resolvePreserveOption(e.preserveScroll??!1,p),b=rf.resolvePreserveOption(e.preserveState??!1,p),x=this.createVisitId();return Ve.set(p,{replace:t,preserveScroll:h,preserveState:b,viewTransition:l,visitId:x}).then(()=>{TM(Ve.get(),{replace:t,visitId:x});const w=Ve.get().flash;Object.keys(w).length>0&&(jf(w),c==null||c(w));const k=Ve.get().props.errors||{};if(Object.keys(k).length===0){d==null||d(Ve.get());return}const C=e.errorBag?k[e.errorBag||""]||{}:k;a==null||a(C)}).finally(()=>u==null?void 0:u(e))}performInstantSwap(e){const t=Ve.get(),n=Object.fromEntries((t.sharedProps??[]).filter(a=>a in t.props).map(a=>[a,t.props[a]])),r=typeof e.pageProps=="function"?e.pageProps(Tt(t.props),Tt(n)):e.pageProps,s=r!==null?{...r}:{...n},o=this.preserveOncePropsOnInstantVisit(t,s),l={component:e.component,url:e.url.pathname+e.url.search+e.url.hash,version:t.version,props:{...s,errors:{}},flash:{},rescuedProps:[],clearHistory:!1,encryptHistory:t.encryptHistory,sharedProps:t.sharedProps,onceProps:o,rememberedState:{}};return Ve.set(l,{replace:e.replace,preserveScroll:rf.resolvePreserveOption(e.preserveScroll,l),preserveState:!1,viewTransition:e.viewTransition,visitId:e.id})}preserveOncePropsOnInstantVisit(e,t){const n={};return Object.entries(e.onceProps??{}).forEach(([r,s])=>{if(qt(t,s.prop)!==void 0)return;const o=qt(e.props,s.prop);o!==void 0&&(fr(t,s.prop,o),n[r]=s)}),n}getPrefetchParams(e,t){return{...this.getPendingVisit(e,{...t,async:!0,showProgress:!1,prefetch:!0,viewTransition:!1}),...this.getVisitEvents(t)}}createVisitId(){return nf()}getPendingVisit(e,t){if(Qr(e)){const u=e;e=u.url,t.method=t.method??u.method}const n=ei.get("visitOptions"),r=n?n(e.toString(),Tt(t))||{}:{},s={method:"get",data:{},replace:!1,preserveScroll:!1,preserveState:!1,only:[],except:[],headers:{},errorBag:"",forceFormData:!1,queryStringArrayFormat:"brackets",async:!1,showProgress:!0,fresh:!1,reset:[],preserveUrl:!1,preserveErrors:!1,prefetch:!1,invalidateCacheTags:[],viewTransition:!1,component:null,pageProps:null,cached:!1,...G_(t),...G_(r)},[o,l]=nD(e,s.data,s.method,s.forceFormData,s.queryStringArrayFormat),a={id:this.createVisitId(),cancelled:!1,completed:!1,interrupted:!1,...s,url:o,data:l};return a.prefetch&&(a.headers.Purpose="prefetch"),a}getVisitEvents(e){return{onCancelToken:e.onCancelToken||sr,onBefore:e.onBefore||sr,onBeforeUpdate:e.onBeforeUpdate||sr,onStart:e.onStart||sr,onProgress:e.onProgress||sr,onFinish:e.onFinish||sr,onCancel:e.onCancel||sr,onSuccess:e.onSuccess||sr,onError:e.onError||sr,onHttpException:e.onHttpException||sr,onNetworkError:e.onNetworkError||sr,onFlash:e.onFlash||sr,onPrefetched:e.onPrefetched||sr,onPrefetching:e.onPrefetching||sr}}applyOptimisticUpdate(e,t){const n=Ve.get().props,r=e(Tt(n));if(!r)return;const s=[];for(const d of Object.keys(r))ts(n[d],r[d])||s.push(d);if(s.length===0)return;const o=Ve.nextOptimisticId(),l=Ve.get().component;for(const d of s)Ve.setBaseline(d,Tt(n[d]));Ve.registerOptimistic(o,e),Ve.setPropsQuietly({...n,...r});let a=!0;const u=t.onSuccess;t.onSuccess=d=>(a=!1,u(d));const c=t.onFinish;t.onFinish=d=>{if(Ve.unregisterOptimistic(o),a&&Ve.get().component===l){const f=Ve.replayOptimistics();Object.keys(f).length>0&&Ve.setPropsQuietly({...Ve.get().props,...f})}return Ve.pendingOptimisticCount()===0&&Ve.clearOptimisticState(),c(d)}}loadDeferredProps(e){e&&Object.values(e).forEach(t=>{this.doReload({only:t,deferredProps:!0,preserveErrors:!0})})}},ca=class{static createWayfinderCallback(...e){return()=>e.length===1?Qr(e[0])?e[0]:e[0]():{method:typeof e[0]=="function"?e[0]():e[0],url:typeof e[1]=="function"?e[1]():e[1]}}static parseUseFormArguments(...e){return e.length===0?{rememberKey:null,data:{},precognitionEndpoint:null}:e.length===1?{rememberKey:null,data:e[0],precognitionEndpoint:null}:e.length===2?typeof e[0]=="string"?{rememberKey:e[0],data:e[1],precognitionEndpoint:null}:{rememberKey:null,data:e[1],precognitionEndpoint:this.createWayfinderCallback(e[0])}:{rememberKey:null,data:e[2],precognitionEndpoint:this.createWayfinderCallback(e[0],e[1])}}static parseSubmitArguments(e,t){return e.length===3||e.length===2&&typeof e[0]=="string"?{method:e[0],url:e[1],options:e[2]??{}}:Qr(e[0])?{...e[0],options:e[1]??{}}:{...t(),options:e[0]??{}}}static mergeHeadersForValidation(e,t,n){const r=s=>(s.headers={...n??{},...s.headers??{}},s);return e&&typeof e=="object"&&!("target"in e)?e=r(e):t&&typeof t=="object"?t=r(t):typeof e=="string"?t=r(t??{}):e=r(e??{}),[e,t]}};function CD(e){if(!e.includes("."))return e;const t=n=>n.startsWith("[")&&n.endsWith("]")?n:n.split(".").reduce((r,s,o)=>o===0?s:`${r}[${s}]`);return e.replace(/\\\./g,"__ESCAPED_DOT__").split(/(\[[^\]]*\])/).filter(Boolean).map(t).join("").replace(/__ESCAPED_DOT__/g,".")}function SD(e){const t=[],n=/([^\[\]]+)|\[(\d*)\]/g;let r;for(;(r=n.exec(e))!==null;)r[1]!==void 0?t.push(r[1]):r[2]!==void 0&&t.push(r[2]===""?"":Number(r[2]));return t}function ED(e,t,n){let r=e;for(let s=0;s/^\d+$/.test(r)).map(Number).sort((r,s)=>r-s);return t.length===n.length&&n.length>0&&n[0]===0&&n.every((r,s)=>r===s)}function of(e){if(Array.isArray(e))return e.map(of);if(typeof e!="object"||e===null||d1(e))return e;if(OD(e)){const n=[];for(let r=0;r/^\d+$/.test(u)).map(Number).sort((u,c)=>u-c);fr(t,o,a.length>0?[...a.map(u=>l[u]),r]:[r])}else fr(t,o,[r]);continue}ED(t,s.map(String),r)}return of(t)}var Za="server";function TD(e,t){return e.match(/\sdata-inertia(=|\s|>)/)?e:e.replace(/^<([a-zA-Z][^\s/>]*)/,`<$1 data-inertia="server-head-${t}"`)}function l0(e,t){if(!t)return[];const n=typeof t=="function"?t(e):e.props[t===!0?"head":t];return Array.isArray(n)?n.map(r=>typeof r=="string"?r.trim():r).filter(r=>typeof r=="string"&&r.length>0).map(TD):[]}var AD={buildDOMElement(e){const t=document.createElement("template");t.innerHTML=e;const n=t.content.firstChild;if(!e.startsWith("