123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- import { createMessage, getNode } from '@formkit/core'
- import { computed, toRef, ref, reactive, watch } from 'vue'
- import type { FormStep } from './types.ts'
- import type { FormKitNode } from '@formkit/core'
- import type { ComputedRef, Ref } from 'vue'
- interface InternalMultiFormSteps {
- label: string
- order: number
- valid: Ref<boolean>
- blockingCount: number
- errorCount: number
- }
- export const useMultiStepForm = (
- formNode: ComputedRef<FormKitNode | undefined>,
- ) => {
- const activeStep = ref('')
- const internalSteps = reactive<Record<string, InternalMultiFormSteps>>({})
- const visitedSteps = ref<string[]>([])
- const stepNames = computed(() => Object.keys(internalSteps))
- const lastStepName = computed(
- () => stepNames.value[stepNames.value.length - 1],
- )
- // Watch the active steps to track the visited steps.
- watch(activeStep, (newStep, oldStep) => {
- if (oldStep && !visitedSteps.value.includes(oldStep)) {
- visitedSteps.value.push(oldStep)
- }
- // Trigger showing validation on fields within all visited steps, otherwise it would only visible
- // after clicking on the "real" submit button.
- visitedSteps.value.forEach((step) => {
- const node = getNode(step)
- if (!node) return
- node.walk((fieldNode) => {
- fieldNode.store.set(
- createMessage({
- key: 'submitted',
- value: true,
- visible: false,
- }),
- )
- })
- })
- formNode.value?.emit('autofocus')
- })
- const setMultiStep = (step?: string) => {
- // Go to next step, when no specific step is given.
- if (!step) {
- const currentIndex = stepNames.value.indexOf(activeStep.value)
- activeStep.value = stepNames.value[currentIndex + 1]
- } else {
- activeStep.value = step
- }
- }
- const multiStepPlugin = (node: FormKitNode) => {
- if (node.props.type === 'group') {
- internalSteps[node.name] = internalSteps[node.name] || {}
- node.on('created', () => {
- if (!node.context) return
- internalSteps[node.name].valid = toRef(node.context.state, 'valid')
- internalSteps[node.name].label =
- Object.keys(internalSteps).length.toString()
- internalSteps[node.name].order = Object.keys(internalSteps).length
- })
- // Listen for changes in error count, which a normally errors from the backend after submitting.
- node.on('count:errors', ({ payload: count }) => {
- internalSteps[node.name].errorCount = count
- })
- // Listen for changes in count of blocking validations messages.
- node.on('count:blocking', ({ payload: count }) => {
- internalSteps[node.name].blockingCount = count
- })
- // The first step should be the default one.
- if (activeStep.value === '') {
- activeStep.value = node.name
- }
- }
- return false
- }
- const allSteps = computed<Record<string, FormStep>>(() => {
- const mappedSteps: Record<string, FormStep> = {}
- stepNames.value.forEach((stepName) => {
- const alreadyVisited = visitedSteps.value.includes(stepName)
- mappedSteps[stepName] = {
- label: internalSteps[stepName].label,
- order: internalSteps[stepName].order,
- errorCount:
- internalSteps[stepName].blockingCount +
- internalSteps[stepName].errorCount,
- valid:
- internalSteps[stepName].valid &&
- internalSteps[stepName].errorCount === 0,
- disabled: !alreadyVisited || activeStep.value === stepName,
- completed: alreadyVisited,
- }
- })
- return mappedSteps
- })
- return {
- multiStepPlugin,
- setMultiStep,
- allSteps,
- stepNames,
- lastStepName,
- activeStep,
- visitedSteps,
- }
- }
|