import React , { useState , createContext , useContext , useCallback } from 'react' ; import { WithMultiContent , useMultiContentContext , HookProps } from '../multi_content' ; export interface Props < T extends object > { onSave : ( data : T ) => void | Promise < void > ; children : JSX . Element | Array < JSX . Element | null | false > ; isEditing ? : boolean ; defaultActiveStep ? : number ; defaultValue ? : HookProps < T > [ 'defaultValue' ] ; onChange ? : HookProps < T > [ 'onChange' ] ; } interface State { activeStepIndex : number ; steps : Steps ; } export interface Step { id : string ; index : number ; label : string ; isRequired : boolean ; isComplete : boolean ; } export interface Steps { [ stepId ] : Step ; } export interface Context < Id extends string = any > extends State { activeStepId : Id ; lastStep : number ; isCurrentStepValid : boolean | undefined ; navigateToStep : ( stepId : number | Id ) => void ; addStep : ( id : Id , label : string , isRequired ? : boolean ) => void ; } const formWizardContext = createContext < Context > ( { } as Context ) ; export const FormWizardProvider = WithMultiContent < Props < any > > ( function FormWizardProvider < T extends object = { [ key ] : any ; } > ( { children , defaultActiveStep = 0 , isEditing , onSave } ) { const { getData , validate , validation } = useMultiContentContext < T > ( ) ; const [ state , setState ] = useState < State > ( { activeStepIndex : defaultActiveStep , steps : { } , } ) ; const activeStepId = state . steps [ state . activeStepIndex ] ?. id ; const lastStep = Object . keys ( state . steps ) . length - 1 ; const isCurrentStepValid = validation . contents [ activeStepId as keyof T ] ; const addStep = useCallback ( ( id , label , isRequired = false ) => { setState ( ( prev ) => { const index = Object . keys ( prev . steps ) . length ; return { ... prev , steps : { ... prev . steps , [ index ] : { id , index , label , isRequired , isComplete : isEditing ?? false } , } , } ; } ) ; } , [ isEditing ] ) ; /**
     * Get the step index from a step id.
     */ const getStepIndex = useCallback ( ( stepId ) => { if ( typeof stepId === 'number' ) { return stepId ; } const targetStep = Object . values ( state . steps ) . find ( ( _step ) => _step . id === stepId ) ; if ( ! targetStep ) { throw new Error ( `Can't navigate to step "${ stepId }" as there are no step with that ID.` ) ; } return targetStep . index ; } , [ state . steps ] ) ; const navigateToStep = useCallback ( async ( stepId ) => { const isValid = await validate ( ) ; if ( ! isValid ) { return ; } const nextStepIndex = getStepIndex ( stepId ) ; if ( nextStepIndex > lastStep ) { onSave ( getData ( ) as T ) ; return ; } setState ( ( prev ) => { const currentStep = prev . steps [ prev . activeStepIndex ] ; const nextState = { ... prev , activeStepIndex : nextStepIndex , } ; if ( nextStepIndex > prev . activeStepIndex && ! currentStep . isComplete ) { nextState . steps [ prev . activeStepIndex ] = { ... currentStep , isComplete : true , } ; } return nextState ; } ) ; } , [ getStepIndex , validate , onSave , getData , lastStep ] ) ; const value = { ... state , activeStepId , lastStep , isCurrentStepValid , addStep , navigateToStep , } ; return < formWizardContext . Provider value = { value } > { children } < /formWizardContext.Provider>; } ) ; export const FormWizardConsumer = formWizardContext . Consumer ; export function useFormWizardContext < T extends string = any > ( ) { const ctx = useContext ( formWizardContext ) ; if ( ctx === undefined ) { throw new Error ( 'useFormWizardContext() must be called within a <FormWizardProvider />' ) ; } return ctx as Context < T > ; }