import { defineStore } from 'pinia' import { ref } from 'vue' import { nanoid } from 'nanoid' import { useLabels } from '@/composables/useLabels' import type { Field } from '@/types/field' import type { FormPage } from '@/types/form-page' import { duplicateFieldForStore } from '@/utils/fieldUtils' export type { FormPage } from '@/types/form-page' export type PageNavigationSettings = { previousButton: { label: string cssClasses: string priority: 'primary' | 'secondary' | 'tertiary' | 'white' type: 'fill' | 'border' | 'border-light' | 'ghost' showIcon: boolean } nextButton: { label: string cssClasses: string priority: 'primary' | 'secondary' | 'tertiary' | 'white' type: 'fill' | 'border' | 'border-light' | 'ghost' showIcon: boolean } submitButton: { label: string cssClasses: string priority: 'primary' | 'secondary' | 'tertiary' | 'white' type: 'fill' | 'border' | 'border-light' | 'ghost' showIcon: boolean } buttonPosition: 'left' | 'center' | 'right' | 'separated' isGrouped: boolean } export const useMultiPageStore = defineStore('multiPageData', () => { const { getLabel } = useLabels() type ProgressIndicatorSettings = { type: 'progress-bar' | 'rootline' | 'none' showPageTitles: boolean hidePageNumbers: boolean hideConnectingLines: boolean } // Pages state const pages = ref([ { id: nanoid(), label: 'Page 1', closable: false, }, ]) const currentPageId = ref(pages.value[0].id) // Progress indicator settings const progressIndicator = ref({ type: 'progress-bar', showPageTitles: true, hidePageNumbers: false, hideConnectingLines: false, }) // Page navigation settings (per page) const pageNavigationSettings = ref>({}) // Selection state for multipage UI elements const isProgressIndicatorSelected = ref(false) const isPageNavigationSelected = ref(false) const isNavigationButtonSelected = ref(false) // ----- Selection actions ----- const selectProgressIndicator = () => { isProgressIndicatorSelected.value = true isPageNavigationSelected.value = false isNavigationButtonSelected.value = false } const deselectProgressIndicator = () => { isProgressIndicatorSelected.value = false } const selectPageNavigation = () => { isProgressIndicatorSelected.value = false isPageNavigationSelected.value = true isNavigationButtonSelected.value = false } const deselectPageNavigation = () => { isPageNavigationSelected.value = false } const selectNavigationButton = () => { isProgressIndicatorSelected.value = false isPageNavigationSelected.value = true isNavigationButtonSelected.value = true } const deselectNavigationButton = () => { isNavigationButtonSelected.value = false isPageNavigationSelected.value = false } const deselectAll = () => { isProgressIndicatorSelected.value = false isPageNavigationSelected.value = false isNavigationButtonSelected.value = false } // ----- Progress indicator ----- const updateProgressIndicator = (settings: ProgressIndicatorSettings) => { progressIndicator.value = { ...progressIndicator.value, ...settings } } const setPages = (value: FormPage[]) => { pages.value = value if (!pages.value.length) { const firstPageId = nanoid() pages.value = [{ id: firstPageId, label: 'Page 1', closable: false }] } if (!pages.value.some((page) => page.id === currentPageId.value)) { currentPageId.value = pages.value[0].id } } const setCurrentPageId = (pageId: string) => { currentPageId.value = pageId } const setProgressIndicator = (value: ProgressIndicatorSettings) => { progressIndicator.value = value } const setPageNavigationSettings = (value: Record) => { // Backend may return an empty PHP array serialized as JSON [] instead of {}. // Arrays are unusable as keyed maps – normalize to empty object. if (Array.isArray(value) || value === null || value === undefined) { pageNavigationSettings.value = {} return } pageNavigationSettings.value = value } const ensurePageNavigationSettings = () => { pages.value.forEach((page) => { if (!pageNavigationSettings.value[page.id]) { getPageNavigationSettings(page.id) } }) } const hydrate = (payload: { pages?: FormPage[] progressIndicator?: ProgressIndicatorSettings pageNavigationSettings?: Record }) => { if (payload.pages && payload.pages.length) { setPages(payload.pages) } if (payload.progressIndicator) { setProgressIndicator(payload.progressIndicator) } if (payload.pageNavigationSettings) { setPageNavigationSettings(payload.pageNavigationSettings) } ensurePageNavigationSettings() } // ----- Page navigation settings ----- const getPageNavigationSettings = (pageId: string): PageNavigationSettings => { if (!pageNavigationSettings.value[pageId]) { pageNavigationSettings.value[pageId] = { previousButton: { label: getLabel('previous') || 'Previous', cssClasses: '', priority: 'tertiary', type: 'border', showIcon: true, }, nextButton: { label: getLabel('next') || 'Next', cssClasses: '', priority: 'tertiary', type: 'border', showIcon: true, }, submitButton: { label: getLabel('submit') || 'Submit', cssClasses: '', priority: 'tertiary', type: 'border', showIcon: false, }, buttonPosition: 'separated', isGrouped: false, } } return pageNavigationSettings.value[pageId] } const updatePageNavigationSettings = ( pageId: string, settings: Partial, ) => { const current = getPageNavigationSettings(pageId) pageNavigationSettings.value[pageId] = { ...current, ...settings } } // ----- Pages CRUD ----- const addPage = () => { const newPageId = nanoid() const index = pages.value.length + 1 pages.value.push({ id: newPageId, label: `Page ${index}`, closable: true }) getPageNavigationSettings(newPageId) } const addPageAfter = (pageIndex: number) => { const newPageId = nanoid() const insertIndex = pageIndex + 1 const pageNumbers = pages.value.map((p) => { const match = p.label.match(/Page (\d+)/) return match ? parseInt(match[1], 10) : 0 }) const nextNumber = pageNumbers.length > 0 ? Math.max(...pageNumbers) + 1 : 1 pages.value.splice(insertIndex, 0, { id: newPageId, label: `Page ${nextNumber}`, closable: true, }) getPageNavigationSettings(newPageId) } /** * Delete a page. Caller is responsible for removing associated fields and * clearing selectedField if it belonged to the deleted page. */ const deletePage = ( pageId: string, onDeleteFields: (pageId: string) => void, clearSelectedFieldIfPage: (pageId: string) => void, ) => { if (pages.value.length <= 1) return const idx = pages.value.findIndex((p) => p.id === pageId) if (idx === -1) return pages.value.splice(idx, 1) if (currentPageId.value === pageId) { const nextIndex = Math.min(idx, pages.value.length - 1) currentPageId.value = pages.value[nextIndex].id } clearSelectedFieldIfPage(pageId) onDeleteFields(pageId) delete pageNavigationSettings.value[pageId] } /** * Duplicate a page. Caller provides increaseCounter and fields array for * duplicating fields onto the new page. */ const duplicatePage = ( pageId: string, fields: Field[], increaseCounter: () => number, pushField: (field: Field) => void, ) => { const pageIndex = pages.value.findIndex((p) => p.id === pageId) if (pageIndex === -1) return const originalPage = pages.value[pageIndex] const newPageId = nanoid() pages.value.splice(pageIndex + 1, 0, { id: newPageId, label: `${originalPage.label} (Copy)`, closable: true, }) const pageFields = fields.filter((f) => f.pageId === pageId) pageFields.forEach((field) => { const newIndex = increaseCounter() const duplicatedField = duplicateFieldForStore(field, newIndex, getLabel) duplicatedField.pageId = newPageId pushField(duplicatedField) }) const originalSettings = getPageNavigationSettings(pageId) pageNavigationSettings.value[newPageId] = JSON.parse(JSON.stringify(originalSettings)) currentPageId.value = newPageId } const renamePage = (pageId: string, newName: string) => { const page = pages.value.find((p) => p.id === pageId) if (page) page.label = newName } const updatePage = (pageId: string, updates: { label?: string; closable?: boolean }) => { const page = pages.value.find((p) => p.id === pageId) if (page) { if (updates.label !== undefined) page.label = updates.label if (updates.closable !== undefined) page.closable = updates.closable } } const reorderPages = (orderedIds: string[]) => { const newOrder = orderedIds .map((id) => pages.value.find((p) => p.id === id)) .filter(Boolean) as typeof pages.value if (newOrder.length === pages.value.length) { pages.value = newOrder } } const reset = () => { const firstPageId = nanoid() pages.value = [{ id: firstPageId, label: 'Page 1', closable: false }] currentPageId.value = firstPageId progressIndicator.value = { type: 'progress-bar', showPageTitles: true, hidePageNumbers: false, hideConnectingLines: false, } pageNavigationSettings.value = {} isProgressIndicatorSelected.value = false isPageNavigationSelected.value = false isNavigationButtonSelected.value = false } return { pages, currentPageId, progressIndicator, pageNavigationSettings, setPages, setCurrentPageId, setProgressIndicator, setPageNavigationSettings, ensurePageNavigationSettings, hydrate, isProgressIndicatorSelected, isPageNavigationSelected, isNavigationButtonSelected, selectProgressIndicator, deselectProgressIndicator, selectPageNavigation, deselectPageNavigation, selectNavigationButton, deselectNavigationButton, deselectAll, updateProgressIndicator, getPageNavigationSettings, updatePageNavigationSettings, addPage, addPageAfter, deletePage, duplicatePage, copyPage: duplicatePage, renamePage, updatePage, reorderPages, reset, } })