Last active
March 28, 2025 11:45
-
-
Save nash403/4e1f32f0cad0c75b50c2a9ab42045770 to your computer and use it in GitHub Desktop.
My helper for building a multi-step linear wizard interface. Base on https://vueuse.org/core/useStepper/#usestepper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useStepper } from '@vueuse/core' | |
import { ref, toValue } from 'vue' | |
/** | |
* A wrapper around VueUse's `useStepper` that adds async-aware navigation methods. | |
* It tracks direction of navigation (forward/backward) and only transitions between steps | |
* if the provided async action succeeds. | |
* | |
* The transition functions (`goXXX`) return a tuple `[success, error, result]`: | |
* - `success`: Boolean indicating whether the async action succeeded. | |
* - `error`: The error caught during the action (if any). | |
* - `result`: The result returned from the async action. | |
*/ | |
export function useStepperWithAsync(steps, initialStep) { | |
const stepper = useStepper(steps, initialStep) | |
const isGoingBack = ref(false) | |
const isGoingNext = ref(false) | |
const transitionState = ref({ isGoingBack, isGoingNext }) // store to add other custom values on runtime if you want for example a `isExiting` state | |
async function goToAsync(step, actionFn, ...args) { | |
const stepExists = stepper.stepNames.value.includes(step) | |
if (stepExists) { | |
if (stepper.isBefore(step)) { | |
isGoingBack.value = true | |
} else if (stepper.isAfter(step)) { | |
isGoingNext.value = true | |
} | |
} | |
const [success, err, data] = await _execAsyncAction(actionFn, ...args) | |
if (success) { | |
stepper.goTo(step) | |
} | |
return [success, err, data] | |
} | |
async function goToNextAsync(actionFn, ...args) { | |
isGoingNext.value = true | |
const [success, err, data] = await _execAsyncAction(actionFn, ...args) | |
if (success) { | |
stepper.goToNext() | |
} | |
return [success, err, data] | |
} | |
async function goToPreviousAsync(actionFn, ...args) { | |
isGoingBack.value = true | |
const [success, err, data] = await _execAsyncAction(actionFn, ...args) | |
if (success && !stepper.isFirst.value) { | |
stepper.goToPrevious() | |
} | |
return [success, err, data] | |
} | |
async function goBackToAsync(step, actionFn, ...args) { | |
isGoingBack.value = true | |
const [success, err, data] = await _execAsyncAction(actionFn, ...args) | |
if (success) { | |
stepper.goBackTo(step) | |
} | |
return [success, err, data] | |
} | |
function reset() { | |
resetTransitionState() | |
stepper.index.value = stepper.stepNames.value.indexOf(toValue(initialStep) ?? stepper.stepNames.value[0]) | |
} | |
function resetTransitionState() { | |
// reset all runtime added keys | |
for (const key in transitionState.value) { | |
transitionState.value[key] = false | |
} | |
} | |
async function _execAsyncAction(actionFn, ...args) { | |
try { | |
const result = await actionFn?.(...args) | |
if (result || result === undefined) { | |
return [true, null, result] | |
} | |
} catch (e) { | |
[false, e, null] | |
} finally { | |
resetTransitionState() | |
} | |
} | |
return { | |
...stepper, | |
isGoingBack, | |
isGoingNext, | |
transitionState, | |
goToAsync, | |
goToNextAsync, | |
goToPreviousAsync, | |
goBackToAsync, | |
resetTransitionState, | |
reset, | |
_execAsyncAction, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment