@stackoverfloweth/prefect-design
Version:
A collection of low-level Vue components.
165 lines (130 loc) • 4.61 kB
text/typescript
/* eslint-disable no-redeclare */
import { computed, inject, InjectionKey, provide, Ref, ref } from 'vue'
import { WizardNotFound } from '@/models'
import { WizardStep, UseWizard, ValidationState, WizardNavigation } from '@/types/wizard'
import { getStepKey } from '@/utilities/wizard'
export const useWizardKey: InjectionKey<UseWizard> = Symbol('UseWizard')
export function createWizard(steps: WizardStep[] | Ref<WizardStep[]>): UseWizard {
const loading = ref(false)
const stepsRef = ref(steps)
const currentStepIndex = ref(0)
const furthestStepIndex = ref(0)
const currentStep = computed(() => stepsRef.value[currentStepIndex.value])
function next(): Promise<WizardNavigation> {
const index = getOneBasedIndex(currentStepIndex.value)
const nextIndex = index + 1
return goto(nextIndex)
}
function previous(): Promise<WizardNavigation> {
const index = getOneBasedIndex(currentStepIndex.value)
const previousIndex = index - 1
return goto(previousIndex)
}
function goto(key: string): Promise<WizardNavigation>
function goto(index: number): Promise<WizardNavigation>
function goto(step: WizardStep): Promise<WizardNavigation>
function goto(keyIndexOrStep: WizardStep | string | number): Promise<WizardNavigation>
function goto(keyIndexOrStep: WizardStep | string | number): Promise<WizardNavigation> {
const index = typeof keyIndexOrStep === 'number' ? getZeroBasedIndex(keyIndexOrStep) : getStepIndex(keyIndexOrStep)
return new Promise<ValidationState[]>(resolve => {
loading.value = true
if (index < 0) {
resolve([{ index: currentStepIndex.value, valid: false }])
}
const validators = getValidators(index)
return resolve(Promise.all(validators))
})
.then(validStates => {
const firstFailure = validStates.find(({ valid }) => !valid)
setCurrentStepIndex(firstFailure?.index ?? index)
return {
success: !firstFailure,
newIndex: currentStepIndex.value,
}
})
.finally(() => loading.value = false)
}
function setCurrentStepIndex(index: number): void {
let newIndex = index
if (index < 0) {
newIndex = 0
}
if (index >= stepsRef.value.length) {
newIndex = stepsRef.value.length - 1
}
currentStepIndex.value = newIndex
furthestStepIndex.value = Math.max(furthestStepIndex.value, newIndex)
}
function getZeroBasedIndex(index: number): number {
return index - 1
}
function getOneBasedIndex(index: number): number {
return index + 1
}
function getStepIndex(key: string): number
function getStepIndex(step: WizardStep): number
function getStepIndex(keyOrStep: string | WizardStep): number
function getStepIndex(keyOrStep: string | WizardStep): number {
const step = typeof keyOrStep === 'object' ? keyOrStep : getStep(keyOrStep)
if (!step) {
return -1
}
return stepsRef.value.indexOf(step)
}
function getStep(key: string): WizardStep | undefined
function getStep(index: number): WizardStep | undefined
function getStep(keyOrIndex: string | number): WizardStep | undefined
function getStep(keyOrIndex: string | number): WizardStep | undefined {
if (typeof keyOrIndex === 'number') {
return stepsRef.value[keyOrIndex]
}
return stepsRef.value.find(step => getStepKey(step) === keyOrIndex)
}
function setStep(key: string, step: WizardStep): void {
const index = getStepIndex(key)
if (index === -1) {
return
}
stepsRef.value.splice(index, 1, step)
}
function getValidators(index?: number): Promise<ValidationState>[] {
const targetIndex = index ?? currentStepIndex.value + 1
return stepsRef.value
.slice(currentStepIndex.value, targetIndex)
.map(async ({ validate }, index) => {
const valid = validate ? await validate() : true
return {
valid,
index: index + currentStepIndex.value,
}
})
}
async function isValid(index?: number): Promise<boolean> {
const validators = getValidators(index)
const valid = await Promise.all(validators)
return valid.every(({ valid }) => valid)
}
const wizard = {
steps: stepsRef,
currentStepIndex,
currentStep,
furthestStepIndex,
loading,
next,
previous,
goto,
getStepIndex,
getStep,
setStep,
isValid,
}
provide(useWizardKey, wizard)
return wizard
}
export function useWizard(): UseWizard {
const wizard = inject(useWizardKey)
if (!wizard) {
throw new WizardNotFound()
}
return wizard
}