Skip to content

Визарды

useWizard(steps, options?) — composable для многошаговых форм, построенный поверх defineMachine. Машина wizard'а генерируется автоматически из массива шагов.

ts
function useWizard<TContext>(
  steps: WizardStep<TContext>[],
  options?: WizardOptions,
): WizardInstance<TContext>

Шаги визарда

Поля WizardStep:

id

string. Уникальный идентификатор шага (внутри становится именем состояния).

label

string?. Отображаемая метка.

component

Component?. Vue-компонент, рендерящийся для этого шага.

canProceed

(ctx) => boolean | Promise<boolean>. Условие для next() и goTo() вперёд; может быть асинхронным. Получает реальный, живой контекст wizard'а — то, что успели слить в него onEnter/onLeave.

onEnter

(ctx) => void | Partial<TContext>. Вызывается, когда wizard входит в этот шаг. Верните частичный объект, чтобы слить его в контекст — например, задать значение по умолчанию.

onLeave

(ctx) => void | Partial<TContext>. Вызывается, когда wizard покидает этот шаг. Верните частичный объект, чтобы слить его в контекст — обычное место для сохранения данных, собранных на шаге (например, полей формы), до того, как canProceed на следующем шаге их прочитает.

Опции визарда

Поля WizardOptions:

id

string? · по умолчанию: автогенерируется. Id машины, регистрируемый в MachineStore (когда установлен VueMachinePlugin) и отображаемый в DevTools. Задайте его явно, если нужен стабильный, предсказуемый id (например, чтобы найти wizard через useMachineStore().get(id)); иначе каждый useWizard() получает свой уникальный id автоматически.

initialStep

number · по умолчанию: 0. Индекс начального шага.

allowSkip

boolean · по умолчанию: false. Пропускать canProceed при goTo() вперёд.

circular

boolean · по умолчанию: false. next() возвращается от последнего шага к первому.

Возвращаемое значение

currentStep

Ref<WizardStep>. Объект текущего активного шага.

currentIndex

ComputedRef<number>. Индекс текущего шага (от нуля).

totalSteps

number. Общее число шагов.

progress

ComputedRef<number>. От 0 до 1 на основе текущего индекса.

isFirst

ComputedRef<boolean>. true на первом шаге.

isLast

ComputedRef<boolean>. true на последнем шаге.

history

Ref<string[]>. ID посещённых шагов.

context

Readonly<Ref<TContext>>. Накопленный контекст — то, что успели слить в него onEnter/onLeave.

next()

Promise<boolean>. Продвигает вперёд; сначала вызывает canProceed; возвращает false, если заблокировано.

prev()

void. Назад (без guard).

goTo(id)

Promise<boolean>. Переход к шагу по id; учитывает canProceed, если не allowSkip.

reset()

void. Возврат к начальному шагу.

Пример

vue
<script setup lang="ts">
import { useWizard } from '@macrulez/vue-state-machine'
import type { WizardStep } from '@macrulez/vue-state-machine'
import StepInfo from './StepInfo.vue'
import StepAddress from './StepAddress.vue'
import StepPayment from './StepPayment.vue'

interface CheckoutCtx {
  name: string
  email: string
  address: string
}

const steps: WizardStep<CheckoutCtx>[] = [
  {
    id: 'info',
    label: 'Your info',
    component: StepInfo,
    // Заполняется собственным onLeave шага StepAddress ниже, ещё до того как
    // этот код выполнится в canProceed *следующего* шага — см. "context" в
    // возвращаемом значении выше.
    canProceed: (ctx) => !!ctx.name && !!ctx.email,
  },
  {
    id: 'address',
    label: 'Delivery',
    component: StepAddress,
    canProceed: (ctx) => !!ctx.address,
    // Верните частичный объект, чтобы слить собранные данные формы в контекст —
    // например, прочитав их из ref, который StepAddress обновляет через v-model,
    // или из выпущенного им emit.
    onLeave: (): Partial<CheckoutCtx> => ({ address: addressFieldRef.value }),
  },
  {
    id: 'payment',
    label: 'Payment',
    component: StepPayment,
    onEnter: () => trackEvent('payment_step_entered'),
  },
]

const { currentStep, context, progress, isFirst, isLast, next, prev } = useWizard(steps)
</script>

<template>
  <div>
    <progress :value="progress" max="1" />

    <component :is="currentStep.component" :context="context" />

    <nav>
      <button :disabled="isFirst" @click="prev">Back</button>
      <button v-if="!isLast" @click="next">Next</button>
      <button v-else @click="submit">Place order</button>
    </nav>
  </div>
</template>

Правила для canProceed

  • Может возвращать boolean или Promise<boolean>
  • Если возвращает false, next() / goTo() вперёд возвращают false, и wizard остаётся на текущем шаге
  • Если выбрасывает исключение, результат тот же — возвращается false, ошибка логируется в console.error в dev-режиме
  • prev() и goTo() назад никогда не проверяют canProceed
  • allowSkip: true отключает canProceed только для goTo(); next() проверяет его всегда