@htlkg/components 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js → AdminWrapper.vue_vue_type_script_setup_true_lang-BhnWQ-b0.js} +26 -29
- package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-BhnWQ-b0.js.map +1 -0
- package/dist/Alert.vue_vue_type_script_setup_true_lang-DxPCS-Hx.js.map +1 -1
- package/dist/DateRange.vue_vue_type_script_setup_true_lang-BLVg1Hah.js.map +1 -1
- package/dist/ProductBadge.vue_vue_type_script_setup_true_lang-Cmr2f4Cy.js.map +1 -1
- package/dist/components.css +4 -4
- package/dist/composables/index.js +23 -22
- package/dist/data/index.js +10 -10
- package/dist/{filterHelpers-DgRyoYSa.js → filterHelpers-DpHSlTuh.js} +11 -11
- package/dist/filterHelpers-DpHSlTuh.js.map +1 -0
- package/dist/index-QK97OdqQ.js.map +1 -1
- package/dist/index.js +34 -33
- package/dist/navigation/index.js +1 -1
- package/dist/{useAdminPage-GhgXp0x8.js → useAdminPage-AgWRvw6o.js} +150 -26
- package/dist/useAdminPage-AgWRvw6o.js.map +1 -0
- package/package.json +3 -3
- package/src/composables/index.ts +1 -0
- package/src/composables/useJsonForm.test.ts +272 -0
- package/src/composables/useJsonForm.ts +261 -0
- package/src/composables/useModal.test.ts +264 -0
- package/src/composables/useModal.ts +54 -8
- package/src/data/Chart/index.ts +2 -0
- package/src/data/DataList/index.ts +1 -0
- package/src/data/{DataTable.vue → DataTable/DataTable.vue} +2 -2
- package/src/data/DataTable/index.ts +8 -0
- package/src/data/SearchableSelect/index.ts +1 -0
- package/src/data/Table/index.ts +1 -0
- package/src/data/index.ts +5 -15
- package/src/domain/BrandCard/index.ts +1 -0
- package/src/domain/BrandSelector/index.ts +1 -0
- package/src/domain/ProductBadge/index.ts +1 -0
- package/src/domain/UserAvatar/index.ts +1 -0
- package/src/domain/index.ts +4 -4
- package/src/forms/DateRange/index.ts +2 -0
- package/src/forms/JsonSchemaForm/index.ts +1 -0
- package/src/forms/index.ts +2 -3
- package/src/navigation/{AdminWrapper.vue → AdminWrapper/AdminWrapper.vue} +41 -30
- package/src/navigation/AdminWrapper/index.ts +1 -0
- package/src/navigation/Breadcrumbs/index.ts +1 -0
- package/src/navigation/Stepper/index.ts +2 -0
- package/src/navigation/Tabs/index.ts +2 -0
- package/src/navigation/index.ts +4 -6
- package/src/overlays/Alert/index.ts +1 -0
- package/src/overlays/Drawer/index.ts +1 -0
- package/src/overlays/Modal/index.ts +1 -0
- package/src/overlays/Notification/index.ts +1 -0
- package/src/overlays/index.ts +4 -4
- package/src/patterns/DASHBOARD_PAGE.md +642 -0
- package/src/patterns/DETAIL_PAGE.md +446 -0
- package/src/patterns/FORM_PAGE.md +439 -0
- package/src/patterns/LIST_PAGE.md +340 -0
- package/src/patterns/PAGE_PATTERNS.md +110 -0
- package/src/patterns/WIZARD_PAGE.md +733 -0
- package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js.map +0 -1
- package/dist/filterHelpers-DgRyoYSa.js.map +0 -1
- package/dist/useAdminPage-GhgXp0x8.js.map +0 -1
- package/src/data/Table.vue +0 -295
- /package/src/data/{Chart.demo.vue → Chart/Chart.demo.vue} +0 -0
- /package/src/data/{Chart.md → Chart/Chart.md} +0 -0
- /package/src/data/{Chart.vue → Chart/Chart.vue} +0 -0
- /package/src/data/{DataList.md → DataList/DataList.md} +0 -0
- /package/src/data/{DataList.test.ts → DataList/DataList.test.ts} +0 -0
- /package/src/data/{DataList.vue → DataList/DataList.vue} +0 -0
- /package/src/data/{SearchableSelect.md → SearchableSelect/SearchableSelect.md} +0 -0
- /package/src/data/{SearchableSelect.vue → SearchableSelect/SearchableSelect.vue} +0 -0
- /package/src/data/{Table.demo.vue → Table/Table.demo.vue} +0 -0
- /package/src/data/{Table.md → Table/Table.md} +0 -0
- /package/src/data/{Table.property.test.ts → Table/Table.property.test.ts} +0 -0
- /package/src/data/{Table.test.ts → Table/Table.test.ts} +0 -0
- /package/src/data/{Table.unit.test.ts → Table/Table.unit.test.ts} +0 -0
- /package/src/domain/{BrandCard.md → BrandCard/BrandCard.md} +0 -0
- /package/src/domain/{BrandCard.vue → BrandCard/BrandCard.vue} +0 -0
- /package/src/domain/{BrandSelector.md → BrandSelector/BrandSelector.md} +0 -0
- /package/src/domain/{BrandSelector.vue → BrandSelector/BrandSelector.vue} +0 -0
- /package/src/domain/{ProductBadge.md → ProductBadge/ProductBadge.md} +0 -0
- /package/src/domain/{ProductBadge.vue → ProductBadge/ProductBadge.vue} +0 -0
- /package/src/domain/{UserAvatar.md → UserAvatar/UserAvatar.md} +0 -0
- /package/src/domain/{UserAvatar.vue → UserAvatar/UserAvatar.vue} +0 -0
- /package/src/forms/{DateRange.demo.vue → DateRange/DateRange.demo.vue} +0 -0
- /package/src/forms/{DateRange.md → DateRange/DateRange.md} +0 -0
- /package/src/forms/{DateRange.vue → DateRange/DateRange.vue} +0 -0
- /package/src/forms/{JsonSchemaForm.demo.vue → JsonSchemaForm/JsonSchemaForm.demo.vue} +0 -0
- /package/src/forms/{JsonSchemaForm.md → JsonSchemaForm/JsonSchemaForm.md} +0 -0
- /package/src/forms/{JsonSchemaForm.property.test.ts → JsonSchemaForm/JsonSchemaForm.property.test.ts} +0 -0
- /package/src/forms/{JsonSchemaForm.test.ts → JsonSchemaForm/JsonSchemaForm.test.ts} +0 -0
- /package/src/forms/{JsonSchemaForm.unit.test.ts → JsonSchemaForm/JsonSchemaForm.unit.test.ts} +0 -0
- /package/src/forms/{JsonSchemaForm.vue → JsonSchemaForm/JsonSchemaForm.vue} +0 -0
- /package/src/navigation/{Breadcrumbs.demo.vue → Breadcrumbs/Breadcrumbs.demo.vue} +0 -0
- /package/src/navigation/{Breadcrumbs.md → Breadcrumbs/Breadcrumbs.md} +0 -0
- /package/src/navigation/{Breadcrumbs.test.ts → Breadcrumbs/Breadcrumbs.test.ts} +0 -0
- /package/src/navigation/{Breadcrumbs.vue → Breadcrumbs/Breadcrumbs.vue} +0 -0
- /package/src/navigation/{Stepper.demo.vue → Stepper/Stepper.demo.vue} +0 -0
- /package/src/navigation/{Stepper.md → Stepper/Stepper.md} +0 -0
- /package/src/navigation/{Stepper.vue → Stepper/Stepper.vue} +0 -0
- /package/src/navigation/{Tabs.demo.vue → Tabs/Tabs.demo.vue} +0 -0
- /package/src/navigation/{Tabs.md → Tabs/Tabs.md} +0 -0
- /package/src/navigation/{Tabs.test.ts → Tabs/Tabs.test.ts} +0 -0
- /package/src/navigation/{Tabs.vue → Tabs/Tabs.vue} +0 -0
- /package/src/overlays/{Alert.demo.vue → Alert/Alert.demo.vue} +0 -0
- /package/src/overlays/{Alert.md → Alert/Alert.md} +0 -0
- /package/src/overlays/{Alert.test.ts → Alert/Alert.test.ts} +0 -0
- /package/src/overlays/{Alert.vue → Alert/Alert.vue} +0 -0
- /package/src/overlays/{Drawer.md → Drawer/Drawer.md} +0 -0
- /package/src/overlays/{Drawer.test.ts → Drawer/Drawer.test.ts} +0 -0
- /package/src/overlays/{Drawer.vue → Drawer/Drawer.vue} +0 -0
- /package/src/overlays/{Modal.demo.vue → Modal/Modal.demo.vue} +0 -0
- /package/src/overlays/{Modal.md → Modal/Modal.md} +0 -0
- /package/src/overlays/{Modal.test.ts → Modal/Modal.test.ts} +0 -0
- /package/src/overlays/{Modal.vue → Modal/Modal.vue} +0 -0
- /package/src/overlays/{Notification.md → Notification/Notification.md} +0 -0
- /package/src/overlays/{Notification.test.ts → Notification/Notification.test.ts} +0 -0
- /package/src/overlays/{Notification.vue → Notification/Notification.vue} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAdminPage-AgWRvw6o.js","sources":["../src/composables/useModal.ts","../src/composables/useTabs.ts","../src/composables/useForm.ts","../src/composables/useFormValidation.ts","../src/composables/useJsonForm.ts","../src/composables/useNotifications.ts","../src/composables/usePageContext.ts","../src/composables/useWizard.ts","../src/composables/useStats.ts","../src/composables/useConfirmation.ts","../src/composables/useAdminPage.ts"],"sourcesContent":["import { ref, type Ref } from 'vue';\n\nexport interface UseModalOptions<T = unknown> {\n initialOpen?: boolean;\n initialData?: T | null;\n onOpen?: (data?: T) => void;\n onClose?: () => void;\n}\n\nexport interface UseModalReturn<T = unknown> {\n /** Whether the modal is open */\n isOpen: Ref<boolean>;\n /** Data context passed when opening the modal */\n data: Ref<T | null>;\n /** Open the modal, optionally with data context */\n open: (context?: T) => void;\n /** Close the modal and clear data */\n close: () => void;\n /** Toggle modal open/closed state */\n toggle: () => void;\n /** Update the data without changing open state */\n setData: (newData: T | null) => void;\n}\n\n/**\n * Modal state management composable with data context support.\n *\n * @example\n * ```typescript\n * // Type-safe modal for editing contacts\n * const contactModal = useModal<Contact>();\n *\n * // Open with data\n * contactModal.open(selectedContact);\n *\n * // Access in template\n * <Modal :open=\"contactModal.isOpen.value\">\n * <p>Editing: {{ contactModal.data.value?.name }}</p>\n * </Modal>\n * ```\n *\n * @example\n * ```typescript\n * // With callbacks\n * const modal = useModal({\n * onOpen: (data) => console.log('Opened with:', data),\n * onClose: () => console.log('Closed'),\n * });\n * ```\n */\nexport function useModal<T = unknown>(options: UseModalOptions<T> = {}): UseModalReturn<T> {\n const isOpen = ref(options.initialOpen ?? false);\n const data = ref<T | null>(options.initialData ?? null) as Ref<T | null>;\n\n function open(context?: T) {\n if (context !== undefined) {\n data.value = context;\n }\n isOpen.value = true;\n options.onOpen?.(context);\n }\n\n function close() {\n isOpen.value = false;\n data.value = null;\n options.onClose?.();\n }\n\n function toggle() {\n if (isOpen.value) {\n close();\n } else {\n open();\n }\n }\n\n function setData(newData: T | null) {\n data.value = newData;\n }\n\n return {\n isOpen,\n data,\n open,\n close,\n toggle,\n setData,\n };\n}\n","import { ref, computed, type Ref, type ComputedRef } from 'vue';\n\nexport interface Tab {\n id: string;\n label: string;\n disabled?: boolean;\n}\n\nexport interface UseTabsOptions {\n tabs: Tab[];\n initialTab?: string;\n onChange?: (tabId: string) => void;\n}\n\nexport interface UseTabsReturn {\n activeTab: Ref<string>;\n tabs: Ref<Tab[]>;\n currentTabIndex: ComputedRef<number>;\n isFirstTab: ComputedRef<boolean>;\n isLastTab: ComputedRef<boolean>;\n setActiveTab: (tabId: string) => void;\n nextTab: () => void;\n previousTab: () => void;\n isTabActive: (tabId: string) => boolean;\n isTabDisabled: (tabId: string) => boolean;\n}\n\nexport function useTabs(options: UseTabsOptions): UseTabsReturn {\n const tabs = ref(options.tabs);\n const activeTab = ref(\n options.initialTab || (tabs.value.length > 0 ? tabs.value[0].id : '')\n );\n\n const currentTabIndex = computed(() =>\n tabs.value.findIndex(tab => tab.id === activeTab.value)\n );\n\n const isFirstTab = computed(() => currentTabIndex.value === 0);\n\n const isLastTab = computed(() =>\n currentTabIndex.value === tabs.value.length - 1\n );\n\n function setActiveTab(tabId: string) {\n const tab = tabs.value.find(t => t.id === tabId);\n if (tab && !tab.disabled) {\n activeTab.value = tabId;\n options.onChange?.(tabId);\n }\n }\n\n function nextTab() {\n if (!isLastTab.value) {\n const nextIndex = currentTabIndex.value + 1;\n const nextTab = tabs.value[nextIndex];\n if (nextTab && !nextTab.disabled) {\n setActiveTab(nextTab.id);\n } else if (nextIndex < tabs.value.length - 1) {\n // Skip disabled tab and try next\n activeTab.value = nextTab.id;\n nextTab();\n }\n }\n }\n\n function previousTab() {\n if (!isFirstTab.value) {\n const prevIndex = currentTabIndex.value - 1;\n const prevTab = tabs.value[prevIndex];\n if (prevTab && !prevTab.disabled) {\n setActiveTab(prevTab.id);\n } else if (prevIndex > 0) {\n // Skip disabled tab and try previous\n activeTab.value = prevTab.id;\n previousTab();\n }\n }\n }\n\n function isTabActive(tabId: string): boolean {\n return activeTab.value === tabId;\n }\n\n function isTabDisabled(tabId: string): boolean {\n const tab = tabs.value.find(t => t.id === tabId);\n return tab?.disabled ?? false;\n }\n\n return {\n activeTab,\n tabs,\n currentTabIndex,\n isFirstTab,\n isLastTab,\n setActiveTab,\n nextTab,\n previousTab,\n isTabActive,\n isTabDisabled\n };\n}\n","import { ref, computed, type Ref, type ComputedRef } from 'vue';\n\nexport interface ValidationRule<T = any> {\n validate: (value: T) => boolean | Promise<boolean>;\n message: string;\n}\n\nexport interface FieldConfig {\n rules?: ValidationRule[];\n initialValue?: any;\n}\n\nexport interface UseFormOptions<T extends Record<string, any>> {\n initialValues: T;\n validationRules?: Partial<Record<keyof T, ValidationRule[]>>;\n onSubmit?: (values: T) => void | Promise<void>;\n}\n\nexport interface UseFormReturn<T extends Record<string, any>> {\n values: Ref<T>;\n errors: Ref<Partial<Record<keyof T, string>>>;\n touched: Ref<Partial<Record<keyof T, boolean>>>;\n isSubmitting: Ref<boolean>;\n isValid: ComputedRef<boolean>;\n setFieldValue: (field: keyof T, value: any) => void;\n setFieldError: (field: keyof T, error: string) => void;\n setFieldTouched: (field: keyof T, touched: boolean) => void;\n validateField: (field: keyof T) => Promise<boolean>;\n validateForm: () => Promise<boolean>;\n handleSubmit: (event?: Event) => Promise<void>;\n resetForm: () => void;\n}\n\nexport function useForm<T extends Record<string, any>>(\n options: UseFormOptions<T>\n): UseFormReturn<T> {\n const values = ref<T>({ ...options.initialValues }) as Ref<T>;\n const errors = ref<Partial<Record<keyof T, string>>>({});\n const touched = ref<Partial<Record<keyof T, boolean>>>({});\n const isSubmitting = ref(false);\n\n const isValid = computed(() => Object.keys(errors.value).length === 0);\n\n function setFieldValue(field: keyof T, value: any) {\n values.value[field] = value;\n }\n\n function setFieldError(field: keyof T, error: string) {\n errors.value[field] = error;\n }\n\n function setFieldTouched(field: keyof T, isTouched: boolean) {\n touched.value[field] = isTouched;\n }\n\n async function validateField(field: keyof T): Promise<boolean> {\n const rules = options.validationRules?.[field];\n if (!rules || rules.length === 0) return true;\n\n const value = values.value[field];\n\n for (const rule of rules) {\n const isValid = await rule.validate(value);\n if (!isValid) {\n setFieldError(field, rule.message);\n return false;\n }\n }\n\n // Clear error if validation passed\n delete errors.value[field];\n return true;\n }\n\n async function validateForm(): Promise<boolean> {\n const fields = Object.keys(values.value) as Array<keyof T>;\n const validationResults = await Promise.all(\n fields.map(field => validateField(field))\n );\n\n return validationResults.every(result => result);\n }\n\n async function handleSubmit(event?: Event) {\n if (event) {\n event.preventDefault();\n }\n\n if (isSubmitting.value) return;\n\n // Mark all fields as touched\n for (const field of Object.keys(values.value) as Array<keyof T>) {\n setFieldTouched(field, true);\n }\n\n // Validate form\n const isFormValid = await validateForm();\n if (!isFormValid) return;\n\n // Submit form\n isSubmitting.value = true;\n try {\n await options.onSubmit?.(values.value);\n } finally {\n isSubmitting.value = false;\n }\n }\n\n function resetForm() {\n values.value = { ...options.initialValues };\n errors.value = {};\n touched.value = {};\n isSubmitting.value = false;\n }\n\n return {\n values,\n errors,\n touched,\n isSubmitting,\n isValid,\n setFieldValue,\n setFieldError,\n setFieldTouched,\n validateField,\n validateForm,\n handleSubmit,\n resetForm\n };\n}\n","import type { ValidationRule } from './useForm';\n\n// Common validation rules\nexport const required = (message = 'This field is required'): ValidationRule => ({\n validate: (value: any) => {\n if (value === null || value === undefined) return false;\n if (typeof value === 'string') return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n },\n message\n});\n\nexport const minLength = (min: number, message?: string): ValidationRule => ({\n validate: (value: string) => {\n if (!value) return true; // Let required handle empty values\n return value.length >= min;\n },\n message: message || `Must be at least ${min} characters`\n});\n\nexport const maxLength = (max: number, message?: string): ValidationRule => ({\n validate: (value: string) => {\n if (!value) return true;\n return value.length <= max;\n },\n message: message || `Must be at most ${max} characters`\n});\n\nexport const email = (message = 'Invalid email address'): ValidationRule => ({\n validate: (value: string) => {\n if (!value) return true;\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return emailRegex.test(value);\n },\n message\n});\n\nexport const pattern = (regex: RegExp, message = 'Invalid format'): ValidationRule => ({\n validate: (value: string) => {\n if (!value) return true;\n return regex.test(value);\n },\n message\n});\n\nexport const min = (minValue: number, message?: string): ValidationRule => ({\n validate: (value: number) => {\n if (value === null || value === undefined) return true;\n return value >= minValue;\n },\n message: message || `Must be at least ${minValue}`\n});\n\nexport const max = (maxValue: number, message?: string): ValidationRule => ({\n validate: (value: number) => {\n if (value === null || value === undefined) return true;\n return value <= maxValue;\n },\n message: message || `Must be at most ${maxValue}`\n});\n\nexport const custom = (\n validator: (value: any) => boolean | Promise<boolean>,\n message: string\n): ValidationRule => ({\n validate: validator,\n message\n});\n\n// Composable for form validation\nexport function useFormValidation() {\n return {\n required,\n minLength,\n maxLength,\n email,\n pattern,\n min,\n max,\n custom\n };\n}\n","/**\n * JsonSchemaForm Composable\n *\n * Wraps JsonSchemaForm component with additional features:\n * - isDirty tracking (has unsaved changes)\n * - confirmOnLeave (warn before navigation with unsaved changes)\n * - Form ref management\n */\n\nimport { ref, computed, watch, onMounted, onUnmounted, type Ref, type ComputedRef } from 'vue';\n\n/**\n * Options for useJsonForm\n */\nexport interface UseJsonFormOptions<T extends Record<string, unknown>> {\n\t/** Initial form values */\n\tinitialValues?: T;\n\t/** Warn before leaving with unsaved changes */\n\tconfirmOnLeave?: boolean;\n\t/** Message to show in browser confirm dialog */\n\tconfirmMessage?: string;\n\t/** Auto-save configuration */\n\tautoSave?: {\n\t\tenabled: boolean;\n\t\tdebounceMs?: number;\n\t\tonAutoSave: (values: T) => Promise<void>;\n\t};\n\t/** Callback when values change */\n\tonChange?: (values: T) => void;\n}\n\n/**\n * Return type for useJsonForm\n */\nexport interface UseJsonFormReturn<T extends Record<string, unknown>> {\n\t/** Ref to attach to JsonSchemaForm component */\n\tformRef: Ref<JsonSchemaFormInstance | null>;\n\t/** Current form values (v-model) */\n\tvalues: Ref<T>;\n\t/** Original values (for dirty checking) */\n\toriginalValues: Ref<T>;\n\t/** Whether form has unsaved changes */\n\tisDirty: ComputedRef<boolean>;\n\t/** Trigger form validation */\n\tvalidate: () => Array<{ field: string; message: string }>;\n\t/** Reset form to original values */\n\treset: () => void;\n\t/** Update both current and original values (after save) */\n\tsetValues: (newValues: Partial<T>) => void;\n\t/** Mark form as saved (updates original values to current) */\n\tmarkAsSaved: () => void;\n}\n\n/**\n * JsonSchemaForm component instance interface\n */\ninterface JsonSchemaFormInstance {\n\tvalidate: () => Array<{ field: string; message: string }>;\n\treset: () => void;\n\tformData: Ref<Record<string, unknown>>;\n\terrors: Ref<Record<string, string>>;\n\ttouched: Ref<Record<string, boolean>>;\n}\n\n/**\n * Deep equality check for objects\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n\tif (a === b) return true;\n\tif (a == null || b == null) return false;\n\tif (typeof a !== 'object' || typeof b !== 'object') return false;\n\n\tconst keysA = Object.keys(a as object);\n\tconst keysB = Object.keys(b as object);\n\n\tif (keysA.length !== keysB.length) return false;\n\n\tfor (const key of keysA) {\n\t\tif (!keysB.includes(key)) return false;\n\t\tif (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Deep clone an object\n */\nfunction deepClone<T>(obj: T): T {\n\tif (obj === null || typeof obj !== 'object') return obj;\n\tif (Array.isArray(obj)) return obj.map(deepClone) as unknown as T;\n\tconst clone = {} as T;\n\tfor (const key in obj) {\n\t\tif (Object.prototype.hasOwnProperty.call(obj, key)) {\n\t\t\tclone[key] = deepClone(obj[key]);\n\t\t}\n\t}\n\treturn clone;\n}\n\n/**\n * Debounce function\n */\nfunction debounce<T extends (...args: unknown[]) => unknown>(fn: T, delay: number): T {\n\tlet timeoutId: ReturnType<typeof setTimeout> | null = null;\n\treturn ((...args: unknown[]) => {\n\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\ttimeoutId = setTimeout(() => fn(...args), delay);\n\t}) as T;\n}\n\n/**\n * useJsonForm composable\n *\n * @example\n * ```vue\n * <script setup>\n * import { JsonSchemaForm } from '@htlkg/components/forms';\n * import { useJsonForm } from '@htlkg/components/composables';\n *\n * const { formRef, values, isDirty, validate, reset } = useJsonForm({\n * initialValues: props.brand,\n * confirmOnLeave: true,\n * });\n *\n * const schema = {\n * type: 'object',\n * properties: {\n * name: { type: 'string', title: 'Name', minLength: 1 },\n * },\n * required: ['name'],\n * };\n *\n * async function handleSubmit(data) {\n * await saveBrand(data);\n * // Update original values so isDirty becomes false\n * setValues(data);\n * }\n * </script>\n *\n * <template>\n * <div v-if=\"isDirty\" class=\"warning\">You have unsaved changes</div>\n * <JsonSchemaForm\n * ref=\"formRef\"\n * v-model=\"values\"\n * :schema=\"schema\"\n * @submit=\"handleSubmit\"\n * />\n * </template>\n * ```\n */\nexport function useJsonForm<T extends Record<string, unknown>>(\n\toptions: UseJsonFormOptions<T> = {}\n): UseJsonFormReturn<T> {\n\tconst {\n\t\tinitialValues = {} as T,\n\t\tconfirmOnLeave = false,\n\t\tconfirmMessage = 'You have unsaved changes. Are you sure you want to leave?',\n\t\tautoSave,\n\t\tonChange,\n\t} = options;\n\n\t// Form ref\n\tconst formRef = ref<JsonSchemaFormInstance | null>(null);\n\n\t// Current values\n\tconst values = ref<T>(deepClone(initialValues)) as Ref<T>;\n\n\t// Original values (for dirty checking)\n\tconst originalValues = ref<T>(deepClone(initialValues)) as Ref<T>;\n\n\t// Computed dirty state\n\tconst isDirty = computed(() => !deepEqual(values.value, originalValues.value));\n\n\t// Validate form\n\tfunction validate(): Array<{ field: string; message: string }> {\n\t\tif (formRef.value?.validate) {\n\t\t\treturn formRef.value.validate();\n\t\t}\n\t\treturn [];\n\t}\n\n\t// Reset to original values\n\tfunction reset(): void {\n\t\tvalues.value = deepClone(originalValues.value);\n\t\tif (formRef.value?.reset) {\n\t\t\tformRef.value.reset();\n\t\t}\n\t}\n\n\t// Update both current and original values\n\tfunction setValues(newValues: Partial<T>): void {\n\t\tconst merged = { ...values.value, ...newValues } as T;\n\t\tvalues.value = merged;\n\t\toriginalValues.value = deepClone(merged);\n\t}\n\n\t// Mark current state as saved\n\tfunction markAsSaved(): void {\n\t\toriginalValues.value = deepClone(values.value);\n\t}\n\n\t// Handle beforeunload for confirmOnLeave\n\tfunction handleBeforeUnload(e: BeforeUnloadEvent): string | undefined {\n\t\tif (isDirty.value) {\n\t\t\te.preventDefault();\n\t\t\te.returnValue = confirmMessage;\n\t\t\treturn confirmMessage;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Setup auto-save\n\tconst debouncedAutoSave = autoSave?.enabled\n\t\t? debounce(async (newValues: T) => {\n\t\t\t\ttry {\n\t\t\t\t\tawait autoSave.onAutoSave(newValues);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error('Auto-save failed:', error);\n\t\t\t\t}\n\t\t\t}, autoSave.debounceMs ?? 2000)\n\t\t: null;\n\n\t// Watch for value changes\n\twatch(\n\t\tvalues,\n\t\t(newValues) => {\n\t\t\tonChange?.(newValues);\n\t\t\tif (debouncedAutoSave && isDirty.value) {\n\t\t\t\tdebouncedAutoSave(newValues);\n\t\t\t}\n\t\t},\n\t\t{ deep: true }\n\t);\n\n\t// Setup/cleanup beforeunload listener\n\tonMounted(() => {\n\t\tif (confirmOnLeave) {\n\t\t\twindow.addEventListener('beforeunload', handleBeforeUnload);\n\t\t}\n\t});\n\n\tonUnmounted(() => {\n\t\tif (confirmOnLeave) {\n\t\t\twindow.removeEventListener('beforeunload', handleBeforeUnload);\n\t\t}\n\t});\n\n\treturn {\n\t\tformRef,\n\t\tvalues,\n\t\toriginalValues,\n\t\tisDirty,\n\t\tvalidate,\n\t\treset,\n\t\tsetValues,\n\t\tmarkAsSaved,\n\t};\n}\n","import { ref, type Ref } from 'vue';\n\nexport interface Notification {\n id: string;\n message: string;\n type: 'info' | 'success' | 'warning' | 'error';\n duration?: number;\n}\n\nexport interface UseNotificationsReturn {\n notifications: Ref<Notification[]>;\n addNotification: (notification: Omit<Notification, 'id'>) => string;\n removeNotification: (id: string) => void;\n clearAll: () => void;\n info: (message: string, duration?: number) => string;\n success: (message: string, duration?: number) => string;\n warning: (message: string, duration?: number) => string;\n error: (message: string, duration?: number) => string;\n}\n\n// Global notifications state\nconst notifications = ref<Notification[]>([]);\n\nexport function useNotifications(): UseNotificationsReturn {\n function addNotification(notification: Omit<Notification, 'id'>): string {\n const id = `notification-${Date.now()}-${Math.random()}`;\n const newNotification: Notification = {\n ...notification,\n id\n };\n\n notifications.value.push(newNotification);\n\n // Auto-remove after duration\n if (notification.duration) {\n setTimeout(() => {\n removeNotification(id);\n }, notification.duration);\n }\n\n return id;\n }\n\n function removeNotification(id: string) {\n const index = notifications.value.findIndex(n => n.id === id);\n if (index !== -1) {\n notifications.value.splice(index, 1);\n }\n }\n\n function clearAll() {\n notifications.value = [];\n }\n\n function info(message: string, duration = 3000): string {\n return addNotification({ message, type: 'info', duration });\n }\n\n function success(message: string, duration = 3000): string {\n return addNotification({ message, type: 'success', duration });\n }\n\n function warning(message: string, duration = 3000): string {\n return addNotification({ message, type: 'warning', duration });\n }\n\n function error(message: string, duration = 5000): string {\n return addNotification({ message, type: 'error', duration });\n }\n\n return {\n notifications,\n addNotification,\n removeNotification,\n clearAll,\n info,\n success,\n warning,\n error\n };\n}\n","/**\n * Page Context Composable\n *\n * Provides access to common page context data (user, brand, routes)\n * within Vue components, without prop drilling.\n */\n\nimport { computed, inject, type InjectionKey, type ComputedRef } from \"vue\";\nimport { useStore } from \"@nanostores/vue\";\nimport { atom } from \"nanostores\";\nimport { routes, type Routes } from \"@htlkg/core\";\n\n/**\n * User information from authentication\n */\nexport interface PageUser {\n\tusername: string;\n\temail?: string;\n\tisAdmin: boolean;\n\tisSuperAdmin: boolean;\n\tbrandIds?: number[];\n\taccountIds?: number[];\n\troles?: string[];\n}\n\n/**\n * Brand context for brand-scoped pages\n */\nexport interface PageBrand {\n\tid: number;\n\tname: string;\n\tlogo?: string;\n}\n\n/**\n * Full page context\n */\nexport interface PageContext {\n\tuser: PageUser | null;\n\tbrand?: PageBrand;\n\tbrandId?: number;\n\tisAdmin: boolean;\n\tisSuperAdmin: boolean;\n\troutes: Routes;\n}\n\n/**\n * Injection key for providing page context\n */\nexport const PAGE_CONTEXT_KEY: InjectionKey<PageContext> = Symbol(\"pageContext\");\n\n/**\n * Nanostore for user state (set by AdminLayout)\n */\nexport const $user = atom<PageUser | null>(null);\n\n/**\n * Nanostore for current brand (set by BrandLayout)\n */\nexport const $currentBrand = atom<PageBrand | null>(null);\n\n/**\n * Set user in the store (called from layout)\n */\nexport function setUser(user: PageUser | null): void {\n\t$user.set(user);\n}\n\n/**\n * Set current brand in the store (called from layout)\n */\nexport function setCurrentBrand(brand: PageBrand | null): void {\n\t$currentBrand.set(brand);\n}\n\n/**\n * Get page context within a Vue component\n *\n * This composable provides access to:\n * - Current user information\n * - Current brand (if in brand context)\n * - Admin/superadmin status\n * - Type-safe routes\n *\n * @example\n * ```vue\n * <script setup>\n * import { usePageContext } from '@htlkg/components';\n *\n * const { user, isAdmin, routes } = usePageContext();\n *\n * function goToAccounts() {\n * window.location.href = routes.admin.accounts();\n * }\n * </script>\n * ```\n *\n * @example With provider pattern\n * ```vue\n * // In parent component\n * import { provide, PAGE_CONTEXT_KEY } from '@htlkg/components';\n * provide(PAGE_CONTEXT_KEY, { user, brand, isAdmin: true, routes });\n *\n * // In child component\n * const context = usePageContext();\n * ```\n */\nexport function usePageContext(): ComputedRef<PageContext> {\n\t// Try injection first (from provider)\n\tconst injected = inject(PAGE_CONTEXT_KEY, null);\n\tif (injected) {\n\t\treturn computed(() => injected);\n\t}\n\n\t// Fallback to nanostores\n\tconst user = useStore($user);\n\tconst brand = useStore($currentBrand);\n\n\treturn computed(() => ({\n\t\tuser: user.value,\n\t\tbrand: brand.value ?? undefined,\n\t\tbrandId: brand.value?.id,\n\t\tisAdmin: user.value?.isAdmin ?? false,\n\t\tisSuperAdmin: user.value?.isSuperAdmin ?? false,\n\t\troutes,\n\t}));\n}\n\n/**\n * Check if user has access to a specific brand\n */\nexport function useHasAccessToBrand(brandId: number): ComputedRef<boolean> {\n\tconst context = usePageContext();\n\n\treturn computed(() => {\n\t\tconst user = context.value.user;\n\t\tif (!user) return false;\n\t\tif (user.isAdmin || user.isSuperAdmin) return true;\n\t\treturn user.brandIds?.includes(brandId) ?? false;\n\t});\n}\n\n/**\n * Check if user has access to a specific account\n */\nexport function useHasAccessToAccount(accountId: number): ComputedRef<boolean> {\n\tconst context = usePageContext();\n\n\treturn computed(() => {\n\t\tconst user = context.value.user;\n\t\tif (!user) return false;\n\t\tif (user.isAdmin || user.isSuperAdmin) return true;\n\t\treturn user.accountIds?.includes(accountId) ?? false;\n\t});\n}\n\n/**\n * Get user roles\n */\nexport function useUserRoles(): ComputedRef<string[]> {\n\tconst context = usePageContext();\n\treturn computed(() => context.value.user?.roles ?? []);\n}\n\n/**\n * Check if user has a specific role\n */\nexport function useHasRole(role: string): ComputedRef<boolean> {\n\tconst roles = useUserRoles();\n\treturn computed(() => roles.value.includes(role));\n}\n","/**\n * Wizard Composable\n *\n * Manages multi-step form/wizard state with validation support.\n * Integrates with Stepper component and JsonSchemaForm.\n */\n\nimport { ref, computed, type Ref, type ComputedRef, type Component } from 'vue';\n\n/**\n * JSON Schema type for form validation\n */\nexport interface JsonSchema {\n\ttype: string;\n\tproperties?: Record<string, any>;\n\trequired?: string[];\n\ttitle?: string;\n\tdescription?: string;\n}\n\n/**\n * Step definition for the wizard\n */\nexport interface WizardStep<T = any> {\n\t/** Unique step identifier */\n\tid: string;\n\t/** Display label for the step */\n\tlabel: string;\n\t/** Optional description */\n\tdescription?: string;\n\t/** JSON Schema for JsonSchemaForm (optional) */\n\tschema?: JsonSchema;\n\t/** Custom validation function (optional) */\n\tvalidate?: (data: T) => boolean | Promise<boolean>;\n\t/** Icon component for the step (optional) */\n\ticon?: Component;\n\t/** Whether step can be skipped */\n\toptional?: boolean;\n}\n\n/**\n * Step status for Stepper component\n */\nexport type StepStatus = 'complete' | 'current' | 'upcoming' | 'error';\n\n/**\n * Stepper-compatible step format\n */\nexport interface StepperStep {\n\tid: string;\n\tlabel: string;\n\tstatus: StepStatus;\n\tvalid?: boolean;\n}\n\n/**\n * Options for useWizard\n */\nexport interface UseWizardOptions<T extends Record<string, any>> {\n\t/** Step definitions */\n\tsteps: WizardStep<T>[];\n\t/** Initial form data */\n\tinitialData?: Partial<T>;\n\t/** Callback when wizard completes */\n\tonComplete?: (data: T) => void | Promise<void>;\n\t/** Callback when step changes */\n\tonStepChange?: (fromIndex: number, toIndex: number, data: T) => void;\n\t/** Whether to validate before advancing */\n\tvalidateOnNext?: boolean;\n\t/** Whether to allow going back without validation */\n\tallowBackWithoutValidation?: boolean;\n}\n\n/**\n * Return type for useWizard\n */\nexport interface UseWizardReturn<T extends Record<string, any>> {\n\t// State\n\tcurrentStepIndex: Ref<number>;\n\tcurrentStep: ComputedRef<WizardStep<T>>;\n\tdata: Ref<Partial<T>>;\n\tisFirstStep: ComputedRef<boolean>;\n\tisLastStep: ComputedRef<boolean>;\n\tisCompleting: Ref<boolean>;\n\tstepErrors: Ref<Record<string, string>>;\n\n\t// For Stepper component\n\tstepperSteps: ComputedRef<StepperStep[]>;\n\n\t// Validation state\n\tstepValidation: Ref<Record<string, boolean>>;\n\tcurrentStepValid: ComputedRef<boolean>;\n\tallStepsValid: ComputedRef<boolean>;\n\n\t// Navigation\n\tgoToNext: () => Promise<boolean>;\n\tgoToPrevious: () => boolean;\n\tgoToStep: (index: number) => Promise<boolean>;\n\tcanGoToStep: (index: number) => boolean;\n\n\t// Data management\n\tupdateData: (newData: Partial<T>) => void;\n\tupdateStepData: (stepId: string, stepData: Partial<T>) => void;\n\tsetStepValid: (stepId: string, valid: boolean) => void;\n\tsetStepError: (stepId: string, error: string | null) => void;\n\tresetStep: (stepId: string) => void;\n\treset: () => void;\n\n\t// Completion\n\tcomplete: () => Promise<boolean>;\n\n\t// Utilities\n\tgetStepIndex: (stepId: string) => number;\n\tgetStep: (stepId: string) => WizardStep<T> | undefined;\n\tprogress: ComputedRef<number>;\n}\n\n/**\n * Creates a wizard composable for managing multi-step forms\n *\n * @example\n * ```typescript\n * const wizard = useWizard({\n * steps: [\n * { id: 'details', label: 'Details', schema: detailsSchema },\n * { id: 'audience', label: 'Audience', schema: audienceSchema },\n * { id: 'review', label: 'Review' },\n * ],\n * initialData: { name: '', audience: [] },\n * onComplete: async (data) => {\n * await createCampaign(data);\n * },\n * });\n *\n * // In template\n * <Stepper :steps=\"wizard.stepperSteps\" v-model:currentStep=\"wizard.currentStepIndex\" />\n * <JsonSchemaForm\n * v-if=\"wizard.currentStep.schema\"\n * :schema=\"wizard.currentStep.schema\"\n * v-model=\"wizard.data\"\n * />\n * <button @click=\"wizard.goToPrevious\" :disabled=\"wizard.isFirstStep\">Back</button>\n * <button @click=\"wizard.goToNext\" v-if=\"!wizard.isLastStep\">Next</button>\n * <button @click=\"wizard.complete\" v-else :disabled=\"wizard.isCompleting\">Complete</button>\n * ```\n */\nexport function useWizard<T extends Record<string, any>>(\n\toptions: UseWizardOptions<T>\n): UseWizardReturn<T> {\n\tconst {\n\t\tsteps,\n\t\tinitialData = {} as Partial<T>,\n\t\tonComplete,\n\t\tonStepChange,\n\t\tvalidateOnNext = true,\n\t\tallowBackWithoutValidation = true,\n\t} = options;\n\n\t// Core state\n\tconst currentStepIndex = ref(0);\n\tconst data = ref<Partial<T>>({ ...initialData }) as Ref<Partial<T>>;\n\tconst isCompleting = ref(false);\n\tconst stepValidation = ref<Record<string, boolean>>({});\n\tconst stepErrors = ref<Record<string, string>>({});\n\n\t// Computed: Current step\n\tconst currentStep = computed(() => steps[currentStepIndex.value]);\n\n\t// Computed: Navigation flags\n\tconst isFirstStep = computed(() => currentStepIndex.value === 0);\n\tconst isLastStep = computed(() => currentStepIndex.value === steps.length - 1);\n\n\t// Computed: Progress percentage\n\tconst progress = computed(() =>\n\t\tMath.round(((currentStepIndex.value + 1) / steps.length) * 100)\n\t);\n\n\t// Computed: Current step validation status\n\tconst currentStepValid = computed(() => {\n\t\tconst step = currentStep.value;\n\t\tif (!step) return false;\n\t\tif (step.optional) return true;\n\t\treturn stepValidation.value[step.id] !== false;\n\t});\n\n\t// Computed: All required steps valid\n\tconst allStepsValid = computed(() => {\n\t\treturn steps.every(step => {\n\t\t\tif (step.optional) return true;\n\t\t\treturn stepValidation.value[step.id] === true;\n\t\t});\n\t});\n\n\t// Computed: Steps formatted for Stepper component\n\tconst stepperSteps = computed<StepperStep[]>(() =>\n\t\tsteps.map((step, index) => {\n\t\t\tlet status: StepStatus;\n\n\t\t\tif (index < currentStepIndex.value) {\n\t\t\t\tstatus = stepValidation.value[step.id] === false ? 'error' : 'complete';\n\t\t\t} else if (index === currentStepIndex.value) {\n\t\t\t\tstatus = 'current';\n\t\t\t} else {\n\t\t\t\tstatus = 'upcoming';\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: step.id,\n\t\t\t\tlabel: step.label,\n\t\t\t\tstatus,\n\t\t\t\tvalid: stepValidation.value[step.id] ?? true,\n\t\t\t};\n\t\t})\n\t);\n\n\t// Utility: Get step index by ID\n\tfunction getStepIndex(stepId: string): number {\n\t\treturn steps.findIndex(s => s.id === stepId);\n\t}\n\n\t// Utility: Get step by ID\n\tfunction getStep(stepId: string): WizardStep<T> | undefined {\n\t\treturn steps.find(s => s.id === stepId);\n\t}\n\n\t// Validate a specific step\n\tasync function validateStep(stepIndex: number): Promise<boolean> {\n\t\tconst step = steps[stepIndex];\n\t\tif (!step) return false;\n\n\t\t// Optional steps are always valid\n\t\tif (step.optional) {\n\t\t\tstepValidation.value[step.id] = true;\n\t\t\treturn true;\n\t\t}\n\n\t\t// Custom validation function\n\t\tif (step.validate) {\n\t\t\ttry {\n\t\t\t\tconst result = await step.validate(data.value as T);\n\t\t\t\tstepValidation.value[step.id] = result;\n\t\t\t\tif (!result) {\n\t\t\t\t\tstepErrors.value[step.id] = 'Validation failed';\n\t\t\t\t} else {\n\t\t\t\t\tdelete stepErrors.value[step.id];\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tstepValidation.value[step.id] = false;\n\t\t\t\tstepErrors.value[step.id] = error instanceof Error ? error.message : 'Validation error';\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// If no custom validation, check if step was marked valid\n\t\t// Default to true if not explicitly set\n\t\tif (stepValidation.value[step.id] === undefined) {\n\t\t\tstepValidation.value[step.id] = true;\n\t\t}\n\n\t\treturn stepValidation.value[step.id] !== false;\n\t}\n\n\t// Check if navigation to step is allowed\n\tfunction canGoToStep(targetIndex: number): boolean {\n\t\tif (targetIndex < 0 || targetIndex >= steps.length) return false;\n\n\t\t// Can always go back if allowed\n\t\tif (targetIndex < currentStepIndex.value && allowBackWithoutValidation) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// For forward navigation, all previous steps must be valid\n\t\tif (targetIndex > currentStepIndex.value && validateOnNext) {\n\t\t\tfor (let i = 0; i < targetIndex; i++) {\n\t\t\t\tconst step = steps[i];\n\t\t\t\tif (!step.optional && stepValidation.value[step.id] === false) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t// Navigation: Go to next step\n\tasync function goToNext(): Promise<boolean> {\n\t\tif (isLastStep.value) return false;\n\n\t\t// Validate current step before advancing\n\t\tif (validateOnNext) {\n\t\t\tconst isValid = await validateStep(currentStepIndex.value);\n\t\t\tif (!isValid) return false;\n\t\t}\n\n\t\tconst fromIndex = currentStepIndex.value;\n\t\tcurrentStepIndex.value++;\n\n\t\tonStepChange?.(fromIndex, currentStepIndex.value, data.value as T);\n\t\treturn true;\n\t}\n\n\t// Navigation: Go to previous step\n\tfunction goToPrevious(): boolean {\n\t\tif (isFirstStep.value) return false;\n\n\t\tconst fromIndex = currentStepIndex.value;\n\t\tcurrentStepIndex.value--;\n\n\t\tonStepChange?.(fromIndex, currentStepIndex.value, data.value as T);\n\t\treturn true;\n\t}\n\n\t// Navigation: Go to specific step\n\tasync function goToStep(targetIndex: number): Promise<boolean> {\n\t\tif (!canGoToStep(targetIndex)) return false;\n\n\t\t// If going forward, validate current step\n\t\tif (targetIndex > currentStepIndex.value && validateOnNext) {\n\t\t\tconst isValid = await validateStep(currentStepIndex.value);\n\t\t\tif (!isValid) return false;\n\t\t}\n\n\t\tconst fromIndex = currentStepIndex.value;\n\t\tcurrentStepIndex.value = targetIndex;\n\n\t\tonStepChange?.(fromIndex, currentStepIndex.value, data.value as T);\n\t\treturn true;\n\t}\n\n\t// Data: Update wizard data\n\tfunction updateData(newData: Partial<T>): void {\n\t\tdata.value = { ...data.value, ...newData };\n\t}\n\n\t// Data: Update data for a specific step\n\tfunction updateStepData(stepId: string, stepData: Partial<T>): void {\n\t\tupdateData(stepData);\n\t\t// Clear error when data is updated\n\t\tdelete stepErrors.value[stepId];\n\t}\n\n\t// Validation: Set step validation status\n\tfunction setStepValid(stepId: string, valid: boolean): void {\n\t\tstepValidation.value[stepId] = valid;\n\t\tif (valid) {\n\t\t\tdelete stepErrors.value[stepId];\n\t\t}\n\t}\n\n\t// Validation: Set step error\n\tfunction setStepError(stepId: string, error: string | null): void {\n\t\tif (error) {\n\t\t\tstepErrors.value[stepId] = error;\n\t\t\tstepValidation.value[stepId] = false;\n\t\t} else {\n\t\t\tdelete stepErrors.value[stepId];\n\t\t}\n\t}\n\n\t// Reset: Reset a specific step\n\tfunction resetStep(stepId: string): void {\n\t\tdelete stepValidation.value[stepId];\n\t\tdelete stepErrors.value[stepId];\n\t}\n\n\t// Reset: Reset entire wizard\n\tfunction reset(): void {\n\t\tcurrentStepIndex.value = 0;\n\t\tdata.value = { ...initialData };\n\t\tstepValidation.value = {};\n\t\tstepErrors.value = {};\n\t\tisCompleting.value = false;\n\t}\n\n\t// Complete: Validate all and trigger completion\n\tasync function complete(): Promise<boolean> {\n\t\tif (isCompleting.value) return false;\n\n\t\tisCompleting.value = true;\n\n\t\ttry {\n\t\t\t// Validate all steps\n\t\t\tfor (let i = 0; i < steps.length; i++) {\n\t\t\t\tconst isValid = await validateStep(i);\n\t\t\t\tif (!isValid && !steps[i].optional) {\n\t\t\t\t\t// Navigate to failed step\n\t\t\t\t\tcurrentStepIndex.value = i;\n\t\t\t\t\tisCompleting.value = false;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Call completion handler\n\t\t\tif (onComplete) {\n\t\t\t\tawait onComplete(data.value as T);\n\t\t\t}\n\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tconsole.error('[useWizard] Completion error:', error);\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tisCompleting.value = false;\n\t\t}\n\t}\n\n\treturn {\n\t\t// State\n\t\tcurrentStepIndex,\n\t\tcurrentStep,\n\t\tdata,\n\t\tisFirstStep,\n\t\tisLastStep,\n\t\tisCompleting,\n\t\tstepErrors,\n\n\t\t// Stepper integration\n\t\tstepperSteps,\n\n\t\t// Validation\n\t\tstepValidation,\n\t\tcurrentStepValid,\n\t\tallStepsValid,\n\n\t\t// Navigation\n\t\tgoToNext,\n\t\tgoToPrevious,\n\t\tgoToStep,\n\t\tcanGoToStep,\n\n\t\t// Data management\n\t\tupdateData,\n\t\tupdateStepData,\n\t\tsetStepValid,\n\t\tsetStepError,\n\t\tresetStep,\n\t\treset,\n\n\t\t// Completion\n\t\tcomplete,\n\n\t\t// Utilities\n\t\tgetStepIndex,\n\t\tgetStep,\n\t\tprogress,\n\t};\n}\n","/**\n * Stats Composable\n *\n * Derives statistics from reactive data with staggered loading animation.\n * Integrates with uiStats component from @hotelinking/ui.\n */\n\nimport { ref, computed, watch, type Ref, type ComputedRef, type Component } from 'vue';\n\n/**\n * Color options matching @hotelinking/ui\n */\nexport type StatColor = 'green' | 'red' | 'yellow' | 'blue' | 'gray' | 'purple' | 'orange' | 'pink';\n\n/**\n * Change type for stat trends\n */\nexport type ChangeType = 'increase' | 'decrease' | 'neutral';\n\n/**\n * Definition for a single stat\n */\nexport interface StatDefinition<T> {\n\t/** Unique identifier */\n\tid: string;\n\t/** Display name */\n\tname: string;\n\t/** Icon component (Heroicons) */\n\ticon: Component;\n\t/** Color theme */\n\tcolor?: StatColor;\n\t/** Function to compute stat value from data */\n\tcompute: (items: T[]) => string | number;\n\t/** Optional: Compute change percentage/value */\n\tcomputeChange?: (items: T[], previousItems?: T[]) => string;\n\t/** Optional: Compute change type (increase/decrease/neutral) */\n\tcomputeChangeType?: (items: T[], previousItems?: T[]) => ChangeType;\n\t/** Show footer with action text */\n\tshowFooter?: boolean;\n\t/** Action text for footer */\n\tactionText?: string;\n}\n\n/**\n * Stat item formatted for uiStats component\n */\nexport interface StatItem {\n\tid: string;\n\tname: string;\n\tstat: string | number;\n\ticon: Component;\n\tcolor: StatColor;\n\tchange?: string;\n\tchangeType?: ChangeType;\n\tshowFooter?: boolean;\n\tactionText?: string;\n}\n\n/**\n * Options for useStats\n */\nexport interface UseStatsOptions<T> {\n\t/** Reactive data source */\n\tdata: Ref<T[]> | ComputedRef<T[]>;\n\t/** Loading state of the data source */\n\tloading: Ref<boolean> | ComputedRef<boolean>;\n\t/** Stat definitions */\n\tdefinitions: StatDefinition<T>[];\n\t/** Delay between each stat reveal (ms) */\n\tstaggerDelay?: number;\n\t/** Initial delay before first stat reveals (ms) */\n\tinitialDelay?: number;\n\t/** Previous data for computing changes (optional) */\n\tpreviousData?: Ref<T[]> | ComputedRef<T[]>;\n}\n\n/**\n * Return type for useStats\n */\nexport interface UseStatsReturn<T> {\n\t/** Computed stats array for uiStats */\n\tstats: ComputedRef<StatItem[]>;\n\t/** Individual loading states for each stat */\n\tloadingStates: Ref<boolean[]>;\n\t/** Combined stats with loading property */\n\tstatsWithLoading: ComputedRef<Array<StatItem & { loading: boolean }>>;\n\t/** Whether all stats are loaded */\n\tallLoaded: ComputedRef<boolean>;\n\t/** Force refresh loading animation */\n\trefreshAnimation: () => void;\n\t/** Get a single stat by ID */\n\tgetStat: (id: string) => StatItem | undefined;\n}\n\n/**\n * Creates a stats composable for deriving statistics from reactive data\n *\n * @example\n * ```typescript\n * import { CheckCircleIcon, ClockIcon, XCircleIcon } from '@heroicons/vue/24/outline';\n *\n * const { statsWithLoading } = useStats({\n * data: campaigns,\n * loading: campaignsLoading,\n * definitions: [\n * {\n * id: 'active',\n * name: 'Active Campaigns',\n * icon: CheckCircleIcon,\n * color: 'green',\n * compute: (items) => items.filter(c => c.status === 'active').length,\n * },\n * {\n * id: 'pending',\n * name: 'Pending',\n * icon: ClockIcon,\n * color: 'yellow',\n * compute: (items) => items.filter(c => c.status === 'pending').length,\n * },\n * {\n * id: 'total-sent',\n * name: 'Total Sent',\n * icon: PaperAirplaneIcon,\n * color: 'blue',\n * compute: (items) => items.reduce((sum, c) => sum + c.sentCount, 0).toLocaleString(),\n * computeChange: (items) => '+12%',\n * computeChangeType: () => 'increase',\n * },\n * ],\n * staggerDelay: 100,\n * });\n *\n * // In template\n * <div class=\"grid grid-cols-4 gap-4\">\n * <uiStats\n * v-for=\"stat in statsWithLoading\"\n * :key=\"stat.id\"\n * :item=\"stat\"\n * :loading=\"stat.loading\"\n * @statClick=\"handleStatClick\"\n * />\n * </div>\n * ```\n */\nexport function useStats<T>(options: UseStatsOptions<T>): UseStatsReturn<T> {\n\tconst {\n\t\tdata,\n\t\tloading,\n\t\tdefinitions,\n\t\tstaggerDelay = 100,\n\t\tinitialDelay = 0,\n\t\tpreviousData,\n\t} = options;\n\n\t// Individual loading states for staggered animation\n\tconst loadingStates = ref<boolean[]>(definitions.map(() => true));\n\n\t// Timers for cleanup\n\tlet timers: ReturnType<typeof setTimeout>[] = [];\n\n\t// Clear all pending timers\n\tfunction clearTimers(): void {\n\t\ttimers.forEach(timer => clearTimeout(timer));\n\t\ttimers = [];\n\t}\n\n\t// Trigger staggered loading animation\n\tfunction triggerStaggeredReveal(): void {\n\t\tclearTimers();\n\n\t\t// Reset all to loading\n\t\tloadingStates.value = definitions.map(() => true);\n\n\t\t// Staggered reveal\n\t\tdefinitions.forEach((_, index) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tloadingStates.value[index] = false;\n\t\t\t}, initialDelay + staggerDelay * (index + 1));\n\t\t\ttimers.push(timer);\n\t\t});\n\t}\n\n\t// Watch for loading state changes\n\twatch(\n\t\t() => loading.value,\n\t\t(isLoading, wasLoading) => {\n\t\t\tif (wasLoading && !isLoading) {\n\t\t\t\t// Data just finished loading, trigger reveal\n\t\t\t\ttriggerStaggeredReveal();\n\t\t\t} else if (isLoading && !wasLoading) {\n\t\t\t\t// Started loading, reset states\n\t\t\t\tclearTimers();\n\t\t\t\tloadingStates.value = definitions.map(() => true);\n\t\t\t}\n\t\t},\n\t\t{ immediate: true }\n\t);\n\n\t// If data is already loaded on mount, trigger reveal\n\tif (!loading.value) {\n\t\ttriggerStaggeredReveal();\n\t}\n\n\t// Computed: Stats derived from data\n\tconst stats = computed<StatItem[]>(() => {\n\t\tconst items = data.value;\n\t\tconst prevItems = previousData?.value;\n\n\t\treturn definitions.map(def => {\n\t\t\tconst stat: StatItem = {\n\t\t\t\tid: def.id,\n\t\t\t\tname: def.name,\n\t\t\t\tstat: def.compute(items),\n\t\t\t\ticon: def.icon,\n\t\t\t\tcolor: def.color ?? 'gray',\n\t\t\t};\n\n\t\t\t// Add change information if provided\n\t\t\tif (def.computeChange) {\n\t\t\t\tstat.change = def.computeChange(items, prevItems);\n\t\t\t}\n\n\t\t\tif (def.computeChangeType) {\n\t\t\t\tstat.changeType = def.computeChangeType(items, prevItems);\n\t\t\t}\n\n\t\t\t// Footer options\n\t\t\tif (def.showFooter !== undefined) {\n\t\t\t\tstat.showFooter = def.showFooter;\n\t\t\t}\n\n\t\t\tif (def.actionText) {\n\t\t\t\tstat.actionText = def.actionText;\n\t\t\t}\n\n\t\t\treturn stat;\n\t\t});\n\t});\n\n\t// Computed: Stats with loading property included\n\tconst statsWithLoading = computed(() =>\n\t\tstats.value.map((stat, index) => ({\n\t\t\t...stat,\n\t\t\tloading: loading.value || loadingStates.value[index],\n\t\t}))\n\t);\n\n\t// Computed: All stats loaded\n\tconst allLoaded = computed(() =>\n\t\t!loading.value && loadingStates.value.every(s => !s)\n\t);\n\n\t// Get a single stat by ID\n\tfunction getStat(id: string): StatItem | undefined {\n\t\treturn stats.value.find(s => s.id === id);\n\t}\n\n\t// Manually refresh animation\n\tfunction refreshAnimation(): void {\n\t\ttriggerStaggeredReveal();\n\t}\n\n\treturn {\n\t\tstats,\n\t\tloadingStates,\n\t\tstatsWithLoading,\n\t\tallLoaded,\n\t\trefreshAnimation,\n\t\tgetStat,\n\t};\n}\n\n/**\n * Helper: Create a simple count stat definition\n */\nexport function countStat<T>(\n\tid: string,\n\tname: string,\n\ticon: Component,\n\tfilter: (item: T) => boolean,\n\toptions?: { color?: StatColor }\n): StatDefinition<T> {\n\treturn {\n\t\tid,\n\t\tname,\n\t\ticon,\n\t\tcolor: options?.color ?? 'gray',\n\t\tcompute: (items) => items.filter(filter).length,\n\t};\n}\n\n/**\n * Helper: Create a sum stat definition\n */\nexport function sumStat<T>(\n\tid: string,\n\tname: string,\n\ticon: Component,\n\tgetValue: (item: T) => number,\n\toptions?: { color?: StatColor; format?: (value: number) => string }\n): StatDefinition<T> {\n\treturn {\n\t\tid,\n\t\tname,\n\t\ticon,\n\t\tcolor: options?.color ?? 'gray',\n\t\tcompute: (items) => {\n\t\t\tconst sum = items.reduce((total, item) => total + getValue(item), 0);\n\t\t\treturn options?.format ? options.format(sum) : sum.toLocaleString();\n\t\t},\n\t};\n}\n\n/**\n * Helper: Create an average stat definition\n */\nexport function averageStat<T>(\n\tid: string,\n\tname: string,\n\ticon: Component,\n\tgetValue: (item: T) => number,\n\toptions?: { color?: StatColor; decimals?: number; suffix?: string }\n): StatDefinition<T> {\n\treturn {\n\t\tid,\n\t\tname,\n\t\ticon,\n\t\tcolor: options?.color ?? 'gray',\n\t\tcompute: (items) => {\n\t\t\tif (items.length === 0) return '0';\n\t\t\tconst sum = items.reduce((total, item) => total + getValue(item), 0);\n\t\t\tconst avg = sum / items.length;\n\t\t\tconst formatted = avg.toFixed(options?.decimals ?? 1);\n\t\t\treturn options?.suffix ? `${formatted}${options.suffix}` : formatted;\n\t\t},\n\t};\n}\n\n/**\n * Helper: Create a percentage stat definition\n */\nexport function percentageStat<T>(\n\tid: string,\n\tname: string,\n\ticon: Component,\n\tfilter: (item: T) => boolean,\n\toptions?: { color?: StatColor; decimals?: number }\n): StatDefinition<T> {\n\treturn {\n\t\tid,\n\t\tname,\n\t\ticon,\n\t\tcolor: options?.color ?? 'gray',\n\t\tcompute: (items) => {\n\t\t\tif (items.length === 0) return '0%';\n\t\t\tconst count = items.filter(filter).length;\n\t\t\tconst percentage = (count / items.length) * 100;\n\t\t\treturn `${percentage.toFixed(options?.decimals ?? 1)}%`;\n\t\t},\n\t};\n}\n","/**\n * Confirmation Composable\n *\n * Manages confirmation dialog state for destructive actions.\n * Works with uiModal from @hotelinking/ui.\n */\n\nimport { ref, computed, type Ref, type ComputedRef, type Component } from 'vue';\n\n/**\n * Confirmation dialog variant\n */\nexport type ConfirmationVariant = 'danger' | 'warning' | 'info' | 'default';\n\n/**\n * Configuration for a confirmation dialog\n */\nexport interface ConfirmationConfig {\n\t/** Dialog title */\n\ttitle: string;\n\t/** Dialog message/description */\n\tmessage: string;\n\t/** Text for confirm button */\n\tconfirmText?: string;\n\t/** Text for cancel button */\n\tcancelText?: string;\n\t/** Visual variant */\n\tvariant?: ConfirmationVariant;\n\t/** Icon component to display */\n\ticon?: Component;\n\t/** Whether action is in progress */\n\tloading?: boolean;\n}\n\n/**\n * Internal state for pending confirmation\n */\ninterface PendingConfirmation {\n\tresolve: (value: boolean) => void;\n\treject: (reason?: unknown) => void;\n}\n\n/**\n * Options for useConfirmation\n */\nexport interface UseConfirmationOptions {\n\t/** Default confirm button text */\n\tdefaultConfirmText?: string;\n\t/** Default cancel button text */\n\tdefaultCancelText?: string;\n\t/** Default variant */\n\tdefaultVariant?: ConfirmationVariant;\n}\n\n/**\n * Return type for useConfirmation\n */\nexport interface UseConfirmationReturn {\n\t/** Whether dialog is open */\n\tisOpen: Ref<boolean>;\n\t/** Current dialog configuration */\n\tconfig: Ref<ConfirmationConfig>;\n\t/** Whether action is in progress */\n\tisLoading: Ref<boolean>;\n\n\t/** Open confirmation dialog and wait for response */\n\tconfirm: (options: ConfirmationConfig) => Promise<boolean>;\n\t/** Handle confirm button click */\n\thandleConfirm: () => void;\n\t/** Handle cancel button click */\n\thandleCancel: () => void;\n\t/** Close dialog without triggering callbacks */\n\tclose: () => void;\n\n\t/** Convenience: Confirm delete action */\n\tconfirmDelete: (itemName: string, options?: Partial<ConfirmationConfig>) => Promise<boolean>;\n\t/** Convenience: Confirm bulk delete action */\n\tconfirmBulkDelete: (count: number, itemType?: string, options?: Partial<ConfirmationConfig>) => Promise<boolean>;\n\t/** Convenience: Confirm destructive action */\n\tconfirmDestructive: (actionName: string, options?: Partial<ConfirmationConfig>) => Promise<boolean>;\n\t/** Convenience: Confirm with custom async action */\n\tconfirmWithAction: <T>(\n\t\toptions: ConfirmationConfig,\n\t\taction: () => Promise<T>\n\t) => Promise<{ confirmed: boolean; result?: T; error?: Error }>;\n\n\t/** Props for uiModal component */\n\tmodalProps: ComputedRef<{\n\t\ttitle: string;\n\t\topen: boolean;\n\t\tmodalName: string;\n\t\tactions: Array<{ name: string; value: string }>;\n\t}>;\n}\n\n/**\n * Creates a confirmation dialog composable\n *\n * @example\n * ```typescript\n * const confirmation = useConfirmation();\n *\n * async function handleDelete(item: Item) {\n * const confirmed = await confirmation.confirmDelete(item.name);\n * if (confirmed) {\n * await deleteItem(item.id);\n * }\n * }\n *\n * // Or with async action\n * async function handleDeleteWithAction(item: Item) {\n * const { confirmed, error } = await confirmation.confirmWithAction(\n * { title: 'Delete Item', message: `Delete \"${item.name}\"?` },\n * () => deleteItem(item.id)\n * );\n * if (confirmed && !error) {\n * showSuccess('Item deleted');\n * }\n * }\n *\n * // In template\n * <Modal\n * v-bind=\"confirmation.modalProps\"\n * @modal-action=\"(e) => e.action === 'confirm' ? confirmation.handleConfirm() : confirmation.handleCancel()\"\n * >\n * <p>{{ confirmation.config.message }}</p>\n * </Modal>\n * ```\n */\nexport function useConfirmation(options: UseConfirmationOptions = {}): UseConfirmationReturn {\n\tconst {\n\t\tdefaultConfirmText = 'Confirm',\n\t\tdefaultCancelText = 'Cancel',\n\t\tdefaultVariant = 'default',\n\t} = options;\n\n\t// State\n\tconst isOpen = ref(false);\n\tconst isLoading = ref(false);\n\tconst config = ref<ConfirmationConfig>({\n\t\ttitle: '',\n\t\tmessage: '',\n\t\tconfirmText: defaultConfirmText,\n\t\tcancelText: defaultCancelText,\n\t\tvariant: defaultVariant,\n\t});\n\n\t// Pending promise resolution\n\tlet pending: PendingConfirmation | null = null;\n\n\t/**\n\t * Open confirmation dialog and wait for user response\n\t */\n\tfunction confirm(confirmConfig: ConfirmationConfig): Promise<boolean> {\n\t\t// Set configuration with defaults\n\t\tconfig.value = {\n\t\t\t...confirmConfig,\n\t\t\tconfirmText: confirmConfig.confirmText ?? defaultConfirmText,\n\t\t\tcancelText: confirmConfig.cancelText ?? defaultCancelText,\n\t\t\tvariant: confirmConfig.variant ?? defaultVariant,\n\t\t};\n\n\t\tisOpen.value = true;\n\t\tisLoading.value = false;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tpending = { resolve, reject };\n\t\t});\n\t}\n\n\t/**\n\t * Handle confirm button click\n\t */\n\tfunction handleConfirm(): void {\n\t\tif (pending) {\n\t\t\tpending.resolve(true);\n\t\t\tpending = null;\n\t\t}\n\t\tisOpen.value = false;\n\t\tisLoading.value = false;\n\t}\n\n\t/**\n\t * Handle cancel button click\n\t */\n\tfunction handleCancel(): void {\n\t\tif (pending) {\n\t\t\tpending.resolve(false);\n\t\t\tpending = null;\n\t\t}\n\t\tisOpen.value = false;\n\t\tisLoading.value = false;\n\t}\n\n\t/**\n\t * Close dialog without resolving\n\t */\n\tfunction close(): void {\n\t\tif (pending) {\n\t\t\tpending.resolve(false);\n\t\t\tpending = null;\n\t\t}\n\t\tisOpen.value = false;\n\t\tisLoading.value = false;\n\t}\n\n\t/**\n\t * Convenience: Confirm delete action\n\t */\n\tfunction confirmDelete(\n\t\titemName: string,\n\t\tconfirmConfig?: Partial<ConfirmationConfig>\n\t): Promise<boolean> {\n\t\treturn confirm({\n\t\t\ttitle: 'Delete Item',\n\t\t\tmessage: `Are you sure you want to delete \"${itemName}\"? This action cannot be undone.`,\n\t\t\tconfirmText: 'Delete',\n\t\t\tcancelText: 'Cancel',\n\t\t\tvariant: 'danger',\n\t\t\t...confirmConfig,\n\t\t});\n\t}\n\n\t/**\n\t * Convenience: Confirm bulk delete action\n\t */\n\tfunction confirmBulkDelete(\n\t\tcount: number,\n\t\titemType: string = 'items',\n\t\tconfirmConfig?: Partial<ConfirmationConfig>\n\t): Promise<boolean> {\n\t\treturn confirm({\n\t\t\ttitle: `Delete ${count} ${itemType}`,\n\t\t\tmessage: `Are you sure you want to delete ${count} ${itemType}? This action cannot be undone.`,\n\t\t\tconfirmText: 'Delete All',\n\t\t\tcancelText: 'Cancel',\n\t\t\tvariant: 'danger',\n\t\t\t...confirmConfig,\n\t\t});\n\t}\n\n\t/**\n\t * Convenience: Confirm destructive action\n\t */\n\tfunction confirmDestructive(\n\t\tactionName: string,\n\t\tconfirmConfig?: Partial<ConfirmationConfig>\n\t): Promise<boolean> {\n\t\treturn confirm({\n\t\t\ttitle: `Confirm ${actionName}`,\n\t\t\tmessage: `Are you sure you want to ${actionName.toLowerCase()}? This action cannot be undone.`,\n\t\t\tconfirmText: actionName,\n\t\t\tcancelText: 'Cancel',\n\t\t\tvariant: 'danger',\n\t\t\t...confirmConfig,\n\t\t});\n\t}\n\n\t/**\n\t * Convenience: Confirm with async action execution\n\t */\n\tasync function confirmWithAction<T>(\n\t\tconfirmConfig: ConfirmationConfig,\n\t\taction: () => Promise<T>\n\t): Promise<{ confirmed: boolean; result?: T; error?: Error }> {\n\t\tconst confirmed = await confirm(confirmConfig);\n\n\t\tif (!confirmed) {\n\t\t\treturn { confirmed: false };\n\t\t}\n\n\t\t// Show loading state\n\t\tisLoading.value = true;\n\n\t\ttry {\n\t\t\tconst result = await action();\n\t\t\tisOpen.value = false;\n\t\t\tisLoading.value = false;\n\t\t\treturn { confirmed: true, result };\n\t\t} catch (error) {\n\t\t\tisLoading.value = false;\n\t\t\treturn {\n\t\t\t\tconfirmed: true,\n\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Props for uiModal component\n\t */\n\tconst modalProps = computed(() => ({\n\t\ttitle: config.value.title,\n\t\topen: isOpen.value,\n\t\tmodalName: 'confirmation-dialog',\n\t\tactions: [\n\t\t\t{\n\t\t\t\tname: config.value.cancelText ?? defaultCancelText,\n\t\t\t\tvalue: 'cancel',\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: config.value.confirmText ?? defaultConfirmText,\n\t\t\t\tvalue: 'confirm',\n\t\t\t},\n\t\t],\n\t}));\n\n\treturn {\n\t\t// State\n\t\tisOpen,\n\t\tconfig,\n\t\tisLoading,\n\n\t\t// Core methods\n\t\tconfirm,\n\t\thandleConfirm,\n\t\thandleCancel,\n\t\tclose,\n\n\t\t// Convenience methods\n\t\tconfirmDelete,\n\t\tconfirmBulkDelete,\n\t\tconfirmDestructive,\n\t\tconfirmWithAction,\n\n\t\t// Modal props\n\t\tmodalProps,\n\t};\n}\n\n/**\n * Global confirmation instance for use across components\n */\nlet globalConfirmation: UseConfirmationReturn | null = null;\n\n/**\n * Get or create global confirmation instance\n *\n * @example\n * ```typescript\n * // In any component\n * const confirmation = useGlobalConfirmation();\n * await confirmation.confirmDelete('Item Name');\n * ```\n */\nexport function useGlobalConfirmation(): UseConfirmationReturn {\n\tif (!globalConfirmation) {\n\t\tglobalConfirmation = useConfirmation();\n\t}\n\treturn globalConfirmation;\n}\n\n/**\n * Reset global confirmation instance (useful for testing)\n */\nexport function resetGlobalConfirmation(): void {\n\tglobalConfirmation = null;\n}\n","/**\n * Admin Page Composable\n *\n * Provides common page-level state and configuration for admin pages.\n * Integrates with AdminWrapper, Breadcrumbs, and page context.\n */\n\nimport { ref, computed, onMounted, type Ref, type ComputedRef, type Component } from 'vue';\nimport { usePageContext, type PageUser, type PageBrand } from './usePageContext';\n\n/**\n * Breadcrumb item\n */\nexport interface BreadcrumbItem {\n\t/** Display name */\n\tname: string;\n\t/** Route path or name */\n\thref?: string;\n\t/** Whether this is the current page */\n\tcurrent?: boolean;\n\t/** Icon component */\n\ticon?: Component;\n}\n\n/**\n * Page action button\n */\nexport interface PageAction {\n\t/** Unique identifier */\n\tid: string;\n\t/** Button text */\n\ttext: string;\n\t/** Button icon */\n\ticon?: Component;\n\t/** Button color variant */\n\tcolor?: 'primary' | 'secondary' | 'danger' | 'warning';\n\t/** Whether button is disabled */\n\tdisabled?: boolean;\n\t/** Whether button shows loading state */\n\tloading?: boolean;\n\t/** Click handler */\n\tonClick?: () => void | Promise<void>;\n}\n\n/**\n * Options for useAdminPage\n */\nexport interface UseAdminPageOptions {\n\t/** Page title */\n\ttitle: string;\n\t/** Page subtitle (optional) */\n\tsubtitle?: string;\n\t/** Page description (optional) */\n\tdescription?: string;\n\t/** Breadcrumb items (without current page) */\n\tbreadcrumbs?: BreadcrumbItem[];\n\t/** Active sidebar item ID */\n\tsidebarActive?: string;\n\t/** Page action buttons */\n\tactions?: PageAction[];\n\t/** Initial loading state */\n\tloading?: boolean;\n\t/** Loading reveal delay (ms) */\n\tloadingDelay?: number;\n}\n\n/**\n * Return type for useAdminPage\n */\nexport interface UseAdminPageReturn {\n\t// Page info\n\ttitle: string;\n\tsubtitle: string | undefined;\n\tdescription: string | undefined;\n\n\t// Breadcrumbs\n\tbreadcrumbs: ComputedRef<BreadcrumbItem[]>;\n\n\t// Loading state (with staggered reveal)\n\tloadingHeader: Ref<boolean>;\n\tloadingContent: Ref<boolean>;\n\tsetLoading: (loading: boolean) => void;\n\n\t// Page context\n\tuser: ComputedRef<PageUser | null>;\n\tbrand: ComputedRef<PageBrand | null>;\n\tisAdmin: ComputedRef<boolean>;\n\tisSuperAdmin: ComputedRef<boolean>;\n\n\t// Actions\n\tactions: Ref<PageAction[]>;\n\tupdateAction: (id: string, updates: Partial<PageAction>) => void;\n\tsetActionLoading: (id: string, loading: boolean) => void;\n\n\t// Wrapper props\n\twrapperProps: ComputedRef<{\n\t\tcurrentPage: string | undefined;\n\t}>;\n\n\t// Header props (for uiViewHeader)\n\theaderProps: ComputedRef<{\n\t\ttitle: string;\n\t\tsubtitle?: string;\n\t\tdescription?: string;\n\t\tpages: BreadcrumbItem[];\n\t\tloading: boolean;\n\t}>;\n}\n\n/**\n * Creates an admin page composable for common page setup\n *\n * @example\n * ```typescript\n * const page = useAdminPage({\n * title: 'Campaigns',\n * subtitle: 'Manage your email campaigns',\n * breadcrumbs: [\n * { name: 'Dashboard', href: '/dashboard' },\n * { name: 'Marketing', href: '/marketing' },\n * ],\n * sidebarActive: 'campaigns',\n * actions: [\n * {\n * id: 'create',\n * text: 'Create Campaign',\n * icon: PlusIcon,\n * color: 'primary',\n * onClick: () => navigateTo('/campaigns/create'),\n * },\n * ],\n * });\n *\n * // In template\n * <AdminWrapper v-bind=\"page.wrapperProps\">\n * <uiViewHeader v-bind=\"page.headerProps\" />\n * <!-- Content -->\n * </AdminWrapper>\n * ```\n */\nexport function useAdminPage(options: UseAdminPageOptions): UseAdminPageReturn {\n\tconst {\n\t\ttitle,\n\t\tsubtitle,\n\t\tdescription,\n\t\tbreadcrumbs: initialBreadcrumbs = [],\n\t\tsidebarActive,\n\t\tactions: initialActions = [],\n\t\tloading: initialLoading = true,\n\t\tloadingDelay = 200,\n\t} = options;\n\n\t// Get page context\n\tconst context = usePageContext();\n\n\t// Loading states\n\tconst loadingHeader = ref(initialLoading);\n\tconst loadingContent = ref(initialLoading);\n\n\t// Actions state\n\tconst actions = ref<PageAction[]>([...initialActions]);\n\n\t// Staggered loading reveal on mount\n\tonMounted(() => {\n\t\tif (initialLoading) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tloadingHeader.value = false;\n\t\t\t}, loadingDelay);\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tloadingContent.value = false;\n\t\t\t}, loadingDelay * 2);\n\t\t}\n\t});\n\n\t// Computed: User from context\n\tconst user = computed(() => context.value.user);\n\n\t// Computed: Brand from context\n\tconst brand = computed(() => context.value.brand);\n\n\t// Computed: Admin status\n\tconst isAdmin = computed(() => user.value?.isAdmin ?? false);\n\n\t// Computed: Super admin status\n\tconst isSuperAdmin = computed(() => user.value?.isSuperAdmin ?? false);\n\n\t// Computed: Breadcrumbs with current page\n\tconst breadcrumbs = computed<BreadcrumbItem[]>(() => [\n\t\t...initialBreadcrumbs.map(b => ({ ...b, current: false })),\n\t\t{ name: title, current: true },\n\t]);\n\n\t// Computed: Props for AdminWrapper\n\tconst wrapperProps = computed(() => ({\n\t\tcurrentPage: sidebarActive,\n\t}));\n\n\t// Computed: Props for uiViewHeader\n\tconst headerProps = computed(() => ({\n\t\ttitle,\n\t\tsubtitle,\n\t\tdescription,\n\t\tpages: breadcrumbs.value,\n\t\tloading: loadingHeader.value,\n\t}));\n\n\t// Set loading state\n\tfunction setLoading(loading: boolean): void {\n\t\tif (loading) {\n\t\t\tloadingHeader.value = true;\n\t\t\tloadingContent.value = true;\n\t\t} else {\n\t\t\tsetTimeout(() => {\n\t\t\t\tloadingHeader.value = false;\n\t\t\t}, loadingDelay);\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tloadingContent.value = false;\n\t\t\t}, loadingDelay * 2);\n\t\t}\n\t}\n\n\t// Update action by ID\n\tfunction updateAction(id: string, updates: Partial<PageAction>): void {\n\t\tconst index = actions.value.findIndex(a => a.id === id);\n\t\tif (index !== -1) {\n\t\t\tactions.value[index] = { ...actions.value[index], ...updates };\n\t\t}\n\t}\n\n\t// Set action loading state\n\tfunction setActionLoading(id: string, loading: boolean): void {\n\t\tupdateAction(id, { loading });\n\t}\n\n\treturn {\n\t\t// Page info\n\t\ttitle,\n\t\tsubtitle,\n\t\tdescription,\n\n\t\t// Breadcrumbs\n\t\tbreadcrumbs,\n\n\t\t// Loading\n\t\tloadingHeader,\n\t\tloadingContent,\n\t\tsetLoading,\n\n\t\t// Context\n\t\tuser,\n\t\tbrand,\n\t\tisAdmin,\n\t\tisSuperAdmin,\n\n\t\t// Actions\n\t\tactions,\n\t\tupdateAction,\n\t\tsetActionLoading,\n\n\t\t// Props\n\t\twrapperProps,\n\t\theaderProps,\n\t};\n}\n\n/**\n * Helper: Create a list page configuration\n *\n * @example\n * const page = useListPage({\n * title: 'Users',\n * entityName: 'user',\n * breadcrumbs: [{ name: 'Settings', href: '/settings' }],\n * sidebarActive: 'users',\n * onCreateClick: () => navigateTo('/users/create'),\n * });\n */\nexport interface UseListPageOptions extends Omit<UseAdminPageOptions, 'actions'> {\n\t/** Entity name for create button (e.g., 'user' -> 'Create User') */\n\tentityName?: string;\n\t/** Create button click handler */\n\tonCreateClick?: () => void;\n\t/** Create button icon */\n\tcreateIcon?: Component;\n\t/** Hide create button */\n\thideCreate?: boolean;\n}\n\nexport function useListPage(options: UseListPageOptions): UseAdminPageReturn {\n\tconst {\n\t\tentityName,\n\t\tonCreateClick,\n\t\tcreateIcon,\n\t\thideCreate = false,\n\t\t...pageOptions\n\t} = options;\n\n\tconst actions: PageAction[] = [];\n\n\tif (!hideCreate && entityName) {\n\t\tactions.push({\n\t\t\tid: 'create',\n\t\t\ttext: `Create ${entityName.charAt(0).toUpperCase() + entityName.slice(1)}`,\n\t\t\ticon: createIcon,\n\t\t\tcolor: 'primary',\n\t\t\tonClick: onCreateClick,\n\t\t});\n\t}\n\n\treturn useAdminPage({\n\t\t...pageOptions,\n\t\tactions,\n\t});\n}\n\n/**\n * Helper: Create a detail page configuration\n *\n * @example\n * const page = useDetailPage({\n * title: 'User Details',\n * entityName: 'user',\n * breadcrumbs: [\n * { name: 'Settings', href: '/settings' },\n * { name: 'Users', href: '/users' },\n * ],\n * onEditClick: () => navigateTo(`/users/${id}/edit`),\n * onDeleteClick: () => confirmDelete(),\n * });\n */\nexport interface UseDetailPageOptions extends Omit<UseAdminPageOptions, 'actions'> {\n\t/** Entity name for action buttons */\n\tentityName?: string;\n\t/** Edit button click handler */\n\tonEditClick?: () => void;\n\t/** Delete button click handler */\n\tonDeleteClick?: () => void;\n\t/** Edit button icon */\n\teditIcon?: Component;\n\t/** Delete button icon */\n\tdeleteIcon?: Component;\n\t/** Hide edit button */\n\thideEdit?: boolean;\n\t/** Hide delete button */\n\thideDelete?: boolean;\n}\n\nexport function useDetailPage(options: UseDetailPageOptions): UseAdminPageReturn {\n\tconst {\n\t\tentityName,\n\t\tonEditClick,\n\t\tonDeleteClick,\n\t\teditIcon,\n\t\tdeleteIcon,\n\t\thideEdit = false,\n\t\thideDelete = false,\n\t\t...pageOptions\n\t} = options;\n\n\tconst actions: PageAction[] = [];\n\n\tif (!hideEdit && onEditClick) {\n\t\tactions.push({\n\t\t\tid: 'edit',\n\t\t\ttext: 'Edit',\n\t\t\ticon: editIcon,\n\t\t\tcolor: 'secondary',\n\t\t\tonClick: onEditClick,\n\t\t});\n\t}\n\n\tif (!hideDelete && onDeleteClick) {\n\t\tactions.push({\n\t\t\tid: 'delete',\n\t\t\ttext: 'Delete',\n\t\t\ticon: deleteIcon,\n\t\t\tcolor: 'danger',\n\t\t\tonClick: onDeleteClick,\n\t\t});\n\t}\n\n\treturn useAdminPage({\n\t\t...pageOptions,\n\t\tactions,\n\t});\n}\n\n/**\n * Helper: Create a form page configuration\n *\n * @example\n * const page = useFormPage({\n * title: 'Create User',\n * breadcrumbs: [\n * { name: 'Settings', href: '/settings' },\n * { name: 'Users', href: '/users' },\n * ],\n * onSaveClick: () => saveUser(),\n * onCancelClick: () => navigateTo('/users'),\n * });\n */\nexport interface UseFormPageOptions extends Omit<UseAdminPageOptions, 'actions'> {\n\t/** Save button click handler */\n\tonSaveClick?: () => void | Promise<void>;\n\t/** Cancel button click handler */\n\tonCancelClick?: () => void;\n\t/** Save button text */\n\tsaveText?: string;\n\t/** Cancel button text */\n\tcancelText?: string;\n\t/** Save button icon */\n\tsaveIcon?: Component;\n\t/** Cancel button icon */\n\tcancelIcon?: Component;\n\t/** Hide save button */\n\thideSave?: boolean;\n\t/** Hide cancel button */\n\thideCancel?: boolean;\n}\n\nexport function useFormPage(options: UseFormPageOptions): UseAdminPageReturn {\n\tconst {\n\t\tonSaveClick,\n\t\tonCancelClick,\n\t\tsaveText = 'Save',\n\t\tcancelText = 'Cancel',\n\t\tsaveIcon,\n\t\tcancelIcon,\n\t\thideSave = false,\n\t\thideCancel = false,\n\t\t...pageOptions\n\t} = options;\n\n\tconst actions: PageAction[] = [];\n\n\tif (!hideCancel && onCancelClick) {\n\t\tactions.push({\n\t\t\tid: 'cancel',\n\t\t\ttext: cancelText,\n\t\t\ticon: cancelIcon,\n\t\t\tcolor: 'secondary',\n\t\t\tonClick: onCancelClick,\n\t\t});\n\t}\n\n\tif (!hideSave && onSaveClick) {\n\t\tactions.push({\n\t\t\tid: 'save',\n\t\t\ttext: saveText,\n\t\t\ticon: saveIcon,\n\t\t\tcolor: 'primary',\n\t\t\tonClick: onSaveClick,\n\t\t});\n\t}\n\n\treturn useAdminPage({\n\t\t...pageOptions,\n\t\tactions,\n\t});\n}\n"],"names":["nextTab","isValid","min","max"],"mappings":";;;;AAkDO,SAAS,SAAsB,UAA8B,IAAuB;AACzF,QAAM,SAAS,IAAI,QAAQ,eAAe,KAAK;AAC/C,QAAM,OAAO,IAAc,QAAQ,eAAe,IAAI;AAEtD,WAAS,KAAK,SAAa;;AACzB,QAAI,YAAY,QAAW;AACzB,WAAK,QAAQ;AAAA,IACf;AACA,WAAO,QAAQ;AACf,kBAAQ,WAAR,iCAAiB;AAAA,EACnB;AAEA,WAAS,QAAQ;;AACf,WAAO,QAAQ;AACf,SAAK,QAAQ;AACb,kBAAQ,YAAR;AAAA,EACF;AAEA,WAAS,SAAS;AAChB,QAAI,OAAO,OAAO;AAChB,YAAA;AAAA,IACF,OAAO;AACL,WAAA;AAAA,IACF;AAAA,EACF;AAEA,WAAS,QAAQ,SAAmB;AAClC,SAAK,QAAQ;AAAA,EACf;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AC7DO,SAAS,QAAQ,SAAwC;AAC9D,QAAM,OAAO,IAAI,QAAQ,IAAI;AAC7B,QAAM,YAAY;AAAA,IAChB,QAAQ,eAAe,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,CAAC,EAAE,KAAK;AAAA,EAAA;AAGpE,QAAM,kBAAkB;AAAA,IAAS,MAC/B,KAAK,MAAM,UAAU,SAAO,IAAI,OAAO,UAAU,KAAK;AAAA,EAAA;AAGxD,QAAM,aAAa,SAAS,MAAM,gBAAgB,UAAU,CAAC;AAE7D,QAAM,YAAY;AAAA,IAAS,MACzB,gBAAgB,UAAU,KAAK,MAAM,SAAS;AAAA,EAAA;AAGhD,WAAS,aAAa,OAAe;;AACnC,UAAM,MAAM,KAAK,MAAM,KAAK,CAAA,MAAK,EAAE,OAAO,KAAK;AAC/C,QAAI,OAAO,CAAC,IAAI,UAAU;AACxB,gBAAU,QAAQ;AAClB,oBAAQ,aAAR,iCAAmB;AAAA,IACrB;AAAA,EACF;AAEA,WAAS,UAAU;AACjB,QAAI,CAAC,UAAU,OAAO;AACpB,YAAM,YAAY,gBAAgB,QAAQ;AAC1C,YAAMA,WAAU,KAAK,MAAM,SAAS;AACpC,UAAIA,YAAW,CAACA,SAAQ,UAAU;AAChC,qBAAaA,SAAQ,EAAE;AAAA,MACzB,WAAW,YAAY,KAAK,MAAM,SAAS,GAAG;AAE5C,kBAAU,QAAQA,SAAQ;AAC1BA,iBAAAA;AAAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,WAAS,cAAc;AACrB,QAAI,CAAC,WAAW,OAAO;AACrB,YAAM,YAAY,gBAAgB,QAAQ;AAC1C,YAAM,UAAU,KAAK,MAAM,SAAS;AACpC,UAAI,WAAW,CAAC,QAAQ,UAAU;AAChC,qBAAa,QAAQ,EAAE;AAAA,MACzB,WAAW,YAAY,GAAG;AAExB,kBAAU,QAAQ,QAAQ;AAC1B,oBAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,WAAS,YAAY,OAAwB;AAC3C,WAAO,UAAU,UAAU;AAAA,EAC7B;AAEA,WAAS,cAAc,OAAwB;AAC7C,UAAM,MAAM,KAAK,MAAM,KAAK,CAAA,MAAK,EAAE,OAAO,KAAK;AAC/C,YAAO,2BAAK,aAAY;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;ACnEO,SAAS,QACd,SACkB;AAClB,QAAM,SAAS,IAAO,EAAE,GAAG,QAAQ,eAAe;AAClD,QAAM,SAAS,IAAsC,EAAE;AACvD,QAAM,UAAU,IAAuC,EAAE;AACzD,QAAM,eAAe,IAAI,KAAK;AAE9B,QAAM,UAAU,SAAS,MAAM,OAAO,KAAK,OAAO,KAAK,EAAE,WAAW,CAAC;AAErE,WAAS,cAAc,OAAgB,OAAY;AACjD,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAEA,WAAS,cAAc,OAAgB,OAAe;AACpD,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAEA,WAAS,gBAAgB,OAAgB,WAAoB;AAC3D,YAAQ,MAAM,KAAK,IAAI;AAAA,EACzB;AAEA,iBAAe,cAAc,OAAkC;;AAC7D,UAAM,SAAQ,aAAQ,oBAAR,mBAA0B;AACxC,QAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AAEzC,UAAM,QAAQ,OAAO,MAAM,KAAK;AAEhC,eAAW,QAAQ,OAAO;AACxB,YAAMC,WAAU,MAAM,KAAK,SAAS,KAAK;AACzC,UAAI,CAACA,UAAS;AACZ,sBAAc,OAAO,KAAK,OAAO;AACjC,eAAO;AAAA,MACT;AAAA,IACF;AAGA,WAAO,OAAO,MAAM,KAAK;AACzB,WAAO;AAAA,EACT;AAEA,iBAAe,eAAiC;AAC9C,UAAM,SAAS,OAAO,KAAK,OAAO,KAAK;AACvC,UAAM,oBAAoB,MAAM,QAAQ;AAAA,MACtC,OAAO,IAAI,CAAA,UAAS,cAAc,KAAK,CAAC;AAAA,IAAA;AAG1C,WAAO,kBAAkB,MAAM,CAAA,WAAU,MAAM;AAAA,EACjD;AAEA,iBAAe,aAAa,OAAe;;AACzC,QAAI,OAAO;AACT,YAAM,eAAA;AAAA,IACR;AAEA,QAAI,aAAa,MAAO;AAGxB,eAAW,SAAS,OAAO,KAAK,OAAO,KAAK,GAAqB;AAC/D,sBAAgB,OAAO,IAAI;AAAA,IAC7B;AAGA,UAAM,cAAc,MAAM,aAAA;AAC1B,QAAI,CAAC,YAAa;AAGlB,iBAAa,QAAQ;AACrB,QAAI;AACF,cAAM,aAAQ,aAAR,iCAAmB,OAAO;AAAA,IAClC,UAAA;AACE,mBAAa,QAAQ;AAAA,IACvB;AAAA,EACF;AAEA,WAAS,YAAY;AACnB,WAAO,QAAQ,EAAE,GAAG,QAAQ,cAAA;AAC5B,WAAO,QAAQ,CAAA;AACf,YAAQ,QAAQ,CAAA;AAChB,iBAAa,QAAQ;AAAA,EACvB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AC9HO,MAAM,WAAW,CAAC,UAAU,8BAA8C;AAAA,EAC/E,UAAU,CAAC,UAAe;AACxB,QAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,QAAI,OAAO,UAAU,iBAAiB,MAAM,KAAA,EAAO,SAAS;AAC5D,QAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,SAAS;AAChD,WAAO;AAAA,EACT;AAAA,EACA;AACF;AAEO,MAAM,YAAY,CAACC,MAAa,aAAsC;AAAA,EAC3E,UAAU,CAAC,UAAkB;AAC3B,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,MAAM,UAAUA;AAAAA,EACzB;AAAA,EACA,SAAS,WAAW,oBAAoBA,IAAG;AAC7C;AAEO,MAAM,YAAY,CAACC,MAAa,aAAsC;AAAA,EAC3E,UAAU,CAAC,UAAkB;AAC3B,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,MAAM,UAAUA;AAAAA,EACzB;AAAA,EACA,SAAS,WAAW,mBAAmBA,IAAG;AAC5C;AAEO,MAAM,QAAQ,CAAC,UAAU,6BAA6C;AAAA,EAC3E,UAAU,CAAC,UAAkB;AAC3B,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,aAAa;AACnB,WAAO,WAAW,KAAK,KAAK;AAAA,EAC9B;AAAA,EACA;AACF;AAEO,MAAM,UAAU,CAAC,OAAe,UAAU,sBAAsC;AAAA,EACrF,UAAU,CAAC,UAAkB;AAC3B,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,MAAM,KAAK,KAAK;AAAA,EACzB;AAAA,EACA;AACF;AAEO,MAAM,MAAM,CAAC,UAAkB,aAAsC;AAAA,EAC1E,UAAU,CAAC,UAAkB;AAC3B,QAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,WAAO,SAAS;AAAA,EAClB;AAAA,EACA,SAAS,WAAW,oBAAoB,QAAQ;AAClD;AAEO,MAAM,MAAM,CAAC,UAAkB,aAAsC;AAAA,EAC1E,UAAU,CAAC,UAAkB;AAC3B,QAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,WAAO,SAAS;AAAA,EAClB;AAAA,EACA,SAAS,WAAW,mBAAmB,QAAQ;AACjD;AAEO,MAAM,SAAS,CACpB,WACA,aACoB;AAAA,EACpB,UAAU;AAAA,EACV;AACF;AAGO,SAAS,oBAAoB;AAClC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;ACfA,SAAS,UAAU,GAAY,GAAqB;AACnD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,KAAK,QAAQ,KAAK,KAAM,QAAO;AACnC,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;AAE3D,QAAM,QAAQ,OAAO,KAAK,CAAW;AACrC,QAAM,QAAQ,OAAO,KAAK,CAAW;AAErC,MAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAE1C,aAAW,OAAO,OAAO;AACxB,QAAI,CAAC,MAAM,SAAS,GAAG,EAAG,QAAO;AACjC,QAAI,CAAC,UAAW,EAA8B,GAAG,GAAI,EAA8B,GAAG,CAAC,GAAG;AACzF,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;AAKA,SAAS,UAAa,KAAW;AAChC,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,MAAI,MAAM,QAAQ,GAAG,EAAG,QAAO,IAAI,IAAI,SAAS;AAChD,QAAM,QAAQ,CAAA;AACd,aAAW,OAAO,KAAK;AACtB,QAAI,OAAO,UAAU,eAAe,KAAK,KAAK,GAAG,GAAG;AACnD,YAAM,GAAG,IAAI,UAAU,IAAI,GAAG,CAAC;AAAA,IAChC;AAAA,EACD;AACA,SAAO;AACR;AAKA,SAAS,SAAoD,IAAO,OAAkB;AACrF,MAAI,YAAkD;AACtD,UAAQ,IAAI,SAAoB;AAC/B,QAAI,wBAAwB,SAAS;AACrC,gBAAY,WAAW,MAAM,GAAG,GAAG,IAAI,GAAG,KAAK;AAAA,EAChD;AACD;AA0CO,SAAS,YACf,UAAiC,IACV;AACvB,QAAM;AAAA,IACL,gBAAgB,CAAA;AAAA,IAChB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,EAAA,IACG;AAGJ,QAAM,UAAU,IAAmC,IAAI;AAGvD,QAAM,SAAS,IAAO,UAAU,aAAa,CAAC;AAG9C,QAAM,iBAAiB,IAAO,UAAU,aAAa,CAAC;AAGtD,QAAM,UAAU,SAAS,MAAM,CAAC,UAAU,OAAO,OAAO,eAAe,KAAK,CAAC;AAG7E,WAAS,WAAsD;;AAC9D,SAAI,aAAQ,UAAR,mBAAe,UAAU;AAC5B,aAAO,QAAQ,MAAM,SAAA;AAAA,IACtB;AACA,WAAO,CAAA;AAAA,EACR;AAGA,WAAS,QAAc;;AACtB,WAAO,QAAQ,UAAU,eAAe,KAAK;AAC7C,SAAI,aAAQ,UAAR,mBAAe,OAAO;AACzB,cAAQ,MAAM,MAAA;AAAA,IACf;AAAA,EACD;AAGA,WAAS,UAAU,WAA6B;AAC/C,UAAM,SAAS,EAAE,GAAG,OAAO,OAAO,GAAG,UAAA;AACrC,WAAO,QAAQ;AACf,mBAAe,QAAQ,UAAU,MAAM;AAAA,EACxC;AAGA,WAAS,cAAoB;AAC5B,mBAAe,QAAQ,UAAU,OAAO,KAAK;AAAA,EAC9C;AAGA,WAAS,mBAAmB,GAA0C;AACrE,QAAI,QAAQ,OAAO;AAClB,QAAE,eAAA;AACF,QAAE,cAAc;AAChB,aAAO;AAAA,IACR;AACA,WAAO;AAAA,EACR;AAGA,QAAM,qBAAoB,qCAAU,WACjC,SAAS,OAAO,cAAiB;AACjC,QAAI;AACH,YAAM,SAAS,WAAW,SAAS;AAAA,IACpC,SAAS,OAAO;AACf,cAAQ,MAAM,qBAAqB,KAAK;AAAA,IACzC;AAAA,EACD,GAAG,SAAS,cAAc,GAAI,IAC7B;AAGH;AAAA,IACC;AAAA,IACA,CAAC,cAAc;AACd,2CAAW;AACX,UAAI,qBAAqB,QAAQ,OAAO;AACvC,0BAAkB,SAAS;AAAA,MAC5B;AAAA,IACD;AAAA,IACA,EAAE,MAAM,KAAA;AAAA,EAAK;AAId,YAAU,MAAM;AACf,QAAI,gBAAgB;AACnB,aAAO,iBAAiB,gBAAgB,kBAAkB;AAAA,IAC3D;AAAA,EACD,CAAC;AAED,cAAY,MAAM;AACjB,QAAI,gBAAgB;AACnB,aAAO,oBAAoB,gBAAgB,kBAAkB;AAAA,IAC9D;AAAA,EACD,CAAC;AAED,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF;AC/OA,MAAM,gBAAgB,IAAoB,EAAE;AAErC,SAAS,mBAA2C;AACzD,WAAS,gBAAgB,cAAgD;AACvE,UAAM,KAAK,gBAAgB,KAAK,IAAA,CAAK,IAAI,KAAK,QAAQ;AACtD,UAAM,kBAAgC;AAAA,MACpC,GAAG;AAAA,MACH;AAAA,IAAA;AAGF,kBAAc,MAAM,KAAK,eAAe;AAGxC,QAAI,aAAa,UAAU;AACzB,iBAAW,MAAM;AACf,2BAAmB,EAAE;AAAA,MACvB,GAAG,aAAa,QAAQ;AAAA,IAC1B;AAEA,WAAO;AAAA,EACT;AAEA,WAAS,mBAAmB,IAAY;AACtC,UAAM,QAAQ,cAAc,MAAM,UAAU,CAAA,MAAK,EAAE,OAAO,EAAE;AAC5D,QAAI,UAAU,IAAI;AAChB,oBAAc,MAAM,OAAO,OAAO,CAAC;AAAA,IACrC;AAAA,EACF;AAEA,WAAS,WAAW;AAClB,kBAAc,QAAQ,CAAA;AAAA,EACxB;AAEA,WAAS,KAAK,SAAiB,WAAW,KAAc;AACtD,WAAO,gBAAgB,EAAE,SAAS,MAAM,QAAQ,UAAU;AAAA,EAC5D;AAEA,WAAS,QAAQ,SAAiB,WAAW,KAAc;AACzD,WAAO,gBAAgB,EAAE,SAAS,MAAM,WAAW,UAAU;AAAA,EAC/D;AAEA,WAAS,QAAQ,SAAiB,WAAW,KAAc;AACzD,WAAO,gBAAgB,EAAE,SAAS,MAAM,WAAW,UAAU;AAAA,EAC/D;AAEA,WAAS,MAAM,SAAiB,WAAW,KAAc;AACvD,WAAO,gBAAgB,EAAE,SAAS,MAAM,SAAS,UAAU;AAAA,EAC7D;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AC/BO,MAAM,mBAA8C,OAAO,aAAa;AAKxE,MAAM,QAAQ,KAAsB,IAAI;AAKxC,MAAM,gBAAgB,KAAuB,IAAI;AAKjD,SAAS,QAAQ,MAA6B;AACpD,QAAM,IAAI,IAAI;AACf;AAKO,SAAS,gBAAgB,OAA+B;AAC9D,gBAAc,IAAI,KAAK;AACxB;AAkCO,SAAS,iBAA2C;AAE1D,QAAM,WAAW,OAAO,kBAAkB,IAAI;AAC9C,MAAI,UAAU;AACb,WAAO,SAAS,MAAM,QAAQ;AAAA,EAC/B;AAGA,QAAM,OAAO,SAAS,KAAK;AAC3B,QAAM,QAAQ,SAAS,aAAa;AAEpC,SAAO,SAAS,MAAA;;AAAO;AAAA,MACtB,MAAM,KAAK;AAAA,MACX,OAAO,MAAM,SAAS;AAAA,MACtB,UAAS,WAAM,UAAN,mBAAa;AAAA,MACtB,WAAS,UAAK,UAAL,mBAAY,YAAW;AAAA,MAChC,gBAAc,UAAK,UAAL,mBAAY,iBAAgB;AAAA,MAC1C;AAAA,IAAA;AAAA,GACC;AACH;AAKO,SAAS,oBAAoB,SAAuC;AAC1E,QAAM,UAAU,eAAA;AAEhB,SAAO,SAAS,MAAM;;AACrB,UAAM,OAAO,QAAQ,MAAM;AAC3B,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI,KAAK,WAAW,KAAK,aAAc,QAAO;AAC9C,aAAO,UAAK,aAAL,mBAAe,SAAS,aAAY;AAAA,EAC5C,CAAC;AACF;AAKO,SAAS,sBAAsB,WAAyC;AAC9E,QAAM,UAAU,eAAA;AAEhB,SAAO,SAAS,MAAM;;AACrB,UAAM,OAAO,QAAQ,MAAM;AAC3B,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI,KAAK,WAAW,KAAK,aAAc,QAAO;AAC9C,aAAO,UAAK,eAAL,mBAAiB,SAAS,eAAc;AAAA,EAChD,CAAC;AACF;AAKO,SAAS,eAAsC;AACrD,QAAM,UAAU,eAAA;AAChB,SAAO,SAAS,MAAA;;AAAM,0BAAQ,MAAM,SAAd,mBAAoB,UAAS;GAAE;AACtD;AAKO,SAAS,WAAW,MAAoC;AAC9D,QAAM,QAAQ,aAAA;AACd,SAAO,SAAS,MAAM,MAAM,MAAM,SAAS,IAAI,CAAC;AACjD;ACxBO,SAAS,UACf,SACqB;AACrB,QAAM;AAAA,IACL;AAAA,IACA,cAAc,CAAA;AAAA,IACd;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB,6BAA6B;AAAA,EAAA,IAC1B;AAGJ,QAAM,mBAAmB,IAAI,CAAC;AAC9B,QAAM,OAAO,IAAgB,EAAE,GAAG,aAAa;AAC/C,QAAM,eAAe,IAAI,KAAK;AAC9B,QAAM,iBAAiB,IAA6B,EAAE;AACtD,QAAM,aAAa,IAA4B,EAAE;AAGjD,QAAM,cAAc,SAAS,MAAM,MAAM,iBAAiB,KAAK,CAAC;AAGhE,QAAM,cAAc,SAAS,MAAM,iBAAiB,UAAU,CAAC;AAC/D,QAAM,aAAa,SAAS,MAAM,iBAAiB,UAAU,MAAM,SAAS,CAAC;AAG7E,QAAM,WAAW;AAAA,IAAS,MACzB,KAAK,OAAQ,iBAAiB,QAAQ,KAAK,MAAM,SAAU,GAAG;AAAA,EAAA;AAI/D,QAAM,mBAAmB,SAAS,MAAM;AACvC,UAAM,OAAO,YAAY;AACzB,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI,KAAK,SAAU,QAAO;AAC1B,WAAO,eAAe,MAAM,KAAK,EAAE,MAAM;AAAA,EAC1C,CAAC;AAGD,QAAM,gBAAgB,SAAS,MAAM;AACpC,WAAO,MAAM,MAAM,CAAA,SAAQ;AAC1B,UAAI,KAAK,SAAU,QAAO;AAC1B,aAAO,eAAe,MAAM,KAAK,EAAE,MAAM;AAAA,IAC1C,CAAC;AAAA,EACF,CAAC;AAGD,QAAM,eAAe;AAAA,IAAwB,MAC5C,MAAM,IAAI,CAAC,MAAM,UAAU;AAC1B,UAAI;AAEJ,UAAI,QAAQ,iBAAiB,OAAO;AACnC,iBAAS,eAAe,MAAM,KAAK,EAAE,MAAM,QAAQ,UAAU;AAAA,MAC9D,WAAW,UAAU,iBAAiB,OAAO;AAC5C,iBAAS;AAAA,MACV,OAAO;AACN,iBAAS;AAAA,MACV;AAEA,aAAO;AAAA,QACN,IAAI,KAAK;AAAA,QACT,OAAO,KAAK;AAAA,QACZ;AAAA,QACA,OAAO,eAAe,MAAM,KAAK,EAAE,KAAK;AAAA,MAAA;AAAA,IAE1C,CAAC;AAAA,EAAA;AAIF,WAAS,aAAa,QAAwB;AAC7C,WAAO,MAAM,UAAU,CAAA,MAAK,EAAE,OAAO,MAAM;AAAA,EAC5C;AAGA,WAAS,QAAQ,QAA2C;AAC3D,WAAO,MAAM,KAAK,CAAA,MAAK,EAAE,OAAO,MAAM;AAAA,EACvC;AAGA,iBAAe,aAAa,WAAqC;AAChE,UAAM,OAAO,MAAM,SAAS;AAC5B,QAAI,CAAC,KAAM,QAAO;AAGlB,QAAI,KAAK,UAAU;AAClB,qBAAe,MAAM,KAAK,EAAE,IAAI;AAChC,aAAO;AAAA,IACR;AAGA,QAAI,KAAK,UAAU;AAClB,UAAI;AACH,cAAM,SAAS,MAAM,KAAK,SAAS,KAAK,KAAU;AAClD,uBAAe,MAAM,KAAK,EAAE,IAAI;AAChC,YAAI,CAAC,QAAQ;AACZ,qBAAW,MAAM,KAAK,EAAE,IAAI;AAAA,QAC7B,OAAO;AACN,iBAAO,WAAW,MAAM,KAAK,EAAE;AAAA,QAChC;AACA,eAAO;AAAA,MACR,SAAS,OAAO;AACf,uBAAe,MAAM,KAAK,EAAE,IAAI;AAChC,mBAAW,MAAM,KAAK,EAAE,IAAI,iBAAiB,QAAQ,MAAM,UAAU;AACrE,eAAO;AAAA,MACR;AAAA,IACD;AAIA,QAAI,eAAe,MAAM,KAAK,EAAE,MAAM,QAAW;AAChD,qBAAe,MAAM,KAAK,EAAE,IAAI;AAAA,IACjC;AAEA,WAAO,eAAe,MAAM,KAAK,EAAE,MAAM;AAAA,EAC1C;AAGA,WAAS,YAAY,aAA8B;AAClD,QAAI,cAAc,KAAK,eAAe,MAAM,OAAQ,QAAO;AAG3D,QAAI,cAAc,iBAAiB,SAAS,4BAA4B;AACvE,aAAO;AAAA,IACR;AAGA,QAAI,cAAc,iBAAiB,SAAS,gBAAgB;AAC3D,eAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACrC,cAAM,OAAO,MAAM,CAAC;AACpB,YAAI,CAAC,KAAK,YAAY,eAAe,MAAM,KAAK,EAAE,MAAM,OAAO;AAC9D,iBAAO;AAAA,QACR;AAAA,MACD;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAGA,iBAAe,WAA6B;AAC3C,QAAI,WAAW,MAAO,QAAO;AAG7B,QAAI,gBAAgB;AACnB,YAAM,UAAU,MAAM,aAAa,iBAAiB,KAAK;AACzD,UAAI,CAAC,QAAS,QAAO;AAAA,IACtB;AAEA,UAAM,YAAY,iBAAiB;AACnC,qBAAiB;AAEjB,iDAAe,WAAW,iBAAiB,OAAO,KAAK;AACvD,WAAO;AAAA,EACR;AAGA,WAAS,eAAwB;AAChC,QAAI,YAAY,MAAO,QAAO;AAE9B,UAAM,YAAY,iBAAiB;AACnC,qBAAiB;AAEjB,iDAAe,WAAW,iBAAiB,OAAO,KAAK;AACvD,WAAO;AAAA,EACR;AAGA,iBAAe,SAAS,aAAuC;AAC9D,QAAI,CAAC,YAAY,WAAW,EAAG,QAAO;AAGtC,QAAI,cAAc,iBAAiB,SAAS,gBAAgB;AAC3D,YAAM,UAAU,MAAM,aAAa,iBAAiB,KAAK;AACzD,UAAI,CAAC,QAAS,QAAO;AAAA,IACtB;AAEA,UAAM,YAAY,iBAAiB;AACnC,qBAAiB,QAAQ;AAEzB,iDAAe,WAAW,iBAAiB,OAAO,KAAK;AACvD,WAAO;AAAA,EACR;AAGA,WAAS,WAAW,SAA2B;AAC9C,SAAK,QAAQ,EAAE,GAAG,KAAK,OAAO,GAAG,QAAA;AAAA,EAClC;AAGA,WAAS,eAAe,QAAgB,UAA4B;AACnE,eAAW,QAAQ;AAEnB,WAAO,WAAW,MAAM,MAAM;AAAA,EAC/B;AAGA,WAAS,aAAa,QAAgB,OAAsB;AAC3D,mBAAe,MAAM,MAAM,IAAI;AAC/B,QAAI,OAAO;AACV,aAAO,WAAW,MAAM,MAAM;AAAA,IAC/B;AAAA,EACD;AAGA,WAAS,aAAa,QAAgB,OAA4B;AACjE,QAAI,OAAO;AACV,iBAAW,MAAM,MAAM,IAAI;AAC3B,qBAAe,MAAM,MAAM,IAAI;AAAA,IAChC,OAAO;AACN,aAAO,WAAW,MAAM,MAAM;AAAA,IAC/B;AAAA,EACD;AAGA,WAAS,UAAU,QAAsB;AACxC,WAAO,eAAe,MAAM,MAAM;AAClC,WAAO,WAAW,MAAM,MAAM;AAAA,EAC/B;AAGA,WAAS,QAAc;AACtB,qBAAiB,QAAQ;AACzB,SAAK,QAAQ,EAAE,GAAG,YAAA;AAClB,mBAAe,QAAQ,CAAA;AACvB,eAAW,QAAQ,CAAA;AACnB,iBAAa,QAAQ;AAAA,EACtB;AAGA,iBAAe,WAA6B;AAC3C,QAAI,aAAa,MAAO,QAAO;AAE/B,iBAAa,QAAQ;AAErB,QAAI;AAEH,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACtC,cAAM,UAAU,MAAM,aAAa,CAAC;AACpC,YAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,UAAU;AAEnC,2BAAiB,QAAQ;AACzB,uBAAa,QAAQ;AACrB,iBAAO;AAAA,QACR;AAAA,MACD;AAGA,UAAI,YAAY;AACf,cAAM,WAAW,KAAK,KAAU;AAAA,MACjC;AAEA,aAAO;AAAA,IACR,SAAS,OAAO;AACf,cAAQ,MAAM,iCAAiC,KAAK;AACpD,aAAO;AAAA,IACR,UAAA;AACC,mBAAa,QAAQ;AAAA,IACtB;AAAA,EACD;AAEA,SAAO;AAAA;AAAA,IAEN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF;AC/SO,SAAS,SAAY,SAAgD;AAC3E,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,eAAe;AAAA,IACf;AAAA,EAAA,IACG;AAGJ,QAAM,gBAAgB,IAAe,YAAY,IAAI,MAAM,IAAI,CAAC;AAGhE,MAAI,SAA0C,CAAA;AAG9C,WAAS,cAAoB;AAC5B,WAAO,QAAQ,CAAA,UAAS,aAAa,KAAK,CAAC;AAC3C,aAAS,CAAA;AAAA,EACV;AAGA,WAAS,yBAA+B;AACvC,gBAAA;AAGA,kBAAc,QAAQ,YAAY,IAAI,MAAM,IAAI;AAGhD,gBAAY,QAAQ,CAAC,GAAG,UAAU;AACjC,YAAM,QAAQ,WAAW,MAAM;AAC9B,sBAAc,MAAM,KAAK,IAAI;AAAA,MAC9B,GAAG,eAAe,gBAAgB,QAAQ,EAAE;AAC5C,aAAO,KAAK,KAAK;AAAA,IAClB,CAAC;AAAA,EACF;AAGA;AAAA,IACC,MAAM,QAAQ;AAAA,IACd,CAAC,WAAW,eAAe;AAC1B,UAAI,cAAc,CAAC,WAAW;AAE7B,+BAAA;AAAA,MACD,WAAW,aAAa,CAAC,YAAY;AAEpC,oBAAA;AACA,sBAAc,QAAQ,YAAY,IAAI,MAAM,IAAI;AAAA,MACjD;AAAA,IACD;AAAA,IACA,EAAE,WAAW,KAAA;AAAA,EAAK;AAInB,MAAI,CAAC,QAAQ,OAAO;AACnB,2BAAA;AAAA,EACD;AAGA,QAAM,QAAQ,SAAqB,MAAM;AACxC,UAAM,QAAQ,KAAK;AACnB,UAAM,YAAY,6CAAc;AAEhC,WAAO,YAAY,IAAI,CAAA,QAAO;AAC7B,YAAM,OAAiB;AAAA,QACtB,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,MAAM,IAAI,QAAQ,KAAK;AAAA,QACvB,MAAM,IAAI;AAAA,QACV,OAAO,IAAI,SAAS;AAAA,MAAA;AAIrB,UAAI,IAAI,eAAe;AACtB,aAAK,SAAS,IAAI,cAAc,OAAO,SAAS;AAAA,MACjD;AAEA,UAAI,IAAI,mBAAmB;AAC1B,aAAK,aAAa,IAAI,kBAAkB,OAAO,SAAS;AAAA,MACzD;AAGA,UAAI,IAAI,eAAe,QAAW;AACjC,aAAK,aAAa,IAAI;AAAA,MACvB;AAEA,UAAI,IAAI,YAAY;AACnB,aAAK,aAAa,IAAI;AAAA,MACvB;AAEA,aAAO;AAAA,IACR,CAAC;AAAA,EACF,CAAC;AAGD,QAAM,mBAAmB;AAAA,IAAS,MACjC,MAAM,MAAM,IAAI,CAAC,MAAM,WAAW;AAAA,MACjC,GAAG;AAAA,MACH,SAAS,QAAQ,SAAS,cAAc,MAAM,KAAK;AAAA,IAAA,EAClD;AAAA,EAAA;AAIH,QAAM,YAAY;AAAA,IAAS,MAC1B,CAAC,QAAQ,SAAS,cAAc,MAAM,MAAM,CAAA,MAAK,CAAC,CAAC;AAAA,EAAA;AAIpD,WAAS,QAAQ,IAAkC;AAClD,WAAO,MAAM,MAAM,KAAK,CAAA,MAAK,EAAE,OAAO,EAAE;AAAA,EACzC;AAGA,WAAS,mBAAyB;AACjC,2BAAA;AAAA,EACD;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF;AAKO,SAAS,UACf,IACA,MACA,MACA,QACA,SACoB;AACpB,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAO,mCAAS,UAAS;AAAA,IACzB,SAAS,CAAC,UAAU,MAAM,OAAO,MAAM,EAAE;AAAA,EAAA;AAE3C;AAKO,SAAS,QACf,IACA,MACA,MACA,UACA,SACoB;AACpB,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAO,mCAAS,UAAS;AAAA,IACzB,SAAS,CAAC,UAAU;AACnB,YAAM,MAAM,MAAM,OAAO,CAAC,OAAO,SAAS,QAAQ,SAAS,IAAI,GAAG,CAAC;AACnE,cAAO,mCAAS,UAAS,QAAQ,OAAO,GAAG,IAAI,IAAI,eAAA;AAAA,IACpD;AAAA,EAAA;AAEF;AAKO,SAAS,YACf,IACA,MACA,MACA,UACA,SACoB;AACpB,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAO,mCAAS,UAAS;AAAA,IACzB,SAAS,CAAC,UAAU;AACnB,UAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,YAAM,MAAM,MAAM,OAAO,CAAC,OAAO,SAAS,QAAQ,SAAS,IAAI,GAAG,CAAC;AACnE,YAAM,MAAM,MAAM,MAAM;AACxB,YAAM,YAAY,IAAI,SAAQ,mCAAS,aAAY,CAAC;AACpD,cAAO,mCAAS,UAAS,GAAG,SAAS,GAAG,QAAQ,MAAM,KAAK;AAAA,IAC5D;AAAA,EAAA;AAEF;AAKO,SAAS,eACf,IACA,MACA,MACA,QACA,SACoB;AACpB,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAO,mCAAS,UAAS;AAAA,IACzB,SAAS,CAAC,UAAU;AACnB,UAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,YAAM,QAAQ,MAAM,OAAO,MAAM,EAAE;AACnC,YAAM,aAAc,QAAQ,MAAM,SAAU;AAC5C,aAAO,GAAG,WAAW,SAAQ,mCAAS,aAAY,CAAC,CAAC;AAAA,IACrD;AAAA,EAAA;AAEF;ACvOO,SAAS,gBAAgB,UAAkC,IAA2B;AAC5F,QAAM;AAAA,IACL,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,iBAAiB;AAAA,EAAA,IACd;AAGJ,QAAM,SAAS,IAAI,KAAK;AACxB,QAAM,YAAY,IAAI,KAAK;AAC3B,QAAM,SAAS,IAAwB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,SAAS;AAAA,EAAA,CACT;AAGD,MAAI,UAAsC;AAK1C,WAAS,QAAQ,eAAqD;AAErE,WAAO,QAAQ;AAAA,MACd,GAAG;AAAA,MACH,aAAa,cAAc,eAAe;AAAA,MAC1C,YAAY,cAAc,cAAc;AAAA,MACxC,SAAS,cAAc,WAAW;AAAA,IAAA;AAGnC,WAAO,QAAQ;AACf,cAAU,QAAQ;AAElB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACvC,gBAAU,EAAE,SAAS,OAAA;AAAA,IACtB,CAAC;AAAA,EACF;AAKA,WAAS,gBAAsB;AAC9B,QAAI,SAAS;AACZ,cAAQ,QAAQ,IAAI;AACpB,gBAAU;AAAA,IACX;AACA,WAAO,QAAQ;AACf,cAAU,QAAQ;AAAA,EACnB;AAKA,WAAS,eAAqB;AAC7B,QAAI,SAAS;AACZ,cAAQ,QAAQ,KAAK;AACrB,gBAAU;AAAA,IACX;AACA,WAAO,QAAQ;AACf,cAAU,QAAQ;AAAA,EACnB;AAKA,WAAS,QAAc;AACtB,QAAI,SAAS;AACZ,cAAQ,QAAQ,KAAK;AACrB,gBAAU;AAAA,IACX;AACA,WAAO,QAAQ;AACf,cAAU,QAAQ;AAAA,EACnB;AAKA,WAAS,cACR,UACA,eACmB;AACnB,WAAO,QAAQ;AAAA,MACd,OAAO;AAAA,MACP,SAAS,oCAAoC,QAAQ;AAAA,MACrD,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,GAAG;AAAA,IAAA,CACH;AAAA,EACF;AAKA,WAAS,kBACR,OACA,WAAmB,SACnB,eACmB;AACnB,WAAO,QAAQ;AAAA,MACd,OAAO,UAAU,KAAK,IAAI,QAAQ;AAAA,MAClC,SAAS,mCAAmC,KAAK,IAAI,QAAQ;AAAA,MAC7D,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,GAAG;AAAA,IAAA,CACH;AAAA,EACF;AAKA,WAAS,mBACR,YACA,eACmB;AACnB,WAAO,QAAQ;AAAA,MACd,OAAO,WAAW,UAAU;AAAA,MAC5B,SAAS,4BAA4B,WAAW,YAAA,CAAa;AAAA,MAC7D,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,GAAG;AAAA,IAAA,CACH;AAAA,EACF;AAKA,iBAAe,kBACd,eACA,QAC6D;AAC7D,UAAM,YAAY,MAAM,QAAQ,aAAa;AAE7C,QAAI,CAAC,WAAW;AACf,aAAO,EAAE,WAAW,MAAA;AAAA,IACrB;AAGA,cAAU,QAAQ;AAElB,QAAI;AACH,YAAM,SAAS,MAAM,OAAA;AACrB,aAAO,QAAQ;AACf,gBAAU,QAAQ;AAClB,aAAO,EAAE,WAAW,MAAM,OAAA;AAAA,IAC3B,SAAS,OAAO;AACf,gBAAU,QAAQ;AAClB,aAAO;AAAA,QACN,WAAW;AAAA,QACX,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MAAA;AAAA,IAEjE;AAAA,EACD;AAKA,QAAM,aAAa,SAAS,OAAO;AAAA,IAClC,OAAO,OAAO,MAAM;AAAA,IACpB,MAAM,OAAO;AAAA,IACb,WAAW;AAAA,IACX,SAAS;AAAA,MACR;AAAA,QACC,MAAM,OAAO,MAAM,cAAc;AAAA,QACjC,OAAO;AAAA,MAAA;AAAA,MAER;AAAA,QACC,MAAM,OAAO,MAAM,eAAe;AAAA,QAClC,OAAO;AAAA,MAAA;AAAA,IACR;AAAA,EACD,EACC;AAEF,SAAO;AAAA;AAAA,IAEN;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,EAAA;AAEF;AAKA,IAAI,qBAAmD;AAYhD,SAAS,wBAA+C;AAC9D,MAAI,CAAC,oBAAoB;AACxB,yBAAqB,gBAAA;AAAA,EACtB;AACA,SAAO;AACR;AAKO,SAAS,0BAAgC;AAC/C,uBAAqB;AACtB;ACzNO,SAAS,aAAa,SAAkD;AAC9E,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,qBAAqB,CAAA;AAAA,IAClC;AAAA,IACA,SAAS,iBAAiB,CAAA;AAAA,IAC1B,SAAS,iBAAiB;AAAA,IAC1B,eAAe;AAAA,EAAA,IACZ;AAGJ,QAAM,UAAU,eAAA;AAGhB,QAAM,gBAAgB,IAAI,cAAc;AACxC,QAAM,iBAAiB,IAAI,cAAc;AAGzC,QAAM,UAAU,IAAkB,CAAC,GAAG,cAAc,CAAC;AAGrD,YAAU,MAAM;AACf,QAAI,gBAAgB;AACnB,iBAAW,MAAM;AAChB,sBAAc,QAAQ;AAAA,MACvB,GAAG,YAAY;AAEf,iBAAW,MAAM;AAChB,uBAAe,QAAQ;AAAA,MACxB,GAAG,eAAe,CAAC;AAAA,IACpB;AAAA,EACD,CAAC;AAGD,QAAM,OAAO,SAAS,MAAM,QAAQ,MAAM,IAAI;AAG9C,QAAM,QAAQ,SAAS,MAAM,QAAQ,MAAM,KAAK;AAGhD,QAAM,UAAU,SAAS,MAAA;;AAAM,uBAAK,UAAL,mBAAY,YAAW;AAAA,GAAK;AAG3D,QAAM,eAAe,SAAS,MAAA;;AAAM,uBAAK,UAAL,mBAAY,iBAAgB;AAAA,GAAK;AAGrE,QAAM,cAAc,SAA2B,MAAM;AAAA,IACpD,GAAG,mBAAmB,IAAI,CAAA,OAAM,EAAE,GAAG,GAAG,SAAS,MAAA,EAAQ;AAAA,IACzD,EAAE,MAAM,OAAO,SAAS,KAAA;AAAA,EAAK,CAC7B;AAGD,QAAM,eAAe,SAAS,OAAO;AAAA,IACpC,aAAa;AAAA,EAAA,EACZ;AAGF,QAAM,cAAc,SAAS,OAAO;AAAA,IACnC;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,YAAY;AAAA,IACnB,SAAS,cAAc;AAAA,EAAA,EACtB;AAGF,WAAS,WAAW,SAAwB;AAC3C,QAAI,SAAS;AACZ,oBAAc,QAAQ;AACtB,qBAAe,QAAQ;AAAA,IACxB,OAAO;AACN,iBAAW,MAAM;AAChB,sBAAc,QAAQ;AAAA,MACvB,GAAG,YAAY;AAEf,iBAAW,MAAM;AAChB,uBAAe,QAAQ;AAAA,MACxB,GAAG,eAAe,CAAC;AAAA,IACpB;AAAA,EACD;AAGA,WAAS,aAAa,IAAY,SAAoC;AACrE,UAAM,QAAQ,QAAQ,MAAM,UAAU,CAAA,MAAK,EAAE,OAAO,EAAE;AACtD,QAAI,UAAU,IAAI;AACjB,cAAQ,MAAM,KAAK,IAAI,EAAE,GAAG,QAAQ,MAAM,KAAK,GAAG,GAAG,QAAA;AAAA,IACtD;AAAA,EACD;AAGA,WAAS,iBAAiB,IAAY,SAAwB;AAC7D,iBAAa,IAAI,EAAE,SAAS;AAAA,EAC7B;AAEA,SAAO;AAAA;AAAA,IAEN;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,EAAA;AAEF;AAyBO,SAAS,YAAY,SAAiD;AAC5E,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,GAAG;AAAA,EAAA,IACA;AAEJ,QAAM,UAAwB,CAAA;AAE9B,MAAI,CAAC,cAAc,YAAY;AAC9B,YAAQ,KAAK;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM,UAAU,WAAW,OAAO,CAAC,EAAE,YAAA,IAAgB,WAAW,MAAM,CAAC,CAAC;AAAA,MACxE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,IAAA,CACT;AAAA,EACF;AAEA,SAAO,aAAa;AAAA,IACnB,GAAG;AAAA,IACH;AAAA,EAAA,CACA;AACF;AAkCO,SAAS,cAAc,SAAmD;AAChF,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,aAAa;AAAA,IACb,GAAG;AAAA,EAAA,IACA;AAEJ,QAAM,UAAwB,CAAA;AAE9B,MAAI,CAAC,YAAY,aAAa;AAC7B,YAAQ,KAAK;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,IAAA,CACT;AAAA,EACF;AAEA,MAAI,CAAC,cAAc,eAAe;AACjC,YAAQ,KAAK;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,IAAA,CACT;AAAA,EACF;AAEA,SAAO,aAAa;AAAA,IACnB,GAAG;AAAA,IACH;AAAA,EAAA,CACA;AACF;AAmCO,SAAS,YAAY,SAAiD;AAC5E,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,aAAa;AAAA,IACb,GAAG;AAAA,EAAA,IACA;AAEJ,QAAM,UAAwB,CAAA;AAE9B,MAAI,CAAC,cAAc,eAAe;AACjC,YAAQ,KAAK;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,IAAA,CACT;AAAA,EACF;AAEA,MAAI,CAAC,YAAY,aAAa;AAC7B,YAAQ,KAAK;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,IAAA,CACT;AAAA,EACF;AAEA,SAAO,aAAa;AAAA,IACnB,GAAG;AAAA,IACH;AAAA,EAAA,CACA;AACF;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@htlkg/components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"ajv": "^8.17.1",
|
|
45
45
|
"ajv-formats": "^3.0.1",
|
|
46
|
-
"@htlkg/
|
|
47
|
-
"@htlkg/
|
|
46
|
+
"@htlkg/core": "0.0.12",
|
|
47
|
+
"@htlkg/data": "0.0.13"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@hotelinking/ui": "^16.0.0",
|
package/src/composables/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { useModal, type UseModalOptions, type UseModalReturn } from './useModal'
|
|
|
3
3
|
export { useTabs, type UseTabsOptions, type UseTabsReturn, type Tab } from './useTabs';
|
|
4
4
|
export { useForm, type UseFormOptions, type UseFormReturn, type ValidationRule, type FieldConfig } from './useForm';
|
|
5
5
|
export { useFormValidation } from './useFormValidation';
|
|
6
|
+
export { useJsonForm, type UseJsonFormOptions, type UseJsonFormReturn } from './useJsonForm';
|
|
6
7
|
export { useNotifications, type Notification, type UseNotificationsReturn } from './useNotifications';
|
|
7
8
|
|
|
8
9
|
// Page context
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
import { useJsonForm } from './useJsonForm';
|
|
4
|
+
|
|
5
|
+
describe('useJsonForm composable', () => {
|
|
6
|
+
describe('initialization', () => {
|
|
7
|
+
it('initializes with provided values', () => {
|
|
8
|
+
const initialValues = { name: 'John', email: 'john@example.com' };
|
|
9
|
+
const { values, originalValues } = useJsonForm({ initialValues });
|
|
10
|
+
|
|
11
|
+
expect(values.value).toEqual(initialValues);
|
|
12
|
+
expect(originalValues.value).toEqual(initialValues);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('initializes with empty object when no values provided', () => {
|
|
16
|
+
const { values, originalValues } = useJsonForm();
|
|
17
|
+
|
|
18
|
+
expect(values.value).toEqual({});
|
|
19
|
+
expect(originalValues.value).toEqual({});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('deep clones initial values', () => {
|
|
23
|
+
const initialValues = { user: { name: 'John' } };
|
|
24
|
+
const { values, originalValues } = useJsonForm({ initialValues });
|
|
25
|
+
|
|
26
|
+
// Modify values
|
|
27
|
+
(values.value as any).user.name = 'Jane';
|
|
28
|
+
|
|
29
|
+
// Original should not be affected
|
|
30
|
+
expect(originalValues.value).toEqual({ user: { name: 'John' } });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('isDirty', () => {
|
|
35
|
+
it('starts as not dirty', () => {
|
|
36
|
+
const { isDirty } = useJsonForm({
|
|
37
|
+
initialValues: { name: 'John' },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(isDirty.value).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('becomes dirty when values change', () => {
|
|
44
|
+
const { values, isDirty } = useJsonForm({
|
|
45
|
+
initialValues: { name: 'John' },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
values.value = { name: 'Jane' };
|
|
49
|
+
|
|
50
|
+
expect(isDirty.value).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('becomes not dirty when values match original', () => {
|
|
54
|
+
const { values, isDirty } = useJsonForm({
|
|
55
|
+
initialValues: { name: 'John' },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
values.value = { name: 'Jane' };
|
|
59
|
+
expect(isDirty.value).toBe(true);
|
|
60
|
+
|
|
61
|
+
values.value = { name: 'John' };
|
|
62
|
+
expect(isDirty.value).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('handles nested objects for dirty checking', () => {
|
|
66
|
+
const { values, isDirty } = useJsonForm({
|
|
67
|
+
initialValues: { user: { name: 'John', age: 30 } },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
values.value = { user: { name: 'John', age: 31 } };
|
|
71
|
+
expect(isDirty.value).toBe(true);
|
|
72
|
+
|
|
73
|
+
values.value = { user: { name: 'John', age: 30 } };
|
|
74
|
+
expect(isDirty.value).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('handles arrays for dirty checking', () => {
|
|
78
|
+
const { values, isDirty } = useJsonForm({
|
|
79
|
+
initialValues: { tags: ['a', 'b', 'c'] },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
values.value = { tags: ['a', 'b'] };
|
|
83
|
+
expect(isDirty.value).toBe(true);
|
|
84
|
+
|
|
85
|
+
values.value = { tags: ['a', 'b', 'c'] };
|
|
86
|
+
expect(isDirty.value).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('reset', () => {
|
|
91
|
+
it('resets values to original', () => {
|
|
92
|
+
const { values, reset } = useJsonForm({
|
|
93
|
+
initialValues: { name: 'John' },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
values.value = { name: 'Jane' };
|
|
97
|
+
reset();
|
|
98
|
+
|
|
99
|
+
expect(values.value).toEqual({ name: 'John' });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('clears isDirty after reset', () => {
|
|
103
|
+
const { values, reset, isDirty } = useJsonForm({
|
|
104
|
+
initialValues: { name: 'John' },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
values.value = { name: 'Jane' };
|
|
108
|
+
expect(isDirty.value).toBe(true);
|
|
109
|
+
|
|
110
|
+
reset();
|
|
111
|
+
expect(isDirty.value).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('setValues', () => {
|
|
116
|
+
it('updates both values and originalValues', () => {
|
|
117
|
+
const { values, originalValues, setValues, isDirty } = useJsonForm({
|
|
118
|
+
initialValues: { name: 'John' },
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
setValues({ name: 'Jane' });
|
|
122
|
+
|
|
123
|
+
expect(values.value).toEqual({ name: 'Jane' });
|
|
124
|
+
expect(originalValues.value).toEqual({ name: 'Jane' });
|
|
125
|
+
expect(isDirty.value).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('merges partial values', () => {
|
|
129
|
+
const { values, setValues } = useJsonForm({
|
|
130
|
+
initialValues: { name: 'John', email: 'john@example.com' },
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
setValues({ name: 'Jane' });
|
|
134
|
+
|
|
135
|
+
expect(values.value).toEqual({ name: 'Jane', email: 'john@example.com' });
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('markAsSaved', () => {
|
|
140
|
+
it('updates originalValues to current values', () => {
|
|
141
|
+
const { values, originalValues, markAsSaved, isDirty } = useJsonForm({
|
|
142
|
+
initialValues: { name: 'John' },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
values.value = { name: 'Jane' };
|
|
146
|
+
expect(isDirty.value).toBe(true);
|
|
147
|
+
|
|
148
|
+
markAsSaved();
|
|
149
|
+
|
|
150
|
+
expect(originalValues.value).toEqual({ name: 'Jane' });
|
|
151
|
+
expect(isDirty.value).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('onChange callback', () => {
|
|
156
|
+
it('calls onChange when values change', async () => {
|
|
157
|
+
const onChange = vi.fn();
|
|
158
|
+
const { values } = useJsonForm({
|
|
159
|
+
initialValues: { name: 'John' },
|
|
160
|
+
onChange,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
values.value = { name: 'Jane' };
|
|
164
|
+
|
|
165
|
+
// onChange is called via watcher, need to wait for next tick
|
|
166
|
+
await nextTick();
|
|
167
|
+
expect(onChange).toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('confirmOnLeave', () => {
|
|
172
|
+
let addEventListenerSpy: ReturnType<typeof vi.spyOn>;
|
|
173
|
+
let removeEventListenerSpy: ReturnType<typeof vi.spyOn>;
|
|
174
|
+
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
addEventListenerSpy = vi.spyOn(window, 'addEventListener');
|
|
177
|
+
removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
afterEach(() => {
|
|
181
|
+
addEventListenerSpy.mockRestore();
|
|
182
|
+
removeEventListenerSpy.mockRestore();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('adds beforeunload listener when confirmOnLeave is true', () => {
|
|
186
|
+
// Note: In actual Vue component, onMounted would be called
|
|
187
|
+
// This test validates the option is properly configured
|
|
188
|
+
const form = useJsonForm({
|
|
189
|
+
initialValues: { name: 'John' },
|
|
190
|
+
confirmOnLeave: true,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(form).toBeDefined();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('validate', () => {
|
|
198
|
+
it('returns empty array when no formRef', () => {
|
|
199
|
+
const { validate } = useJsonForm({
|
|
200
|
+
initialValues: { name: 'John' },
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const errors = validate();
|
|
204
|
+
expect(errors).toEqual([]);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('calls formRef.validate when available', () => {
|
|
208
|
+
const mockValidate = vi.fn().mockReturnValue([{ field: 'name', message: 'Required' }]);
|
|
209
|
+
const { formRef, validate } = useJsonForm({
|
|
210
|
+
initialValues: { name: '' },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Simulate formRef being set (would happen in template via ref="formRef")
|
|
214
|
+
formRef.value = {
|
|
215
|
+
validate: mockValidate,
|
|
216
|
+
reset: vi.fn(),
|
|
217
|
+
formData: { value: {} },
|
|
218
|
+
errors: { value: {} },
|
|
219
|
+
touched: { value: {} },
|
|
220
|
+
} as any;
|
|
221
|
+
|
|
222
|
+
const errors = validate();
|
|
223
|
+
expect(mockValidate).toHaveBeenCalled();
|
|
224
|
+
expect(errors).toEqual([{ field: 'name', message: 'Required' }]);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('formRef', () => {
|
|
229
|
+
it('initializes formRef as null', () => {
|
|
230
|
+
const { formRef } = useJsonForm({
|
|
231
|
+
initialValues: { name: 'John' },
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(formRef.value).toBe(null);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe('useJsonForm deep equality', () => {
|
|
240
|
+
it('handles null values correctly', () => {
|
|
241
|
+
const { values, isDirty } = useJsonForm({
|
|
242
|
+
initialValues: { field: null },
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
values.value = { field: null };
|
|
246
|
+
expect(isDirty.value).toBe(false);
|
|
247
|
+
|
|
248
|
+
values.value = { field: 'value' };
|
|
249
|
+
expect(isDirty.value).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('handles undefined values correctly', () => {
|
|
253
|
+
const { values, isDirty } = useJsonForm({
|
|
254
|
+
initialValues: { field: undefined },
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
values.value = { field: undefined };
|
|
258
|
+
expect(isDirty.value).toBe(false);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('handles mixed types correctly', () => {
|
|
262
|
+
const { values, isDirty } = useJsonForm({
|
|
263
|
+
initialValues: { count: 0 },
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
values.value = { count: '0' as any };
|
|
267
|
+
expect(isDirty.value).toBe(true);
|
|
268
|
+
|
|
269
|
+
values.value = { count: 0 };
|
|
270
|
+
expect(isDirty.value).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
});
|