@wizzard-packages/vue 0.2.2-canary-20260908083257 โ†’ 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +70 -775
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -1,822 +1,117 @@
1
- # @wizzard-packages/vue ๐Ÿง™โ€โ™‚๏ธ
1
+ # @wizzard-packages/vue
2
2
 
3
3
  ![npm](https://img.shields.io/npm/v/@wizzard-packages/vue)
4
4
  ![downloads](https://img.shields.io/npm/dm/@wizzard-packages/vue)
5
5
  ![license](https://img.shields.io/npm/l/@wizzard-packages/vue)
6
6
 
7
- Vue 3 Composition API bindings for **Wizzard Stepper**.
8
- Build flexible, headless, and strictly typed multi-step wizards with Vue 3.
7
+ The Vue binding for [`@wizzard-packages/core`](https://www.npmjs.com/package/@wizzard-packages/core).
8
+ It bridges the engine into Vue and does nothing else: navigation, guards and validation are the
9
+ engine's, so this package is one `provide` and eight composables. That is why it is 732 B gzipped
10
+ against 5.07 kB for its 0.x equivalent โ€” nothing was optimised, the logic moved.
9
11
 
10
- ## โœจ Features
11
-
12
- - ๐ŸŸข **Vue 3 Native**: Built with Composition API and fine-grained reactivity.
13
- - ๐Ÿ— **Headless**: Total control over your UI/components.
14
- - ๐Ÿงช **Strictly Typed**: Full TypeScript support with the Factory Pattern.
15
- - โšก **Atomic Updates**: Components re-render only when the specific data they use changes.
16
- - ๐Ÿ”Œ **Core Powered**: Uses the same battle-tested engine as `@wizzard-packages/react`.
17
- - ๐Ÿ”„ **Feature Parity**: All React adapter features available in Vue (validation, guards, persistence, middleware).
18
-
19
- ## ๐Ÿ“ฆ Installation
12
+ ## Install
20
13
 
21
14
  ```bash
22
- pnpm add @wizzard-packages/vue @wizzard-packages/core
23
- # or
24
- npm install @wizzard-packages/vue @wizzard-packages/core
25
- ```
26
-
27
- Optional add-ons:
28
-
29
- ```bash
30
- pnpm add @wizzard-packages/persistence @wizzard-packages/middleware
31
- pnpm add @wizzard-packages/adapter-zod zod
32
- pnpm add @wizzard-packages/adapter-yup yup
33
- ```
34
-
35
- ## ๐Ÿš€ Quick Start
36
-
37
- ### 1. Define your Schema and Factory
38
-
39
- Create a typed wizard instance for your data schema.
40
-
41
- ```typescript
42
- // wizard.ts
43
- import { createWizardFactory } from '@wizzard-packages/vue';
44
-
45
- export interface MySchema {
46
- user: {
47
- name: string;
48
- email: string;
49
- };
50
- plan: 'basic' | 'pro';
51
- }
52
-
53
- export type StepId = 'info' | 'plan' | 'review';
54
-
55
- export const {
56
- useProvideWizard,
57
- useWizardActions,
58
- useWizardValue,
59
- useWizardState,
60
- useWizardField,
61
- useWizardSelector,
62
- useWizardError,
63
- } = createWizardFactory<MySchema>();
64
- ```
65
-
66
- ### 2. Provide the Wizard in a Parent Component
67
-
68
- ```vue
69
- <script setup lang="ts">
70
- import { useProvideWizard } from './wizard';
71
- import type { IWizardConfig } from '@wizzard-packages/core';
72
-
73
- const config: IWizardConfig<'info' | 'plan' | 'review', any> = {
74
- steps: [
75
- { id: 'info', label: 'Personal Info' },
76
- { id: 'plan', label: 'Select Plan' },
77
- { id: 'review', label: 'Review' },
78
- ],
79
- };
80
-
81
- useProvideWizard({
82
- config,
83
- initialData: {
84
- user: { name: '', email: '' },
85
- plan: 'basic',
86
- },
87
- });
88
- </script>
89
-
90
- <template>
91
- <div class="wizard-container">
92
- <StepRenderer />
93
- <NavigationControls />
94
- </div>
95
- </template>
96
- ```
97
-
98
- ### 3. Consume State in Child Components
99
-
100
- #### Reading and Writing Values (`useWizardField`)
101
-
102
- ```vue
103
- <script setup lang="ts">
104
- import { useWizardField } from './wizard';
105
-
106
- // Reactive [ref, setter]
107
- const [name, setName] = useWizardField('user.name');
108
- const [email, setEmail] = useWizardField('user.email');
109
- </script>
110
-
111
- <template>
112
- <div>
113
- <input
114
- :value="name"
115
- @input="(e) => setName((e.target as HTMLInputElement).value)"
116
- placeholder="Enter name"
117
- />
118
- <input
119
- :value="email"
120
- @input="(e) => setEmail((e.target as HTMLInputElement).value)"
121
- placeholder="Enter email"
122
- type="email"
123
- />
124
- </div>
125
- </template>
126
- ```
127
-
128
- #### Navigation Actions (`useWizardActions`)
129
-
130
- ```vue
131
- <script setup lang="ts">
132
- import { useWizardActions, useWizardState } from './wizard';
133
-
134
- const state = useWizardState();
135
- const { nextStep, prevStep, goToStep } = useWizardActions();
136
- </script>
137
-
138
- <template>
139
- <div class="navigation">
140
- <button @click="prevStep" :disabled="state.isFirstStep">Back</button>
141
- <span>Step {{ state.currentStepIndex + 1 }} of {{ state.totalSteps }}</span>
142
- <button @click="nextStep" :disabled="state.isLastStep">Next</button>
143
- </div>
144
- </template>
145
- ```
146
-
147
- ## ๐Ÿ›  API Reference
148
-
149
- ### `createWizardFactory<TSchema>()`
150
-
151
- Returns a set of typed hooks for your schema:
152
-
153
- #### `useProvideWizard(options)`
154
-
155
- Initializes and provides the wizard store to the component tree.
156
-
157
- ```typescript
158
- interface IWizardProviderOptions<StepId, TSchema> {
159
- config: IWizardConfig<StepId, TSchema>;
160
- initialData?: TSchema;
161
- initialStepId?: StepId;
162
- }
163
-
164
- useProvideWizard({
165
- config: {
166
- steps: [
167
- { id: 'step1', label: 'Step 1' },
168
- { id: 'step2', label: 'Step 2' },
169
- ],
170
- validationMode: 'onChange', // 'onChange' | 'onBlur' | 'onSubmit'
171
- validationDebounceTime: 300,
172
- },
173
- initialData: { name: '' },
174
- initialStepId: 'step1',
175
- });
176
- ```
177
-
178
- #### `useWizardState()`
179
-
180
- Returns a reactive computed ref with the complete wizard state:
181
-
182
- ```typescript
183
- const state = useWizardState();
184
-
185
- // Available properties:
186
- state.currentStepId; // Current step ID
187
- state.currentStepIndex; // Current step index (0-based)
188
- state.totalSteps; // Total number of active steps
189
- state.isFirstStep; // Boolean: is first step
190
- state.isLastStep; // Boolean: is last step
191
- state.canGoNext; // Boolean: can navigate forward
192
- state.canGoPrev; // Boolean: can navigate backward
193
- state.visitedSteps; // Set of visited step IDs
194
- state.completedSteps; // Set of completed step IDs
195
- state.errorSteps; // Set of steps with errors
196
- state.progress; // Progress percentage (0-100)
197
- state.isBusy; // Boolean: async operation in progress
198
- state.data; // Current wizard data
199
- state.activeSteps; // Array of currently active steps
200
- ```
201
-
202
- #### `useWizardValue(path, defaultValue?)`
203
-
204
- Returns a reactive ref for a specific path in the wizard data:
205
-
206
- ```typescript
207
- const name = useWizardValue('user.name');
208
- const email = useWizardValue('user.email', 'default@example.com');
209
-
210
- // Automatically updates when the value changes
211
- console.log(name.value); // 'John'
212
- ```
213
-
214
- #### `useWizardField(path, defaultValue?)`
215
-
216
- Returns `[ref, setter]` tuple for a specific field:
217
-
218
- ```typescript
219
- const [name, setName] = useWizardField('user.name');
220
- const [age, setAge] = useWizardField('user.age', 18);
221
-
222
- // Use in template
223
- <input :value="name" @input="e => setName(e.target.value)" />
224
-
225
- // Or with v-model workaround
226
- const nameModel = computed({
227
- get: () => name.value,
228
- set: (val) => setName(val)
229
- });
230
-
231
- <input v-model="nameModel" />
232
- ```
233
-
234
- #### `useWizardActions()`
235
-
236
- Returns navigation and data manipulation methods:
237
-
238
- ```typescript
239
- const actions = useWizardActions();
240
-
241
- // Navigation
242
- actions.nextStep(); // Go to next step
243
- actions.prevStep(); // Go to previous step
244
- actions.goToStep('stepId'); // Go to specific step
245
- actions.goToStepIndex(2); // Go to step by index
246
-
247
- // Data manipulation
248
- actions.setData('user.name', 'John'); // Set single value
249
- actions.updateData({
250
- // Update multiple values
251
- user: { name: 'John', email: 'john@example.com' },
252
- });
253
- actions.getData('user.name'); // Get value by path
254
- actions.getData('user.name', 'Default'); // With default value
255
-
256
- // Validation
257
- actions.validateStep('stepId'); // Validate specific step
258
- actions.validateAll(); // Validate all steps
259
-
260
- // Error management
261
- actions.setErrors({
262
- // Set errors for a step
263
- stepId: {
264
- fieldName: 'Error message',
265
- },
266
- });
267
-
268
- // Reset
269
- actions.reset(); // Reset to initial state
270
- actions.reset({ name: 'New' }); // Reset with new data
271
- ```
272
-
273
- #### `useWizardSelector(selector)`
274
-
275
- Returns a reactive computed value derived from wizard state:
276
-
277
- ```typescript
278
- const progress = useWizardSelector((state) => state.progress);
279
- const isValid = useWizardSelector((state) => state.errorSteps.size === 0);
280
-
281
- // Complex derived state
282
- const wizardStatus = useWizardSelector((state) => ({
283
- step: state.currentStepId,
284
- progress: state.progress,
285
- canProceed: state.canGoNext && !state.isBusy,
286
- hasErrors: state.errorSteps.size > 0,
287
- }));
288
- ```
289
-
290
- #### `useWizardError(path)`
291
-
292
- Returns a reactive ref with the error message for a specific field:
293
-
294
- ```typescript
295
- const nameError = useWizardError('user.name');
296
- const emailError = useWizardError('user.email');
297
-
298
- // Use in template
299
- <div v-if="nameError" class="error">{{ nameError }}</div>
300
- ```
301
-
302
- ## ๐Ÿ“š Advanced Examples
303
-
304
- ### Example 1: Step Guards (beforeLeave)
305
-
306
- Prevent navigation until validation passes:
307
-
308
- ```typescript
309
- const config: IWizardConfig<StepId, MySchema> = {
310
- steps: [
311
- {
312
- id: 'info',
313
- label: 'Personal Info',
314
- beforeLeave: async (data) => {
315
- if (!data.user.name) {
316
- return { canLeave: false, error: 'Name is required' };
317
- }
318
- if (!data.user.email.includes('@')) {
319
- return { canLeave: false, error: 'Valid email required' };
320
- }
321
- return { canLeave: true };
322
- },
323
- },
324
- { id: 'review', label: 'Review' },
325
- ],
326
- };
327
- ```
328
-
329
- ### Example 2: Conditional Steps (isVisible)
330
-
331
- Show/hide steps based on data:
332
-
333
- ```typescript
334
- const config: IWizardConfig<StepId, MySchema> = {
335
- steps: [
336
- { id: 'plan', label: 'Select Plan' },
337
- {
338
- id: 'payment',
339
- label: 'Payment',
340
- isVisible: (data) => data.plan === 'pro', // Only show for pro plan
341
- },
342
- { id: 'review', label: 'Review' },
343
- ],
344
- };
345
- ```
346
-
347
- ### Example 3: Async Step Conditions
348
-
349
- Dynamic step visibility with async checks:
350
-
351
- ```typescript
352
- const config: IWizardConfig<StepId, MySchema> = {
353
- steps: [
354
- { id: 'account', label: 'Account' },
355
- {
356
- id: 'premium',
357
- label: 'Premium Features',
358
- isVisible: async (data) => {
359
- const hasAccess = await checkPremiumAccess(data.user.id);
360
- return hasAccess;
361
- },
362
- },
363
- ],
364
- };
365
- ```
366
-
367
- ### Example 4: Zod Validation
368
-
369
- Integrate with Zod for schema validation:
370
-
371
- ```vue
372
- <script setup lang="ts">
373
- import { z } from 'zod';
374
- import { ZodAdapter } from '@wizzard-packages/adapter-zod';
375
- import { useProvideWizard } from './wizard';
376
-
377
- const userSchema = z.object({
378
- name: z.string().min(2, 'Name must be at least 2 characters'),
379
- email: z.string().email('Invalid email address'),
380
- age: z.number().min(18, 'Must be 18 or older'),
381
- });
382
-
383
- const config = {
384
- steps: [
385
- {
386
- id: 'info',
387
- label: 'Info',
388
- validate: ZodAdapter(userSchema),
389
- },
390
- ],
391
- };
392
-
393
- useProvideWizard({ config, initialData: { name: '', email: '', age: 0 } });
394
- </script>
395
- ```
396
-
397
- ### Example 5: LocalStorage Persistence
398
-
399
- Persist wizard state across sessions:
400
-
401
- ```typescript
402
- import { LocalStorageAdapter } from '@wizzard-packages/persistence';
403
-
404
- const config = {
405
- steps: [
406
- { id: 'step1', label: 'Step 1' },
407
- { id: 'step2', label: 'Step 2' },
408
- ],
409
- persistence: LocalStorageAdapter('my-wizard-state'),
410
- };
411
-
412
- useProvideWizard({ config, initialData: {} });
15
+ pnpm add @wizzard-packages/core@canary @wizzard-packages/vue@canary
413
16
  ```
414
17
 
415
- ### Example 6: Middleware (Logger)
416
-
417
- Add logging middleware for debugging:
418
-
419
- ```typescript
420
- import { loggerMiddleware } from '@wizzard-packages/middleware';
421
-
422
- const config = {
423
- steps: [{ id: 'step1', label: 'Step 1' }],
424
- middlewares: [loggerMiddleware],
425
- };
18
+ Vue 3.3 or newer. Reactivity rides on snapshot identity: the engine returns the same object
19
+ until a commit, so a `shallowRef` holding it invalidates exactly once per commit and every
20
+ `computed` derived from it caches for free.
426
21
 
427
- useProvideWizard({ config, initialData: {} });
428
- ```
429
-
430
- ### Example 7: Progress Indicator Component
431
-
432
- ```vue
433
- <script setup lang="ts">
434
- import { useWizardState } from './wizard';
435
-
436
- const state = useWizardState();
437
- </script>
438
-
439
- <template>
440
- <div class="progress-bar">
441
- <div class="progress-fill" :style="{ width: `${state.progress}%` }" />
442
- </div>
443
- <div class="progress-text">
444
- {{ state.progress }}% Complete ({{ state.currentStepIndex + 1 }} / {{ state.totalSteps }})
445
- </div>
446
- </template>
447
-
448
- <style scoped>
449
- .progress-bar {
450
- width: 100%;
451
- height: 8px;
452
- background: #e0e0e0;
453
- border-radius: 4px;
454
- overflow: hidden;
455
- }
456
-
457
- .progress-fill {
458
- height: 100%;
459
- background: linear-gradient(90deg, #4caf50, #8bc34a);
460
- transition: width 0.3s ease;
461
- }
462
- </style>
463
- ```
22
+ ## Use
464
23
 
465
- ### Example 8: Step Breadcrumbs
24
+ `provideWizard` goes in a parent, because `inject` reads the parent chain โ€” the component that
25
+ provides the wizard is not the one that uses it. Pass a flow and the scope owns and destroys
26
+ the engine, or pass a `Wizard` you built yourself and it stays yours.
466
27
 
467
28
  ```vue
468
29
  <script setup lang="ts">
469
- import { useWizardState, useWizardActions } from './wizard';
30
+ import { provideWizard } from '@wizzard-packages/vue/v1';
470
31
 
471
- const state = useWizardState();
472
- const { goToStep } = useWizardActions();
32
+ import { signup } from './flow';
33
+ import Wizard from './Wizard.vue';
473
34
 
474
- const canNavigateToStep = (stepId: string) => {
475
- return state.visitedSteps.has(stepId) || state.completedSteps.has(stepId);
476
- };
35
+ provideWizard({ flow: signup });
477
36
  </script>
478
37
 
479
38
  <template>
480
- <div class="breadcrumbs">
481
- <div
482
- v-for="(step, index) in state.activeSteps"
483
- :key="step.id"
484
- class="breadcrumb"
485
- :class="{
486
- active: step.id === state.currentStepId,
487
- completed: state.completedSteps.has(step.id),
488
- error: state.errorSteps.has(step.id),
489
- }"
490
- @click="canNavigateToStep(step.id) && goToStep(step.id)"
491
- >
492
- <span class="number">{{ index + 1 }}</span>
493
- <span class="label">{{ step.label }}</span>
494
- </div>
495
- </div>
39
+ <Wizard />
496
40
  </template>
497
41
  ```
498
42
 
499
- ### Example 9: Form with Validation Errors
43
+ <!-- example:quickstart-vue -->
500
44
 
45
+ <!-- prettier-ignore -->
501
46
  ```vue
502
47
  <script setup lang="ts">
503
- import { useWizardField, useWizardError, useWizardActions } from './wizard';
48
+ import { useField, useNavigation, useStep } from '@wizzard-packages/vue/v1';
504
49
 
505
- const [name, setName] = useWizardField('user.name');
506
- const [email, setEmail] = useWizardField('user.email');
507
-
508
- const nameError = useWizardError('user.name');
509
- const emailError = useWizardError('user.email');
510
-
511
- const { validateStep, nextStep } = useWizardActions();
512
-
513
- const handleSubmit = async () => {
514
- const result = await validateStep('info');
515
- if (result.isValid) {
516
- await nextStep();
517
- }
518
- };
50
+ const { current, isLast } = useStep();
51
+ const { next, back, canBack } = useNavigation();
52
+ const full = useField<string>('name.full');
519
53
  </script>
520
54
 
521
55
  <template>
522
- <form @submit.prevent="handleSubmit">
523
- <div class="field">
524
- <label>Name</label>
525
- <input
526
- :value="name"
527
- @input="(e) => setName((e.target as HTMLInputElement).value)"
528
- :class="{ error: nameError }"
529
- />
530
- <span v-if="nameError" class="error-message">{{ nameError }}</span>
531
- </div>
532
-
533
- <div class="field">
534
- <label>Email</label>
535
- <input
536
- type="email"
537
- :value="email"
538
- @input="(e) => setEmail((e.target as HTMLInputElement).value)"
539
- :class="{ error: emailError }"
540
- />
541
- <span v-if="emailError" class="error-message">{{ emailError }}</span>
542
- </div>
543
-
544
- <button type="submit">Continue</button>
56
+ <form @submit.prevent>
57
+ <label v-if="current === 'name'">
58
+ Your name
59
+ <input v-model="full" />
60
+ </label>
61
+ <p v-else-if="current === 'review'">Hello, {{ full || 'stranger' }}.</p>
62
+
63
+ <button type="button" :disabled="!canBack" @click="back()">Back</button>
64
+ <button type="button" :disabled="isLast" @click="next()">Next</button>
545
65
  </form>
546
66
  </template>
547
67
  ```
548
68
 
549
- ### Example 10: Computed v-model Wrapper
550
-
551
- For easier v-model integration:
552
-
553
- ```vue
554
- <script setup lang="ts">
555
- import { computed } from 'vue';
556
- import { useWizardField } from './wizard';
557
-
558
- const [nameRef, setName] = useWizardField('user.name');
559
- const [emailRef, setEmail] = useWizardField('user.email');
560
-
561
- // Create computed properties for v-model
562
- const name = computed({
563
- get: () => nameRef.value,
564
- set: (val) => setName(val),
565
- });
566
-
567
- const email = computed({
568
- get: () => emailRef.value,
569
- set: (val) => setEmail(val),
570
- });
571
- </script>
572
-
573
- <template>
574
- <div>
575
- <input v-model="name" placeholder="Name" />
576
- <input v-model="email" type="email" placeholder="Email" />
577
- </div>
578
- </template>
579
- ```
580
-
581
- ### Example 11: Complex Selector with Performance
582
-
583
- ```vue
584
- <script setup lang="ts">
585
- import { useWizardSelector } from './wizard';
586
-
587
- // Only re-renders when these specific values change
588
- const wizardMeta = useWizardSelector((state) => ({
589
- currentStep: state.activeSteps.find((s) => s.id === state.currentStepId),
590
- isLastAndValid: state.isLastStep && state.errorSteps.size === 0,
591
- completionRate: (state.completedSteps.size / state.totalSteps) * 100,
592
- }));
593
- </script>
594
-
595
- <template>
596
- <div class="wizard-meta">
597
- <h2>{{ wizardMeta.currentStep?.label }}</h2>
598
- <p v-if="wizardMeta.isLastAndValid">Ready to submit!</p>
599
- <p>{{ wizardMeta.completionRate.toFixed(0) }}% of steps completed</p>
600
- </div>
601
- </template>
602
- ```
603
-
604
- ### Example 12: Dynamic Step Configuration
605
-
606
- ```vue
607
- <script setup lang="ts">
608
- import { ref, computed } from 'vue';
609
- import { useProvideWizard } from './wizard';
610
- import type { IStepConfig } from '@wizzard-packages/core';
611
-
612
- const userType = ref<'personal' | 'business'>('personal');
613
-
614
- const steps = computed(() => {
615
- const baseSteps: IStepConfig<string, any>[] = [{ id: 'type', label: 'Account Type' }];
616
-
617
- if (userType.value === 'business') {
618
- baseSteps.push({ id: 'company', label: 'Company Info' }, { id: 'tax', label: 'Tax Details' });
619
- } else {
620
- baseSteps.push({ id: 'personal', label: 'Personal Info' });
621
- }
622
-
623
- baseSteps.push({ id: 'review', label: 'Review' });
624
-
625
- return baseSteps;
626
- });
627
-
628
- const config = computed(() => ({
629
- steps: steps.value,
630
- }));
631
-
632
- useProvideWizard({
633
- config: config.value,
634
- initialData: { type: userType.value },
635
- });
636
- </script>
637
- ```
638
-
639
- ## ๐ŸŽฏ Best Practices
640
-
641
- ### 1. Memoize Configuration
642
-
643
- Use `computed` for step configuration to avoid unnecessary recalculations:
644
-
645
- ```typescript
646
- const config = computed(() => ({
647
- steps: [{ id: 'step1', label: 'Step 1' }],
648
- middlewares: [loggerMiddleware],
649
- validationMode: 'onChange',
650
- }));
651
- ```
652
-
653
- ### 2. Granular Subscriptions
654
-
655
- Prefer specific hooks over full state for better performance:
656
-
657
- ```typescript
658
- // โœ… Good: Only re-renders when 'user.name' changes
659
- const name = useWizardValue('user.name');
660
-
661
- // โŒ Avoid: Re-renders on any state change
662
- const state = useWizardState();
663
- const name = state.data.user.name;
664
- ```
665
-
666
- ### 3. Use Selectors for Derived State
667
-
668
- Create computed values with `useWizardSelector`:
669
-
670
- ```typescript
671
- // Only re-computes when progress or isBusy changes
672
- const status = useWizardSelector((state) => ({
673
- progress: state.progress,
674
- loading: state.isBusy,
675
- }));
676
- ```
677
-
678
- ### 4. Validation Timing
679
-
680
- Choose appropriate validation mode:
681
-
682
- - `onChange`: Real-time validation (best UX, more API calls)
683
- - `onBlur`: Validate on field blur (balanced)
684
- - `onSubmit`: Validate on step navigation (least intrusive)
685
-
686
- ### 5. Error Handling
687
-
688
- Always handle async errors in guards and validation:
689
-
690
- ```typescript
691
- {
692
- id: 'step1',
693
- beforeLeave: async (data) => {
694
- try {
695
- await validateAPI(data);
696
- return { canLeave: true };
697
- } catch (error) {
698
- return {
699
- canLeave: false,
700
- error: error.message || 'Validation failed'
701
- };
702
- }
703
- }
704
- }
705
- ```
706
-
707
- ## ๐Ÿ”Œ Integration with Vue Router
708
-
709
- ```vue
710
- <script setup lang="ts">
711
- import { watch } from 'vue';
712
- import { useRoute, useRouter } from 'vue-router';
713
- import { useWizardActions, useWizardState } from './wizard';
69
+ <!-- /example -->
714
70
 
715
- const route = useRoute();
716
- const router = useRouter();
717
- const { goToStep } = useWizardActions();
718
- const state = useWizardState();
71
+ That file and the flow it imports are `examples/quickstart`, which CI runs โ€” this block is
72
+ generated from them, so what you paste is what is tested.
719
73
 
720
- // Sync wizard step with route
721
- watch(
722
- () => route.params.step,
723
- (stepId) => {
724
- if (stepId && stepId !== state.currentStepId) {
725
- goToStep(stepId as string);
726
- }
727
- }
728
- );
729
-
730
- // Sync route with wizard step
731
- watch(
732
- () => state.currentStepId,
733
- (stepId) => {
734
- if (route.params.step !== stepId) {
735
- router.push({ params: { step: stepId } });
736
- }
737
- }
738
- );
739
- </script>
740
- ```
74
+ ## Composables
741
75
 
742
- ## ๐Ÿงช Testing
743
-
744
- ```typescript
745
- import { mount } from '@vue/test-utils';
746
- import { useProvideWizard, useWizardState } from './wizard';
747
-
748
- describe('Wizard', () => {
749
- it('navigates between steps', async () => {
750
- const wrapper = mount({
751
- setup() {
752
- useProvideWizard({
753
- config: {
754
- steps: [
755
- { id: 'step1', label: 'Step 1' },
756
- { id: 'step2', label: 'Step 2' },
757
- ],
758
- },
759
- initialData: {},
760
- });
761
-
762
- const state = useWizardState();
763
- const { nextStep } = useWizardActions();
764
-
765
- return { state, nextStep };
766
- },
767
- template: '<div>{{ state.currentStepId }}</div>',
768
- });
769
-
770
- expect(wrapper.text()).toBe('step1');
771
-
772
- await wrapper.vm.nextStep();
773
- await wrapper.vm.$nextTick();
774
-
775
- expect(wrapper.text()).toBe('step2');
776
- });
777
- });
778
- ```
779
-
780
- ## ๐ŸŒ SSR Support
781
-
782
- The Vue adapter is SSR-friendly and works with Nuxt 3:
783
-
784
- ```typescript
785
- // Avoid persistence adapters that access `window` during module initialization
786
- // Use lazy initialization instead:
787
-
788
- const config = computed(() => ({
789
- steps: [...],
790
- persistence: typeof window !== 'undefined'
791
- ? LocalStorageAdapter('key')
792
- : undefined
793
- }));
794
- ```
76
+ | Composable | Returns |
77
+ | ----------------------- | -------------------------------------------------------------------------------------------- |
78
+ | `useStep()` | `current`, `isFirst`, `isLast`, `progress`, `breadcrumbs` and the rest, each a `ComputedRef` |
79
+ | `useNavigation()` | `next`, `back`, `go`, `cancel`, and `canBack`, `isBusy`, `isLast` as `ComputedRef`s |
80
+ | `useField<T>(path)` | a `WritableComputedRef`, usable directly with `v-model` |
81
+ | `useErrors(stepId?)` | a `ComputedRef` of the error map for a step, or for the current one |
82
+ | `useWizardSelector(fn)` | one derived value as a `ComputedRef` |
83
+ | `useWizard()` | the engine itself, for anything the composables above do not cover |
795
84
 
796
- ## ๐Ÿ“ฆ Package Structure
85
+ `useOptionalWizard()` returns `null` outside a provider instead of throwing, which is what a
86
+ component rendered both inside and outside a wizard needs.
797
87
 
798
- - **ESM**: `dist/index.js` (tree-shakeable)
799
- - **CJS**: `dist/index.cjs` (Node.js compatibility)
800
- - **Types**: `dist/index.d.ts` (TypeScript definitions)
88
+ Navigation is async and returns a result, not a boolean: `await next()` gives
89
+ `{ ok: false, reason: 'blocked', by: 'age-check' }` when a guard refuses. Every `await` inside
90
+ the engine re-checks a navigation epoch, so a validator that resolves after the user pressed
91
+ Back cannot move them.
801
92
 
802
- ## ๐Ÿ”— How It Fits
93
+ On the server `onMounted` never runs, so a wizard rendered by SSR navigates nowhere and hydrates
94
+ into its first step on the client.
803
95
 
804
- - **Core engine**: `@wizzard-packages/core` (shared with React)
805
- - **Optional persistence**: `@wizzard-packages/persistence`
806
- - **Optional middleware**: `@wizzard-packages/middleware`
807
- - **Optional validation**: `@wizzard-packages/adapter-zod` or `@wizzard-packages/adapter-yup`
96
+ ## Documentation
808
97
 
809
- ## ๐Ÿ“– Links
98
+ [Getting started](https://zizzx.github.io/wizzard-packages/docs/start/) ยท
99
+ [The flow](https://zizzx.github.io/wizzard-packages/docs/flow/) ยท
100
+ [Navigation](https://zizzx.github.io/wizzard-packages/docs/navigation/) ยท
101
+ [Validation](https://zizzx.github.io/wizzard-packages/docs/validation/) ยท
102
+ [Persistence](https://zizzx.github.io/wizzard-packages/docs/persistence/)
810
103
 
811
- - **Repository**: https://github.com/ZizzX/wizzard-packages
812
- - **Documentation**: https://zizzx.github.io/wizzard-packages/
813
- - **Issues**: https://github.com/ZizzX/wizzard-packages/issues
814
- - **Changelog**: [CHANGELOG.md](./CHANGELOG.md)
104
+ The same names, as hooks, are in
105
+ [`@wizzard-packages/react`](https://www.npmjs.com/package/@wizzard-packages/react). A shared
106
+ contract suite runs against both, which is what stops them drifting apart.
815
107
 
816
- ## ๐Ÿ“„ License
108
+ ## Upgrading from 0.x
817
109
 
818
- MIT ยฉ [ZizzX](https://github.com/ZizzX)
110
+ 0.x was a different library with the same name: `createWizardFactory<TSchema>()`,
111
+ `useProvideWizard`, `useWizardState`, `useWizardActions`. In v1 the names are shorter and the
112
+ import path carries the version โ€” `@wizzard-packages/vue/v1`. v1 is on the `canary` tag while
113
+ the launch lands; the 0.x line on `latest` is being retired.
819
114
 
820
- ---
115
+ ## License
821
116
 
822
- **Made with โค๏ธ for the Vue community**
117
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wizzard-packages/vue",
3
- "version": "0.2.2-canary-20260908083257",
3
+ "version": "0.2.2",
4
4
  "description": "Vue 3 composition API and components for Wizzard Stepper.",
5
5
  "keywords": [
6
6
  "wizard",
@@ -59,9 +59,9 @@
59
59
  "vue": "^3.3.0"
60
60
  },
61
61
  "dependencies": {
62
- "@wizzard-packages/core": "0.5.0-canary-20260908083257",
63
- "@wizzard-packages/middleware": "0.1.5-canary-20260908083257",
64
- "@wizzard-packages/persistence": "0.1.5-canary-20260908083257"
62
+ "@wizzard-packages/core": "0.5.0",
63
+ "@wizzard-packages/middleware": "0.1.5",
64
+ "@wizzard-packages/persistence": "0.1.5"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@vitejs/plugin-vue": "^5.2.1",